// 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/ps_located.h" #include "nel/3d/driver.h" #include "nel/3d/vertex_buffer.h" #include "nel/3d/material.h" #include "nel/3d/index_buffer.h" #include "nel/3d/nelu.h" #include "nel/3d/ps_util.h" #include "nel/3d/ps_particle.h" #include "nel/3d/ps_emitter.h" #include "nel/3d/ps_sound.h" #include "nel/3d/particle_system_shape.h" #include "nel/3d/ps_located.h" #include "nel/misc/aabbox.h" #include "nel/misc/file.h" #include "nel/misc/stream.h" // tmp #include "nel/3d/particle_system_model.h" #ifdef NL_DEBUG #define CHECK_INTEGRITY checkIntegrity(); #else #define CHECK_INTEGRITY #endif namespace NL3D { uint32 CParticleSystem::NbParticlesDrawn = 0; UPSSoundServer * CParticleSystem::_SoundServer = NULL; CParticleSystem::TGlobalValuesMap CParticleSystem::_GlobalValuesMap; CParticleSystem::TGlobalVectorValuesMap CParticleSystem::_GlobalVectorValuesMap; // sim step infos TAnimationTime CParticleSystem::EllapsedTime = 0.f; TAnimationTime CParticleSystem::InverseTotalEllapsedTime = 0.f; TAnimationTime CParticleSystem::RealEllapsedTime = 0.f; float CParticleSystem::RealEllapsedTimeRatio = 1.f; bool CParticleSystem::InsideSimLoop = false; bool CParticleSystem::InsideRemoveLoop = false; bool CParticleSystem::InsideNewElementsLoop = false;; std::vector CParticleSystem::_SpawnPos; //bool FilterPS[10] = { false, false, false, false, false, false, false, false, false, false }; #ifdef NL_DEBUG uint CParticleSystem::_NumInstances = 0; #endif static const float PS_MIN_TIMEOUT = 1.f; // the test that check if there are no particles left #if defined(NL_DEBUG) || defined(NL_PS_DEBUG) bool CParticleSystem::_SerialIdentifiers = true; #else bool CParticleSystem::_SerialIdentifiers = false; #endif bool CParticleSystem::_ForceDisplayBBox = false; CParticleSystemModel *CParticleSystem::OwnerModel = NULL; /////////////////////////////////// // CPaticleSystem implementation // /////////////////////////////////// /// the default max distance of view for particle systems const float PSDefaultMaxViewDist = 300.f; /* * Constructor */ CParticleSystem::CParticleSystem() : _Driver(NULL), _FontGenerator(NULL), _FontManager(NULL), _UserCoordSystemInfo(NULL), _Date(0), _LastUpdateDate(-1), _CurrEditedElementLocated(NULL), _CurrEditedElementIndex(0), _Scene(NULL), _TimeThreshold(0.15f), _SystemDate(0.f), _MaxNbIntegrations(2), _LODRatio(0.5f), _OneMinusCurrentLODRatio(0), _MaxViewDist(PSDefaultMaxViewDist), _MaxDistLODBias(0.05f), _InvMaxViewDist(1.f / PSDefaultMaxViewDist), _InvCurrentViewDist(1.f / PSDefaultMaxViewDist), _AutoLODEmitRatio(0.f), _DieCondition(none), _DelayBeforeDieTest(-1.f), _NumWantedTris(0), _AnimType(AnimInCluster), _UserParamGlobalValue(NULL), _BypassGlobalUserParam(0), _PresetBehaviour(UserBehaviour), _AutoLODStartDistPercent(0.1f), _AutoLODDegradationExponent(1), _ColorAttenuationScheme(NULL), _GlobalColor(NLMISC::CRGBA::White), _GlobalColorLighted(NLMISC::CRGBA::White), _LightingColor(NLMISC::CRGBA::White), _UserColor(NLMISC::CRGBA::White), _ComputeBBox(true), _BBoxTouched(true), _AccurateIntegration(true), _CanSlowDown(true), _DestroyModelWhenOutOfRange(false), _DestroyWhenOutOfFrustum(false), _Sharing(false), _AutoLOD(false), _KeepEllapsedTimeForLifeUpdate(false), _AutoLODSkipParticles(false), _EnableLoadBalancing(true), _EmitThreshold(true), _BypassIntegrationStepLimit(false), _ForceGlobalColorLighting(false), _AutoComputeDelayBeforeDeathTest(true), _AutoCount(false), _HiddenAtCurrentFrame(true), _HiddenAtPreviousFrame(true) { NL_PS_FUNC_MAIN(CParticleSystem_CParticleSystem) std::fill(_UserParam, _UserParam + MaxPSUserParam, 0.0f); #ifdef NL_DEBUG ++_NumInstances; #endif } std::vector > CParticleSystem::_Spawns; // spawns for the current system being processed std::vector CParticleSystem::_ParticleToRemove; std::vector CParticleSystem::_ParticleRemoveListIndex; ///======================================================================================= /// immediatly shut down all the sound in this system void CParticleSystem::stopSound() { NL_PS_FUNC_MAIN(CParticleSystem_stopSound) for (uint k = 0; k < this->getNbProcess(); ++k) { CPSLocated *psl = dynamic_cast(this->getProcess(k)); if (psl) { for (uint l = 0; l < psl->getNbBoundObjects(); ++l) { if (psl->getBoundObject(l)->getType() == PSSound) { static_cast(psl->getBoundObject(l))->stopSound(); } } } } } ///======================================================================================= void CParticleSystem::reactivateSound() { NL_PS_FUNC_MAIN(CParticleSystem_reactivateSound) for (uint k = 0; k < this->getNbProcess(); ++k) { CPSLocated *psl = dynamic_cast(this->getProcess(k)); if (psl) { for (uint l = 0; l < psl->getNbBoundObjects(); ++l) { if (psl->getBoundObject(l)->getType() == PSSound) { static_cast(psl->getBoundObject(l))->reactivateSound(); } } } } } ///======================================================================================= void CParticleSystem::enableLoadBalancing(bool enabled /*=true*/) { NL_PS_FUNC_MAIN(CParticleSystem_enableLoadBalancing) if (enabled) { //notifyMaxNumFacesChanged(); } _EnableLoadBalancing = enabled; } ///======================================================================================= /* void CParticleSystem::notifyMaxNumFacesChanged(void) { if (!_EnableLoadBalancing) return; _MaxNumFacesWanted = 0; for (TProcessVect::iterator it = _ProcessVect.begin(); it != _ProcessVect.end(); ++it) { _MaxNumFacesWanted += (*it)->querryMaxWantedNumFaces(); } } */ ///======================================================================================= void CParticleSystem::updateNumWantedTris() { NL_PS_FUNC_MAIN(CParticleSystem_updateNumWantedTris) _NumWantedTris = 0; for (TProcessVect::iterator it = _ProcessVect.begin(); it != _ProcessVect.end(); ++it) { _NumWantedTris += (*it)->getNumWantedTris(); } } ///======================================================================================= float CParticleSystem::getWantedNumTris(float dist) { NL_PS_FUNC_MAIN(CParticleSystem_getWantedNumTris) if (!_EnableLoadBalancing) return 0; // no contribution to the load balancing if (dist > _MaxViewDist) return 0; float retValue = ((1.f - dist * _InvMaxViewDist) * _NumWantedTris); ///nlassertex(retValue >= 0 && retValue < 10000, ("dist = %f, _MaxViewDist = %f, _MaxNumFacesWanted = %d, retValue = %f", dist, _MaxViewDist, _MaxNumFacesWanted, retValue)); return retValue; } ///======================================================================================= void CParticleSystem::setNumTris(uint numFaces) { NL_PS_FUNC_MAIN(CParticleSystem_setNumTris) if (_EnableLoadBalancing) { float modelDist = (getSysMat().getPos() - _InvertedViewMat.getPos()).norm(); /*uint numFaceWanted = (uint) getWantedNumTris(modelDist);*/ const float epsilon = 10E-5f; uint wantedNumTri = (uint) getWantedNumTris(modelDist); if (numFaces >= wantedNumTri || wantedNumTri == 0 || _NumWantedTris == 0 || modelDist < epsilon) { _InvCurrentViewDist = _InvMaxViewDist; } else { _InvCurrentViewDist = (_NumWantedTris - numFaces) / (_NumWantedTris * modelDist); } } else { // always take full detail when there's no load balancing _InvCurrentViewDist = _InvMaxViewDist; } } ///======================================================================================= /// dtor CParticleSystem::~CParticleSystem() { NL_PS_FUNC_MAIN(CParticleSystem_CParticleSystemDtor) for (TProcessVect::iterator it = _ProcessVect.begin(); it != _ProcessVect.end(); ++it) { delete *it; } if (_ColorAttenuationScheme) delete _ColorAttenuationScheme; if (_UserParamGlobalValue) delete _UserParamGlobalValue; delete _UserCoordSystemInfo; #ifdef NL_DEBUG --_NumInstances; #endif } ///======================================================================================= void CParticleSystem::setViewMat(const NLMISC::CMatrix &m) { NL_PS_FUNC_MAIN(CParticleSystem_setViewMat) _ViewMat = m; _InvertedViewMat = m.inverted(); } ///======================================================================================= bool CParticleSystem::hasEmitters(void) const { NL_PS_FUNC_MAIN(CParticleSystem_hasEmitters) for (TProcessVect::const_iterator it = _ProcessVect.begin(); it != _ProcessVect.end(); ++it) { if ((*it)->hasEmitters()) return true; } return false; } ///======================================================================================= bool CParticleSystem::hasParticles() const { NL_PS_FUNC_MAIN(CParticleSystem_hasParticles) for (TProcessVect::const_iterator it = _ProcessVect.begin(); it != _ProcessVect.end(); ++it) { if ((*it)->hasParticles()) return true; } return false; } ///======================================================================================= bool CParticleSystem::hasTemporaryParticles() const { NL_PS_FUNC_MAIN(CParticleSystem_hasTemporaryParticles) for (TProcessVect::const_iterator it = _ProcessVect.begin(); it != _ProcessVect.end(); ++it) { if ((*it)->isLocated()) { CPSLocated *loc = static_cast(*it); if (loc->hasParticles()) return true; } } return false; } ///======================================================================================= void CParticleSystem::stepLocated(TPSProcessPass pass) { NL_PS_FUNC_MAIN(CParticleSystem_stepLocated) for (TProcessVect::iterator it = _ProcessVect.begin(); it != _ProcessVect.end(); ++it) { (*it)->step(pass); } } ///======================================================================================= #ifndef NL_DEBUG inline #endif float CParticleSystem::getDistFromViewer() const { NL_PS_FUNC_MAIN(CParticleSystem_getDistFromViewer) const CVector d = getSysMat().getPos() - _InvertedViewMat.getPos(); return d.norm(); } ///======================================================================================= #ifndef NL_DEBUG inline #endif float CParticleSystem::updateLODRatio() { NL_PS_FUNC_MAIN(CParticleSystem_updateLODRatio) float dist = getDistFromViewer(); _OneMinusCurrentLODRatio = 1.f - (dist * _InvCurrentViewDist); NLMISC::clamp(_OneMinusCurrentLODRatio, 0.f, 1.f); return dist; } ///======================================================================================= inline void CParticleSystem::updateColor(float distFromViewer) { NL_PS_FUNC_MAIN(CParticleSystem_updateColor) if (_ColorAttenuationScheme) { float ratio = distFromViewer * _InvMaxViewDist; NLMISC::clamp(ratio, 0.f, 1.f); _GlobalColor = _ColorAttenuationScheme->get(ratio); } else { _GlobalColor = NLMISC::CRGBA::White; } _GlobalColor.modulateFromColor(_GlobalColor, _UserColor); _GlobalColorLighted.modulateFromColor(_GlobalColor, _LightingColor); } /* static void displaySysPos(IDriver *drv, const CVector &pos, CRGBA col) { if (!drv) return; drv->setupModelMatrix(CMatrix::Identity); CPSUtil::displayArrow(drv, pos, CVector::K, 1.f, CRGBA::White, col); } */ ///======================================================================================= void CParticleSystem::step(TPass pass, TAnimationTime ellapsedTime, CParticleSystemShape &shape, CParticleSystemModel &model) { NL_PS_FUNC_MAIN(CParticleSystem_step) CHECK_INTEGRITY OwnerModel = &model; nlassert(_CoordSystemInfo.Matrix); // matrix not set for position of system if (_UserCoordSystemInfo) { nlassert(_CoordSystemInfo.Matrix); } switch (pass) { case SolidRender: EllapsedTime = RealEllapsedTime = ellapsedTime; RealEllapsedTimeRatio = 1.f; /// When shared, the LOD ratio must be computed there if (_Sharing) { float dist = updateLODRatio(); updateColor(dist); } else { updateColor(getDistFromViewer()); } // update time ++_Date; // update global color stepLocated(PSSolidRender); break; case BlendRender: EllapsedTime = RealEllapsedTime = ellapsedTime; RealEllapsedTimeRatio = 1.f; /// When shared, the LOD ratio must be computed there /// When shared, the LOD ratio must be computed there if (_Sharing) { float dist = updateLODRatio(); updateColor(dist); } else { updateColor(getDistFromViewer()); } // update time ++_Date; // update global color stepLocated(PSBlendRender); if (_ForceDisplayBBox) { NLMISC::CAABBox box; computeBBox(box); getDriver()->setupModelMatrix(*_CoordSystemInfo.Matrix); CPSUtil::displayBBox(getDriver(), box); } break; case ToolRender: EllapsedTime = RealEllapsedTime = ellapsedTime; RealEllapsedTimeRatio = 1.f; stepLocated(PSToolRender); break; case Anim: { if (ellapsedTime <= 0.f) return; // update user param from global value if needed, unless this behaviour is bypassed has indicated by a flag in _BypassGlobalUserParam if (_UserParamGlobalValue) { nlctassert(MaxPSUserParam < 8); // there should be less than 8 parameters because of mask stored in a byte uint8 bypassMask = 1; for(uint k = 0; k < MaxPSUserParam; ++k) { if (_UserParamGlobalValue[k] && !(_BypassGlobalUserParam & bypassMask)) // if there is a global value for this param and if the update is not bypassed { _UserParam[k] = _UserParamGlobalValue[k]->second; } bypassMask <<= 1; } } // uint nbPass = 1; EllapsedTime = ellapsedTime; _BBoxTouched = true; if (_AccurateIntegration) { if (EllapsedTime > _TimeThreshold) { nbPass = (uint32) ceilf(EllapsedTime / _TimeThreshold); if (!_BypassIntegrationStepLimit && nbPass > _MaxNbIntegrations) { nbPass = _MaxNbIntegrations; if (_CanSlowDown) { EllapsedTime = _TimeThreshold; nlassert(_TimeThreshold != 0); InverseTotalEllapsedTime = 1.f / (_TimeThreshold * nbPass); } else { EllapsedTime = ellapsedTime / nbPass; InverseTotalEllapsedTime = ellapsedTime != 0 ? 1.f / ellapsedTime : 0.f; } } else { EllapsedTime = ellapsedTime / nbPass; InverseTotalEllapsedTime = ellapsedTime != 0 ? 1.f / ellapsedTime : 0.f; } } else { InverseTotalEllapsedTime = ellapsedTime != 0 ? 1.f / ellapsedTime : 0.f; } } else { InverseTotalEllapsedTime = ellapsedTime != 0 ? 1.f / ellapsedTime : 0.f; } updateLODRatio(); { MINI_TIMER(PSAnim3) if (_AutoLOD && !_Sharing) { float currLODRatio = 1.f - _OneMinusCurrentLODRatio; if (currLODRatio <= _AutoLODStartDistPercent) { _AutoLODEmitRatio = 1.f; // no LOD applied } else { float lodValue = (currLODRatio - 1.f) / (_AutoLODStartDistPercent - 1.f); NLMISC::clamp(lodValue, 0.f, 1.f); float finalValue = lodValue; for(uint l = 1; l < _AutoLODDegradationExponent; ++l) { finalValue *= lodValue; } _AutoLODEmitRatio = (1.f - _MaxDistLODBias) * finalValue + _MaxDistLODBias; if (_AutoLODEmitRatio < 0.f) _AutoLODEmitRatio = 0.f; } } } { MINI_TIMER(PSAnim4) // set start position. Used by emitters that emit from Local basis to world if (!_HiddenAtPreviousFrame && !_HiddenAtCurrentFrame) { _CoordSystemInfo.CurrentDeltaPos = _CoordSystemInfo.OldPos - _CoordSystemInfo.Matrix->getPos(); if (_UserCoordSystemInfo) { CCoordSystemInfo &csi = _UserCoordSystemInfo->CoordSystemInfo; csi.CurrentDeltaPos = csi.OldPos - csi.Matrix->getPos(); } } else { _CoordSystemInfo.CurrentDeltaPos = NLMISC::CVector::Null; _CoordSystemInfo.OldPos = _CoordSystemInfo.Matrix->getPos(); if (_UserCoordSystemInfo) { CCoordSystemInfo &csi = _UserCoordSystemInfo->CoordSystemInfo; csi.CurrentDeltaPos = NLMISC::CVector::Null; csi.OldPos = csi.Matrix->getPos(); } } //displaySysPos(_Driver, _CurrentDeltaPos + _OldSysMat.getPos(), CRGBA::Red); // process passes } RealEllapsedTime = _KeepEllapsedTimeForLifeUpdate ? (ellapsedTime / nbPass) : EllapsedTime; RealEllapsedTimeRatio = RealEllapsedTime / EllapsedTime; /*PSMaxET = std::max(PSMaxET, ellapsedTime); PSMaxNBPass = std::max(PSMaxNBPass, nbPass);*/ // Sort by emission depth. We assume that the ps is a directed acyclic graph (so no loop will be encountered) if (shape._ProcessOrder.empty()) { getSortingByEmitterPrecedence(shape._ProcessOrder); } // nodes sorted by degree InsideSimLoop = true; // make enough room for spawns uint numProcess = (uint)_ProcessVect.size(); if (numProcess > _Spawns.size()) { uint oldSize = (uint)_Spawns.size(); _Spawns.resize(numProcess); for(uint k = oldSize; k < numProcess; ++k) { _Spawns[k] = new CSpawnVect; } } for(uint k = 0; k < numProcess; ++k) { if (!_ProcessVect[k]->isLocated()) continue; CPSLocated *loc = static_cast(_ProcessVect[k]); if (loc->hasLODDegradation()) { loc->doLODDegradation(); } _Spawns[k]->MaxNumSpawns = loc->getMaxSize(); } do { { MINI_TIMER(PSAnim5) // position of the system at the end of the integration _CoordSystemInfo.CurrentDeltaPos += (_CoordSystemInfo.Matrix->getPos() - _CoordSystemInfo.OldPos) * (EllapsedTime * InverseTotalEllapsedTime); if (_UserCoordSystemInfo) { CCoordSystemInfo &csi = _UserCoordSystemInfo->CoordSystemInfo; csi.CurrentDeltaPos += (csi.Matrix->getPos() - csi.OldPos) * (EllapsedTime * InverseTotalEllapsedTime); } } for(uint k = 0; k < shape._ProcessOrder.size(); ++k) { if (!_ProcessVect[shape._ProcessOrder[k]]->isLocated()) continue; CPSLocated *loc = static_cast(_ProcessVect[shape._ProcessOrder[k]]); if (_ParticleRemoveListIndex.size() < loc->getMaxSize()) { _ParticleRemoveListIndex.resize(loc->getMaxSize(), -1); } if (loc->getSize() != 0) { #ifdef NL_DEBUG loc->checkLife(); #endif if (loc->hasCollisionInfos()) { loc->resetCollisions(loc->getSize()); } // compute motion (including collisions) if (!loc->isParametricMotionEnabled()) loc->computeMotion(); // Update life and mark particles that must be removed loc->updateLife(); // Spawn particles. Emitters date is updated only after so we check in CPSLocated::postNewElement // if the emitter was still alive at this date, otherwise we discard the post loc->computeSpawns(0, false); if (loc->hasCollisionInfos()) loc->updateCollisions(); // Remove too old particles, making room for new ones if (!_ParticleToRemove.empty()) { loc->removeOldParticles(); } #ifdef NL_DEBUG loc->checkLife(); #endif } if (!_Spawns[shape._ProcessOrder[k]]->SpawnInfos.empty()) { uint insertionIndex = loc->getSize(); // index at which new particles will be inserted // add new particles that where posted by ancestor emitters, and also mark those that must already be deleted loc->addNewlySpawnedParticles(); if (insertionIndex != loc->getSize()) { // Compute motion for spawned particles. This is useful only if particle can collide because if (loc->hasCollisionInfos()) { loc->resetCollisions(loc->getSize()); loc->computeNewParticleMotion(insertionIndex); } loc->computeSpawns(insertionIndex, true); if (loc->hasCollisionInfos()) loc->updateCollisions(); // Remove too old particles among the newly created ones. if (!_ParticleToRemove.empty()) { loc->removeOldParticles(); } #ifdef NL_DEBUG loc->checkLife(); #endif } } if (!loc->isParametricMotionEnabled()) loc->computeForces(); } _SystemDate += RealEllapsedTime; for (TProcessVect::iterator it = _ProcessVect.begin(); it != _ProcessVect.end(); ++it) { #ifdef NL_DEBUG if ((*it)->isLocated()) { CPSLocated *loc = static_cast(*it); loc->checkLife(); } #endif { MINI_TIMER(PSAnim2) (*it)->step(PSMotion); } } } while (--nbPass); { MINI_TIMER(PSAnim10) // perform parametric motion if present for (TProcessVect::iterator it = _ProcessVect.begin(); it != _ProcessVect.end(); ++it) { if ((*it)->isParametricMotionEnabled()) (*it)->performParametricMotion(_SystemDate); } } updateNumWantedTris(); InsideSimLoop = false; { MINI_TIMER(PSAnim11) // memorize position of matrix for next frame (becomes old position) _CoordSystemInfo.OldPos = _CoordSystemInfo.Matrix->getPos(); if (_UserCoordSystemInfo) { CCoordSystemInfo &csi = _UserCoordSystemInfo->CoordSystemInfo; csi.OldPos = csi.Matrix->getPos(); } _HiddenAtPreviousFrame = _HiddenAtCurrentFrame; } } } RealEllapsedTimeRatio = 0.f; CHECK_INTEGRITY } ///======================================================================================= void CParticleSystem::serial(NLMISC::IStream &f) throw(NLMISC::EStream) { CHECK_INTEGRITY NL_PS_FUNC_MAIN(CParticleSystem_serial) sint version = f.serialVersion(19); // version 19: sysmat no more serialized (useless) // version 18: _AutoComputeDelayBeforeDeathTest // version 17: _ForceGlobalColorLighting flag // version 16: _BypassIntegrationStepLimit flag // version 14: emit threshold // version 13: max dist lod bias for auto-LOD // version 12: global userParams // version 11: enable load balancing flag // version 9: Sharing flag added // Auto-lod parameters // New integration flag // Global color attenuation // version 8: Replaced the attribute '_PerformMotionWhenOutOfFrustum' by a _AnimType field which allow more precise control //f.serial(_ViewMat); // patch for old fx : force to recompute duration when fx is saved to avoid prbs if (!f.isReading()) { if (_AutoComputeDelayBeforeDeathTest) { _DelayBeforeDieTest = evalDuration(); } } if (version < 19) { NLMISC::CMatrix dummy; f.serial(dummy); } f.serial(_Date); if (f.isReading()) { delete _ColorAttenuationScheme; // delete previous multimap _LBMap.clear(); // delete previously attached process TProcessVect::iterator it; for (it = _ProcessVect.begin(); it != _ProcessVect.end(); ++it) { delete (*it); } _ProcessVect.clear(); f.serialContPolyPtr(_ProcessVect); _FontGenerator = NULL; _FontManager = NULL; if (_UserParamGlobalValue) delete _UserParamGlobalValue; _UserParamGlobalValue = NULL; _BypassGlobalUserParam = 0; // see if some process need to access the user matrix delete _UserCoordSystemInfo; _UserCoordSystemInfo = NULL; for (TProcessVect::iterator it = _ProcessVect.begin(); it != _ProcessVect.end(); ++it) { addRefForUserSysCoordInfo((*it)->getUserMatrixUsageCount()); } } else { f.serialContPolyPtr(_ProcessVect); } if (version > 1) // name of the system { if (f.isReading() && !getSerializeIdentifierFlag()) { // just skip the name sint32 len; f.serial(len); f.seek(len, NLMISC::IStream::current); } else { f.serial(_Name); } } if (version > 2) // infos about integration, and LOD { bool accurateIntegration = _AccurateIntegration; // read from bitfield f.serial(accurateIntegration); _AccurateIntegration = accurateIntegration; if (_AccurateIntegration) { bool canSlowDown = _CanSlowDown; f.serial(canSlowDown); _CanSlowDown = canSlowDown; f.serial(_TimeThreshold, _MaxNbIntegrations); } f.serial(_InvMaxViewDist, _LODRatio); _MaxViewDist = 1.f / _InvMaxViewDist; _InvCurrentViewDist = _InvMaxViewDist; } if (version > 3) // tell whether the system must compute his bbox, hold a precomputed bbox { bool computeBBox = _ComputeBBox; f.serial(computeBBox); _ComputeBBox = computeBBox; if (!computeBBox) { f.serial(_PreComputedBBox); } } if (version > 4) // lifetime information { bool destroyModelWhenOutOfRange = _DestroyModelWhenOutOfRange; // read from bitfield f.serial(destroyModelWhenOutOfRange); _DestroyModelWhenOutOfRange = destroyModelWhenOutOfRange; f.serialEnum(_DieCondition); if (_DieCondition != none) { f.serial(_DelayBeforeDieTest); } } if (version > 5) { bool destroyWhenOutOfFrustum = _DestroyWhenOutOfFrustum; // read from bitfield f.serial(destroyWhenOutOfFrustum); _DestroyWhenOutOfFrustum = destroyWhenOutOfFrustum; } if (version > 6 && version < 8) { bool performMotionWOOF; if (f.isReading()) { f.serial(performMotionWOOF); performMotionWhenOutOfFrustum(performMotionWOOF); } else { performMotionWOOF = doesPerformMotionWhenOutOfFrustum(); f.serial(performMotionWOOF); } } if (version > 7) { f.serialEnum(_AnimType); f.serialEnum(_PresetBehaviour); } if (version > 8) { bool sharing = _Sharing; // read from bitfield f.serial(sharing); _Sharing = sharing; bool autoLOD = _AutoLOD; // read from bitfield f.serial(autoLOD); _AutoLOD = autoLOD; if (_AutoLOD) { f.serial(_AutoLODStartDistPercent, _AutoLODDegradationExponent); bool autoLODSkipParticles = _AutoLODSkipParticles; // read from bitfield f.serial(autoLODSkipParticles); _AutoLODSkipParticles = autoLODSkipParticles; } bool keepEllapsedTimeForLifeUpdate = _KeepEllapsedTimeForLifeUpdate; f.serial(keepEllapsedTimeForLifeUpdate); _KeepEllapsedTimeForLifeUpdate = keepEllapsedTimeForLifeUpdate; f.serialPolyPtr(_ColorAttenuationScheme); } if (version >= 11) { bool enableLoadBalancing = _EnableLoadBalancing; // read from bitfield f.serial(enableLoadBalancing); _EnableLoadBalancing = enableLoadBalancing; } if (version >= 12) { // serial infos about global user params nlctassert(MaxPSUserParam < 8); // In this version mask of used global user params are stored in a byte.. if (f.isReading()) { uint8 mask; f.serial(mask); if (mask) { std::string globalValueName; uint8 testMask = 1; for(uint k = 0; k < MaxPSUserParam; ++k) { if (mask & testMask) { f.serial(globalValueName); bindGlobalValueToUserParam(globalValueName.c_str(), k); } testMask <<= 1; } } } else { uint8 mask = 0; if (_UserParamGlobalValue) { for(uint k = 0; k < MaxPSUserParam; ++k) { if (_UserParamGlobalValue[k]) mask |= (1 << k); } } f.serial(mask); if (_UserParamGlobalValue) { for(uint k = 0; k < MaxPSUserParam; ++k) { if (_UserParamGlobalValue[k]) { std::string valueName = _UserParamGlobalValue[k]->first; f.serial(valueName); } } } } } if (version >= 13) { if (_AutoLOD && !_Sharing) { f.serial(_MaxDistLODBias); } } if (version >= 14) { bool emitThreshold = _EmitThreshold; // read from bitfiled f.serial(emitThreshold); _EmitThreshold = emitThreshold; } if (version >= 15) { bool bypassIntegrationStepLimit = _BypassIntegrationStepLimit; // read from bitfield f.serial(bypassIntegrationStepLimit); _BypassIntegrationStepLimit = bypassIntegrationStepLimit; } if (version >= 17) { bool forceGlobalColorLighting = _ForceGlobalColorLighting; // read from bitfield f.serial(forceGlobalColorLighting); _ForceGlobalColorLighting = forceGlobalColorLighting; } if (version >= 18) { bool autoComputeDelayBeforeDeathTest = _AutoComputeDelayBeforeDeathTest; // read from bitfield f.serial(autoComputeDelayBeforeDeathTest); _AutoComputeDelayBeforeDeathTest = autoComputeDelayBeforeDeathTest; } else { nlassert(f.isReading()); // for all previously created system, force to eval the system duration in an automatyic way setDelayBeforeDeathConditionTest(-1.f); _AutoComputeDelayBeforeDeathTest = true; } if (f.isReading()) { //notifyMaxNumFacesChanged(); updateNumWantedTris(); activatePresetBehaviour(_PresetBehaviour); // apply behaviour changes updateProcessIndices(); } CHECK_INTEGRITY // tmp //if (f.isReading()) // { // dumpHierarchy(); // } } ///======================================================================================= bool CParticleSystem::attach(CParticleSystemProcess *ptr) { NL_PS_FUNC_MAIN(CParticleSystem_attach) nlassert(ptr); nlassert(std::find(_ProcessVect.begin(), _ProcessVect.end(), ptr) == _ProcessVect.end() ); // can't attach twice //nlassert(ptr->getOwner() == NULL); _ProcessVect.push_back(ptr); ptr->setOwner(this); ptr->setIndex((uint32)_ProcessVect.size() - 1); //notifyMaxNumFacesChanged(); if (getBypassMaxNumIntegrationSteps()) { if (!canFinish()) { remove(ptr); nlwarning(" Can't attach object : this causes the system to last forever, and it has been flagged with 'BypassMaxNumIntegrationSteps'. Object is not attached"); return false; } } systemDurationChanged(); return true; } ///======================================================================================= void CParticleSystem::remove(CParticleSystemProcess *ptr) { NL_PS_FUNC_MAIN(CParticleSystem_remove) TProcessVect::iterator it = std::find(_ProcessVect.begin(), _ProcessVect.end(), ptr); nlassert(it != _ProcessVect.end() ); ptr->setOwner(NULL); _ProcessVect.erase(it); delete ptr; systemDurationChanged(); updateProcessIndices(); } ///======================================================================================= void CParticleSystem::forceComputeBBox(NLMISC::CAABBox &aabbox) { NL_PS_FUNC_MAIN(CParticleSystem_forceComputeBBox) bool foundOne = false; NLMISC::CAABBox tmpBox; for (TProcessVect::const_iterator it = _ProcessVect.begin(); it != _ProcessVect.end(); ++it) { if ((*it)->computeBBox(tmpBox)) { // rotate the aabbox so that it is in the correct basis const CMatrix &convMat = CPSLocated::getConversionMatrix(*this, PSFXWorldMatrix, (*it)->getMatrixMode()); tmpBox = NLMISC::CAABBox::transformAABBox(convMat, tmpBox); if (foundOne) { aabbox = NLMISC::CAABBox::computeAABBoxUnion(aabbox, tmpBox); } else { aabbox = tmpBox; foundOne = true; } } } if (!foundOne) { aabbox.setCenter(NLMISC::CVector::Null); aabbox.setHalfSize(NLMISC::CVector::Null); } } ///======================================================================================= void CParticleSystem::computeBBox(NLMISC::CAABBox &aabbox) { NL_PS_FUNC_MAIN(CParticleSystem_computeBBox) if (!_ComputeBBox || !_BBoxTouched) { aabbox = _PreComputedBBox; return; } forceComputeBBox(aabbox); _BBoxTouched = false; _PreComputedBBox = aabbox; } ///======================================================================================= void CParticleSystem::setSysMat(const CMatrix *m) { NL_PS_FUNC_MAIN(CParticleSystem_setSysMat) _CoordSystemInfo.Matrix = m; if (_SystemDate == 0.f) { _CoordSystemInfo.OldPos = m ? m->getPos() : CVector::Null; } if (!m) return; _CoordSystemInfo.InvMatrix = _CoordSystemInfo.Matrix->inverted(); } ///======================================================================================= void CParticleSystem::setUserMatrix(const NLMISC::CMatrix *m) { NL_PS_FUNC_MAIN(CParticleSystem_setUserMatrix) if (!_UserCoordSystemInfo) return; // no process in the system references the user matrix CCoordSystemInfo &csi = _UserCoordSystemInfo->CoordSystemInfo; csi.Matrix = m; if (_SystemDate == 0.f) { csi.OldPos = m ? m->getPos() : getSysMat().getPos(); // _CoordSystemInfo.Matrix is relevant if at least one call to setSysMat has been performed before } if (!m) return; csi.InvMatrix = csi.Matrix->inverted(); // build conversion matrix between father user matrix & fx matrix // TODO : lazy evaluation for this ? _UserCoordSystemInfo->UserBasisToFXBasis = _CoordSystemInfo.InvMatrix * *(csi.Matrix); _UserCoordSystemInfo->FXBasisToUserBasis = csi.InvMatrix * getSysMat(); } ///======================================================================================= bool CParticleSystem::hasOpaqueObjects(void) const { NL_PS_FUNC_MAIN(CParticleSystem_hasOpaqueObjects) /// for each process for (TProcessVect::const_iterator it = _ProcessVect.begin(); it != _ProcessVect.end(); ++it) { if ((*it)->isLocated()) { CPSLocated *loc = static_cast(*it); for (uint k = 0; k < loc->getNbBoundObjects(); ++k) { CPSLocatedBindable *lb = loc->getBoundObject(k); if (lb->getType() == PSParticle) { if (((CPSParticle *) lb)->hasOpaqueFaces()) return true; } } } } return false; } ///======================================================================================= bool CParticleSystem::hasTransparentObjects(void) const { NL_PS_FUNC_MAIN(CParticleSystem_hasTransparentObjects) /// for each process for (TProcessVect::const_iterator it = _ProcessVect.begin(); it != _ProcessVect.end(); ++it) { if ((*it)->isLocated()) { CPSLocated *loc = static_cast(*it); for (uint k = 0; k < loc->getNbBoundObjects(); ++k) { CPSLocatedBindable *lb = loc->getBoundObject(k); if (lb->getType() == PSParticle) { if (((CPSParticle *) lb)->hasTransparentFaces()) return true; } } } } return false; } ///======================================================================================= bool CParticleSystem::hasLightableObjects() const { NL_PS_FUNC_MAIN(CParticleSystem_hasLightableObjects) if (_ForceGlobalColorLighting) return true; /// for each process for (TProcessVect::const_iterator it = _ProcessVect.begin(); it != _ProcessVect.end(); ++it) { if ((*it)->isLocated()) { CPSLocated *loc = static_cast(*it); for (uint k = 0; k < loc->getNbBoundObjects(); ++k) { CPSLocatedBindable *lb = loc->getBoundObject(k); if (lb->getType() == PSParticle) { if (((CPSParticle *) lb)->hasLightableFaces()) return true; if (((CPSParticle *) lb)->usesGlobalColorLighting()) return true; } } } } return false; } ///======================================================================================= void CParticleSystem::getLODVect(NLMISC::CVector &v, float &offset, TPSMatrixMode matrixMode) { NL_PS_FUNC_MAIN(CParticleSystem_getLODVect) switch(matrixMode) { case PSFXWorldMatrix: { const CVector tv = getInvertedSysMat().mulVector(_InvertedViewMat.getJ()); const CVector org = getInvertedSysMat() * _InvertedViewMat.getPos(); v = _InvCurrentViewDist * tv; offset = - org * v; } break; case PSIdentityMatrix: { v = _InvCurrentViewDist * _InvertedViewMat.getJ(); offset = - _InvertedViewMat.getPos() * v; } break; case PSUserMatrix: { const CVector tv = getInvertedUserMatrix().mulVector(_InvertedViewMat.getJ()); const CVector org = getInvertedUserMatrix() * _InvertedViewMat.getPos(); v = _InvCurrentViewDist * tv; offset = - org * v; } break; default: nlassert(0); break; } } ///======================================================================================= TPSLod CParticleSystem::getLOD(void) const { NL_PS_FUNC_MAIN(CParticleSystem_getLOD) const float dist = fabsf(_InvCurrentViewDist * (getSysMat().getPos() - _InvertedViewMat.getPos()) * _InvertedViewMat.getJ()); return dist > _LODRatio ? PSLod2 : PSLod1; } ///======================================================================================= void CParticleSystem::registerLocatedBindableExternID(uint32 id, CPSLocatedBindable *lb) { NL_PS_FUNC_MAIN(CParticleSystem_registerLocatedBindableExternID) nlassert(lb); nlassert(lb->getOwner() && lb->getOwner()->getOwner() == this); // the located bindable must belong to that system #ifdef NL_DEBUG // check that this lb hasn't been inserted yet TLBMap::iterator lbd = _LBMap.lower_bound(id), ubd = _LBMap.upper_bound(id); nlassert(std::find(lbd, ubd, TLBMap::value_type (id, lb)) == ubd); nlassert(std::find(lbd, ubd, TLBMap::value_type (id, lb)) == ubd ); #endif _LBMap.insert(TLBMap::value_type (id, lb) ); } ///======================================================================================= void CParticleSystem::unregisterLocatedBindableExternID(CPSLocatedBindable *lb) { NL_PS_FUNC_MAIN(CParticleSystem_unregisterLocatedBindableExternID) nlassert(lb); nlassert(lb->getOwner() && lb->getOwner()->getOwner() == this); // the located bindable must belong to that system uint32 id = lb->getExternID(); if (!id) return; TLBMap::iterator lbd = _LBMap.lower_bound(id), ubd = _LBMap.upper_bound(id); TLBMap::iterator el = std::find(lbd, ubd, TLBMap::value_type (id, lb)); nlassert(el != ubd); _LBMap.erase(el); } ///======================================================================================= uint CParticleSystem::getNumLocatedBindableByExternID(uint32 id) const { NL_PS_FUNC_MAIN(CParticleSystem_getNumLocatedBindableByExternID) return (uint)_LBMap.count(id); } ///======================================================================================= CPSLocatedBindable *CParticleSystem::getLocatedBindableByExternID(uint32 id, uint index) { NL_PS_FUNC_MAIN(CParticleSystem_getLocatedBindableByExternID) if (index >= _LBMap.count(id)) { return NULL; } TLBMap::const_iterator el = _LBMap.lower_bound(id); uint left = index; while (left--) ++el; return el->second; } ///======================================================================================= const CPSLocatedBindable *CParticleSystem::getLocatedBindableByExternID(uint32 id, uint index) const { NL_PS_FUNC_MAIN(CParticleSystem_getLocatedBindableByExternID) if (index >= _LBMap.count(id)) { return NULL; } TLBMap::const_iterator el = _LBMap.lower_bound(id); uint left = index; while (left--) ++el; return el->second; } ///======================================================================================= bool CParticleSystem::merge(CParticleSystemShape *pss) { NL_PS_FUNC_MAIN(CParticleSystem_merge) nlassert(pss); nlassert(_Scene); CParticleSystem *duplicate = pss->instanciatePS(*this->_Scene); // duplicate the p.s. to merge // now we transfer the located of the duplicated ps to this object... for (TProcessVect::iterator it = duplicate->_ProcessVect.begin(); it != duplicate->_ProcessVect.end(); ++it) { if (!attach(*it)) { for (TProcessVect::iterator clearIt = duplicate->_ProcessVect.begin(); clearIt != it; ++it) { detach(getIndexOf(**it)); } nlwarning(" Can't do the merge : this causes the system to last forever, and it has been flagged with 'BypassMaxNumIntegrationSteps'. Merge is not done."); return false; } } // if (getBypassMaxNumIntegrationSteps()) { if (!canFinish()) { for (TProcessVect::iterator it = duplicate->_ProcessVect.begin(); it != duplicate->_ProcessVect.end(); ++it) { detach(getIndexOf(**it)); } nlwarning(" Can't do the merge : this causes the system to last forever, and it has been flagged with 'BypassMaxNumIntegrationSteps'. Merge is not done."); return false; } } // duplicate->_ProcessVect.clear(); delete duplicate; systemDurationChanged(); CHECK_INTEGRITY return true; } ///======================================================================================= void CParticleSystem::activatePresetBehaviour(TPresetBehaviour behaviour) { NL_PS_FUNC_MAIN(CParticleSystem_activatePresetBehaviour) switch(behaviour) { case EnvironmentFX: setDestroyModelWhenOutOfRange(false); setDestroyCondition(none); destroyWhenOutOfFrustum(false); setAnimType(AnimVisible); setBypassMaxNumIntegrationSteps(false); _KeepEllapsedTimeForLifeUpdate = false; break; case RunningEnvironmentFX: setDestroyModelWhenOutOfRange(false); setDestroyCondition(none); destroyWhenOutOfFrustum(false); setAnimType(AnimInCluster); setBypassMaxNumIntegrationSteps(false); _KeepEllapsedTimeForLifeUpdate = false; break; case SpellFX: setDestroyModelWhenOutOfRange(true); setDestroyCondition(noMoreParticles); destroyWhenOutOfFrustum(false); setAnimType(AnimAlways); setBypassMaxNumIntegrationSteps(true); _KeepEllapsedTimeForLifeUpdate = false; break; case LoopingSpellFX: setDestroyModelWhenOutOfRange(false); setDestroyCondition(noMoreParticles); destroyWhenOutOfFrustum(false); // setAnimType(AnimInCluster); // TODO : AnimAlways could be better ? setAnimType(AnimVisible); setBypassMaxNumIntegrationSteps(false); _KeepEllapsedTimeForLifeUpdate = false; break; case MinorFX: setDestroyModelWhenOutOfRange(true); setDestroyCondition(noMoreParticles); destroyWhenOutOfFrustum(true); setAnimType(AnimVisible); setBypassMaxNumIntegrationSteps(false); _KeepEllapsedTimeForLifeUpdate = false; break; case MovingLoopingFX: setDestroyModelWhenOutOfRange(false); setDestroyCondition(none); destroyWhenOutOfFrustum(false); setAnimType(AnimVisible); setBypassMaxNumIntegrationSteps(false); _KeepEllapsedTimeForLifeUpdate = true; break; case SpawnedEnvironmentFX: setDestroyModelWhenOutOfRange(true); setDestroyCondition(noMoreParticles); destroyWhenOutOfFrustum(false); setAnimType(AnimAlways); setBypassMaxNumIntegrationSteps(false); _KeepEllapsedTimeForLifeUpdate = false; break; case GroundFX: setDestroyModelWhenOutOfRange(false); setDestroyCondition(none); destroyWhenOutOfFrustum(false); setAnimType(AnimAlways); setBypassMaxNumIntegrationSteps(false); _KeepEllapsedTimeForLifeUpdate = true; break; case Projectile: setDestroyModelWhenOutOfRange(false); setDestroyCondition(noMoreParticles); destroyWhenOutOfFrustum(false); setAnimType(AnimVisible); setBypassMaxNumIntegrationSteps(false); _KeepEllapsedTimeForLifeUpdate = true; break; default: break; } _PresetBehaviour = behaviour; } ///======================================================================================= CParticleSystemProcess *CParticleSystem::detach(uint index) { NL_PS_FUNC_MAIN(CParticleSystem_detach) nlassert(index < _ProcessVect.size()); CParticleSystemProcess *proc = _ProcessVect[index]; // release references other process may have to this system for(TProcessVect::iterator it = _ProcessVect.begin(); it != _ProcessVect.end(); ++it) { (*it)->releaseRefTo(proc); } // erase from the vector _ProcessVect.erase(_ProcessVect.begin() + index); proc->setOwner(NULL); // systemDurationChanged(); // not part of this system any more return proc; } ///======================================================================================= bool CParticleSystem::isProcess(const CParticleSystemProcess *process) const { NL_PS_FUNC_MAIN(CParticleSystem_isProcess) for(TProcessVect::const_iterator it = _ProcessVect.begin(); it != _ProcessVect.end(); ++it) { if (*it == process) return true; } return false; } ///======================================================================================= uint CParticleSystem::getIndexOf(const CParticleSystemProcess &process) const { NL_PS_FUNC_MAIN(CParticleSystem_getIndexOf) #ifdef NL_DEBUG nlassert(isProcess(&process)); #endif return process.getIndex(); } ///======================================================================================= uint CParticleSystem::getNumID() const { NL_PS_FUNC_MAIN(CParticleSystem_getNumID) return (uint)_LBMap.size(); } ///======================================================================================= uint32 CParticleSystem::getID(uint index) const { NL_PS_FUNC_MAIN(CParticleSystem_getID) TLBMap::const_iterator it = _LBMap.begin(); for(uint k = 0; k < index; ++k) { if (it == _LBMap.end()) return 0; ++it; } return it->first; } ///======================================================================================= void CParticleSystem::getIDs(std::vector &dest) const { NL_PS_FUNC_MAIN(CParticleSystem_getIDs) dest.resize(_LBMap.size()); uint k = 0; for(TLBMap::const_iterator it = _LBMap.begin(); it != _LBMap.end(); ++it) { dest[k] = it->first; ++k; } } ///======================================================================================= void CParticleSystem::interpolateFXPosDelta(NLMISC::CVector &dest, TAnimationTime deltaT) { NL_PS_FUNC_MAIN(CParticleSystem_interpolateFXPosDelta) nlassert(_CoordSystemInfo.Matrix); dest = _CoordSystemInfo.CurrentDeltaPos - (deltaT * InverseTotalEllapsedTime) * (_CoordSystemInfo.Matrix->getPos() - _CoordSystemInfo.OldPos); } ///======================================================================================= void CParticleSystem::interpolateUserPosDelta(NLMISC::CVector &dest, TAnimationTime deltaT) { NL_PS_FUNC_MAIN(CParticleSystem_interpolateUserPosDelta) if (!_UserCoordSystemInfo) { interpolateFXPosDelta(dest, deltaT); } else { CCoordSystemInfo &csi = _UserCoordSystemInfo->CoordSystemInfo; dest = csi.CurrentDeltaPos - (deltaT * InverseTotalEllapsedTime) * (csi.Matrix->getPos() - csi.OldPos); } } ///======================================================================================= void CParticleSystem::bindGlobalValueToUserParam(const std::string &globalValueName, uint userParamIndex) { NL_PS_FUNC_MAIN(CParticleSystem_bindGlobalValueToUserParam) nlassert(userParamIndex < MaxPSUserParam); if (globalValueName.empty()) // disable a user param global value { if (!_UserParamGlobalValue) return; _UserParamGlobalValue[userParamIndex] = NULL; for(uint k = 0; k < MaxPSUserParam; ++k) { if (_UserParamGlobalValue[k] != NULL) return; } // no more entry used delete _UserParamGlobalValue; _UserParamGlobalValue = NULL; } else // enable a user param global value { if (!_UserParamGlobalValue) { // no table has been allocated yet, so create one _UserParamGlobalValue = new const TGlobalValuesMap::value_type *[MaxPSUserParam]; std::fill(_UserParamGlobalValue, _UserParamGlobalValue + MaxPSUserParam, (TGlobalValuesMap::value_type *) NULL); } // has the global value be created yet ? TGlobalValuesMap::const_iterator it = _GlobalValuesMap.find(globalValueName); if (it != _GlobalValuesMap.end()) { // yes, make a reference on it _UserParamGlobalValue[userParamIndex] = &(*it); } else { // create a new entry std::pair itPair = _GlobalValuesMap.insert(TGlobalValuesMap::value_type(globalValueName, 0.f)); _UserParamGlobalValue[userParamIndex] = &(*(itPair.first)); } } } ///======================================================================================= void CParticleSystem::setGlobalValue(const std::string &name, float value) { NL_PS_FUNC(CParticleSystem_setGlobalValue) nlassert(!name.empty()); NLMISC::clamp(value, 0.f, 1.f); _GlobalValuesMap[name] = value; } ///======================================================================================= float CParticleSystem::getGlobalValue(const std::string &name) { NL_PS_FUNC(CParticleSystem_getGlobalValue) TGlobalValuesMap::const_iterator it = _GlobalValuesMap.find(name); if (it != _GlobalValuesMap.end()) return it->second; return 0.f; // not a known value } ///======================================================================================= std::string CParticleSystem::getGlobalValueName(uint userParamIndex) const { NL_PS_FUNC_MAIN(CParticleSystem_getGlobalValueName) nlassert(userParamIndex < MaxPSUserParam); if (!_UserParamGlobalValue) return ""; if (!_UserParamGlobalValue[userParamIndex]) return ""; return _UserParamGlobalValue[userParamIndex]->first; } ///======================================================================================= void CParticleSystem::setGlobalVectorValue(const std::string &name, const NLMISC::CVector &value) { NL_PS_FUNC(CParticleSystem_setGlobalVectorValue) nlassert(!name.empty()); _GlobalVectorValuesMap[name] = value; } ///======================================================================================= NLMISC::CVector CParticleSystem::getGlobalVectorValue(const std::string &name) { NL_PS_FUNC(CParticleSystem_getGlobalVectorValue) nlassert(!name.empty()); TGlobalVectorValuesMap::const_iterator it = _GlobalVectorValuesMap.find(name); if (it != _GlobalVectorValuesMap.end()) return it->second; return NLMISC::CVector::Null; // not a known value } ///======================================================================================= CParticleSystem::CGlobalVectorValueHandle CParticleSystem::getGlobalVectorValueHandle(const std::string &name) { NL_PS_FUNC(CParticleSystem_getGlobalVectorValueHandle) nlassert(!name.empty()); TGlobalVectorValuesMap::iterator it = _GlobalVectorValuesMap.find(name); if (it == _GlobalVectorValuesMap.end()) { it = _GlobalVectorValuesMap.insert(TGlobalVectorValuesMap::value_type(name, NLMISC::CVector::Null)).first; } CGlobalVectorValueHandle handle; handle._Value = &it->second; handle._Name = &it->first; return handle; } ///======================================================================================= void CParticleSystem::setMaxDistLODBias(float lodBias) { NL_PS_FUNC_MAIN(CParticleSystem_setMaxDistLODBias) NLMISC::clamp(lodBias, 0.f, 1.f); _MaxDistLODBias = lodBias; } ///======================================================================================= bool CParticleSystem::canFinish(CPSLocatedBindable **lastingForeverObj /*= NULL*/) const { NL_PS_FUNC_MAIN(CParticleSystem_canFinish) if (hasLoop(lastingForeverObj)) return false; for(uint k = 0; k < _ProcessVect.size(); ++k) { if (_ProcessVect[k]->isLocated()) { CPSLocated *loc = static_cast(_ProcessVect[k]); if (loc->getLastForever()) { for(uint l = 0; l < loc->getNbBoundObjects(); ++l) { CPSEmitter *em = dynamic_cast(loc->getBoundObject(l)); if (em && em->testEmitForever()) { if (lastingForeverObj) *lastingForeverObj = em; return false; } CPSParticle *p = dynamic_cast(loc->getBoundObject(l)); if (p) { if (lastingForeverObj) *lastingForeverObj = p; return false; // particles shouldn't live forever, too } } } } } return true; } ///======================================================================================= bool CParticleSystem::hasLoop(CPSLocatedBindable **loopingObj /*= NULL*/) const { NL_PS_FUNC_MAIN(CParticleSystem_hasLoop) // we want to check for loop like A emit B emit A // NB : there's room for a smarter algo here, but should not be useful for now for(uint k = 0; k < _ProcessVect.size(); ++k) { if (_ProcessVect[k]->isLocated()) { CPSLocated *loc = static_cast(_ProcessVect[k]); for(uint l = 0; l < loc->getNbBoundObjects(); ++l) { CPSEmitter *em = dynamic_cast(loc->getBoundObject(l)); if (em) { if (em->checkLoop()) { if (loopingObj) *loopingObj = em; return true; } } } } } return false; } ///======================================================================================= void CParticleSystem::systemDurationChanged() { NL_PS_FUNC_MAIN(CParticleSystem_systemDurationChanged) if (getAutoComputeDelayBeforeDeathConditionTest()) { setDelayBeforeDeathConditionTest(-1.f); } } ///======================================================================================= void CParticleSystem::setAutoComputeDelayBeforeDeathConditionTest(bool computeAuto) { NL_PS_FUNC_MAIN(CParticleSystem_setAutoComputeDelayBeforeDeathConditionTest) if (computeAuto == _AutoComputeDelayBeforeDeathTest) return; _AutoComputeDelayBeforeDeathTest = computeAuto; if (computeAuto) setDelayBeforeDeathConditionTest(-1.f); } ///======================================================================================= TAnimationTime CParticleSystem::getDelayBeforeDeathConditionTest() const { NL_PS_FUNC_MAIN(CParticleSystem_getDelayBeforeDeathConditionTest) if (_DelayBeforeDieTest < 0.f) { _DelayBeforeDieTest = evalDuration(); } return std::max(PS_MIN_TIMEOUT, _DelayBeforeDieTest); } ///======================================================================================= // struct to eval duration of an emitter chain struct CToVisitEmitter { float Duration; // cumuled duration of this emitter parent emitters const CPSLocated *Located; }; ///======================================================================================= float CParticleSystem::evalDuration() const { NL_PS_FUNC_MAIN(CParticleSystem_evalDuration) std::vector visitedEmitter; std::vector toVisitEmitter; float maxDuration = 0.f; for(uint k = 0; k < _ProcessVect.size(); ++k) { if (_ProcessVect[k]->isLocated()) { bool emitterFound = false; const CPSLocated *loc = static_cast(_ProcessVect[k]); for(uint l = 0; l < loc->getNbBoundObjects(); ++l) { const CPSLocatedBindable *bind = loc->getBoundObject(l); if (loc->getSize() > 0) { switch(bind->getType()) { case PSParticle: { if (loc->getLastForever()) { return -1; } else { maxDuration = std::max(maxDuration, loc->evalMaxDuration()); } } break; case PSEmitter: { if (!emitterFound) { CToVisitEmitter tve; tve.Located = loc; tve.Duration = 0.f; toVisitEmitter.push_back(tve); emitterFound = true; } } break; } } } visitedEmitter.clear(); while (!toVisitEmitter.empty()) { const CPSLocated *loc = toVisitEmitter.back().Located; float duration = toVisitEmitter.back().Duration; toVisitEmitter.pop_back(); visitedEmitter.push_back(loc); bool emitterFound = false; for(uint m = 0; m < loc->getNbBoundObjects(); ++m) { const CPSLocatedBindable *bind = loc->getBoundObject(m); if (bind->getType() == PSEmitter) { const CPSEmitter *em = NLMISC::safe_cast(loc->getBoundObject(m)); const CPSLocated *emittedType = em->getEmittedType(); // continue if there's no loop if (std::find(visitedEmitter.begin(), visitedEmitter.end(), emittedType) == visitedEmitter.end()) { if (emittedType != NULL) { emitterFound = true; CToVisitEmitter tve; tve.Located = emittedType; // if emitter has limited lifetime, use it if (!loc->getLastForever()) { tve.Duration = duration + loc->evalMaxDuration(); } else { // try to eval duration depending on type switch(em->getEmissionType()) { case CPSEmitter::regular: { if (em->getMaxEmissionCount() != 0) { float period = em->getPeriodScheme() ? em->getPeriodScheme()->getMaxValue() : em->getPeriod(); tve.Duration = duration + em->getEmitDelay() + period * em->getMaxEmissionCount(); } else { tve.Duration = duration + em->getEmitDelay(); } } break; case CPSEmitter::onDeath: case CPSEmitter::once: case CPSEmitter::onBounce: case CPSEmitter::externEmit: tve.Duration = duration; // can't eval duration .. break; default: break; } } toVisitEmitter.push_back(tve); } } } } if (!emitterFound) { if (!loc->getLastForever()) { duration += loc->evalMaxDuration(); } maxDuration = std::max(maxDuration, duration); } } } } return maxDuration; } ///======================================================================================= bool CParticleSystem::isDestroyConditionVerified() const { NL_PS_FUNC_MAIN(CParticleSystem_isDestroyConditionVerified) if (getDestroyCondition() != CParticleSystem::none) { if (getSystemDate() > getDelayBeforeDeathConditionTest()) { switch (getDestroyCondition()) { case CParticleSystem::noMoreParticles: return !hasParticles(); case CParticleSystem::noMoreParticlesAndEmitters: return !hasParticles() && !hasEmitters(); default: nlassert(0); return false; } } } return false; } ///======================================================================================= void CParticleSystem::setSystemDate(float date) { NL_PS_FUNC_MAIN(CParticleSystem_setSystemDate) if (date == _SystemDate) return; _SystemDate = date; for(uint k = 0; k < _ProcessVect.size(); ++k) { _ProcessVect[k]->systemDateChanged(); } } ///======================================================================================= void CParticleSystem::registerSoundServer(UPSSoundServer *soundServer) { NL_PS_FUNC(CParticleSystem_registerSoundServer) if (soundServer == _SoundServer) return; if (_SoundServer) { CParticleSystemManager::stopSoundForAllManagers(); } _SoundServer = soundServer; if (_SoundServer) { CParticleSystemManager::reactivateSoundForAllManagers(); } } ///======================================================================================= void CParticleSystem::activateEmitters(bool active) { NL_PS_FUNC_MAIN(CParticleSystem_activateEmitters) for(uint k = 0; k < getNbProcess(); ++k) { if (getProcess(k)->isLocated()) { CPSLocated *loc = static_cast(getProcess(k)); if (loc) { for(uint l = 0; l < loc->getNbBoundObjects(); ++l) { if (loc->getBoundObject(l)->getType() == PSEmitter) loc->getBoundObject(l)->setActive(active); } } } } } ///======================================================================================= bool CParticleSystem::hasActiveEmitters() const { NL_PS_FUNC_MAIN(CParticleSystem_hasActiveEmitters) for(uint k = 0; k < getNbProcess(); ++k) { if (getProcess(k)->isLocated()) { const CPSLocated *loc = static_cast(getProcess(k)); if (loc) { for(uint l = 0; l < loc->getNbBoundObjects(); ++l) { if (loc->getBoundObject(l)->getType() == PSEmitter) { if (loc->getBoundObject(l)->isActive()) return true; } } } } } return false; } ///======================================================================================= bool CParticleSystem::hasEmittersTemplates() const { NL_PS_FUNC_MAIN(CParticleSystem_hasEmittersTemplates) for(uint k = 0; k < getNbProcess(); ++k) { if (getProcess(k)->isLocated()) { const CPSLocated *loc = static_cast(getProcess(k)); if (loc) { for(uint l = 0; l < loc->getNbBoundObjects(); ++l) { if (loc->getBoundObject(l)->getType() == PSEmitter) { return true; } } } } } return false; } ///======================================================================================= void CParticleSystem::matchArraySize() { NL_PS_FUNC_MAIN(CParticleSystem_matchArraySize) for(uint k = 0; k < getNbProcess(); ++k) { if (getProcess(k)->isLocated()) { CPSLocated *loc = static_cast(getProcess(k)); loc->resize(loc->getSize()); // match the max size with the number of instances } } } ///======================================================================================= uint CParticleSystem::getMaxNumParticles() const { NL_PS_FUNC_MAIN(CParticleSystem_getMaxNumParticles) uint numParts = 0; for(uint k = 0; k < getNbProcess(); ++k) { if (getProcess(k)->isLocated()) { const CPSLocated *loc = static_cast(getProcess(k)); if (loc) { for(uint l = 0; l < loc->getNbBoundObjects(); ++l) { if (loc->getBoundObject(l)->getType() == PSParticle) { numParts += loc->getMaxSize(); } } } } } return numParts; } ///======================================================================================= uint CParticleSystem::getCurrNumParticles() const { NL_PS_FUNC_MAIN(CParticleSystem_getCurrNumParticles) uint numParts = 0; for(uint k = 0; k < getNbProcess(); ++k) { if (getProcess(k)->isLocated()) { const CPSLocated *loc = static_cast(getProcess(k)); if (loc) { for(uint l = 0; l < loc->getNbBoundObjects(); ++l) { if (loc->getBoundObject(l)->getType() == PSParticle) { numParts += loc->getSize(); } } } } } return numParts; } ///======================================================================================= void CParticleSystem::getTargeters(const CPSLocated *target, std::vector &targeters) { NL_PS_FUNC_MAIN(CParticleSystem_getTargeters) nlassert(target); nlassert(isProcess(target)); targeters.clear(); for(uint k = 0; k < getNbProcess(); ++k) { if (getProcess(k)->isLocated()) { CPSLocated *loc = static_cast(getProcess(k)); if (loc) { for(uint l = 0; l < loc->getNbBoundObjects(); ++l) { CPSTargetLocatedBindable *targeter = dynamic_cast(loc->getBoundObject(l)); if (targeter) { for(uint m = 0; m < targeter->getNbTargets(); ++m) { if (targeter->getTarget(m) == target) { targeters.push_back(targeter); break; } } } } } } } } ///======================================================================================= void CParticleSystem::matrixModeChanged(CParticleSystemProcess *proc, TPSMatrixMode oldMode, TPSMatrixMode newMode) { NL_PS_FUNC_MAIN(CParticleSystem_matrixModeChanged) nlassert(proc); // check that the located belong to that system nlassert(isProcess(proc)); if (oldMode != PSUserMatrix && newMode == PSUserMatrix) { addRefForUserSysCoordInfo(); } else if (oldMode == PSUserMatrix && newMode != PSUserMatrix) { releaseRefForUserSysCoordInfo(); } } ///======================================================================================= void CParticleSystem::addRefForUserSysCoordInfo(uint numRefs) { NL_PS_FUNC_MAIN(CParticleSystem_addRefForUserSysCoordInfo) if (!numRefs) return; if (!_UserCoordSystemInfo) { _UserCoordSystemInfo = new CUserCoordSystemInfo; } nlassert(_UserCoordSystemInfo); _UserCoordSystemInfo->NumRef += numRefs; } ///======================================================================================= void CParticleSystem::releaseRefForUserSysCoordInfo(uint numRefs) { NL_PS_FUNC_MAIN(CParticleSystem_releaseRefForUserSysCoordInfo) if (!numRefs) return; nlassert(_UserCoordSystemInfo); nlassert(numRefs <= _UserCoordSystemInfo->NumRef); _UserCoordSystemInfo->NumRef -= numRefs; if (_UserCoordSystemInfo->NumRef == 0) { delete _UserCoordSystemInfo; _UserCoordSystemInfo = NULL; } } ///======================================================================================= void CParticleSystem::checkIntegrity() { NL_PS_FUNC_MAIN(CParticleSystem_checkIntegrity) // do some checks uint userMatrixUsageCount = 0; for (TProcessVect::iterator it = _ProcessVect.begin(); it != _ProcessVect.end(); ++it) { userMatrixUsageCount += (*it)->getUserMatrixUsageCount(); } if (userMatrixUsageCount == 0) { nlassert(_UserCoordSystemInfo == NULL); } else { nlassert(_UserCoordSystemInfo != NULL); nlassert(_UserCoordSystemInfo->NumRef == userMatrixUsageCount); } for(uint k = 0; k < _ProcessVect.size(); ++k) { nlassert(_ProcessVect[k]->getOwner() == this); } } ///======================================================================================= void CParticleSystem::enumTexs(std::vector > &dest, IDriver &drv) { NL_PS_FUNC_MAIN(CParticleSystem_enumTexs) for (TProcessVect::iterator it = _ProcessVect.begin(); it != _ProcessVect.end(); ++it) { (*it)->enumTexs(dest, drv); } } ///======================================================================================= void CParticleSystem::setZBias(float value) { NL_PS_FUNC_MAIN(CParticleSystem_setZBias) for (TProcessVect::iterator it = _ProcessVect.begin(); it != _ProcessVect.end(); ++it) { (*it)->setZBias(value); } } ///======================================================================================= void CParticleSystem::getSortingByEmitterPrecedence(std::vector &result) const { NL_PS_FUNC_MAIN(CParticleSystem_getSortingByEmitterPrecedence) #ifdef NL_DEBUG nlassert(!hasLoop()); // should be an acyclic graph, otherwise big problem.... #endif typedef std::list TProcessList; std::vector degreeToNodes; std::vector nodeToIterator(_ProcessVect.size()); // std::vector inDegree(_ProcessVect.size(), 0); // degree for each node for(uint k = 0; k < _ProcessVect.size(); ++k) { if (_ProcessVect[k]->isLocated()) { CPSLocated *loc = static_cast(_ProcessVect[k]); for(uint l = 0; l < loc->getNbBoundObjects(); ++l) { if (loc->getBoundObject(l)->getType() == PSEmitter) { CPSEmitter *pEmit = NLMISC::safe_cast(loc->getBoundObject(l)); if (pEmit->getEmittedType()) { ++ inDegree[getIndexOf(*pEmit->getEmittedType())]; } } } } } // make enough room in degreeToNodes. for(uint k = 0; k < inDegree.size(); ++k) { if (degreeToNodes.size() <= inDegree[k]) { degreeToNodes.resize(inDegree[k] + 1); } } // sort nodes by degree for(uint k = 0; k < inDegree.size(); ++k) { // NB : could not do resize there because we keep iterators in the list, so it's done in the previous loop degreeToNodes[inDegree[k]].push_front(_ProcessVect[k]); nodeToIterator[k] = degreeToNodes[inDegree[k]].begin(); } // #ifdef NL_DEBUG #define DO_SBEP_CHECK \ { for(uint k = 0; k < degreeToNodes.size(); ++k) \ { \ for(TProcessList::const_iterator it = degreeToNodes[k].begin(); it != degreeToNodes[k].end(); ++it) \ { \ nlassert(inDegree[(*it)->getIndex()] == k); \ } \ }} #else #define DO_SBEP_CHECK #endif // DO_SBEP_CHECK result.reserve(_ProcessVect.size()); result.clear(); if (degreeToNodes.empty()) return; // now, do the sort -> add each node with a degree of 0, and removes arc to their son (and insert in new good list according to their degree) while (!degreeToNodes[0].empty()) { DO_SBEP_CHECK CParticleSystemProcess *pr = degreeToNodes[0].front(); degreeToNodes[0].pop_front(); result.push_back(getIndexOf(*pr)); if (pr->isLocated()) { CPSLocated *loc = static_cast(pr); for(uint l = 0; l < loc->getNbBoundObjects(); ++l) { if (loc->getBoundObject(l)->getType() == PSEmitter) { CPSEmitter *pEmit = NLMISC::safe_cast(loc->getBoundObject(l)); // update degree of node if (pEmit->getEmittedType()) { uint emittedLocIndex = getIndexOf(*pEmit->getEmittedType()); uint degree = inDegree[emittedLocIndex]; nlassert(degree != 0); degreeToNodes[degree - 1].splice(degreeToNodes[degree - 1].begin(), degreeToNodes[degree], nodeToIterator[emittedLocIndex]); nodeToIterator[emittedLocIndex] = degreeToNodes[degree - 1].begin(); // update iterator -- inDegree[emittedLocIndex]; DO_SBEP_CHECK } } } } } } ///======================================================================================= void CParticleSystem::updateProcessIndices() { NL_PS_FUNC_MAIN(CParticleSystem_updateProcessIndices) for(uint k = 0; k < _ProcessVect.size(); ++k) { _ProcessVect[k]->setIndex(k); } } ///======================================================================================= void CParticleSystem::dumpHierarchy() { NL_PS_FUNC_MAIN(CParticleSystem_dumpHierarchy) for(uint k = 0; k < _ProcessVect.size(); ++k) { CPSLocated *loc = dynamic_cast(_ProcessVect[k]); if (loc) { nlinfo("Located k : %s @%p", loc->getName().c_str(), loc); for(uint l = 0; l < loc->getNbBoundObjects(); ++l) { CPSEmitter *emitter = dynamic_cast(loc->getBoundObject(l)); if (emitter) { nlinfo(" emitter %s : emit %s @%p", emitter->getName().c_str(), emitter->getEmittedType() ? emitter->getEmittedType()->getName().c_str() : "none", emitter->getEmittedType()); } } } } } ///======================================================================================= void CParticleSystem::onShow(bool shown) { NL_PS_FUNC_MAIN(CParticleSystem_onShow) for(uint k = 0; k < _ProcessVect.size(); ++k) { _ProcessVect[k]->onShow(shown); } } } // NL3D