// 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/ps_emitter.h" #include "nel/3d/material.h" #include "nel/misc/line.h" #include "nel/3d/dru.h" #include "nel/3d/particle_system.h" namespace NL3D { static const uint EMITTER_BUFF_SIZE = 512; // number of emitter to be processed at once static const float EMIT_PERIOD_THRESHOLD = 1.f / 75.f; // assuming the same behaviour than with a 75 hz rendering bool CPSEmitter::_BypassEmitOnDeath = false; ////////////////////// // STATIC FUNCTIONS // ////////////////////// /** In an arrey of float, all value that are 0.f are replaced by EMIT_PERIOD_THRESHOLD * A period of 0 is allowed for emitter and means "emit at each frame" * This is deprecated now, and this helps to avoid that behaviour */ static void replaceNullPeriodsByThreshold(float *tab, uint numElem) { NL_PS_FUNC(replaceNullPeriodsByThreshold) const float *endTab = tab + numElem; while (tab != endTab) { if (*tab == 0.f) *tab = EMIT_PERIOD_THRESHOLD; ++ tab; } } /////////////////////////////// // CPSEmitter implementation // /////////////////////////////// CPSEmitter::CPSEmitter() : _EmittedType(NULL), _SpeedInheritanceFactor(0.f), _EmissionType(regular), _Period(0.02f), _PeriodScheme(NULL), _GenNb(1), _GenNbScheme(NULL), _EmitDelay(0), _MaxEmissionCount(0), _SpeedBasisEmission(false), _ConsistentEmission(true), _BypassAutoLOD(false), _UserMatrixModeForEmissionDirection(false), _EmitTrigger(false), _UserDirectionMatrixMode(PSFXWorldMatrix) { NL_PS_FUNC(CPSEmitter_CPSEmitter) } ///========================================================================== CPSEmitter::~CPSEmitter() { NL_PS_FUNC(CPSEmitter_CPSEmitterDtor) delete _PeriodScheme; delete _GenNbScheme; // if a located is emitted, unregister us as an observer if (_EmittedType) { _EmittedType->unregisterDtorObserver(this); } } ///========================================================================== void CPSEmitter::releaseRefTo(const CParticleSystemProcess *other) { NL_PS_FUNC(CPSEmitter_releaseRefTo) if (_EmittedType == other) { setEmittedType(NULL); } } void CPSEmitter::releaseAllRef() { NL_PS_FUNC(CPSEmitter_releaseAllRef) setEmittedType(NULL); } ///========================================================================== void CPSEmitter::setOwner(CPSLocated *psl) { NL_PS_FUNC(CPSEmitter_setOwner) CPSLocatedBindable::setOwner(psl); updateMaxCountVect(); } ///========================================================================== inline void CPSEmitter::processEmit(uint32 index, sint nbToGenerate) { NL_PS_FUNC(CPSEmitter_processEmit) NLMISC::CVector speed, pos; nlassert(_Owner); if (!_SpeedBasisEmission) { if (_SpeedInheritanceFactor == 0.f) { if (!_UserMatrixModeForEmissionDirection) { while (nbToGenerate > 0) { nbToGenerate --; emit(_Owner->getPos()[index], index, pos, speed); _EmittedType->postNewElement(pos, speed, *this->_Owner, index, _Owner->getMatrixMode(), 0.f); } } else { while (nbToGenerate > 0) { nbToGenerate --; emit(_Owner->getPos()[index], index, pos, speed); _EmittedType->postNewElement(pos, speed, *this->_Owner, index, _UserDirectionMatrixMode, 0.f); } } } else { while (nbToGenerate --) { emit(_Owner->getPos()[index], index, pos, speed); _EmittedType->postNewElement(pos, speed + _SpeedInheritanceFactor * _Owner->getSpeed()[index], *this->_Owner, 0, _Owner->getMatrixMode(), 0.f); } } } else { NLMISC::CMatrix m; CPSUtil::buildSchmidtBasis(_Owner->getSpeed()[index], m); if (_SpeedInheritanceFactor == 0.f) { while (nbToGenerate > 0) { nbToGenerate --; emit(_Owner->getPos()[index], index, pos, speed); _EmittedType->postNewElement(pos, m * speed, *this->_Owner, index, _Owner->getMatrixMode(), 0.f); } } else { while (nbToGenerate --) { emit(_Owner->getPos()[index], index, pos, speed); _EmittedType->postNewElement(pos, m * speed + _SpeedInheritanceFactor * _Owner->getSpeed()[index], *this->_Owner, index, _Owner->getMatrixMode(), 0.f); } } } } ///========================================================================== void CPSEmitter::processEmitOutsideSimLoop(uint32 index,sint nbToGenerate) { NL_PS_FUNC(CPSEmitter_processEmitOutsideSimLoop) NLMISC::CVector speed, pos; nlassert(_Owner); if (!_SpeedBasisEmission) { if (_SpeedInheritanceFactor == 0.f) { if (!_UserMatrixModeForEmissionDirection) { while (nbToGenerate > 0) { nbToGenerate --; emit(_Owner->getPos()[index], index, pos, speed); _EmittedType->newElement(pos, speed, this->_Owner, index, _Owner->getMatrixMode(), true); } } else { while (nbToGenerate > 0) { nbToGenerate --; emit(_Owner->getPos()[index], index, pos, speed); _EmittedType->newElement(pos, speed, this->_Owner, index, _UserDirectionMatrixMode); } } } else { while (nbToGenerate --) { emit(_Owner->getPos()[index], index, pos, speed); _EmittedType->newElement(pos, speed + _SpeedInheritanceFactor * _Owner->getSpeed()[index], this->_Owner, 0, _Owner->getMatrixMode(), true); } } } else { NLMISC::CMatrix m; CPSUtil::buildSchmidtBasis(_Owner->getSpeed()[index], m); if (_SpeedInheritanceFactor == 0.f) { while (nbToGenerate > 0) { nbToGenerate --; emit(_Owner->getPos()[index], index, pos, speed); _EmittedType->newElement(pos, m * speed, this->_Owner, index, _Owner->getMatrixMode(), true); } } else { while (nbToGenerate --) { emit(_Owner->getPos()[index], index, pos, speed); _EmittedType->newElement(pos, m * speed + _SpeedInheritanceFactor * _Owner->getSpeed()[index], this->_Owner, index, _Owner->getMatrixMode(), true); } } } } ///========================================================================== inline void CPSEmitter::processEmitConsistent(const NLMISC::CVector &emitterPos, uint32 index, sint nbToGenerate, TAnimationTime deltaT) { NL_PS_FUNC(CPSEmitter_processEmitConsistent) static NLMISC::CVector speed, pos; /// speed and pos of emittee nlassert(_Owner); if (!_SpeedBasisEmission) { if (_SpeedInheritanceFactor == 0.f) { if (!_UserMatrixModeForEmissionDirection) { while (nbToGenerate > 0) { nbToGenerate --; emit(emitterPos, index, pos, speed); _EmittedType->postNewElement(pos, speed, *this->_Owner, index, _Owner->getMatrixMode(), deltaT); } } else { while (nbToGenerate > 0) { nbToGenerate --; emit(emitterPos, index, pos, speed); _EmittedType->postNewElement(pos, speed, *this->_Owner, index, _UserDirectionMatrixMode, deltaT); } } } else { while (nbToGenerate --) { emit(emitterPos, index, pos, speed); _EmittedType->postNewElement(pos, speed + _SpeedInheritanceFactor * _Owner->getSpeed()[index], *this->_Owner, index, _Owner->getMatrixMode(), deltaT); } } } else { NLMISC::CMatrix m; CPSUtil::buildSchmidtBasis(_Owner->getSpeed()[index], m); if (_SpeedInheritanceFactor == 0.f) { while (nbToGenerate > 0) { nbToGenerate --; emit(emitterPos, index, pos, speed); _EmittedType->postNewElement(pos, m * speed, *this->_Owner, index, _Owner->getMatrixMode(), deltaT); } } else { while (nbToGenerate --) { emit(emitterPos, index, pos, speed); _EmittedType->postNewElement(pos, m * speed + _SpeedInheritanceFactor * _Owner->getSpeed()[index], *this->_Owner, index, _Owner->getMatrixMode(), deltaT); } } } } ///========================================================================== bool CPSEmitter::setEmissionType(TEmissionType freqType) { NL_PS_FUNC(CPSEmitter_setEmissionType) if (_Owner && _Owner->getOwner()) { CParticleSystem *ps = _Owner->getOwner(); if (ps->getBypassMaxNumIntegrationSteps()) { if (!_Owner) { nlwarning(" The emitter should be inserted in a CPSLocated instance"); nlassert(0); } // check if the new value is valid TEmissionType oldType = _EmissionType; _EmissionType = freqType; if (testEmitForever() == true) { _EmissionType = oldType; std::string mess = " can't set emission type to '" + NLMISC::toString(freqType) + "' with the current configuration : the system has been flagged with \ 'BypassMaxNumIntegrationSteps', and should have a finite duration. \ The flag is not set"; nlwarning(mess.c_str()); return false; } } ps->systemDurationChanged(); } _EmissionType = freqType; return true; } ///========================================================================== bool CPSEmitter::setEmittedType(CPSLocated *et) { NL_PS_FUNC(CPSEmitter_setEmittedType) if (_EmittedType) { _EmittedType->unregisterDtorObserver(this); } if (et) { et->registerDtorObserver(this); } CPSLocated *oldType = _EmittedType; _EmittedType = et; if (_Owner && _Owner->getOwner()) { CParticleSystem *ps = _Owner->getOwner(); if (_EmittedType) { bool ok = true; if (ps->getBypassMaxNumIntegrationSteps()) { ok = ps->canFinish(); } else { ok = !ps->hasLoop(); } if (!ok) { setEmittedType(oldType); nlwarning(" Can't set new emitted type : this causes the system to last forever, and it has been flagged with 'BypassMaxNumIntegrationSteps'. New emitted type is not set"); return false; } } ps->systemDurationChanged(); } return true; } ///========================================================================== void CPSEmitter::notifyTargetRemoved(CPSLocated *ptr) { NL_PS_FUNC(CPSEmitter_notifyTargetRemoved) nlassert(ptr == _EmittedType && _EmittedType); setEmittedType(NULL); } ///========================================================================== void CPSEmitter::setPeriod(float period) { NL_PS_FUNC(CPSEmitter_setPeriod) if (_PeriodScheme) { delete _PeriodScheme; _PeriodScheme = NULL; } _Period = period; if (_Owner && _Owner->getOwner()) { _Owner->getOwner()->systemDurationChanged(); } } ///========================================================================== void CPSEmitter::setPeriodScheme(CPSAttribMaker *scheme) { NL_PS_FUNC(CPSEmitter_setPeriodScheme) delete _PeriodScheme; _PeriodScheme = scheme; if (_Owner && scheme->hasMemory()) scheme->resize(_Owner->getMaxSize(), _Owner->getSize()); if (_Owner && _Owner->getOwner()) { _Owner->getOwner()->systemDurationChanged(); } } ///========================================================================== void CPSEmitter::setGenNb(uint32 genNb) { NL_PS_FUNC(CPSEmitter_setGenNb) if (_GenNbScheme) { delete _GenNbScheme; _GenNbScheme = NULL; } _GenNb = genNb; } ///========================================================================== void CPSEmitter::setGenNbScheme(CPSAttribMaker *scheme) { NL_PS_FUNC(CPSEmitter_setGenNbScheme) delete _GenNbScheme; _GenNbScheme = scheme; if (_Owner && scheme->hasMemory()) scheme->resize(_Owner->getMaxSize(), _Owner->getSize()); } ///========================================================================== void CPSEmitter::showTool(void) { NL_PS_FUNC(CPSEmitter_showTool) uint32 size = _Owner->getSize(); if (!size) return; setupDriverModelMatrix(); const CVector I = computeI(); const CVector K = computeK(); // ugly slow code, but not for runtime for (uint k = 0; k < size; ++k) { // center of the current particle const CVector p = _Owner->getPos()[k]; const float sSize =0.1f; std::vector lines; NLMISC::CLine l; l.V0 = p - sSize * I; l.V1 = p + sSize * I; lines.push_back(l); l.V0 = p - sSize * K; l.V1 = p + sSize * K; lines.push_back(l); l.V0 = p - sSize * (I + K); l.V1 = p + sSize * (I + K); lines.push_back(l); l.V0 = p - sSize * (I - K); l.V1 = p + sSize * (I - K); lines.push_back(l); CMaterial mat; mat.setBlendFunc(CMaterial::one, CMaterial::one); mat.setZWrite(false); mat.setLighting(false); mat.setBlend(true); mat.setZFunc(CMaterial::less); CPSLocated *loc; uint32 index; CPSLocatedBindable *lb; _Owner->getOwner()->getCurrentEditedElement(loc, index, lb); mat.setColor((lb == NULL || this == lb) && loc == _Owner && index == k ? CRGBA::Red : CRGBA(127, 127, 127)); CDRU::drawLinesUnlit(lines, mat, *getDriver() ); } } ///========================================================================== void CPSEmitter::singleEmit(uint32 index, uint quantity) { NL_PS_FUNC(CPSEmitter_singleEmit) nlassert(_Owner); const uint32 nbToGenerate = _GenNbScheme ? _GenNbScheme->get(_Owner,0) : _GenNb; processEmitOutsideSimLoop(index, quantity * nbToGenerate); } ///========================================================================== void CPSEmitter::processRegularEmissionWithNoLOD(uint firstInstanceIndex) { NL_PS_FUNC(CPSEmitter_processRegularEmissionWithNoLOD) nlassert(_Owner); nlassert(_Owner->getOwner()); // const bool emitThreshold = _Owner->getOwner()->isEmitThresholdEnabled(); // const uint size = _Owner->getSize(); nlassert(firstInstanceIndex < size); uint leftToDo = size - firstInstanceIndex, toProcess; float emitPeriod[EMITTER_BUFF_SIZE]; const float *currEmitPeriod; uint currEmitPeriodPtrInc = _PeriodScheme ? 1 : 0; sint32 nbToGenerate; TPSAttribTime::iterator phaseIt = _Phase.begin() + firstInstanceIndex, endPhaseIt; TPSAttribUInt8::iterator numEmitIt = _NumEmission.begin() + firstInstanceIndex; // we don't use an iterator here // because it could be invalidated if size change (a located could generate itself) do { toProcess = leftToDo < EMITTER_BUFF_SIZE ? leftToDo : EMITTER_BUFF_SIZE; if (_PeriodScheme) { // compute period // NB : we ask to clamp entry because life counter of emitter are incremented, then spawn is called, and only after that dead emitters are removed // so we may have a life counter that is > to 1 currEmitPeriod = (float *) (_PeriodScheme->make(_Owner, size - leftToDo, emitPeriod, sizeof(float), toProcess, true, 1 << 16, true)); if (emitThreshold) { /** Test if 'make' filled our buffer. If this is not the case, we assume that values where precomputed, and that * all null period have already been replaced by the threshold */ if (currEmitPeriod == emitPeriod) { // if there possibility to have 0 in the scheme ? if (_PeriodScheme->getMinValue() <= 0.f && _PeriodScheme->getMaxValue() >= 0.f) { replaceNullPeriodsByThreshold(emitPeriod, toProcess); } } } } else { if (_Period != 0.f || !emitThreshold) { currEmitPeriod = &_Period; } else { currEmitPeriod = &EMIT_PERIOD_THRESHOLD; } } endPhaseIt = phaseIt + toProcess; if (_MaxEmissionCount == 0) // no emission count limit { /// is there an emission delay ? if (_EmitDelay == 0.f) // no emission delay { do { *phaseIt += CParticleSystem::EllapsedTime; if ( *phaseIt >= *currEmitPeriod) // phase is greater than period -> must emit { if (*currEmitPeriod != 0) { *phaseIt -= ::floorf(*phaseIt / *currEmitPeriod) * *currEmitPeriod; } const uint32 k = (uint32)(phaseIt - _Phase.begin()); nbToGenerate = _GenNbScheme ? _GenNbScheme->get(_Owner, k) : _GenNb; processEmit(k, nbToGenerate); } ++phaseIt; currEmitPeriod += currEmitPeriodPtrInc; } while (phaseIt != endPhaseIt); } else // there's an emission delay { do { *phaseIt += CParticleSystem::EllapsedTime; if ( *phaseIt >= *currEmitPeriod + _EmitDelay) // phase is greater than period -> must emit { if (*currEmitPeriod != 0) { *phaseIt -= ::floorf((*phaseIt - _EmitDelay) / *currEmitPeriod) * *currEmitPeriod; } const uint32 k = (uint32)(phaseIt - _Phase.begin()); nbToGenerate = _GenNbScheme ? _GenNbScheme->get(_Owner, k) : _GenNb; processEmit(k, nbToGenerate); } ++phaseIt; currEmitPeriod += currEmitPeriodPtrInc; } while (phaseIt != endPhaseIt); } } else // there's an emission count limit { /// is there an emission delay ? if (_EmitDelay == 0.f) // no emission delay { do { if (*numEmitIt < _MaxEmissionCount) { *phaseIt += CParticleSystem::EllapsedTime; if ( *phaseIt >= *currEmitPeriod) // phase is greater than period -> must emit { if (*currEmitPeriod != 0) { *phaseIt -= ::floorf(*phaseIt / *currEmitPeriod) * *currEmitPeriod; } const uint32 k = (uint32)(phaseIt - _Phase.begin()); nbToGenerate = _GenNbScheme ? _GenNbScheme->get(_Owner, k) : _GenNb; processEmit(k, nbToGenerate); ++*numEmitIt; } } ++phaseIt; currEmitPeriod += currEmitPeriodPtrInc; ++ numEmitIt; } while (phaseIt != endPhaseIt); } else // there's an emission delay { do { if (*numEmitIt < _MaxEmissionCount) { *phaseIt += CParticleSystem::EllapsedTime; if ( *phaseIt >= *currEmitPeriod + _EmitDelay) // phase is greater than period -> must emit { if (*currEmitPeriod != 0) { *phaseIt -= ::floorf((*phaseIt - _EmitDelay) / *currEmitPeriod) * *currEmitPeriod; } const uint32 k = (uint32)(phaseIt - _Phase.begin()); nbToGenerate = _GenNbScheme ? _GenNbScheme->get(_Owner, k) : _GenNb; processEmit(k, nbToGenerate); ++*numEmitIt; } } ++phaseIt; currEmitPeriod += currEmitPeriodPtrInc; ++numEmitIt; } while (phaseIt != endPhaseIt); } } leftToDo -= toProcess; } while (leftToDo); } ///========================================================================== void CPSEmitter::processRegularEmission(uint firstInstanceIndex, float emitLOD) { NL_PS_FUNC(CPSEmitter_processRegularEmission) nlassert(_Owner); nlassert(_Owner->getOwner()); // const bool emitThreshold = _Owner->getOwner()->isEmitThresholdEnabled(); // const uint size = _Owner->getSize(); nlassert(firstInstanceIndex < size); uint leftToDo = size - firstInstanceIndex, toProcess; float emitPeriod[EMITTER_BUFF_SIZE]; const float *currEmitPeriod; uint currEmitPeriodPtrInc = _PeriodScheme ? 1 : 0; sint32 nbToGenerate; TPSAttribTime::iterator phaseIt = _Phase.begin() + firstInstanceIndex, endPhaseIt; TPSAttribUInt8::iterator numEmitIt = _NumEmission.begin() + firstInstanceIndex; float ellapsedTimeLOD = emitLOD * CParticleSystem::EllapsedTime; uint maxEmissionCountLOD = (uint8) (_MaxEmissionCount * emitLOD); maxEmissionCountLOD = std::max(1u, maxEmissionCountLOD); // we don't use an iterator here // because it could be invalidated if size change (a located could generate itself) do { toProcess = leftToDo < EMITTER_BUFF_SIZE ? leftToDo : EMITTER_BUFF_SIZE; if (_PeriodScheme) { // compute period // NB : we ask to clamp entry because life counter of emitter are incremented, then spawn is called, and only after that dead emitters are removed // so we may have a life counter that is > to 1 currEmitPeriod = (float *) (_PeriodScheme->make(_Owner, size - leftToDo, emitPeriod, sizeof(float), toProcess, true, 1 << 16, true)); if (emitThreshold) { /** Test if 'make' filled our buffer. If this is not the case, we assume that values where precomputed, and that * all null period have already been replaced by the threshold */ if (currEmitPeriod == emitPeriod) { // if there possibility to have 0 in the scheme ? if (_PeriodScheme->getMinValue() <= 0.f && _PeriodScheme->getMaxValue() >= 0.f) { replaceNullPeriodsByThreshold(emitPeriod, toProcess); } } } } else { if (_Period != 0.f || !emitThreshold) { currEmitPeriod = &_Period; } else { currEmitPeriod = &EMIT_PERIOD_THRESHOLD; } } endPhaseIt = phaseIt + toProcess; if (_MaxEmissionCount == 0) // no emission count limit { /// is there an emission delay ? if (_EmitDelay == 0.f) // no emission delay { do { *phaseIt += ellapsedTimeLOD; if ( *phaseIt >= *currEmitPeriod) // phase is greater than period -> must emit { if (*currEmitPeriod != 0) { *phaseIt -= ::floorf(*phaseIt / *currEmitPeriod) * *currEmitPeriod; } const uint32 k = (uint32)(phaseIt - _Phase.begin()); nbToGenerate = _GenNbScheme ? _GenNbScheme->get(_Owner, k) : _GenNb; if (nbToGenerate) { nbToGenerate = (sint32) (emitLOD * nbToGenerate); if (!nbToGenerate) nbToGenerate = 1; processEmit(k, nbToGenerate); } } ++phaseIt; currEmitPeriod += currEmitPeriodPtrInc; } while (phaseIt != endPhaseIt); } else // there's an emission delay { do { if (*phaseIt < _EmitDelay) { *phaseIt += CParticleSystem::EllapsedTime; if (*phaseIt < _EmitDelay) { ++phaseIt; currEmitPeriod += currEmitPeriodPtrInc; continue; } else { *phaseIt = (*phaseIt - _EmitDelay) * emitLOD + _EmitDelay; } } else { *phaseIt += ellapsedTimeLOD; } if ( *phaseIt >= *currEmitPeriod + _EmitDelay) // phase is greater than period -> must emit { if (*currEmitPeriod != 0) { *phaseIt -= ::floorf((*phaseIt - _EmitDelay) / *currEmitPeriod) * *currEmitPeriod; } const uint32 k = (uint32)(phaseIt - _Phase.begin()); nbToGenerate = _GenNbScheme ? _GenNbScheme->get(_Owner, k) : _GenNb; if (nbToGenerate) { nbToGenerate = (sint32) (emitLOD * nbToGenerate); if (!nbToGenerate) nbToGenerate = 1; processEmit(k, nbToGenerate); } } ++phaseIt; currEmitPeriod += currEmitPeriodPtrInc; } while (phaseIt != endPhaseIt); } } else // there's an emission count limit { /// is there an emission delay ? if (_EmitDelay == 0.f) // no emission delay { do { if (*numEmitIt < maxEmissionCountLOD) { *phaseIt += ellapsedTimeLOD; if ( *phaseIt >= *currEmitPeriod) // phase is greater than period -> must emit { if (*currEmitPeriod != 0) { *phaseIt -= ::floorf(*phaseIt / *currEmitPeriod) * *currEmitPeriod; } const uint32 k = (uint32)(phaseIt - _Phase.begin()); nbToGenerate = _GenNbScheme ? _GenNbScheme->get(_Owner, k) : _GenNb; if (nbToGenerate) { nbToGenerate = (sint32) (emitLOD * nbToGenerate); if (!nbToGenerate) nbToGenerate = 1; processEmit(k, nbToGenerate); } ++*numEmitIt; } } else { *numEmitIt = _MaxEmissionCount; } ++phaseIt; currEmitPeriod += currEmitPeriodPtrInc; ++ numEmitIt; } while (phaseIt != endPhaseIt); } else // there's an emission delay { do { if (*numEmitIt < maxEmissionCountLOD) { if (*phaseIt < _EmitDelay) { *phaseIt += CParticleSystem::EllapsedTime; if (*phaseIt < _EmitDelay) { ++phaseIt; currEmitPeriod += currEmitPeriodPtrInc; ++numEmitIt; currEmitPeriod += currEmitPeriodPtrInc; continue; } else { *phaseIt = (*phaseIt - _EmitDelay) * emitLOD + _EmitDelay; } } else { *phaseIt += ellapsedTimeLOD; } if ( *phaseIt >= *currEmitPeriod + _EmitDelay) // phase is greater than period -> must emit { if (*currEmitPeriod != 0) { *phaseIt -= ::floorf((*phaseIt - _EmitDelay) / *currEmitPeriod) * *currEmitPeriod; } const uint32 k = (uint32)(phaseIt - _Phase.begin()); nbToGenerate = _GenNbScheme ? _GenNbScheme->get(_Owner, k) : _GenNb; if (nbToGenerate) { nbToGenerate = (sint32) (nbToGenerate * emitLOD); if (!nbToGenerate) nbToGenerate = 1; processEmit(k, nbToGenerate); } ++*numEmitIt; } } else { *numEmitIt = _MaxEmissionCount; } ++phaseIt; currEmitPeriod += currEmitPeriodPtrInc; ++numEmitIt; } while (phaseIt != endPhaseIt); } } leftToDo -= toProcess; } while (leftToDo); } /// private : generate the various position of an emitter in the given tab for the given slice of time, // depending on whether its motion is parametric or incremental. This is used to create emittees at the right position static #ifndef NL_DEBUG inline #endif uint GenEmitterPositions(CPSLocated *emitter, CPSLocated *emittee, uint emitterIndex, uint numStep, TAnimationTime deltaT, /* fraction of time needed to reach the first emission */ TAnimationTime step, std::vector &dest ) { NL_PS_FUNC(GenEmitterPositions) const uint toProcess = std::max(1U, std::min(numStep, (uint) emittee->getMaxSize())); dest.resize(toProcess); if (!emitter->isParametricMotionEnabled()) // standard case : take current pos and integrate { if (toProcess == 1) // only one emission -> takes current pos { dest[0] = emitter->getPos()[emitterIndex] - deltaT * emitter->getSpeed()[emitterIndex]; } else { std::vector::iterator outIt = dest.end(); std::vector::iterator endIt = dest.begin(); NLMISC::CVector pos = emitter->getPos()[emitterIndex] - deltaT * emitter->getSpeed()[emitterIndex]; NLMISC::CVector speed = step * emitter->getSpeed()[emitterIndex]; do { -- outIt; *outIt = pos; pos -= speed; } while (outIt != endIt); } } else // compute parametric trajectory { emitter->integrateSingle(emitter->getOwner()->getSystemDate() + CParticleSystem::RealEllapsedTime - deltaT, -step, toProcess, emitterIndex, &dest[0] ); } return toProcess; } /** The same as GenEmitterPositions, but with LOD taken in account. */ static inline uint GenEmitterPositionsWithLOD(CPSLocated *emitter, CPSLocated *emittee, uint emitterIndex, uint numStep, TAnimationTime deltaT, /* fraction of time needed to reach the first emission */ TAnimationTime step, float invLODRatio, std::vector &dest ) { NL_PS_FUNC(GenEmitterPositionsWithLOD) const uint toProcess = std::max(1U, std::min(numStep, (uint) emittee->getMaxSize())); dest.resize(toProcess); if (!emitter->isParametricMotionEnabled()) // standard case : take current pos and integrate { if (toProcess == 1) // only one emission -> takes current pos { dest[0] = emitter->getPos()[emitterIndex] - deltaT * emitter->getSpeed()[emitterIndex]; } else { std::vector::iterator outIt = dest.end(); std::vector::iterator endIt = dest.begin(); NLMISC::CVector pos = emitter->getPos()[emitterIndex] - deltaT * emitter->getSpeed()[emitterIndex]; NLMISC::CVector speed = step * invLODRatio * emitter->getSpeed()[emitterIndex]; do { -- outIt; *outIt = pos; pos -= speed; } while (outIt != endIt); } } else // compute parametric trajectory { emitter->integrateSingle(emitter->getOwner()->getSystemDate() - deltaT - step * toProcess, step, toProcess, emitterIndex, &dest[0] ); } return toProcess; } ///========================================================================== void CPSEmitter::processRegularEmissionConsistent(uint firstInstanceIndex, float emitLOD, float inverseEmitLOD) { NL_PS_FUNC(CPSEmitter_processRegularEmissionConsistent) /// hmm some code factorisation would do no harm, but we want to keep tests outside the loops as much as possible... nlassert(_Owner); nlassert(_Owner->getOwner()); // const bool emitThreshold = _Owner->getOwner()->isEmitThresholdEnabled(); // static std::vector emitterPositions; // Positions for the emitter. They are computed by using a parametric trajectory or by using integration const uint size = _Owner->getSize(); nlassert(firstInstanceIndex < size); uint leftToDo = size - firstInstanceIndex, toProcess; float emitPeriod[EMITTER_BUFF_SIZE]; const float *currEmitPeriod; uint currEmitPeriodPtrInc = _PeriodScheme ? 1 : 0; sint32 nbToGenerate; TPSAttribTime::iterator phaseIt = _Phase.begin() + firstInstanceIndex, endPhaseIt; TPSAttribUInt8::iterator numEmitIt; if(firstInstanceIndex < _NumEmission.getSize()) { numEmitIt = _NumEmission.begin() + firstInstanceIndex; } else { numEmitIt = _NumEmission.end(); } float ellapsedTimeLOD = CParticleSystem::EllapsedTime * emitLOD; uint maxEmissionCountLOD = (uint8) (_MaxEmissionCount * emitLOD); maxEmissionCountLOD = std::max(1u, maxEmissionCountLOD); // we don't use an iterator here // because it could be invalidated if size change (a located could generate itself) do { toProcess = leftToDo < EMITTER_BUFF_SIZE ? leftToDo : EMITTER_BUFF_SIZE; if (_PeriodScheme) { // compute period // NB : we ask to clamp entry because life counter of emitter are incremented, then spawn is called, and only after that dead emitters are removed // so we may have a life counter that is > to 1 currEmitPeriod = (float *) (_PeriodScheme->make(_Owner, size - leftToDo, emitPeriod, sizeof(float), toProcess, true, 1 << 16, true)); if (emitThreshold) { /** Test if 'make' filled our buffer. If this is not the case, we assume that values where precomputed, and that * all null period have already been replaced by the threshold */ if (currEmitPeriod == emitPeriod) { // if there possibility to have 0 in the scheme ? if (_PeriodScheme->getMinValue() <= 0.f && _PeriodScheme->getMaxValue() >= 0.f) { replaceNullPeriodsByThreshold(emitPeriod, toProcess); } } } } else { if (_Period != 0.f || !emitThreshold) { currEmitPeriod = &_Period; } else { currEmitPeriod = &EMIT_PERIOD_THRESHOLD; } } endPhaseIt = phaseIt + toProcess; if (_MaxEmissionCount == 0) // no emission count limit { /// is there an emission delay ? if (_EmitDelay == 0.f) // no emission delay { do { *phaseIt += ellapsedTimeLOD; if ( *phaseIt >= *currEmitPeriod) // phase is greater than period -> must emit { if (*currEmitPeriod != 0) { /** Must ensure phase is valid if period decrease over time */ *phaseIt = std::min(*phaseIt, *currEmitPeriod + ellapsedTimeLOD); // /// compute the number of emissions uint numEmissions = (uint) ::floorf(*phaseIt / *currEmitPeriod); *phaseIt -= *currEmitPeriod * numEmissions; uint emitterIndex = (uint)(phaseIt - _Phase.begin()); nbToGenerate = _GenNbScheme ? _GenNbScheme->get(_Owner, emitterIndex) : _GenNb; if (nbToGenerate) { float deltaT = std::max(0.f, *phaseIt); /// compute the position of the emitter for the needed dates numEmissions = GenEmitterPositionsWithLOD(_Owner, _EmittedType, emitterIndex, numEmissions, deltaT, *currEmitPeriod, inverseEmitLOD, emitterPositions ); /// process each emission at the right pos at the right date nbToGenerate = (sint32) (emitLOD * nbToGenerate); if (!nbToGenerate) nbToGenerate = 1; uint k = numEmissions; float deltaTInc = *currEmitPeriod * inverseEmitLOD; do { --k; processEmitConsistent(emitterPositions[k], emitterIndex, nbToGenerate, deltaT ); deltaT += deltaTInc; } while (k); } } else { const uint32 emitterIndex = (uint32)(phaseIt - _Phase.begin()); nbToGenerate = _GenNbScheme ? _GenNbScheme->get(_Owner, emitterIndex) : _GenNb; if (nbToGenerate) { nbToGenerate = (sint32) (emitLOD * nbToGenerate); if (!nbToGenerate) nbToGenerate = 1; processEmit(emitterIndex, nbToGenerate); } } } ++phaseIt; currEmitPeriod += currEmitPeriodPtrInc; } while (phaseIt != endPhaseIt); } else // thhere's an emission delay { do { if (*phaseIt < _EmitDelay) { *phaseIt += CParticleSystem::EllapsedTime; if (*phaseIt < _EmitDelay) { ++phaseIt; currEmitPeriod += currEmitPeriodPtrInc; continue; } else { *phaseIt = (*phaseIt - _EmitDelay) * emitLOD + _EmitDelay; } } else { *phaseIt += ellapsedTimeLOD; } if ( *phaseIt >= *currEmitPeriod + _EmitDelay) // phase is greater than period -> must emit { if (*currEmitPeriod != 0) { /** Must ensure phase is valid if period decrease over time */ *phaseIt = std::min(*phaseIt, *currEmitPeriod + ellapsedTimeLOD + _EmitDelay); // uint numEmissions = (uint) ::floorf((*phaseIt - _EmitDelay) / *currEmitPeriod); *phaseIt -= *currEmitPeriod * numEmissions; float deltaT = std::max(*phaseIt - _EmitDelay, 0.f); //nlassert(deltaT >= 0.f); /// process each emission at the right pos at the right date uint emitterIndex = (uint)(phaseIt - _Phase.begin()); nbToGenerate = _GenNbScheme ? _GenNbScheme->get(_Owner, emitterIndex) : _GenNb; if (nbToGenerate) { /// compute the position of the emitter for the needed date numEmissions = GenEmitterPositionsWithLOD( _Owner, _EmittedType, emitterIndex, numEmissions, deltaT, *currEmitPeriod, inverseEmitLOD, emitterPositions ); nbToGenerate = (sint32) (emitLOD * nbToGenerate); if (!nbToGenerate) nbToGenerate = 1; uint k = numEmissions; float deltaTInc = *currEmitPeriod * inverseEmitLOD; do { --k; processEmitConsistent(emitterPositions[k], emitterIndex, nbToGenerate, deltaT); deltaT += deltaTInc; } while (k); } } else { const uint32 emitterIndex = (uint32)(phaseIt - _Phase.begin()); nbToGenerate = _GenNbScheme ? _GenNbScheme->get(_Owner, emitterIndex) : _GenNb; if (nbToGenerate) { nbToGenerate = (sint32) (emitLOD * nbToGenerate); if (!nbToGenerate) nbToGenerate = 1; processEmit(emitterIndex, nbToGenerate); } } } ++phaseIt; currEmitPeriod += currEmitPeriodPtrInc; } while (phaseIt != endPhaseIt); } } else // there's an emission count limit { /// is there an emission delay ? if (_EmitDelay == 0.f) // no emission delay { do { if (*numEmitIt < maxEmissionCountLOD) { *phaseIt += ellapsedTimeLOD; if ( *phaseIt >= *currEmitPeriod) // phase is greater than period -> must emit { if (*currEmitPeriod != 0) { /** Must ensure phase is valid if period decrease over time */ *phaseIt = std::min(*phaseIt, *currEmitPeriod + ellapsedTimeLOD); // uint numEmissions = (uint) ::floorf(*phaseIt / *currEmitPeriod); *numEmitIt += numEmissions; *phaseIt -= *currEmitPeriod * numEmissions; float deltaT = std::max(*phaseIt, 0.f); //nlassert(deltaT >= 0.f); uint emitterIndex = (uint)(phaseIt - _Phase.begin()); if (*numEmitIt > _MaxEmissionCount) // make sure we don't go over the emission limit { numEmissions -= *numEmitIt - _MaxEmissionCount; *numEmitIt = _MaxEmissionCount; } nbToGenerate = _GenNbScheme ? _GenNbScheme->get(_Owner, emitterIndex) : _GenNb; if (nbToGenerate) { /// compute the position of the emitter for the needed date numEmissions = GenEmitterPositionsWithLOD(_Owner, _EmittedType, emitterIndex, numEmissions, deltaT, *currEmitPeriod, inverseEmitLOD, emitterPositions ); uint k = numEmissions; /// process each emission at the right pos at the right date nbToGenerate = (sint32) (emitLOD * nbToGenerate); if (!nbToGenerate) nbToGenerate = 1; float deltaTInc = *currEmitPeriod * inverseEmitLOD; do { --k; processEmitConsistent(emitterPositions[k], emitterIndex, nbToGenerate, deltaT ); deltaT += deltaTInc; } while (k); } } else { const uint32 emitterIndex = (uint32)(phaseIt - _Phase.begin()); nbToGenerate = _GenNbScheme ? _GenNbScheme->get(_Owner, emitterIndex) : _GenNb; if (nbToGenerate) { nbToGenerate = (sint32) (emitLOD * nbToGenerate); if (!nbToGenerate) nbToGenerate = 1; processEmit(emitterIndex, nbToGenerate); ++*numEmitIt; } } } } else { *numEmitIt = _MaxEmissionCount; // if the lod change, must ensure that the } ++phaseIt; currEmitPeriod += currEmitPeriodPtrInc; ++ numEmitIt; } while (phaseIt != endPhaseIt); } else // there's an emission delay { do { if (*numEmitIt < maxEmissionCountLOD) { if (*phaseIt < _EmitDelay) { *phaseIt += CParticleSystem::EllapsedTime; if (*phaseIt < _EmitDelay) { ++phaseIt; currEmitPeriod += currEmitPeriodPtrInc; ++numEmitIt; continue; } else { *phaseIt = (*phaseIt - _EmitDelay) * emitLOD + _EmitDelay; } } else { *phaseIt += ellapsedTimeLOD; } // if ( *phaseIt >= *currEmitPeriod + _EmitDelay) // phase is greater than period -> must emit { if (*currEmitPeriod != 0) { /** Must ensure phase is valid if period decrease over time */ *phaseIt = std::min(*phaseIt, *currEmitPeriod + ellapsedTimeLOD + _EmitDelay); // uint numEmissions = (uint) ::floorf((*phaseIt - _EmitDelay) / *currEmitPeriod); *numEmitIt += numEmissions; *phaseIt -= *currEmitPeriod * numEmissions; float deltaT = std::max(*phaseIt - _EmitDelay, 0.f); //nlassert(deltaT >= 0.f); uint emitterIndex = (uint)(phaseIt - _Phase.begin()); if (*numEmitIt > _MaxEmissionCount) // make sure we don't go over the emission limit { numEmissions -= *numEmitIt - _MaxEmissionCount; *numEmitIt = _MaxEmissionCount; } nbToGenerate = _GenNbScheme ? _GenNbScheme->get(_Owner, emitterIndex) : _GenNb; if (nbToGenerate) { /// compute the position of the emitter for the needed date numEmissions = GenEmitterPositionsWithLOD(_Owner, _EmittedType, emitterIndex, numEmissions, deltaT, *currEmitPeriod, inverseEmitLOD, emitterPositions ); uint k = numEmissions; /// process each emission at the right pos at the right date nbToGenerate = (sint32) (emitLOD * nbToGenerate); if (!nbToGenerate) nbToGenerate = 1; float deltaTInc = *currEmitPeriod * inverseEmitLOD; do { --k; processEmitConsistent(emitterPositions[k], emitterIndex, nbToGenerate, deltaT); deltaT += deltaTInc; } while (k); } } else { const uint32 emitterIndex = (uint32)(phaseIt - _Phase.begin()); nbToGenerate = _GenNbScheme ? _GenNbScheme->get(_Owner, emitterIndex) : _GenNb; if (nbToGenerate) { nbToGenerate = (sint32) (emitLOD * nbToGenerate); if (!nbToGenerate) nbToGenerate = 1; processEmit(emitterIndex, nbToGenerate); ++*numEmitIt; } } } } else { *numEmitIt = _MaxEmissionCount; // if the lod change, must ensure that the } ++phaseIt; currEmitPeriod += currEmitPeriodPtrInc; ++numEmitIt; } while (phaseIt != endPhaseIt); } } leftToDo -= toProcess; } while (leftToDo); } ///========================================================================== void CPSEmitter::processRegularEmissionConsistentWithNoLOD(uint firstInstanceIndex) { NL_PS_FUNC(CPSEmitter_processRegularEmissionConsistentWithNoLOD) /// hum, some code factorization would do no harm, but we want to keep tests outside the loops as much as possible... nlassert(_Owner); nlassert(_Owner->getOwner()); // const bool emitThreshold = _Owner->getOwner()->isEmitThresholdEnabled(); // static std::vector emitterPositions; // Positions for the emitter. They are computed by using a parametric trajectory or by using integration const uint size = _Owner->getSize(); nlassert(firstInstanceIndex < size); uint leftToDo = size - firstInstanceIndex, toProcess; float emitPeriod[EMITTER_BUFF_SIZE]; const float *currEmitPeriod; uint currEmitPeriodPtrInc = _PeriodScheme ? 1 : 0; sint32 nbToGenerate; TPSAttribTime::iterator phaseIt = _Phase.begin() + firstInstanceIndex, endPhaseIt; TPSAttribUInt8::iterator numEmitIt; if (firstInstanceIndex < _NumEmission.getSize()) numEmitIt = _NumEmission.begin() + firstInstanceIndex; else numEmitIt = _NumEmission.end(); do { toProcess = leftToDo < EMITTER_BUFF_SIZE ? leftToDo : EMITTER_BUFF_SIZE; if (_PeriodScheme) { // compute period // NB : we ask to clamp entry because life counter of emitter are incremented, then spawn is called, and only after that dead emitters are removed // so we may have a life counter that is > to 1 currEmitPeriod = (float *) (_PeriodScheme->make(_Owner, size - leftToDo, emitPeriod, sizeof(float), toProcess, true, 1 << 16, true)); if (emitThreshold) { /** Test if 'make' filled our buffer. If this is not the case, we assume that values where precomputed, and that * all null period have already been replaced by the threshold */ if (currEmitPeriod == emitPeriod) { // if there possibility to have 0 in the scheme ? if (_PeriodScheme->getMinValue() <= 0.f && _PeriodScheme->getMaxValue() >= 0.f) { replaceNullPeriodsByThreshold(emitPeriod, toProcess); } } } } else { if (_Period != 0.f || !emitThreshold) { currEmitPeriod = &_Period; } else { currEmitPeriod = &EMIT_PERIOD_THRESHOLD; } } endPhaseIt = phaseIt + toProcess; if (_MaxEmissionCount == 0) // no emission count limit { /// is there an emission delay ? if (_EmitDelay == 0.f) // no emission delay { do { *phaseIt += CParticleSystem::EllapsedTime; if ( *phaseIt >= *currEmitPeriod) // phase is greater than period -> must emit { if (*currEmitPeriod != 0) { /** Must ensure phase is valid if period decrease over time */ *phaseIt = std::min(*phaseIt, *currEmitPeriod + CParticleSystem::EllapsedTime); // /// compute the number of emissions uint numEmissions = (uint) ::floorf(*phaseIt / *currEmitPeriod); *phaseIt -= *currEmitPeriod * numEmissions; float deltaT = std::max(0.f, *phaseIt); //nlassert(deltaT >= 0.f); uint emitterIndex = (uint)(phaseIt - _Phase.begin()); /// compute the position of the emitter for the needed dates numEmissions = GenEmitterPositions(_Owner, _EmittedType, emitterIndex, numEmissions, deltaT, *currEmitPeriod, emitterPositions ); /// process each emission at the right pos at the right date nbToGenerate = _GenNbScheme ? _GenNbScheme->get(_Owner, emitterIndex) : _GenNb; uint k = numEmissions; do { --k; processEmitConsistent(emitterPositions[k], emitterIndex, nbToGenerate, deltaT); deltaT += *currEmitPeriod; } while (k); } else { const uint32 emitterIndex = (uint32)(phaseIt - _Phase.begin()); nbToGenerate = _GenNbScheme ? _GenNbScheme->get(_Owner, emitterIndex) : _GenNb; processEmit(emitterIndex, nbToGenerate); } } ++phaseIt; currEmitPeriod += currEmitPeriodPtrInc; } while (phaseIt != endPhaseIt); } else // thhere's an emission delay { do { *phaseIt += CParticleSystem::EllapsedTime; if ( *phaseIt >= *currEmitPeriod + _EmitDelay) // phase is greater than period -> must emit { if (*currEmitPeriod != 0) { /** Must ensure phase is valid if period decrease over time */ *phaseIt = std::min(*phaseIt, *currEmitPeriod + CParticleSystem::EllapsedTime + _EmitDelay); // uint numEmissions = (uint) ::floorf((*phaseIt - _EmitDelay) / *currEmitPeriod); *phaseIt -= *currEmitPeriod * numEmissions; float deltaT = std::max(*phaseIt - _EmitDelay, 0.f); //nlassert(deltaT >= 0.f); uint emitterIndex = (uint)(phaseIt - _Phase.begin()); /// compute the position of the emitter for the needed date numEmissions = GenEmitterPositions(_Owner, _EmittedType, emitterIndex, numEmissions, deltaT, *currEmitPeriod, emitterPositions ); /// process each emission at the right pos at the right date nbToGenerate = _GenNbScheme ? _GenNbScheme->get(_Owner, emitterIndex) : _GenNb; uint k = numEmissions; do { --k; processEmitConsistent(emitterPositions[k], emitterIndex, nbToGenerate, deltaT); deltaT += *currEmitPeriod; } while (k); } else { const uint32 emitterIndex = (uint32)(phaseIt - _Phase.begin()); nbToGenerate = _GenNbScheme ? _GenNbScheme->get(_Owner, emitterIndex) : _GenNb; processEmit(emitterIndex, nbToGenerate); } } ++phaseIt; currEmitPeriod += currEmitPeriodPtrInc; } while (phaseIt != endPhaseIt); } } else // there's an emission count limit { /// is there an emission delay ? if (_EmitDelay == 0.f) // no emission delay { do { if (*numEmitIt < _MaxEmissionCount) { *phaseIt += CParticleSystem::EllapsedTime; if ( *phaseIt >= *currEmitPeriod) // phase is greater than period -> must emit { if (*currEmitPeriod != 0) { /** Must ensure phase is valid if period decrease over time */ *phaseIt = std::min(*phaseIt, *currEmitPeriod + CParticleSystem::EllapsedTime); // uint numEmissions = (uint) ::floorf(*phaseIt / *currEmitPeriod); *numEmitIt += numEmissions; *phaseIt -= *currEmitPeriod * numEmissions; float deltaT = std::max(*phaseIt, 0.f); //nlassert(deltaT >= 0.f); uint emitterIndex = (uint)(phaseIt - _Phase.begin()); if (*numEmitIt > _MaxEmissionCount) // make sure we don't go over the emission limit { numEmissions -= *numEmitIt - _MaxEmissionCount; *numEmitIt = _MaxEmissionCount; } /// compute the position of the emitter for the needed date numEmissions = GenEmitterPositions(_Owner, _EmittedType, emitterIndex, numEmissions, deltaT, *currEmitPeriod, emitterPositions ); uint k = numEmissions; /// process each emission at the right pos at the right date nbToGenerate = _GenNbScheme ? _GenNbScheme->get(_Owner, emitterIndex) : _GenNb; do { --k; processEmitConsistent(emitterPositions[k], emitterIndex, nbToGenerate, deltaT); deltaT += *currEmitPeriod; } while (k); } else { const uint32 emitterIndex = (uint32)(phaseIt - _Phase.begin()); nbToGenerate = _GenNbScheme ? _GenNbScheme->get(_Owner, emitterIndex) : _GenNb; processEmit(emitterIndex, nbToGenerate); ++*numEmitIt; } } } ++phaseIt; currEmitPeriod += currEmitPeriodPtrInc; ++ numEmitIt; } while (phaseIt != endPhaseIt); } else // there's an emission delay { do { if (*numEmitIt < _MaxEmissionCount) { *phaseIt += CParticleSystem::EllapsedTime; if ( *phaseIt >= *currEmitPeriod + _EmitDelay) // phase is greater than period -> must emit { if (*currEmitPeriod != 0) { /** Must ensure phase is valid if period decrease over time */ *phaseIt = std::min(*phaseIt, *currEmitPeriod + CParticleSystem::EllapsedTime + _EmitDelay); // uint numEmissions = (uint) ::floorf((*phaseIt - _EmitDelay) / *currEmitPeriod); *numEmitIt += numEmissions; *phaseIt -= *currEmitPeriod * numEmissions; float deltaT = std::max(*phaseIt - _EmitDelay, 0.f); //nlassert(deltaT >= 0.f); uint emitterIndex = (uint)(phaseIt - _Phase.begin()); if (*numEmitIt > _MaxEmissionCount) // make sure we don't go over the emission limit { numEmissions -= *numEmitIt - _MaxEmissionCount; *numEmitIt = _MaxEmissionCount; } /// compute the position of the emitter for the needed date numEmissions = GenEmitterPositions(_Owner, _EmittedType, emitterIndex, numEmissions, deltaT, *currEmitPeriod, emitterPositions ); uint k = numEmissions; /// process each emission at the right pos at the right date nbToGenerate = _GenNbScheme ? _GenNbScheme->get(_Owner, emitterIndex) : _GenNb; do { --k; processEmitConsistent(emitterPositions[k], emitterIndex, nbToGenerate, deltaT); deltaT += *currEmitPeriod; } while (k); } else { const uint32 emitterIndex = (uint32)(phaseIt - _Phase.begin()); nbToGenerate = _GenNbScheme ? _GenNbScheme->get(_Owner, emitterIndex) : _GenNb; processEmit(emitterIndex, nbToGenerate); ++*numEmitIt; } } } ++phaseIt; currEmitPeriod += currEmitPeriodPtrInc; ++numEmitIt; } while (phaseIt != endPhaseIt); } } leftToDo -= toProcess; } while (leftToDo); } ///========================================================================== void CPSEmitter::step(TPSProcessPass pass) { NL_PS_FUNC(CPSEmitter_step) if (pass == PSToolRender) { showTool(); } } ///========================================================================== void CPSEmitter::computeSpawns(uint firstInstanceIndex) { NL_PS_FUNC(CPSEmitter_computeSpawns) if (!_EmittedType) return; nlassert(CParticleSystem::InsideSimLoop); const uint32 size = _Owner->getSize(); if (!size) return; if (CParticleSystem::EllapsedTime == 0.f) return; // do nothing when paused CParticleSystem *ps = _Owner->getOwner(); nlassert(ps); float emitLOD; if (ps->isAutoLODEnabled() && !ps->isSharingEnabled() && !_BypassAutoLOD) { // temp test for auto lod emitLOD = ps->getAutoLODEmitRatio(); } else { emitLOD = 1.f; } nlassert(_EmissionType == CPSEmitter::regular); if (!_ConsistentEmission) { if (emitLOD != 1.f) { processRegularEmission(firstInstanceIndex, emitLOD); } else { processRegularEmissionWithNoLOD(firstInstanceIndex); } } else { if (emitLOD != 1.f) { if (emitLOD != 0.f) { processRegularEmissionConsistent(firstInstanceIndex, emitLOD, 1.f / emitLOD); } } else { processRegularEmissionConsistentWithNoLOD(firstInstanceIndex); } } } ///========================================================================== void CPSEmitter::newElement(const CPSEmitterInfo &info) { NL_PS_FUNC(CPSEmitter_newElement) nlassert(_Phase.getSize() != _Phase.getMaxSize()); _Phase.insert(0.f); if (_MaxEmissionCount != 0) { _NumEmission.insert(0); } if (_PeriodScheme && _PeriodScheme->hasMemory()) _PeriodScheme->newElement(info); if (_GenNbScheme && _GenNbScheme->hasMemory()) _GenNbScheme->newElement(info); } ///========================================================================== inline void CPSEmitter::deleteElementBase(uint32 index) { NL_PS_FUNC(CPSEmitter_deleteElementBase) if (_PeriodScheme && _PeriodScheme->hasMemory()) _PeriodScheme->deleteElement(index); if (_GenNbScheme && _GenNbScheme->hasMemory()) _GenNbScheme->deleteElement(index); _Phase.remove(index); if (_MaxEmissionCount != 0) { _NumEmission.remove(index); } } ///========================================================================== void CPSEmitter::deleteElement(uint32 index) { NL_PS_FUNC(CPSEmitter_deleteElement) if (_EmissionType == CPSEmitter::onDeath && _EmittedType && _Active) { if (!_BypassEmitOnDeath) { const uint32 nbToGenerate = _GenNbScheme ? _GenNbScheme->get(_Owner, index) : _GenNb; processEmitOutsideSimLoop(index, nbToGenerate); } } deleteElementBase(index); } ///========================================================================== void CPSEmitter::deleteElement(uint32 index, TAnimationTime timeUntilNextSimStep) { NL_PS_FUNC(CPSEmitter_deleteElement) if (_EmissionType == CPSEmitter::onDeath && _EmittedType && _Active) { const uint32 nbToGenerate = _GenNbScheme ? _GenNbScheme->get(_Owner, index) : _GenNb; processEmitConsistent(_Owner->getPos()[index], index, nbToGenerate, timeUntilNextSimStep); } deleteElementBase(index); } ///========================================================================== void CPSEmitter::resize(uint32 size) { NL_PS_FUNC(CPSEmitter_resize) nlassert(size < (1 << 16)); if (_PeriodScheme && _PeriodScheme->hasMemory()) _PeriodScheme->resize(size, _Owner->getSize()); if (_GenNbScheme && _GenNbScheme->hasMemory()) _GenNbScheme->resize(size, _Owner->getSize()); _Phase.resize(size); if (_MaxEmissionCount != 0) { _NumEmission.resize(size); } } ///========================================================================== void CPSEmitter::bounceOccurred(uint32 index, TAnimationTime timeToNextSimStep) { NL_PS_FUNC(CPSEmitter_bounceOccurred) // TODO : avoid duplication with deleteElement if (_EmittedType && _EmissionType == CPSEmitter::onBounce) { const uint32 nbToGenerate = _GenNbScheme ? _GenNbScheme->get(_Owner, index) : _GenNb; processEmitConsistent(_Owner->getPos()[index], index, nbToGenerate, timeToNextSimStep); } } ///========================================================================== void CPSEmitter::serial(NLMISC::IStream &f) throw(NLMISC::EStream) { NL_PS_FUNC(CPSEmitter_serial) /// version 6 : the flag _EmitDirBasis no longer exist, it has been replaced by _UserMatrixModeForEmissionDirection // /// version 5 : added _BypassAutoLOD /// version 4 : added consistent emissions sint ver = f.serialVersion(6); CPSLocatedBindable::serial(f); f.serialPolyPtr(_EmittedType); f.serial(_Phase); f.serial(_SpeedInheritanceFactor); bool speedBasisEmission = _SpeedBasisEmission; // tmp copy because of bitfield serialization scheme f.serial(speedBasisEmission); _SpeedBasisEmission = speedBasisEmission; f.serialEnum(_EmissionType); // this is for use with serial bool trueB = true, falseB = false; if (!f.isReading()) { switch (_EmissionType) { case CPSEmitter::regular: if (_PeriodScheme) { f.serial(trueB); f.serialPolyPtr(_PeriodScheme); } else { f.serial(falseB); f.serial(_Period); } if (ver >= 3) { f.serial(_EmitDelay, _MaxEmissionCount); } break; default: break; } if (_GenNbScheme) { f.serial(trueB); f.serialPolyPtr(_GenNbScheme); } else { f.serial(falseB); f.serial(_GenNb); } } else { bool useScheme; switch (_EmissionType) { case CPSEmitter::regular: { f.serial(useScheme); if (useScheme) { delete _PeriodScheme; f.serialPolyPtr(_PeriodScheme); } else { f.serial(_Period); } if (ver >= 3) { f.serial(_EmitDelay, _MaxEmissionCount); updateMaxCountVect(); } } break; default: break; } f.serial(useScheme); if (useScheme) { delete _GenNbScheme; f.serialPolyPtr(_GenNbScheme); } else { f.serial(_GenNb); } } if (ver > 1 && ver < 6) { nlassert(f.isReading()); bool emitDirBasis; f.serial(emitDirBasis); if (emitDirBasis) { _UserMatrixModeForEmissionDirection = false; _UserDirectionMatrixMode = PSFXWorldMatrix; } else { _UserMatrixModeForEmissionDirection = true; if (_Owner) { _UserDirectionMatrixMode = _Owner->getMatrixMode() == PSFXWorldMatrix ? PSIdentityMatrix : PSFXWorldMatrix; } else { _UserDirectionMatrixMode = PSFXWorldMatrix; } } } if (ver >= 4) { bool consistentEmission = _ConsistentEmission; // tmp copy because of bitfield serialization scheme f.serial(consistentEmission); _ConsistentEmission = consistentEmission; } if (ver >= 5) { bool byassAutoLOD = _BypassAutoLOD; // tmp copy because of bitfield serialization scheme f.serial(byassAutoLOD); _BypassAutoLOD = byassAutoLOD; } if (ver >= 6) { bool userMatrixModeForEmissionDirection = _UserMatrixModeForEmissionDirection; // tmp copy because of bitfield serialization scheme f.serial(userMatrixModeForEmissionDirection); _UserMatrixModeForEmissionDirection = userMatrixModeForEmissionDirection; f.serialEnum(_UserDirectionMatrixMode); } } ///========================================================================== void CPSEmitter::updateMaxCountVect() { NL_PS_FUNC(CPSEmitter_updateMaxCountVect) if (!_MaxEmissionCount || !_Owner) { _NumEmission.resize(0); } else { _NumEmission.resize(_Owner->getMaxSize()); while (_NumEmission.getSize() != 0) { _NumEmission.remove(0); } while (_NumEmission.getSize() != _Owner->getSize()) { _NumEmission.insert(0); } } } ///========================================================================== void CPSEmitter::setEmitDelay(float delay) { NL_PS_FUNC(CPSEmitter_setEmitDelay) _EmitDelay = delay; if (_Owner && _Owner->getOwner()) { _Owner->getOwner()->systemDurationChanged(); } } ///========================================================================== bool CPSEmitter::setMaxEmissionCount(uint8 count) { NL_PS_FUNC(CPSEmitter_setMaxEmissionCount) if (count == _MaxEmissionCount) return true; nlassert(_Owner && _Owner->getOwner()); CParticleSystem *ps = _Owner->getOwner(); if (ps->getBypassMaxNumIntegrationSteps()) { uint8 oldEmissiontCount = _MaxEmissionCount; // should check that the new value is valid _MaxEmissionCount = count; if (testEmitForever()) { _MaxEmissionCount = oldEmissiontCount; nlwarning(" can't set max emission count to %d \ with the current configuration : the system has been flagged with \ 'BypassMaxNumIntegrationSteps', and should have a finite duration. \ The new value is not set", (int) count); return false; } } ps->systemDurationChanged(); _MaxEmissionCount = count; updateMaxCountVect(); return true; } ///========================================================================== bool CPSEmitter::checkLoop() const { NL_PS_FUNC(CPSEmitter_checkLoop) nlassert(_Owner); nlassert(_Owner->getOwner()); if (!_EmittedType) return false; std::set seenLocated; // the located we've already seen std::vector leftLoc(1); // the located that are left to see leftLoc[0] = _EmittedType; do { const CPSLocated *curr = leftLoc.back(); if (curr == this->_Owner) return true; leftLoc.pop_back(); seenLocated.insert(curr); for(uint32 k = 0; k < curr->getNbBoundObjects(); ++k) { const CPSEmitter *emitter = dynamic_cast(curr->getBoundObject(k)); if (emitter && emitter->_EmittedType) { if (seenLocated.find(emitter->_EmittedType) == seenLocated.end()) // not already seen this one ? { leftLoc.push_back(emitter->_EmittedType); } } } } while (!leftLoc.empty()); return false; } ///========================================================================== bool CPSEmitter::testEmitForever() const { NL_PS_FUNC(CPSEmitter_testEmitForever) if (!_Owner) { nlwarning(" The emitter should be inserted in a CPSLocated instance for this call to work."); nlassert(0); return true; } if (!_Owner->getLastForever()) return false; switch(getEmissionType()) { case CPSEmitter::onBounce: case CPSEmitter::externEmit: case CPSEmitter::regular: // it is ok only if a limited number of located is emitted if (getMaxEmissionCount() == 0) return true; break; case CPSEmitter::onDeath: return true; // the emitter never dies, so .. case CPSEmitter::once: return false; break; default: nlassert(0); // not a known type break; } return false; } //////////////////////////////////////////// // implementation of CPSModulatedEmitter // //////////////////////////////////////////// void CPSModulatedEmitter::serialEmitteeSpeedScheme(NLMISC::IStream &f) throw(NLMISC::EStream) { NL_PS_FUNC(CPSModulatedEmitter_IStream ) bool useScheme; if (!f.isReading()) { useScheme = useEmitteeSpeedScheme(); } f.serial(useScheme); if (useScheme) { f.serialPolyPtr(_EmitteeSpeedScheme); } else { f.serial(_EmitteeSpeed); } } //////////////////////////////////////////// // implementation of CPSEmitterOmni // //////////////////////////////////////////// ///========================================================================== void CPSEmitterOmni::emit(const NLMISC::CVector &srcPos, uint32 index, CVector &pos, CVector &speed) { NL_PS_FUNC(CPSEmitterOmni_emit) // TODO : verifier que ca marche si une particule s'emet elle-mem nlassert(_EmittedType); CVector v( ((rand() % 1000) - 500) / 500.0f , ((rand() % 1000) - 500) / 500.0f , ((rand() % 1000) - 500) / 500.0f); v.normalize(); v *= _EmitteeSpeedScheme ? _EmitteeSpeedScheme->get(_Owner, index) : _EmitteeSpeed; pos = srcPos; speed = v; } ///========================================================================== void CPSEmitterOmni::serial(NLMISC::IStream &f) throw(NLMISC::EStream) { NL_PS_FUNC(CPSEmitterOmni_serial) f.serialVersion(1); CPSEmitter::serial(f); CPSModulatedEmitter::serialEmitteeSpeedScheme(f); } ///========================================================================== void CPSEmitterOmni::newElement(const CPSEmitterInfo &info) { NL_PS_FUNC(CPSEmitterOmni_newElement) CPSEmitter::newElement(info); newEmitteeSpeedElement(info); } ///========================================================================== inline void CPSEmitterOmni::deleteElementBase(uint32 index) { NL_PS_FUNC(CPSEmitterOmni_deleteElementBase) deleteEmitteeSpeedElement(index); } ///========================================================================== void CPSEmitterOmni::deleteElement(uint32 index, TAnimationTime timeUntilNextSimStep) { NL_PS_FUNC(CPSEmitterOmni_deleteElement) CPSEmitter::deleteElement(index, timeUntilNextSimStep); deleteElementBase(index); } ///========================================================================== void CPSEmitterOmni::deleteElement(uint32 index) { NL_PS_FUNC(CPSEmitterOmni_deleteElement) CPSEmitter::deleteElement(index); deleteElementBase(index); } ///========================================================================== void CPSEmitterOmni::resize(uint32 capacity) { NL_PS_FUNC(CPSEmitterOmni_resize) nlassert(capacity < (1 << 16)); CPSEmitter::resize(capacity); resizeEmitteeSpeed(capacity); } ///========================================================================== void CPSEmitterDirectionnal::emit(const NLMISC::CVector &srcPos, uint32 index, CVector &pos, CVector &speed) { NL_PS_FUNC(CPSEmitterDirectionnal_emit) // TODO : verifier que ca marche si une particule s'emet elle-mem nlassert(_EmittedType); speed = (_EmitteeSpeedScheme ? _EmitteeSpeedScheme->get(_Owner, index) : _EmitteeSpeed) * _Dir; pos = srcPos; } ///========================================================================== void CPSEmitterDirectionnal::newElement(const CPSEmitterInfo &info) { NL_PS_FUNC(CPSEmitterDirectionnal_newElement) CPSEmitter::newElement(info); newEmitteeSpeedElement(info); } ///========================================================================== inline void CPSEmitterDirectionnal::deleteElementBase(uint32 index) { NL_PS_FUNC(CPSEmitterDirectionnal_deleteElementBase) deleteEmitteeSpeedElement(index); } ///========================================================================== void CPSEmitterDirectionnal::deleteElement(uint32 index, TAnimationTime timeUntilNextSimStep) { NL_PS_FUNC(CPSEmitterDirectionnal_deleteElement) CPSEmitter::deleteElement(index, timeUntilNextSimStep); deleteElementBase(index); } ///========================================================================== void CPSEmitterDirectionnal::deleteElement(uint32 index) { NL_PS_FUNC(CPSEmitterDirectionnal_deleteElement) CPSEmitter::deleteElement(index); deleteElementBase(index); } ///========================================================================== void CPSEmitterDirectionnal::resize(uint32 capacity) { NL_PS_FUNC(CPSEmitterDirectionnal_resize) nlassert(capacity < (1 << 16)); CPSEmitter::resize(capacity); resizeEmitteeSpeed(capacity); } ///========================================================================== void CPSEmitterDirectionnal::serial(NLMISC::IStream &f) throw(NLMISC::EStream) { NL_PS_FUNC(CPSEmitterDirectionnal_IStream ) f.serialVersion(1); CPSEmitter::serial(f); CPSModulatedEmitter::serialEmitteeSpeedScheme(f); f.serial(_Dir); } //////////////////////////////////////////// // implementation of CPSEmitterRectangle // //////////////////////////////////////////// ///========================================================================== void CPSEmitterRectangle::serial(NLMISC::IStream &f) throw(NLMISC::EStream) { NL_PS_FUNC(CPSEmitterRectangle_IStream ) f.serialVersion(1); CPSEmitter::serial(f); CPSModulatedEmitter::serialEmitteeSpeedScheme(f); f.serial(_Basis); f.serial(_Width); f.serial(_Height); f.serial(_Dir); } ///========================================================================== void CPSEmitterRectangle::emit(const NLMISC::CVector &srcPos, uint32 index, CVector &pos, CVector &speed) { NL_PS_FUNC(CPSEmitterRectangle_emit) CVector N = _Basis[index].X ^ _Basis[index].Y; pos = srcPos + ((rand() % 32000) * (1.f / 16000) - 1.f) * _Width[index] * _Basis[index].X + ((rand() % 32000) * (1.f / 16000) - 1.f) * _Height[index] * _Basis[index].Y; speed = (_EmitteeSpeedScheme ? _EmitteeSpeedScheme->get(_Owner, index) : _EmitteeSpeed) * (_Dir.x * _Basis[index].X+ _Dir.y * _Basis[index].Y + _Dir.z * N); } ///========================================================================== void CPSEmitterRectangle::setMatrix(uint32 index, const CMatrix &m) { NL_PS_FUNC(CPSEmitterRectangle_setMatrix) _Owner->getPos()[index] = m.getPos(); _Basis[index].X = m.getI(); _Basis[index].Y = m.getJ(); } ///========================================================================== CMatrix CPSEmitterRectangle::getMatrix(uint32 index) const { NL_PS_FUNC(CPSEmitterRectangle_getMatrix) CMatrix m; m.setPos(_Owner->getPos()[index]); m.setRot(_Basis[index].X, _Basis[index].Y, _Basis[index].X ^ _Basis[index].Y, true); return m; } ///========================================================================== void CPSEmitterRectangle::setScale(uint32 index, float scale) { NL_PS_FUNC(CPSEmitterRectangle_setScale) _Width[index] = scale; _Height[index] = scale; } ///========================================================================== void CPSEmitterRectangle::setScale(uint32 index, const CVector &s) { NL_PS_FUNC(CPSEmitterRectangle_setScale) _Width[index] = s.x; _Height[index] = s.y; } ///========================================================================== CVector CPSEmitterRectangle::getScale(uint32 index) const { NL_PS_FUNC(CPSEmitterRectangle_getScale) return CVector(_Width[index], _Height[index], 1.f); } ///========================================================================== void CPSEmitterRectangle::newElement(const CPSEmitterInfo &info) { NL_PS_FUNC( CPSEmitterRectangle_newElement) CPSEmitter::newElement(info); newEmitteeSpeedElement(info); _Basis.insert(CPlaneBasis(CVector::K)); _Width.insert(1.f); _Height.insert(1.f); } ///========================================================================== inline void CPSEmitterRectangle::deleteElementBase(uint32 index) { NL_PS_FUNC(CPSEmitterRectangle_deleteElementBase) deleteEmitteeSpeedElement(index); _Basis.remove(index); _Width.remove(index); _Height.remove(index); } ///========================================================================== void CPSEmitterRectangle::deleteElement(uint32 index) { NL_PS_FUNC(CPSEmitterRectangle_deleteElement) CPSEmitter::deleteElement(index); deleteElementBase(index); } ///========================================================================== void CPSEmitterRectangle::deleteElement(uint32 index, TAnimationTime timeUntilNextSimStep) { NL_PS_FUNC(CPSEmitterRectangle_deleteElement) CPSEmitter::deleteElement(index, timeUntilNextSimStep); deleteElementBase(index); } ///========================================================================== void CPSEmitterRectangle::resize(uint32 size) { NL_PS_FUNC(CPSEmitterRectangle_resize) nlassert(size < (1 << 16)); CPSEmitter::resize(size); resizeEmitteeSpeed(size); _Basis.resize(size); _Width.resize(size); _Height.resize(size); } ///========================================================================== void CPSEmitterRectangle::showTool(void) { NL_PS_FUNC(CPSEmitterRectangle_showTool) nlassert(_Owner); const uint size = _Owner->getSize(); if (!size) return; setupDriverModelMatrix(); CMatrix mat; CPSLocated *loc; uint32 index; CPSLocatedBindable *lb; _Owner->getOwner()->getCurrentEditedElement(loc, index, lb); for (uint k = 0; k < size; ++k) { const CVector &I = _Basis[k].X; const CVector &J = _Basis[k].Y; mat.setRot(I, J , I ^J); mat.setPos(_Owner->getPos()[k]); CPSUtil::displayBasis(getDriver() ,getLocalToWorldMatrix(), mat, 1.f, *getFontGenerator(), *getFontManager()); setupDriverModelMatrix(); const CRGBA col = ((lb == NULL || this == lb) && loc == _Owner && index == k ? CRGBA::Red : CRGBA(127, 127, 127)); const CVector &pos = _Owner->getPos()[k]; CPSUtil::display3DQuad(*getDriver(), pos + I * _Width[k] + J * _Height[k] , pos + I * _Width[k] - J * _Height[k] , pos - I * _Width[k] - J * _Height[k] , pos - I * _Width[k] + J * _Height[k], col); } } //////////////////////////////////// // CPSEmitterconic implementation // //////////////////////////////////// ///========================================================================== void CPSEmitterConic::serial(NLMISC::IStream &f) throw(NLMISC::EStream) { NL_PS_FUNC(CPSEmitterConic_serial) f.serialVersion(1); CPSEmitterDirectionnal::serial(f); f.serial(_Radius); } ///========================================================================== void CPSEmitterConic::setDir(const CVector &v) { NL_PS_FUNC(CPSEmitterConic_setDir) CPSEmitterDirectionnal::setDir(v); } ///========================================================================== void CPSEmitterConic::emit(const NLMISC::CVector &srcPos, uint32 index, CVector &pos, CVector &speed) { NL_PS_FUNC(CPSEmitterConic_emit) // TODO : optimize that nlassert(_EmittedType); // we choose a custom direction like with omnidirectionnal emitter // then we force the direction vect to have the unit size static const double divRand = (2.0 / RAND_MAX); CVector dir((float) (rand() * divRand - 1) , (float) (rand() * divRand - 1) , (float) (rand() * divRand - 1) ); const float n =dir.norm(); dir *= _Radius / n; dir -= (_Dir * dir) * _Dir; dir += _Dir; dir.normalize(); speed = (_EmitteeSpeedScheme ? _EmitteeSpeedScheme->get(_Owner, index) : _EmitteeSpeed) * dir; pos = srcPos; } //////////////////////////////////////// // CPSSphericalEmitter implementation // //////////////////////////////////////// ///========================================================================== void CPSSphericalEmitter::emit(const NLMISC::CVector &srcPos, uint32 index, CVector &pos, CVector &speed) { NL_PS_FUNC(CPSSphericalEmitter_emit) static const double divRand = (2.0 / RAND_MAX); CVector dir((float) (rand() * divRand - 1), (float) (rand() * divRand - 1) , (float) (rand() * divRand - 1) ); dir.normalize(); pos = srcPos + _Radius[index] * dir; speed = (_EmitteeSpeedScheme ? _EmitteeSpeedScheme->get(_Owner, index) : _EmitteeSpeed) * dir; } ///========================================================================== void CPSSphericalEmitter::showTool(void) { NL_PS_FUNC(CPSSphericalEmitter_showTool) CPSLocated *loc; uint32 index; CPSLocatedBindable *lb; _Owner->getOwner()->getCurrentEditedElement(loc, index, lb); TPSAttribFloat::const_iterator radiusIt = _Radius.begin(); TPSAttribVector::const_iterator posIt = _Owner->getPos().begin(), endPosIt = _Owner->getPos().end(); setupDriverModelMatrix(); for (uint k = 0; posIt != endPosIt; ++posIt, ++radiusIt, ++k) { const CRGBA col = ((lb == NULL || this == lb) && loc == _Owner && index == k ? CRGBA::Red : CRGBA(127, 127, 127)); CPSUtil::displaySphere(*getDriver(), *radiusIt, *posIt, 5, col); } } ///========================================================================== void CPSSphericalEmitter::setMatrix(uint32 index, const CMatrix &m) { NL_PS_FUNC(CPSSphericalEmitter_setMatrix) nlassert(index < _Radius.getSize()); // compute new pos _Owner->getPos()[index] = m.getPos(); } ///========================================================================== CMatrix CPSSphericalEmitter::getMatrix(uint32 index) const { NL_PS_FUNC(CPSSphericalEmitter_getMatrix) nlassert(index < _Radius.getSize()); CMatrix m; m.identity(); m.translate(_Owner->getPos()[index]); return m; } ///========================================================================== void CPSSphericalEmitter::serial(NLMISC::IStream &f) throw(NLMISC::EStream) { NL_PS_FUNC(CPSSphericalEmitter_serial) f.serialVersion(1); CPSEmitter::serial(f); CPSModulatedEmitter::serialEmitteeSpeedScheme(f); f.serial(_Radius); } ///========================================================================== void CPSSphericalEmitter::newElement(const CPSEmitterInfo &info) { NL_PS_FUNC(CPSSphericalEmitter_newElement) CPSEmitter::newElement(info); newEmitteeSpeedElement(info); _Radius.insert(1.f); } ///========================================================================== inline void CPSSphericalEmitter::deleteElementBase(uint32 index) { NL_PS_FUNC(CPSSphericalEmitter_deleteElementBase) deleteEmitteeSpeedElement(index); _Radius.remove(index); } ///========================================================================== void CPSSphericalEmitter::deleteElement(uint32 index) { NL_PS_FUNC(CPSSphericalEmitter_deleteElement) CPSEmitter::deleteElement(index); deleteElementBase(index); } ///========================================================================== void CPSSphericalEmitter::deleteElement(uint32 index, TAnimationTime timeUntilNextSimStep) { NL_PS_FUNC(CPSSphericalEmitter_deleteElement) CPSEmitter::deleteElement(index, timeUntilNextSimStep); deleteElementBase(index); } ///========================================================================== void CPSSphericalEmitter::resize(uint32 size) { NL_PS_FUNC(CPSSphericalEmitter_resize) nlassert(size < (1 << 16)); CPSEmitter::resize(size); resizeEmitteeSpeed(size); _Radius.resize(size); } ///////////////////////////////////// // CPSRadialEmitter implementation // ///////////////////////////////////// ///========================================================================== void CPSRadialEmitter::serial(NLMISC::IStream &f) throw(NLMISC::EStream) { NL_PS_FUNC(CPSRadialEmitter_serial) f.serialVersion(1); CPSEmitterDirectionnal::serial(f); } ///========================================================================== void CPSRadialEmitter::emit(const NLMISC::CVector &srcPos, uint32 index, NLMISC::CVector &pos, NLMISC::CVector &speed) { NL_PS_FUNC(CPSRadialEmitter_emit) // TODO : verify if it works when a particle emits itself nlassert(_EmittedType); static const double divRand = (2.0 / RAND_MAX); CVector dir((float) (rand() * divRand - 1), (float) (rand() * divRand - 1), (float) (rand() * divRand - 1) ); dir -= (dir * _Dir) * _Dir; //keep tangential direction dir.normalize(); speed = (_EmitteeSpeedScheme ? _EmitteeSpeedScheme->get(_Owner, index) : _EmitteeSpeed) * dir; pos = srcPos; } ///=============================================================================== void CPSEmitter::enableSpeedBasisEmission(bool enabled /*=true*/) { NL_PS_FUNC(CPSEmitter_enableSpeedBasisEmission) bool wasUserMatNeeded = isUserMatrixUsed(); _SpeedBasisEmission = enabled; updatePSRefCountForUserMatrixUsage(isUserMatrixUsed(), wasUserMatNeeded); } ///=============================================================================== void CPSEmitter::enableUserMatrixModeForEmissionDirection(bool enable /*=true*/) { NL_PS_FUNC(CPSEmitter_enableUserMatrixModeForEmissionDirection) bool wasUserMatNeeded = isUserMatrixUsed(); _UserMatrixModeForEmissionDirection = enable; updatePSRefCountForUserMatrixUsage(isUserMatrixUsed(), wasUserMatNeeded); } ///=============================================================================== void CPSEmitter::setUserMatrixModeForEmissionDirection(TPSMatrixMode matrixMode) { NL_PS_FUNC(CPSEmitter_setUserMatrixModeForEmissionDirection) bool wasUserMatNeeded = isUserMatrixUsed(); _UserDirectionMatrixMode = matrixMode; updatePSRefCountForUserMatrixUsage(isUserMatrixUsed(), wasUserMatNeeded); } ///========================================================================== void CPSEmitter::updatePSRefCountForUserMatrixUsage(bool matrixIsNeededNow, bool matrixWasNeededBefore) { NL_PS_FUNC(CPSEmitter_updatePSRefCountForUserMatrixUsage) if (_Owner && _Owner->getOwner()) { if (matrixIsNeededNow && !matrixWasNeededBefore) { _Owner->getOwner()->addRefForUserSysCoordInfo(); } else if (!matrixIsNeededNow && matrixWasNeededBefore) { _Owner->getOwner()->releaseRefForUserSysCoordInfo(); } } } ///========================================================================== bool CPSEmitter::isUserMatrixUsed() const { NL_PS_FUNC(CPSEmitter_isUserMatrixUsed) return !_SpeedBasisEmission && _UserMatrixModeForEmissionDirection && _UserDirectionMatrixMode == PSUserMatrix; } ///========================================================================== bool CPSEmitter::getUserMatrixUsageCount() const { NL_PS_FUNC(CPSEmitter_getUserMatrixUsageCount) return isUserMatrixUsed() ? 1 : 0; } ///========================================================================== void CPSEmitter::doEmitOnce(uint firstInstanceIndex) { NL_PS_FUNC(CPSEmitter_doEmitOnce) if (!_EmittedType) return; if (!_GenNbScheme && _GenNb == 0) return; nlassert(_Owner); nlassert(CParticleSystem::InsideSimLoop); // should only be called by the sim loop float emitLOD; nlassert(_Owner); nlassert(_Owner->getOwner()); const CParticleSystem *ps = _Owner->getOwner(); if (ps->isAutoLODEnabled() && !ps->isSharingEnabled() && !_BypassAutoLOD) { // temp test for auto lod emitLOD = ps->getAutoLODEmitRatio(); } else { emitLOD = 1.f; } nlassert(emitLOD >= 0.f); if (_GenNbScheme) { const uint BATCH_SIZE = 1024; uint32 numToEmit[BATCH_SIZE]; uint k = firstInstanceIndex; nlassert(firstInstanceIndex < _Owner->getSize()); uint leftToDo = _Owner->getSize() - firstInstanceIndex; while (leftToDo) { uint toProcess = std::min((uint) BATCH_SIZE, leftToDo); uint32 *numToEmitPtr = (uint32 *) _GenNbScheme->make(_Owner, k, numToEmit, sizeof(uint32), true); leftToDo -= toProcess; while (toProcess) { CVector startPos; if (!_Owner->isParametricMotionEnabled()) { startPos = _Owner->getPos()[k] - _Owner->getSpeed()[k] * CParticleSystem::EllapsedTime; } else { startPos = _Owner->getParametricInfos()[k].Pos; } float currTime = _Owner->getTime()[k]; _Owner->getTime()[k] = 0.f; // when emit occurred, time was 0 sint32 nbToGenerate = (sint32) (emitLOD * *numToEmitPtr); if (nbToGenerate > 0) { nbToGenerate = std::min(nbToGenerate, (sint32) _EmittedType->getMaxSize()); processEmitConsistent(startPos, k, nbToGenerate, _Owner->getAgeInSeconds(k) / CParticleSystem::RealEllapsedTimeRatio); } // restore time & pos _Owner->getTime()[k] = currTime; ++ k; ++ numToEmitPtr; -- toProcess; } } } else { sint nbToGenerate = (sint) (emitLOD * _GenNb); if (nbToGenerate <= 0) nbToGenerate = 1; nbToGenerate = std::min(nbToGenerate, (sint) _EmittedType->getMaxSize()); for(uint k = firstInstanceIndex; k < _Owner->getSize(); ++k) { // retrieve previous position (because motion step is done before spawn step) CVector startPos; if (!_Owner->isParametricMotionEnabled()) { startPos = _Owner->getPos()[k] - _Owner->getSpeed()[k] * CParticleSystem::EllapsedTime; } else { startPos = _Owner->getParametricInfos()[k].Pos; } float currTime = _Owner->getTime()[k]; _Owner->getTime()[k] = 0.f; // when emit occurred, time was 0 processEmitConsistent(startPos, k, nbToGenerate, _Owner->getAgeInSeconds(k) / CParticleSystem::RealEllapsedTimeRatio); // restore time & pos _Owner->getTime()[k] = currTime; } } } ///========================================================================== void CPSEmitter::updateEmitTrigger() { NL_PS_FUNC(CPSEmitter_updateEmitTrigger) if (!_EmitTrigger) return; nlassert(_Owner); nlassert(_Owner->getOwner()); const CParticleSystem *ps = _Owner->getOwner(); float emitLOD; if (ps->isAutoLODEnabled() && !ps->isSharingEnabled() && !_BypassAutoLOD) { // temp test for auto lod emitLOD = ps->getAutoLODEmitRatio(); } else { emitLOD = 1.f; } if (_GenNbScheme) { const uint BATCH_SIZE = 1024; uint32 numToEmit[BATCH_SIZE]; uint k = 0; uint leftToDo = _Owner->getSize(); while (leftToDo) { uint toProcess = std::min(BATCH_SIZE, leftToDo); uint32 *numToEmitPtr = (uint32 *) _GenNbScheme->make(_Owner, k, numToEmit, sizeof(uint32), true); while (toProcess) { uint32 nbToGenerate = (sint32) (emitLOD * *numToEmitPtr); if (!nbToGenerate) nbToGenerate = 1; processEmit(k, nbToGenerate); ++ k; ++ numToEmitPtr; } leftToDo -= toProcess; } } else { uint nbToGenerate = (sint32) (emitLOD * _GenNb); if (!nbToGenerate) nbToGenerate = 1; for(uint k = 0; k < _Owner->getSize(); ++k) { processEmit(k, nbToGenerate); } } _EmitTrigger = false; } } // NL3D namespace NLMISC { std::string toString(NL3D::CPSEmitter::TEmissionType type) { NL_PS_FUNC(toString_CPSEmitter_TEmissionType) nlctassert(NL3D::CPSEmitter::numEmissionType == 5); // If this ct assertion is raised, the content of TEmissionType has changed, so should change this function ! switch (type) { case NL3D::CPSEmitter::regular: return "regular"; case NL3D::CPSEmitter::onDeath: return "onDeath"; case NL3D::CPSEmitter::once: return "once"; case NL3D::CPSEmitter::onBounce: return "onBounce"; case NL3D::CPSEmitter::externEmit: return "externEmit"; default: nlassert(0); return ""; break; } } } // NLMISC