// 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 #include "nel/misc/aabbox.h" #include "nel/misc/matrix.h" #include "nel/misc/common.h" #include "nel/3d/ps_util.h" #include "nel/3d/particle_system.h" #include "nel/3d/ps_zone.h" #include "nel/3d/driver.h" #include "nel/3d/material.h" #include "nel/3d/dru.h" #include "nel/3d/ps_located.h" #include "nel/3d/ps_particle.h" #include "nel/3d/ps_force.h" #include "nel/3d/ps_emitter.h" #include "nel/3d/ps_misc.h" #include "nel/misc/line.h" #include "nel/misc/system_info.h" #include "nel/misc/common.h" // #include "nel/3d/particle_system_model.h" #ifdef NL_DEBUG #define CHECK_PS_INTEGRITY checkIntegrity(); #else #define CHECK_PS_INTEGRITY #endif namespace NL3D { std::vector CPSLocated::_Collisions; CPSCollisionInfo *CPSLocated::_FirstCollision = NULL; /// *************************************************************************************** /** * Constructor */ CPSLocated::CPSLocated() : /*_MaxNumFaces(0),*/ _Size(0), _MaxSize(DefaultMaxLocatedInstance), _CollisionInfoNbRef(0), _CollisionNextPos(NULL), _InitialLife(1.f), _LifeScheme(NULL), _InitialMass(1.f), _MassScheme(NULL), _LODDegradation(false), _ParametricMotion(false), _TriggerOnDeath(false), _LastForever(true), _TriggerID((uint32) 'NONE'), _NonIntegrableForceNbRefs(0), _NumIntegrableForceWithDifferentBasis(0) { NL_PS_FUNC(CPSLocated_CPSLocated) } // ***************************************************************************************************** const NLMISC::CMatrix &CPSLocated::getLocalToWorldMatrix() const { NL_PS_FUNC(CPSLocated_getLocalToWorldMatrix) nlassert(_Owner); switch(getMatrixMode()) { case PSFXWorldMatrix: return _Owner->getSysMat(); case PSIdentityMatrix: return NLMISC::CMatrix::Identity; case PSUserMatrix: return _Owner->getUserMatrix(); default: nlassert(0); } nlassert(0); return NLMISC::CMatrix::Identity; } // ***************************************************************************************************** const NLMISC::CMatrix &CPSLocated::getWorldToLocalMatrix() const { NL_PS_FUNC(CPSLocated_getWorldToLocalMatrix) nlassert(_Owner); switch(getMatrixMode()) { case PSFXWorldMatrix: return _Owner->getInvertedSysMat(); case PSIdentityMatrix: return NLMISC::CMatrix::Identity; case PSUserMatrix: return _Owner->getInvertedUserMatrix(); default: nlassert(0); } nlassert(0); return NLMISC::CMatrix::Identity; } /// *************************************************************************************** float CPSLocated::evalMaxDuration() const { NL_PS_FUNC(CPSLocated_evalMaxDuration) if (_LastForever) return -1.f; return _LifeScheme ? _LifeScheme->getMaxValue() : _InitialLife; } /// *************************************************************************************** void CPSLocated::checkIntegrity() const { NL_PS_FUNC(CPSLocated_checkIntegrity) nlassert(_InvMass.getMaxSize() == _Pos.getMaxSize()); nlassert(_Pos.getMaxSize() == _Speed.getMaxSize()); nlassert(_Speed.getMaxSize() == _Time.getMaxSize()); nlassert(_Time.getMaxSize() == _TimeIncrement.getMaxSize()); // nlassert(_InvMass.getSize() == _Pos.getSize()); nlassert(_Pos.getSize() == _Speed.getSize()); nlassert(_Speed.getSize() == _Time.getSize()); nlassert(_Time.getSize() == _TimeIncrement.getSize()); // if (hasCollisionInfos()) { nlassert(_CollisionNextPos->getSize() == _Pos.getSize()); nlassert(_CollisionNextPos->getMaxSize() == _Pos.getMaxSize()); } // } /// *************************************************************************************** bool CPSLocated::setLastForever() { NL_PS_FUNC(CPSLocated_setLastForever) CHECK_PS_INTEGRITY _LastForever = true; if (_Owner && _Owner->getBypassMaxNumIntegrationSteps()) { // Should test that the system is still valid. if (!_Owner->canFinish()) { _LastForever = false; nlwarning(" Can't set flag : this causes the system to last forever, and it has been flagged with 'BypassMaxNumIntegrationSteps'. Flag is not set"); return false; } } CHECK_PS_INTEGRITY return true; } /// *************************************************************************************** void CPSLocated::systemDateChanged() { NL_PS_FUNC(CPSLocated_systemDateChanged) CHECK_PS_INTEGRITY for(TLocatedBoundCont::iterator it = _LocatedBoundCont.begin(); it != _LocatedBoundCont.end(); ++it) { (*it)->systemDateChanged(); } CHECK_PS_INTEGRITY } /// *************************************************************************************** void CPSLocated::releaseRefTo(const CParticleSystemProcess *other) { NL_PS_FUNC(CPSLocated_releaseRefTo) CHECK_PS_INTEGRITY // located bindables { for(TLocatedBoundCont::iterator it = _LocatedBoundCont.begin(); it != _LocatedBoundCont.end(); ++it) { (*it)->releaseRefTo(other); } } // dtor observers { for(TDtorObserversVect::iterator it = _DtorObserversVect.begin(); it != _DtorObserversVect.end(); ++it) { if ((*it)->getOwner() == other) { CPSLocatedBindable *refMaker = *it; refMaker->notifyTargetRemoved(this); break; } } } CHECK_PS_INTEGRITY } /// *************************************************************************************** void CPSLocated::releaseAllRef() { NL_PS_FUNC(CPSLocated_releaseAllRef) CHECK_PS_INTEGRITY // located bindables { for(TLocatedBoundCont::iterator it = _LocatedBoundCont.begin(); it != _LocatedBoundCont.end(); ++it) { (*it)->releaseAllRef(); } } // we must do a copy, because the subsequent call can modify this vector TDtorObserversVect copyVect(_DtorObserversVect.begin(), _DtorObserversVect.end()); // call all the dtor observers for (TDtorObserversVect::iterator it = copyVect.begin(); it != copyVect.end(); ++it) { (*it)->notifyTargetRemoved(this); } _DtorObserversVect.clear(); nlassert(_CollisionInfoNbRef == 0); //If this is not = 0, then someone didnt call releaseCollisionInfo // If this happen, you can register with the registerDTorObserver // (observer pattern) // and override notifyTargetRemove to call releaseCollisionInfo nlassert(_IntegrableForces.size() == 0); nlassert(_NonIntegrableForceNbRefs == 0); CHECK_PS_INTEGRITY } /// *************************************************************************************** void CPSLocated::notifyMotionTypeChanged(void) { NL_PS_FUNC(CPSLocated_notifyMotionTypeChanged) CHECK_PS_INTEGRITY for (TLocatedBoundCont::const_iterator it = _LocatedBoundCont.begin(); it != _LocatedBoundCont.end(); ++it) { (*it)->motionTypeChanged(_ParametricMotion); } CHECK_PS_INTEGRITY } /// *************************************************************************************** void CPSLocated::integrateSingle(float startDate, float deltaT, uint numStep, uint32 indexInLocated, NLMISC::CVector *destPos, uint stride) const { NL_PS_FUNC(CPSLocated_integrateSingle) CHECK_PS_INTEGRITY nlassert(supportParametricMotion() && _ParametricMotion); if (_IntegrableForces.size() != 0) { bool accumulate = false; for (TForceVect::const_iterator it = _IntegrableForces.begin(); it != _IntegrableForces.end(); ++it) { nlassert((*it)->isIntegrable()); (*it)->integrateSingle(startDate, deltaT, numStep, this, indexInLocated, destPos, accumulate, stride); accumulate = true; } } else // no forces applied, just deduce position from date, initial pos and speed { #ifdef NL_DEBUG NLMISC::CVector *endPos = (NLMISC::CVector *) ( (uint8 *) destPos + stride * numStep); #endif const CPSLocated::CParametricInfo &pi = _PInfo[indexInLocated]; destPos = FillBufUsingSubdiv(pi.Pos, pi.Date, startDate, deltaT, numStep, destPos, stride); if (numStep != 0) { float currDate = startDate - pi.Date; nlassert(currDate >= 0); do { #ifdef NL_DEBUG nlassert(destPos < endPos); #endif destPos->x = pi.Pos.x + currDate * pi.Speed.x; destPos->y = pi.Pos.y + currDate * pi.Speed.y; destPos->z = pi.Pos.z + currDate * pi.Speed.z; currDate += deltaT; destPos = (NLMISC::CVector *) ( (uint8 *) destPos + stride); } while (--numStep); } } CHECK_PS_INTEGRITY } /// *************************************************************************************** void CPSLocated::performParametricMotion(TAnimationTime date) { NL_PS_FUNC(CPSLocated_performParametricMotion) CHECK_PS_INTEGRITY if (!_Size) return; nlassert(supportParametricMotion() && _ParametricMotion); if (_IntegrableForces.size() != 0) { bool accumulate = false; for (TForceVect::iterator it = _IntegrableForces.begin(); it != _IntegrableForces.end(); ++it) { nlassert((*it)->isIntegrable()); (*it)->integrate(date, this, 0, _Size, &_Pos[0], &_Speed[0], accumulate); accumulate = true; } } else { CPSLocated::TPSAttribParametricInfo::const_iterator it = _PInfo.begin(), endIt = _PInfo.end(); TPSAttribVector::iterator posIt = _Pos.begin(); float deltaT; do { deltaT = date - it->Date; posIt->x = it->Pos.x + deltaT * it->Speed.x; posIt->y = it->Pos.y + deltaT * it->Speed.y; posIt->z = it->Pos.z + deltaT * it->Speed.z; ++posIt; ++it; } while (it != endIt); } CHECK_PS_INTEGRITY } /// *************************************************************************************** /// allocate parametric infos void CPSLocated::allocateParametricInfos(void) { NL_PS_FUNC(CPSLocated_allocateParametricInfos) CHECK_PS_INTEGRITY if (_ParametricMotion) return; nlassert(supportParametricMotion()); nlassert(_Owner); const float date = _Owner->getSystemDate(); _PInfo.resize(_MaxSize); // copy back infos from current position and speeds TPSAttribVector::const_iterator posIt = _Pos.begin(), endPosIt = _Pos.end(); TPSAttribVector::const_iterator speedIt = _Speed.begin(); while (posIt != endPosIt) { _PInfo.insert( CParametricInfo(*posIt, *speedIt, date) ); ++posIt; } _ParametricMotion = true; notifyMotionTypeChanged(); CHECK_PS_INTEGRITY } /// *************************************************************************************** /// release parametric infos void CPSLocated::releaseParametricInfos(void) { NL_PS_FUNC(CPSLocated_releaseParametricInfos) CHECK_PS_INTEGRITY if (!_ParametricMotion) return; NLMISC::contReset(_PInfo); _ParametricMotion = false; notifyMotionTypeChanged(); CHECK_PS_INTEGRITY } /// *************************************************************************************** /// Test whether this located support parametric motion bool CPSLocated::supportParametricMotion(void) const { NL_PS_FUNC(CPSLocated_supportParametricMotion) return _NonIntegrableForceNbRefs == 0 && _NumIntegrableForceWithDifferentBasis == 0; } /// *************************************************************************************** /** When set to true, this tells the system to use parametric motion. This is needed in a few case only, * and can only work if all the forces that apply to the system are integrable */ void CPSLocated::enableParametricMotion(bool enable /*= true*/) { NL_PS_FUNC(CPSLocated_enableParametricMotion) CHECK_PS_INTEGRITY nlassert(supportParametricMotion()); if (enable) { allocateParametricInfos(); } else { releaseParametricInfos(); } CHECK_PS_INTEGRITY } /// *************************************************************************************** void CPSLocated::setMatrixMode(TPSMatrixMode matrixMode) { NL_PS_FUNC(CPSLocated_setMatrixMode) CHECK_PS_INTEGRITY if (matrixMode != getMatrixMode()) { for (TLocatedBoundCont::const_iterator it = _LocatedBoundCont.begin(); it != _LocatedBoundCont.end(); ++it) { (*it)->basisChanged(matrixMode); } CParticleSystemProcess::setMatrixMode(matrixMode); for (TForceVect::iterator fIt = _IntegrableForces.begin(); fIt != _IntegrableForces.end(); ++fIt) { integrableForceBasisChanged( (*fIt)->getOwner()->getMatrixMode() ); } } CHECK_PS_INTEGRITY } /// *************************************************************************************** /* void CPSLocated::notifyMaxNumFacesChanged(void) { CHECK_PS_INTEGRITY if (!_Owner) return; // we examine whether we have particle attached to us, and ask for the max number of faces they may want _MaxNumFaces = 0; for (TLocatedBoundCont::const_iterator it = _LocatedBoundCont.begin(); it != _LocatedBoundCont.end(); ++it) { if ((*it)->getType() == PSParticle) { uint maxNumFaces = ((CPSParticle *) (*it))->getMaxNumFaces(); ///nlassertex(maxNumFaces < ((1 << 16) - 1), ("%s", (*it)->getClassName().c_str())); _MaxNumFaces += maxNumFaces; } } CHECK_PS_INTEGRITY } */ /// *************************************************************************************** uint CPSLocated::getNumWantedTris() const { NL_PS_FUNC(CPSLocated_getNumWantedTris) CHECK_PS_INTEGRITY if (!_Owner) return 0; uint numWantedTris = 0; for (TLocatedBoundCont::const_iterator it = _LocatedBoundCont.begin(); it != _LocatedBoundCont.end(); ++it) { if ((*it)->getType() == PSParticle) { numWantedTris += NLMISC::safe_cast(*it)->getNumWantedTris(); } } CHECK_PS_INTEGRITY return numWantedTris; } /* /// *************************************************************************************** uint CPSLocated::querryMaxWantedNumFaces(void) { return _MaxNumFaces; } */ /// *************************************************************************************** /// tells whether there are alive entities / particles in the system bool CPSLocated::hasParticles(void) const { NL_PS_FUNC(CPSLocated_hasParticles) CHECK_PS_INTEGRITY for (TLocatedBoundCont::const_iterator it = _LocatedBoundCont.begin(); it != _LocatedBoundCont.end(); ++it) { if ((*it)->getType() == PSParticle && (*it)->hasParticles()) return true; } CHECK_PS_INTEGRITY return false; } /// *************************************************************************************** /// tells whether there are alive emitters bool CPSLocated::hasEmitters(void) const { NL_PS_FUNC(CPSLocated_hasEmitters) CHECK_PS_INTEGRITY for (TLocatedBoundCont::const_iterator it = _LocatedBoundCont.begin(); it != _LocatedBoundCont.end(); ++it) { if ((*it)->getType() == PSEmitter && (*it)->hasEmitters()) return true; } CHECK_PS_INTEGRITY return false; } /// *************************************************************************************** void CPSLocated::getLODVect(NLMISC::CVector &v, float &offset, TPSMatrixMode matrixMode) { NL_PS_FUNC(CPSLocated_getLODVect) nlassert(_Owner); CHECK_PS_INTEGRITY _Owner->getLODVect(v, offset, matrixMode); CHECK_PS_INTEGRITY } /// *************************************************************************************** float CPSLocated::getUserParam(uint numParam) const { NL_PS_FUNC(CPSLocated_getUserParam) nlassert(_Owner); CHECK_PS_INTEGRITY return _Owner->getUserParam(numParam); } /// *************************************************************************************** CScene *CPSLocated::getScene(void) { NL_PS_FUNC(CPSLocated_getScene) nlassert(_Owner); CHECK_PS_INTEGRITY return _Owner->getScene(); } /// *************************************************************************************** void CPSLocated::incrementNbDrawnParticles(uint num) { NL_PS_FUNC(CPSLocated_incrementNbDrawnParticles) CHECK_PS_INTEGRITY CParticleSystem::NbParticlesDrawn += num; // for benchmark purpose } /// *************************************************************************************** void CPSLocated::setInitialLife(TAnimationTime lifeTime) { NL_PS_FUNC(CPSLocated_setInitialLife) CHECK_PS_INTEGRITY _LastForever = false; _InitialLife = lifeTime; delete _LifeScheme; _LifeScheme = NULL; /** Reset all particles current date to 0. This is needed because we do not check * if particle life is over when the date of the system has not gone beyond the life duration of particles */ for (uint k = 0; k < _Size; ++k) { _Time[k] = 0.f; } // if (_Owner) { _Owner->systemDurationChanged(); } CHECK_PS_INTEGRITY } /// *************************************************************************************** void CPSLocated::setLifeScheme(CPSAttribMaker *scheme) { NL_PS_FUNC(CPSLocated_setLifeScheme) CHECK_PS_INTEGRITY nlassert(scheme); nlassert(!scheme->hasMemory()); // scheme with memory is invalid there !! _LastForever = false; delete _LifeScheme; _LifeScheme = scheme; // if (_Owner) { _Owner->systemDurationChanged(); } CHECK_PS_INTEGRITY } /// *************************************************************************************** void CPSLocated::setInitialMass(float mass) { NL_PS_FUNC(CPSLocated_setInitialMass) CHECK_PS_INTEGRITY _InitialMass = mass; delete _MassScheme; _MassScheme = NULL; CHECK_PS_INTEGRITY } /// *************************************************************************************** void CPSLocated::setMassScheme(CPSAttribMaker *scheme) { NL_PS_FUNC(CPSLocated_setMassScheme) CHECK_PS_INTEGRITY nlassert(scheme); nlassert(!scheme->hasMemory()); // scheme with memory is invalid there !! delete _MassScheme; _MassScheme = scheme; CHECK_PS_INTEGRITY } /// *************************************************************************************** /// get a matrix that helps to express located B coordinate in located A basis const NLMISC::CMatrix &CPSLocated::getConversionMatrix(const CParticleSystem &ps, TPSMatrixMode destMode, TPSMatrixMode srcMode) { NL_PS_FUNC(CPSLocated_getConversionMatrix) switch(destMode) { case PSFXWorldMatrix: switch(srcMode) { case PSFXWorldMatrix: return NLMISC::CMatrix::Identity; case PSIdentityMatrix: return ps.getInvertedSysMat(); case PSUserMatrix: return ps.getUserToFXMatrix(); default: nlassert(0); } break; case PSIdentityMatrix: switch(srcMode) { case PSFXWorldMatrix: return ps.getSysMat(); case PSIdentityMatrix: return NLMISC::CMatrix::Identity; case PSUserMatrix: return ps.getUserMatrix(); default: nlassert(0); } break; case PSUserMatrix: switch(srcMode) { case PSFXWorldMatrix: return ps.getFXToUserMatrix(); case PSIdentityMatrix: return ps.getInvertedUserMatrix(); case PSUserMatrix: return NLMISC::CMatrix::Identity; default: nlassert(0); } break; default: nlassert(0); } nlassert(0); return NLMISC::CMatrix::Identity; } /// *************************************************************************************** NLMISC::CVector CPSLocated::computeI(void) const { NL_PS_FUNC(CPSLocated_computeI) CHECK_PS_INTEGRITY const NLMISC::CMatrix &sysMat = _Owner->getSysMat(); if (getMatrixMode() == PSIdentityMatrix) { if (!sysMat.hasScalePart()) { return _Owner->getInvertedViewMat().getI(); } else { return sysMat.getScaleUniform() * _Owner->getInvertedViewMat().getI(); } } else { if (!sysMat.hasScalePart()) { // we must express the I vector in the system basis, so we need to multiply it by the inverted matrix of the system return getWorldToLocalMatrix().mulVector(_Owner->getInvertedViewMat().getI()); } else { return sysMat.getScaleUniform() * getWorldToLocalMatrix().mulVector(_Owner->getInvertedViewMat().getI()); } } } /// *************************************************************************************** NLMISC::CVector CPSLocated::computeIWithZAxisAligned(void) const { NL_PS_FUNC(CPSLocated_computeIWithZAxisAligned) CHECK_PS_INTEGRITY const NLMISC::CMatrix &sysMat = _Owner->getSysMat(); const CVector &camI = _Owner->getInvertedViewMat().getI(); CVector I(camI.x, camI.y, 0.f); I.normalize(); if (getMatrixMode() == PSIdentityMatrix) { if (!sysMat.hasScalePart()) { return I; } else { return sysMat.getScaleUniform() * I; } } else { if (!sysMat.hasScalePart()) { // we must express the I vector in the system basis, so we need to multiply it by the inverted matrix of the system return getWorldToLocalMatrix().mulVector(I); } else { return sysMat.getScaleUniform() * getWorldToLocalMatrix().mulVector(I); } } } /// *************************************************************************************** NLMISC::CVector CPSLocated::computeJ(void) const { NL_PS_FUNC(CPSLocated_computeJ) CHECK_PS_INTEGRITY const NLMISC::CMatrix &sysMat = _Owner->getSysMat(); if (getMatrixMode() == PSIdentityMatrix) { if (!sysMat.hasScalePart()) { return _Owner->getInvertedViewMat().getJ(); } else { return sysMat.getScaleUniform() * _Owner->getInvertedViewMat().getJ(); } } else { if (!sysMat.hasScalePart()) { // we must express the J vector in the system basis, so we need to multiply it by the inverted matrix of the system return getWorldToLocalMatrix().mulVector(_Owner->getInvertedViewMat().getJ()); } else { return sysMat.getScaleUniform() * getWorldToLocalMatrix().mulVector(_Owner->getInvertedViewMat().getJ()); } } } /// *************************************************************************************** NLMISC::CVector CPSLocated::computeK(void) const { NL_PS_FUNC(CPSLocated_computeK) CHECK_PS_INTEGRITY const NLMISC::CMatrix &sysMat = _Owner->getSysMat(); if (getMatrixMode() == PSIdentityMatrix) { if (!sysMat.hasScalePart()) { return _Owner->getInvertedViewMat().getK(); } else { return sysMat.getScaleUniform() * _Owner->getInvertedViewMat().getK(); } } else { if (!sysMat.hasScalePart()) { // we must express the K vector in the system basis, so we need to multiply it by the inverted matrix of the system return getWorldToLocalMatrix().mulVector(_Owner->getInvertedViewMat().getK()); } else { return sysMat.getScaleUniform() * getWorldToLocalMatrix().mulVector(_Owner->getInvertedViewMat().getK()); } } } /// *************************************************************************************** NLMISC::CVector CPSLocated::computeKWithZAxisAligned(void) const { NL_PS_FUNC(CPSLocated_computeKWithZAxisAligned) CHECK_PS_INTEGRITY const NLMISC::CMatrix &sysMat = _Owner->getSysMat(); if (getMatrixMode() == PSIdentityMatrix) { if (!sysMat.hasScalePart()) { return CVector::K; } else { return CVector(0.f, 0.f, sysMat.getScaleUniform()); } } else { if (!sysMat.hasScalePart()) { // we must express the K vector in the system basis, so we need to multiply it by the inverted matrix of the system return getWorldToLocalMatrix().mulVector(CVector::K); } else { return getWorldToLocalMatrix().mulVector(CVector(0.f, 0.f, sysMat.getScaleUniform())); } } } /// *************************************************************************************** IDriver *CPSLocated::getDriver() const { NL_PS_FUNC(CPSLocated_getDriver) CHECK_PS_INTEGRITY nlassert(_Owner); nlassert (_Owner->getDriver() ); // you haven't called setDriver on the system return _Owner->getDriver(); } /// *************************************************************************************** /// dtor CPSLocated::~CPSLocated() { NL_PS_FUNC(CPSLocated_CPSLocated) CHECK_PS_INTEGRITY // we must do a copy, because the subsequent call can modify this vector TDtorObserversVect copyVect(_DtorObserversVect.begin(), _DtorObserversVect.end()); // call all the dtor observers for (TDtorObserversVect::iterator it = copyVect.begin(); it != copyVect.end(); ++it) { (*it)->notifyTargetRemoved(this); } nlassert(_CollisionInfoNbRef == 0); //If this is not = 0, then someone didnt call releaseCollisionInfo // If this happen, you can register with the registerDTorObserver // (observer pattern) // and override notifyTargetRemove to call releaseCollisionInfo nlassert(_IntegrableForces.size() == 0); nlassert(_NonIntegrableForceNbRefs == 0); // delete all bindable for (TLocatedBoundCont::iterator it2 = _LocatedBoundCont.begin(); it2 != _LocatedBoundCont.end(); ++it2) { (*it2)->finalize(); delete *it2; } _LocatedBoundCont.clear(); delete _LifeScheme; delete _MassScheme; delete _CollisionNextPos; CHECK_PS_INTEGRITY } /// *************************************************************************************** /** * sorted insertion (by decreasing priority order) of a bindable (particle e.g an aspect, emitter) in a located */ bool CPSLocated::bind(CPSLocatedBindable *lb) { NL_PS_FUNC(CPSLocated_bind) CHECK_PS_INTEGRITY nlassert(std::find(_LocatedBoundCont.begin(), _LocatedBoundCont.end(), lb) == _LocatedBoundCont.end()); TLocatedBoundCont::iterator it = _LocatedBoundCont.begin(); while (it != _LocatedBoundCont.end() && **it < *lb) // the "<" operator sort them correctly { ++it; } _LocatedBoundCont.insert(it, lb); lb->setOwner(this); lb->resize(_MaxSize); // any located bindable that is bound to us should have no element in it for now !! // we resize it anyway... uint32 initialSize = _Size; CPSEmitterInfo ei; ei.setDefaults(); for (uint k = 0; k < initialSize; ++k) { _Size = k; lb->newElement(ei); } _Size = initialSize; if (_ParametricMotion) lb->motionTypeChanged(true); /// the max number of shapes may have changed //notifyMaxNumFacesChanged(); if (_Owner) { CParticleSystem *ps = _Owner; if (ps->getBypassMaxNumIntegrationSteps()) { if (!ps->canFinish()) { unbind(getIndexOf(lb)); nlwarning(" Can't bind the located : this causes the system to last forever, and it has been flagged with 'BypassMaxNumIntegrationSteps'. Located is not bound."); return false; } } // if there's an extern id, register in lb list if (lb->getExternID() != 0) { // register in ID list ps->registerLocatedBindableExternID(lb->getExternID(), lb); } _Owner->systemDurationChanged(); } CHECK_PS_INTEGRITY return true; } /// *************************************************************************************** void CPSLocated::remove(const CPSLocatedBindable *p) { NL_PS_FUNC(CPSLocated_remove) CHECK_PS_INTEGRITY TLocatedBoundCont::iterator it = std::find(_LocatedBoundCont.begin(), _LocatedBoundCont.end(), p); nlassert(it != _LocatedBoundCont.end()); (*it)->finalize(); delete *it; _LocatedBoundCont.erase(it); if (_Owner) { _Owner->systemDurationChanged(); } CHECK_PS_INTEGRITY } /// *************************************************************************************** void CPSLocated::registerDtorObserver(CPSLocatedBindable *anObserver) { NL_PS_FUNC(CPSLocated_registerDtorObserver) CHECK_PS_INTEGRITY // check whether the observer wasn't registered twice nlassert(std::find(_DtorObserversVect.begin(), _DtorObserversVect.end(), anObserver) == _DtorObserversVect.end()); _DtorObserversVect.push_back(anObserver); CHECK_PS_INTEGRITY } /// *************************************************************************************** void CPSLocated::unregisterDtorObserver(CPSLocatedBindable *anObserver) { NL_PS_FUNC(CPSLocated_unregisterDtorObserver) CHECK_PS_INTEGRITY // check that it was registered TDtorObserversVect::iterator it = std::find(_DtorObserversVect.begin(), _DtorObserversVect.end(), anObserver); nlassert(it != _DtorObserversVect.end()); _DtorObserversVect.erase(it); CHECK_PS_INTEGRITY } /// *************************************************************************************** void CPSLocated::postNewElement(const NLMISC::CVector &pos, const NLMISC::CVector &speed, CPSLocated &emitterLocated, uint32 indexInEmitter, TPSMatrixMode speedCoordSystem, TAnimationTime lifeTime) { NL_PS_FUNC(CPSLocated_postNewElement) nlassert(CParticleSystem::InsideSimLoop); // should be called only inside the sim loop! // In the event loop life of emitter is updated just after particles are spawned, so we must check there if the particle wasn't emitted when the // emitter was already destroyed // When postNewElement is called, the particle and the emitter that created it live at the same date, so EmitterLife - ParticleLife should be > 1.f float emitterLife; if (!emitterLocated.getLastForever()) { if (emitterLocated._LifeScheme) { emitterLife = emitterLocated._Time[indexInEmitter] - lifeTime * CParticleSystem::RealEllapsedTimeRatio * emitterLocated._TimeIncrement[indexInEmitter]; if (emitterLife >= 1.f) { return; // emitter had finished its life } } else { emitterLife = emitterLocated._Time[indexInEmitter] * emitterLocated._InitialLife - lifeTime * CParticleSystem::RealEllapsedTimeRatio; if (emitterLife >= emitterLocated._InitialLife) { return; // emitter had finished its life } if (emitterLocated._InitialLife != 0.f) { emitterLife /= emitterLocated._InitialLife; } } } else { emitterLife = emitterLocated.getTime()[indexInEmitter]; } // now check that the emitter didn't collide before it spawned a particle if (emitterLocated.hasCollisionInfos()) { const CPSCollisionInfo &ci = _Collisions[indexInEmitter]; if (ci.Dist != -1.f) { // a collision occured, check time from collision to next time step if ((emitterLocated.getPos()[indexInEmitter] - ci.NewPos) * (pos - ci.NewPos) > 0.f) return; // discard emit that are farther than the collision } } // create a request to create a new element CParticleSystem::CSpawnVect &sp = *CParticleSystem::_Spawns[getIndex()]; if (!_Owner->getAutoCountFlag() && sp.MaxNumSpawns == sp.SpawnInfos.size()) return; // no more place to spawn if (getMaxSize() >= ((1 << 16) - 1)) return; sp.SpawnInfos.resize(sp.SpawnInfos.size() + 1); CPSSpawnInfo &si = sp.SpawnInfos.back(); si.EmitterInfo.Pos = emitterLocated.getPos()[indexInEmitter]; si.EmitterInfo.Speed = emitterLocated.getSpeed()[indexInEmitter]; si.EmitterInfo.InvMass = emitterLocated.getInvMass()[indexInEmitter]; si.EmitterInfo.Life = emitterLife; si.EmitterInfo.Loc = &emitterLocated; si.SpawnPos = pos; si.Speed = speed; si.SpeedCoordSystem = speedCoordSystem; si.LifeTime = lifeTime; } /// *************************************************************************************** sint32 CPSLocated::newElement(const CPSSpawnInfo &si, bool doEmitOnce /* = false */, TAnimationTime ellapsedTime) { NL_PS_FUNC(CPSLocated_newElement) CHECK_PS_INTEGRITY sint32 creationIndex; // get the convertion matrix from the emitter basis to the emittee basis // if the emitter is null, we assume that the coordinate are given in the chosen basis for this particle type if (_MaxSize == _Size) { if (_Owner && _Owner->getAutoCountFlag() && getMaxSize() < ((1 << 16) - 1) ) { // we are probably in edition mode -> auto-count mode helps to compute ideal particle array size // but at the expense of costly allocations uint maxSize = getMaxSize(); resize((uint32) std::min((uint) NLMISC::raiseToNextPowerOf2(maxSize + 1), (uint) ((1 << 16) - 1))); // force a reserve with next power of 2 (no important in edition mode) resize(maxSize + 1); CParticleSystem::_SpawnPos.resize(maxSize + 1); } else { return -1; } } // During creation, we interpolate the position of the system (by using the ellapsed time) if particle are created in world basis and if the emitter is in local basis. // Example a fireball FX let particles in world basis, but the fireball is moving. If we dont interpolate position between 2 frames, emission will appear to be "sporadic". // For now, we manage the local to world case. The world to local is possible, but not very useful switch(si.EmitterInfo.Loc ? si.EmitterInfo.Loc->getMatrixMode() : this->getMatrixMode()) { case PSFXWorldMatrix: switch(this->getMatrixMode()) { case PSFXWorldMatrix: { creationIndex =_Pos.insert(si.SpawnPos); } break; case PSIdentityMatrix: { CVector fxPosDelta; _Owner->interpolateFXPosDelta(fxPosDelta, si.LifeTime); creationIndex =_Pos.insert(_Owner->getSysMat() * si.SpawnPos + fxPosDelta); } break; case PSUserMatrix: { CVector fxPosDelta; _Owner->interpolateFXPosDelta(fxPosDelta, si.LifeTime); CVector userMatrixPosDelta; _Owner->interpolateUserPosDelta(userMatrixPosDelta, si.LifeTime); creationIndex =_Pos.insert(_Owner->getInvertedUserMatrix() * (_Owner->getSysMat() * si.SpawnPos + fxPosDelta - userMatrixPosDelta)); } break; default: nlassert(0); } break; case PSIdentityMatrix: switch(this->getMatrixMode()) { case PSFXWorldMatrix: { CVector fxPosDelta; _Owner->interpolateFXPosDelta(fxPosDelta, si.LifeTime); creationIndex =_Pos.insert(_Owner->getInvertedSysMat() * (si.SpawnPos - fxPosDelta)); } break; case PSIdentityMatrix: { creationIndex =_Pos.insert(si.SpawnPos); } break; case PSUserMatrix: { CVector userMatrixPosDelta; _Owner->interpolateUserPosDelta(userMatrixPosDelta, si.LifeTime); creationIndex =_Pos.insert(_Owner->getInvertedUserMatrix() * (si.SpawnPos - userMatrixPosDelta)); } break; default: nlassert(0); } break; case PSUserMatrix: switch(this->getMatrixMode()) { case PSFXWorldMatrix: { CVector fxPosDelta; _Owner->interpolateFXPosDelta(fxPosDelta, si.LifeTime); CVector userMatrixPosDelta; _Owner->interpolateUserPosDelta(userMatrixPosDelta, si.LifeTime); creationIndex =_Pos.insert(_Owner->getInvertedSysMat() * (_Owner->getUserMatrix() * si.SpawnPos + userMatrixPosDelta- fxPosDelta)); } break; case PSIdentityMatrix: { CVector userMatrixPosDelta; _Owner->interpolateUserPosDelta(userMatrixPosDelta, si.LifeTime); creationIndex =_Pos.insert(_Owner->getUserMatrix() * si.SpawnPos + userMatrixPosDelta); } break; case PSUserMatrix: { creationIndex =_Pos.insert(si.SpawnPos); } break; default: nlassert(0); } break; default: nlassert(0); } nlassert(creationIndex != -1); // all attributs must contains the same number of elements if (si.SpeedCoordSystem == this->getMatrixMode()) // is speed vector expressed in the good basis ? { _Speed.insert(si.Speed); } else { // must do conversion of speed nlassert(_Owner); const NLMISC::CMatrix &convMat = getConversionMatrix(*_Owner, this->getMatrixMode(), si.SpeedCoordSystem); _Speed.insert(convMat.mulVector(si.Speed)); } _InvMass.insert(1.f / ((_MassScheme && si.EmitterInfo.Loc) ? _MassScheme->get(si.EmitterInfo) : _InitialMass ) ); if (CParticleSystem::InsideSimLoop) { CParticleSystem::_SpawnPos[creationIndex] = _Pos[creationIndex]; } // compute age of particle when it has been created if (getLastForever()) { // age of particle is in seconds _Time.insert(CParticleSystem::RealEllapsedTimeRatio * si.LifeTime); _TimeIncrement.insert(_InitialLife != 0.f ? 1.f / _InitialLife : 1.f); } else { const float totalLifeTime = (_LifeScheme && si.EmitterInfo.Loc) ? _LifeScheme->get(si.EmitterInfo) : _InitialLife ; float timeIncrement = totalLifeTime ? 1.f / totalLifeTime : 10E6f; _TimeIncrement.insert(timeIncrement); _Time.insert(CParticleSystem::RealEllapsedTimeRatio * si.LifeTime * timeIncrement); } // test whether parametric motion is used, and generate the infos that are needed then if (_ParametricMotion) { _PInfo.insert( CParametricInfo(_Pos[creationIndex], _Speed[creationIndex], _Owner->getSystemDate() + CParticleSystem::RealEllapsedTime - CParticleSystem::RealEllapsedTimeRatio * si.LifeTime) ); } else { _Pos[creationIndex] += si.LifeTime * _Speed[creationIndex]; } /////////////////////////////////////////// // generate datas for all bound objects // /////////////////////////////////////////// for (TLocatedBoundCont::iterator it = _LocatedBoundCont.begin(); it != _LocatedBoundCont.end(); ++it) { (*it)->newElement(si.EmitterInfo); // if element is an emitter, then must bias the emission time counter because it will be updated of frameDT, but the particle has been alive for (frameDT - deltaT) if ((*it)->getType() == PSEmitter) { CPSEmitter *pEmit = NLMISC::safe_cast(*it); pEmit->_Phase[creationIndex] -= std::max(0.f, (ellapsedTime - si.LifeTime)); } } if (doEmitOnce && !CPSEmitter::getBypassEmitOnDeath()) { // can be called only outside the sim loop (when the user triggers an emitters for example) for (TLocatedBoundCont::iterator it = _LocatedBoundCont.begin(); it != _LocatedBoundCont.end(); ++it) { if ((*it)->getType() == PSEmitter) { CPSEmitter *pEmit = NLMISC::safe_cast(*it); if (pEmit->getEmissionType() == CPSEmitter::once) { for(uint k = 0; k < getSize(); ++k) { pEmit->singleEmit(k, 1); } } } } } if (_CollisionNextPos) { _CollisionNextPos->insert(); } ++_Size; // if this is modified, you must also modify the getNewElementIndex in this class // because that method give the index of the element being created for overrider of the newElement method // of the CPSLocatedClass (which is called just above) CHECK_PS_INTEGRITY return creationIndex; } /// *************************************************************************************** sint32 CPSLocated::newElement(const CVector &pos, const CVector &speed, CPSLocated *emitter, uint32 indexInEmitter, TPSMatrixMode speedCoordSystem, bool doEmitOnce /* = false */) { NL_PS_FUNC(CPSLocated_newElement) CPSSpawnInfo si; si.EmitterInfo.Loc = emitter; if (emitter) { si.EmitterInfo.Pos = emitter->getPos()[indexInEmitter]; si.EmitterInfo.Speed = emitter->getSpeed()[indexInEmitter]; si.EmitterInfo.InvMass = emitter->getInvMass()[indexInEmitter]; si.EmitterInfo.Life = emitter->getTime()[indexInEmitter]; } else { si.EmitterInfo.Pos = NLMISC::CVector::Null; si.EmitterInfo.Speed = NLMISC::CVector::Null; si.EmitterInfo.InvMass = 1.f; si.EmitterInfo.Life = 0.f; } si.SpawnPos = pos; si.Speed = speed; si.SpeedCoordSystem = speedCoordSystem; si.LifeTime = 0.f; return newElement(si, doEmitOnce, 0.f); } /// *************************************************************************************** static inline uint32 IDToLittleEndian(uint32 input) { NL_PS_FUNC(IDToLittleEndian) #ifdef NL_LITTLE_ENDIAN return input; #else return ((input & (0xff<<24))>>24) || ((input & (0xff<<16))>>8) || ((input & (0xff<<8))<<8) || ((input & 0xff)<<24); #endif } /// *************************************************************************************** inline void CPSLocated::deleteElementBase(uint32 index) { NL_PS_FUNC(CPSLocated_deleteElementBase) // remove common located's attributes _InvMass.remove(index); _Pos.remove(index); _Speed.remove(index); _Time.remove(index); _TimeIncrement.remove(index); if (_CollisionNextPos) { _CollisionNextPos->remove(index); } if (_ParametricMotion) { _PInfo.remove(index); } --_Size; if (_TriggerOnDeath) { const uint32 id = IDToLittleEndian(_TriggerID); nlassert(_Owner); uint numLb = _Owner->getNumLocatedBindableByExternID(id); for (uint k = 0; k < numLb; ++k) { CPSLocatedBindable *lb = _Owner->getLocatedBindableByExternID(id, k); if (lb->getType() == PSEmitter) { CPSEmitter *e = NLMISC::safe_cast(lb); e->setEmitTrigger(); } } } CHECK_PS_INTEGRITY } /// *************************************************************************************** void CPSLocated::deleteElement(uint32 index) { NL_PS_FUNC(CPSLocated_deleteElement) #ifdef NL_DEBUG if (CParticleSystem::InsideSimLoop) { nlassert(CParticleSystem::InsideRemoveLoop); } #endif CHECK_PS_INTEGRITY nlassert(index < _Size); for (TLocatedBoundCont::iterator it = _LocatedBoundCont.begin(); it != _LocatedBoundCont.end(); ++it) { (*it)->deleteElement(index); } deleteElementBase(index); } /// *************************************************************************************** void CPSLocated::deleteElement(uint32 index, TAnimationTime timeToNextSimStep) { NL_PS_FUNC(CPSLocated_deleteElement) #ifdef NL_DEBUG if (CParticleSystem::InsideSimLoop) { nlassert(CParticleSystem::InsideRemoveLoop); } #endif nlassert(index < _Size); for (TLocatedBoundCont::iterator it = _LocatedBoundCont.begin(); it != _LocatedBoundCont.end(); ++it) { (*it)->deleteElement(index, timeToNextSimStep); } deleteElementBase(index); } /// Resize the located container /// *************************************************************************************** void CPSLocated::resize(uint32 newSize) { NL_PS_FUNC(CPSLocated_resize) CHECK_PS_INTEGRITY nlassert(newSize < (1 << 16)); if (newSize < _Size) { for (uint32 k = _Size - 1; k >= newSize; --k) { deleteElement(k); if (k == 0) break; // we're dealing with unsigned quantities } _Size = newSize; } _MaxSize = newSize; _InvMass.resize(newSize); _Pos.resize(newSize); _Speed.resize(newSize); _Time.resize(newSize); _TimeIncrement.resize(newSize); if (_ParametricMotion) { _PInfo.resize(newSize); } if (_CollisionNextPos) { _CollisionNextPos->resize(newSize); } // resize attributes for all bound objects for (TLocatedBoundCont::iterator it = _LocatedBoundCont.begin(); it != _LocatedBoundCont.end(); ++it) { (*it)->resize(newSize); } /// compute the new max number of faces //notifyMaxNumFacesChanged(); CHECK_PS_INTEGRITY } // dummy struct for serial of a field that has been removed class CDummyCollision { public: void serial(NLMISC::IStream &f) throw(NLMISC::EStream) { NL_PS_FUNC(CDummyCollision_serial) f.serialVersion(1); float dummyDist = 0.f; NLMISC::CVector dummyNewPos, dummyNewSpeed; f.serial(dummyDist, dummyNewPos, dummyNewSpeed); }; }; /// *************************************************************************************** void CPSLocated::serial(NLMISC::IStream &f) throw(NLMISC::EStream) { NL_PS_FUNC(CPSLocated_serial) // version 7 : - removed the field _NbFramesToSkip to get some space (it is never used) // - removed the requestStack (system graph can't contains loops now) // - removed _CollisonInfos because they are now static // version 4 to version 5 : bugfix with reading of collisions sint ver = f.serialVersion(7); CParticleSystemProcess::serial(f); if (f.isReading() && !CParticleSystem::getSerializeIdentifierFlag()) { // just skip the name sint32 len; f.serial(len); f.seek(len, NLMISC::IStream::current); } else { f.serial(_Name); } f.serial(_InvMass); f.serial(_Pos); f.serial(_Speed); f.serial(_Time); if (f.isReading()) { // tmp fix : some fx were saved with a life that is != to 0 // this cause an assertion in CPSLocated::updateLife, because all particle are assumed to have an age of 0 when the system is started // TODO : saving _Time is maybe unecessary... or find something better for CPSLocated::updateLife uint timeSize = _Time.getSize(); if (timeSize != 0) { std::fill(&_Time[0], &_Time[0] + timeSize, 0.f); } } f.serial(_TimeIncrement); f.serial(_Size); f.serial(_MaxSize); bool lastForever = _LastForever; f.serial(lastForever); _LastForever = lastForever; if (ver < 7) { nlassert(f.isReading()); // serial a dummy ptr (previous code did a serial ptr) uint64 dummyPtr; f.serial(dummyPtr); if (dummyPtr) { #ifdef PS_FAST_ALLOC extern NLMISC::CContiguousBlockAllocator *PSBlockAllocator; NLMISC::CContiguousBlockAllocator *oldAlloc = PSBlockAllocator; PSBlockAllocator = NULL; #endif static CPSAttrib col; col.clear(); f.serial(col); #ifdef PS_FAST_ALLOC PSBlockAllocator = oldAlloc; #endif } } f.serial(_CollisionInfoNbRef); // TODO avoid to serialize this ? // if (f.isReading()) { if (_CollisionInfoNbRef) { _CollisionNextPos = new TPSAttribVector; _CollisionNextPos->resize(_Pos.getMaxSize()); for(uint k = 0; k < _Size; ++k) { _CollisionNextPos->insert(); } } } //CHECK_PS_INTEGRITY if (f.isReading()) { delete _LifeScheme; delete _MassScheme; bool useScheme; f.serial(useScheme); if (useScheme) { f.serialPolyPtr(_LifeScheme); } else { f.serial(_InitialLife); _LifeScheme = NULL; } f.serial(useScheme); if (useScheme) { f.serialPolyPtr(_MassScheme); } else { f.serial(_InitialMass); nlassert(_InitialMass > 0); _MassScheme = NULL; } } else { bool bFalse = false, bTrue = true; if (_LifeScheme) { f.serial(bTrue); f.serialPolyPtr(_LifeScheme); } else { f.serial(bFalse); f.serial(_InitialLife); } if (_MassScheme) { f.serial(bTrue); f.serialPolyPtr(_MassScheme); } else { f.serial(bFalse); nlassert(_InitialMass > 0); f.serial(_InitialMass); } } if (ver < 7) { uint32 dummy = 0; // was previously the field "_NbFramesToSkip" f.serial(dummy); } f.serialContPolyPtr(_DtorObserversVect); if (ver < 7) { nlassert(f.isReading()); // previously, there was a request stack serialized (because system permitted loops) uint32 size; f.serial(size); nlassert(size == 0); /* for (uint32 k = 0; k < size; ++k) { TNewElementRequestStack::value_type t; f.serial(t); _RequestStack.push(t); } */ } if (ver < 7) { nlassert(f.isReading()); bool dummy; f.serial(dummy); // was previously the flag "_UpdateLock" } f.serialContPolyPtr(_LocatedBoundCont); // check that owners are good #ifdef NL_DEBUG for(uint k = 0; k < _LocatedBoundCont.size(); ++k) { nlassert(_LocatedBoundCont[k]->getOwner() == this); } #endif if (ver > 1) { bool lodDegradation = _LODDegradation; f.serial(lodDegradation); _LODDegradation = lodDegradation; } if (ver > 2) { bool parametricMotion = _ParametricMotion; f.serial(parametricMotion); _ParametricMotion = parametricMotion; } if (f.isReading()) { // evaluate our max number of faces //notifyMaxNumFacesChanged(); if (_ParametricMotion) { _ParametricMotion = false; allocateParametricInfos(); _ParametricMotion = true; } } if (ver > 3) { bool triggerOnDeath = _TriggerOnDeath; f.serial(triggerOnDeath); _TriggerOnDeath = triggerOnDeath; f.serial(_TriggerID); } CHECK_PS_INTEGRITY } /// *************************************************************************************** // integrate speed of particles. Makes eventually use of SSE instructions when present static void IntegrateSpeed(uint count, float *src1, const float *src2, float *dest, float ellapsedTime) { NL_PS_FUNC(IntegrateSpeed) #if 0 // this works, but is not enabled for now. The precision is not that good... /* #ifdef NL_OS_WINDOWS if (NLMISC::CCpuInfo::hasSSE() && ((uint) src1 & 15) == ((uint) src2 & 15) && ! ((uint) src1 & 3) && ! ((uint) src2 & 3) ) // must must be sure that memory alignment is identical { // compute first datas in order to align to 16 byte boudary uint alignCount = ((uint) src1 >> 2) & 3; // number of float to process while (alignCount --) { *src1++ += ellapsedTime * *src2 ++; } count -= alignCount; if (count > 3) { float et[4] = { ellapsedTime, ellapsedTime, ellapsedTime, ellapsedTime}; // sse part of computation __asm { mov ecx, count shr ecx, 2 xor edx, edx mov eax, src1 mov ebx, src2 movups xmm0, et[0] myLoop: movaps xmm2, [ebx + 8 * edx] movaps xmm1, [eax + 8 * edx] mulps xmm2, xmm0 addps xmm1, xmm2 movaps [eax + 8 * edx], xmm1 add edx, 2 dec ecx jne myLoop } } // proceed with left float count &= 3; if (count) { src1 += alignCount; src2 += alignCount; do { *src1 += ellapsedTime * *src2; ++src1; ++src2; } while (--count); } } else #endif */ #endif { // standard version // standard version uint countDiv8 = count>>3; count &= 7; // takes count % 8 if (dest == src1) { while (countDiv8 --) { src1[0] += ellapsedTime * src2[0]; src1[1] += ellapsedTime * src2[1]; src1[2] += ellapsedTime * src2[2]; src1[3] += ellapsedTime * src2[3]; src1[4] += ellapsedTime * src2[4]; src1[5] += ellapsedTime * src2[5]; src1[6] += ellapsedTime * src2[6]; src1[7] += ellapsedTime * src2[7]; src2 += 8; src1 += 8; } while (count--) { *src1++ += ellapsedTime * *src2++; } } else { while (countDiv8 --) { dest[0] = src1[0] + ellapsedTime * src2[0]; dest[1] = src1[1] + ellapsedTime * src2[1]; dest[2] = src1[2] + ellapsedTime * src2[2]; dest[3] = src1[3] + ellapsedTime * src2[3]; dest[4] = src1[4] + ellapsedTime * src2[4]; dest[5] = src1[5] + ellapsedTime * src2[5]; dest[6] = src1[6] + ellapsedTime * src2[6]; dest[7] = src1[7] + ellapsedTime * src2[7]; src2 += 8; src1 += 8; dest += 8; } while (count--) { *dest++ = *src1++ + ellapsedTime * *src2++; } } } } /// *************************************************************************************** void CPSLocated::computeMotion() { NL_PS_FUNC(CPSLocated_computeMotion) nlassert(_Size); // there are 2 integration steps : with and without collisions if (!_CollisionNextPos) // no collisionner are used { { MINI_TIMER(PSMotion3) if (_Size != 0) // avoid referencing _Pos[0] if there's no size, causes STL vectors to assert... IntegrateSpeed(_Size * 3, &_Pos[0].x, &_Speed[0].x, &_Pos[0].x, CParticleSystem::EllapsedTime); } } else { { MINI_TIMER(PSMotion4) // compute new position after the timeStep IntegrateSpeed(_Size * 3, &_Pos[0].x, &_Speed[0].x, &(*_CollisionNextPos)[0].x, CParticleSystem::EllapsedTime); nlassert(CPSLocated::_Collisions.size() >= _Size); computeCollisions(0, &_Pos[0], &(*_CollisionNextPos)[0]); // update new poositions by just swapping the 2 vectors _CollisionNextPos->swap(_Pos); } } } /// *************************************************************************************** void CPSLocated::computeNewParticleMotion(uint firstInstanceIndex) { NL_PS_FUNC(CPSLocated_computeNewParticleMotion) nlassert(_CollisionNextPos); resetCollisions(_Size); computeCollisions(firstInstanceIndex, &CParticleSystem::_SpawnPos[0], &_Pos[0]); } /// *************************************************************************************** void CPSLocated::resetCollisions(uint numInstances) { NL_PS_FUNC(CPSLocated_resetCollisions) CPSCollisionInfo *currCollision = _FirstCollision; while (currCollision) { currCollision->Dist = -1.f; currCollision = currCollision->Next; } _FirstCollision = NULL; if (numInstances > _Collisions.size()) { uint oldSize = (uint) _Collisions.size(); _Collisions.resize(numInstances); for(uint k = oldSize; k < numInstances; ++k) { _Collisions[k].Index = k; } } } /// *************************************************************************************** void CPSLocated::updateCollisions() { NL_PS_FUNC(CPSLocated_updateCollisions) CPSCollisionInfo *currCollision = _FirstCollision; if (getLastForever()) { while (currCollision) { _Pos[currCollision->Index] = currCollision->NewPos; std::swap(_Speed[currCollision->Index], currCollision->NewSpeed); // keep speed because may be needed when removing particles // notify each located bindable that a bounce occured ... for (TLocatedBoundCont::iterator it = _LocatedBoundCont.begin(); it != _LocatedBoundCont.end(); ++it) { (*it)->bounceOccured(currCollision->Index, computeDateFromCollisionToNextSimStep(currCollision->Index, getAgeInSeconds(currCollision->Index))); } if (currCollision->CollisionZone->getCollisionBehaviour() == CPSZone::destroy) { #ifdef NL_DEBUG nlassert(CParticleSystem::_ParticleRemoveListIndex[currCollision->Index] == -1); #endif CParticleSystem::_ParticleToRemove.push_back(currCollision->Index); #ifdef NL_DEBUG nlassert(CParticleSystem::_ParticleToRemove.size() <= _Size); #endif CParticleSystem::_ParticleRemoveListIndex[currCollision->Index] = (sint)CParticleSystem::_ParticleToRemove.size() - 1; } currCollision = currCollision->Next; } } else { while (currCollision) { if (_Time[currCollision->Index] >= 1.f) { // check whether particles died before the collision. If so, just continue (particle has already been inserted in the remove list), and cancel the collision float timeToCollision = currCollision->Dist / _Speed[currCollision->Index].norm(); if (_Time[currCollision->Index] / _TimeIncrement[currCollision->Index] - timeToCollision * CParticleSystem::RealEllapsedTimeRatio >= 1.f) { // says that collision did not occurs currCollision->Dist = -1.f; currCollision = currCollision->Next; continue; } } // if particle is too old, check whether it died before the collision _Pos[currCollision->Index] = currCollision->NewPos; std::swap(_Speed[currCollision->Index], currCollision->NewSpeed); // notify each located bindable that a bounce occured ... if (!_LocatedBoundCont.empty()) { TAnimationTime timeFromcollisionToNextSimStep = computeDateFromCollisionToNextSimStep(currCollision->Index, getAgeInSeconds(currCollision->Index)); for (TLocatedBoundCont::iterator it = _LocatedBoundCont.begin(); it != _LocatedBoundCont.end(); ++it) { (*it)->bounceOccured(currCollision->Index, timeFromcollisionToNextSimStep); } } if (currCollision->CollisionZone->getCollisionBehaviour() == CPSZone::destroy) { if (_Time[currCollision->Index] < 1.f) { // insert particle only if not already dead #ifdef NL_DEBUG nlassert(CParticleSystem::_ParticleRemoveListIndex[currCollision->Index] == -1); #endif CParticleSystem::_ParticleToRemove.push_back(currCollision->Index); #ifdef NL_DEBUG nlassert(CParticleSystem::_ParticleToRemove.size() <= _Size); #endif CParticleSystem::_ParticleRemoveListIndex[currCollision->Index] = (sint)CParticleSystem::_ParticleToRemove.size() - 1; } } currCollision = currCollision->Next; } } } /// *************************************************************************************** void CPSLocated::doLODDegradation() { NL_PS_FUNC(CPSLocated_doLODDegradation) nlassert(CParticleSystem::InsideSimLoop); nlassert(!CParticleSystem::InsideRemoveLoop); CParticleSystem::InsideRemoveLoop = true; if (CParticleSystem::EllapsedTime > 0) { nlassert(_Owner); // compute the number of particles to show const uint maxToHave = (uint) (_MaxSize * _Owner->getOneMinusCurrentLODRatio()); if (_Size > maxToHave) // too much instances ? { // choose a random element to start at, and a random step // this will avoid a pulse effect when the system is far away uint pos = maxToHave ? rand() % maxToHave : 0; uint step = maxToHave ? rand() % maxToHave : 0; do { deleteElement(pos); pos += step; if (pos >= maxToHave) pos -= maxToHave; } while (_Size !=maxToHave); } } CParticleSystem::InsideRemoveLoop = false; } /// *************************************************************************************** void CPSLocated::step(TPSProcessPass pass) { NL_PS_FUNC(CPSLocated_step) CHECK_PS_INTEGRITY if (!_Size) return; if (pass != PSMotion) { { /* uint64 *target; switch(pass) { case PSEmit: target = &PSStatEmit; break; case PSCollision: target = &PSStatCollision; break; default: target = &PSStatRender; break; } MINI_TIMER(*target) */ // apply the pass to all bound objects for (TLocatedBoundCont::iterator it = _LocatedBoundCont.begin(); it != _LocatedBoundCont.end(); ++it) { if ((*it)->isActive()) { if ((*it)->getLOD() == PSLod1n2 || _Owner->getLOD() == (*it)->getLOD()) // has this object the right LOD ? { (*it)->step(pass); } } } } } else { for (TLocatedBoundCont::iterator it = _LocatedBoundCont.begin(); it != _LocatedBoundCont.end(); ++it) { if ((*it)->isActive()) { (*it)->step(pass); } } } CHECK_PS_INTEGRITY } /// *************************************************************************************** void CPSLocated::updateLife() { NL_PS_FUNC(CPSLocated_updateLife) CHECK_PS_INTEGRITY if (!_Size) return; if (! _LastForever) { if (_LifeScheme != NULL) { TPSAttribTime::iterator itTime = _Time.begin(), itTimeInc = _TimeIncrement.begin(); for (uint32 k = 0; k < _Size; ++k) { *itTime += CParticleSystem::RealEllapsedTime * *itTimeInc; if (*itTime >= 1.0f) { #ifdef NL_DEBUG nlassert(CParticleSystem::_ParticleRemoveListIndex[k] == -1); #endif CParticleSystem::_ParticleToRemove.push_back(k); #ifdef NL_DEBUG nlassert(CParticleSystem::_ParticleToRemove.size() <= _Size); #endif CParticleSystem::_ParticleRemoveListIndex[k] = (sint)CParticleSystem::_ParticleToRemove.size() - 1; } ++itTime; ++itTimeInc; } } else /// all particles have the same lifetime { if (_InitialLife != 0) { nlassert(_Owner); float timeInc = CParticleSystem::RealEllapsedTime / _InitialLife; if (_Owner->getSystemDate() + 0.1f + 2.f * timeInc >= (_InitialLife - CParticleSystem::RealEllapsedTime)) { // NB : 0.1f + 2.f * timeInc added to avoid case were time of particle is slighty greater than 1.f after life update because that test failed TPSAttribTime::iterator itTime = _Time.begin(); for (uint32 k = 0; k < _Size; ++k) { *itTime += timeInc; if (*itTime >= 1.0f) { #ifdef NL_DEBUG nlassert(CParticleSystem::_ParticleRemoveListIndex[k] == -1); #endif CParticleSystem::_ParticleToRemove.push_back(k); #ifdef NL_DEBUG nlassert(CParticleSystem::_ParticleToRemove.size() <= _Size); #endif CParticleSystem::_ParticleRemoveListIndex[k] = (sint)CParticleSystem::_ParticleToRemove.size() - 1; } ++ itTime; } } else { // system has not lasted enough for any particle to die TPSAttribTime::iterator itTime = _Time.begin(), itEndTime = _Time.end(); do { *itTime += timeInc; ++itTime; } while (itTime != itEndTime); } } else { for(uint k = 0; k < _Size; ++k) { #ifdef NL_DEBUG nlassert(CParticleSystem::_ParticleRemoveListIndex[k] == -1); #endif CParticleSystem::_ParticleToRemove.push_back(k); #ifdef NL_DEBUG nlassert(CParticleSystem::_ParticleToRemove.size() <= _Size); #endif CParticleSystem::_ParticleRemoveListIndex[k] = (sint)CParticleSystem::_ParticleToRemove.size() - 1; } } } } else { // the time attribute gives the life in seconds TPSAttribTime::iterator itTime = _Time.begin(), endItTime = _Time.end(); for (; itTime != endItTime; ++itTime) { *itTime += CParticleSystem::RealEllapsedTime; } } CHECK_PS_INTEGRITY } /// *************************************************************************************** // When a particle is deleted, it is replaced by the last particle in the array // if this particle is to be deleted to, must update its new index static inline void removeParticleFromRemoveList(uint indexToRemove, uint arraySize) { NL_PS_FUNC(removeParticleFromRemoveList) if (indexToRemove != arraySize) { if (CParticleSystem::_ParticleRemoveListIndex[arraySize] != -1) { // when a particle is deleted, it is replaced by the last particle in the array // if this particle is to be deleted too, must update its new index (becomes the index of the particle that has just been deleted) CParticleSystem::_ParticleToRemove[CParticleSystem::_ParticleRemoveListIndex[arraySize]] = indexToRemove; CParticleSystem::_ParticleRemoveListIndex[indexToRemove] = CParticleSystem::_ParticleRemoveListIndex[arraySize]; CParticleSystem::_ParticleRemoveListIndex[arraySize] = -1; // not to remove any more } else { CParticleSystem::_ParticleRemoveListIndex[indexToRemove] = -1; } } else { CParticleSystem::_ParticleRemoveListIndex[arraySize] = -1; } } void checkRemoveArray(uint size) { NL_PS_FUNC(checkRemoveArray) for(uint k = 0; k < size; ++k) { if (CParticleSystem::_ParticleRemoveListIndex[k] != -1) { nlassert(std::find(CParticleSystem::_ParticleRemoveListIndex.begin(), CParticleSystem::_ParticleRemoveListIndex.end(), CParticleSystem::_ParticleRemoveListIndex[k]) != CParticleSystem::_ParticleRemoveListIndex.end()); } } for(uint k = 0; k < CParticleSystem::_ParticleToRemove.size(); ++k) { nlassert(CParticleSystem::_ParticleRemoveListIndex[CParticleSystem::_ParticleToRemove[k]] == (sint) k); } } /// *************************************************************************************** #ifndef NL_DEBUG inline #endif TAnimationTime CPSLocated::computeDateFromCollisionToNextSimStep(uint particleIndex, float particleAgeInSeconds) { NL_PS_FUNC( CPSLocated_computeDateFromCollisionToNextSimStep) // compute time from the start of the sim step to the birth of the particle (or 0 if already born) float ageAtStart = CParticleSystem::RealEllapsedTime > particleAgeInSeconds ? CParticleSystem::RealEllapsedTime - particleAgeInSeconds : 0.f; ageAtStart /= CParticleSystem::RealEllapsedTimeRatio; // compute time to collision. The 'NewSpeed' field is swapped with speed of particle at the sim step start when 'updateCollision' is called, and thus contains the old speed. float norm = _Collisions[particleIndex].NewSpeed.norm(); if (norm == 0.f) return 0.f; float timeToCollision = _Collisions[particleIndex].Dist / norm; // So time from collision to end of sim step is : TAnimationTime result = CParticleSystem::EllapsedTime - ageAtStart - timeToCollision; return std::max(0.f, result); } /// *************************************************************************************** void CPSLocated::removeOldParticles() { NL_PS_FUNC(CPSLocated_removeOldParticles) nlassert(CParticleSystem::RealEllapsedTime > 0.f); #ifdef NL_DEBUG CParticleSystem::InsideRemoveLoop = true; checkRemoveArray(_Size); #endif // remove all elements that were marked as too old // if there are emitters marked as 'on' death, should correct position by moving backward (because motion is done on a whole time step, so particle is further than it should be) if (getLastForever()) { // if the particle lasts for ever it can be destroyed only if it touch a collision zone flaged as 'destroy' // during the call to 'updateCollisions', the list of particles to remove will be updated so just test it if (hasCollisionInfos()) { for(std::vector::iterator it = CParticleSystem::_ParticleToRemove.begin(); it != CParticleSystem::_ParticleToRemove.end(); ++it) { if (_Collisions[*it].Dist != -1.f) { deleteElement(*it, computeDateFromCollisionToNextSimStep(*it, _Time[*it])); } else { deleteElement(*it); } removeParticleFromRemoveList(*it, _Size); } } } else if (hasCollisionInfos()) // particle has collision, and limited lifetime { float ellapsedTimeRatio = CParticleSystem::EllapsedTime / CParticleSystem::RealEllapsedTime; for(std::vector::iterator it = CParticleSystem::_ParticleToRemove.begin(); it != CParticleSystem::_ParticleToRemove.end(); ++it) { TAnimationTime timeUntilNextSimStep; if (_Collisions[*it].Dist == -1.f) { // no collision occured if (_Time[*it] > 1.f) { if (_LifeScheme) { _Pos[*it] -= _Speed[*it] * ((_Time[*it] - 1.f) / _TimeIncrement[*it]) * ellapsedTimeRatio; timeUntilNextSimStep = (_Time[*it] - 1.f) / _TimeIncrement[*it]; } else { _Pos[*it] -= _Speed[*it] * ((_Time[*it] - 1.f) * _InitialLife) * ellapsedTimeRatio; timeUntilNextSimStep = (_Time[*it] - 1.f) * _InitialLife; } _Time[*it] = 0.9999f; } else { timeUntilNextSimStep = 0.f; } } else { // a collision occured before particle died, so pos is already good if (_LifeScheme) { timeUntilNextSimStep = computeDateFromCollisionToNextSimStep(*it, _Time[*it] / _TimeIncrement[*it]); // compute age of particle when collision occured _Time[*it] -= timeUntilNextSimStep * _TimeIncrement[*it]; NLMISC::clamp(_Time[*it], 0.f, 1.f); // avoid imprecisions } else { timeUntilNextSimStep = computeDateFromCollisionToNextSimStep(*it, _Time[*it] * _InitialLife); // compute age of particle when collision occured _Time[*it] -= timeUntilNextSimStep / (_InitialLife == 0.f ? 1.f : _InitialLife); NLMISC::clamp(_Time[*it], 0.f, 1.f); // avoid imprecisions } } deleteElement(*it, timeUntilNextSimStep); removeParticleFromRemoveList(*it, _Size); } } else // particle has no collisions, and limited lifetime { float ellapsedTimeRatio = CParticleSystem::EllapsedTime / CParticleSystem::RealEllapsedTime; if (!isParametricMotionEnabled()) { if (_LifeScheme) { for(std::vector::iterator it = CParticleSystem::_ParticleToRemove.begin(); it != CParticleSystem::_ParticleToRemove.end(); ++it) { #ifdef NL_DEBUG for(std::vector::iterator it2 = it; it2 != CParticleSystem::_ParticleToRemove.end(); ++it2) { nlassert(*it2 < _Size); } #endif TAnimationTime timeUntilNextSimStep; if (_Time[*it] > 1.f) { // move position backward (compute its position at death) timeUntilNextSimStep = ((_Time[*it] - 1.f) / _TimeIncrement[*it]) * ellapsedTimeRatio; _Pos[*it] -= _Speed[*it] * timeUntilNextSimStep; // force time to 1 because emitter 'on death' may rely on the date of emitter to compute its attributes _Time[*it] = 0.9999f; } else { timeUntilNextSimStep = 0.f; } deleteElement(*it, timeUntilNextSimStep); removeParticleFromRemoveList(*it, _Size); #ifdef NL_DEBUG for(std::vector::iterator it2 = it + 1; it2 != CParticleSystem::_ParticleToRemove.end(); ++it2) { nlassert(*it2 < _Size); } #endif } } else { for(std::vector::iterator it = CParticleSystem::_ParticleToRemove.begin(); it != CParticleSystem::_ParticleToRemove.end(); ++it) { TAnimationTime timeUntilNextSimStep; if (_Time[*it] > 1.f) { // move position backward timeUntilNextSimStep = (_Time[*it] - 1.f) * _InitialLife * ellapsedTimeRatio; _Pos[*it] -= _Speed[*it] * timeUntilNextSimStep; // force time to 1 because emitter 'on death' may rely on the date of emitter to compute its attributes _Time[*it] = 0.9999f; } else { timeUntilNextSimStep = 0.f; } deleteElement(*it, timeUntilNextSimStep); removeParticleFromRemoveList(*it, _Size); } } } else { // parametric case if (_LifeScheme) { for(std::vector::iterator it = CParticleSystem::_ParticleToRemove.begin(); it != CParticleSystem::_ParticleToRemove.end(); ++it) { TAnimationTime timeUntilNextSimStep; if (_Time[*it] > 1.f) { // move position backward (compute its position at death) timeUntilNextSimStep = (_Time[*it] - 1.f) / _TimeIncrement[*it]; computeParametricPos(_Owner->getSystemDate() + CParticleSystem::RealEllapsedTime - timeUntilNextSimStep, *it, _Pos[*it]); timeUntilNextSimStep *= ellapsedTimeRatio; // force time to 1 because emitter 'on death' may rely on the date of emitter to compute its attributes _Time[*it] = 0.9999f; } else { timeUntilNextSimStep = 0.f; } deleteElement(*it, timeUntilNextSimStep); removeParticleFromRemoveList(*it, _Size); } } else { for(std::vector::iterator it = CParticleSystem::_ParticleToRemove.begin(); it != CParticleSystem::_ParticleToRemove.end(); ++it) { TAnimationTime timeUntilNextSimStep; if (_Time[*it] > 1.f) { // move position backward timeUntilNextSimStep = (_Time[*it] - 1.f) * _InitialLife; computeParametricPos(_Owner->getSystemDate() + CParticleSystem::RealEllapsedTime - timeUntilNextSimStep, *it, _Pos[*it]); timeUntilNextSimStep *= ellapsedTimeRatio; // force time to 1 because emitter 'on death' may rely on the date of emitter to compute its attributes _Time[*it] = 0.9999f; } else { timeUntilNextSimStep = 0.f; } deleteElement(*it, timeUntilNextSimStep); removeParticleFromRemoveList(*it, _Size); } } } } #ifdef NL_DEBUG CParticleSystem::InsideRemoveLoop = false; #endif CParticleSystem::_ParticleToRemove.clear(); #ifdef NL_DEBUG if (!_LastForever) { for(uint k = 0; k < _Size; ++k) { nlassert(_Time[k] >= 0.f && _Time[k] <= 1.f); } } #endif } /// *************************************************************************************** void CPSLocated::addNewlySpawnedParticles() { NL_PS_FUNC(CPSLocated_addNewlySpawnedParticles) #ifdef NL_DEBUG CParticleSystem::InsideNewElementsLoop = true; #endif CParticleSystem::CSpawnVect &spawns = *CParticleSystem::_Spawns[getIndex()]; if (spawns.SpawnInfos.empty()) return; uint numSpawns = 0; if (!_Owner->getAutoCountFlag()) { CParticleSystem::_SpawnPos.resize(getMaxSize()); numSpawns = std::min((uint) (_MaxSize - _Size), (uint) spawns.SpawnInfos.size()); } else { numSpawns = (uint) spawns.SpawnInfos.size(); } CParticleSystem::TSpawnInfoVect::const_iterator endIt = spawns.SpawnInfos.begin() + numSpawns; if (_LastForever) { for (CParticleSystem::TSpawnInfoVect::const_iterator it = spawns.SpawnInfos.begin(); it !=endIt; ++it) { // sint32 insertionIndex = newElement(*it, false, CParticleSystem::EllapsedTime); } } else { // to avoid warning in autocount mode //CParticleSystem::InsideSimLoop = false; for (CParticleSystem::TSpawnInfoVect::const_iterator it = spawns.SpawnInfos.begin(); it !=endIt; ++it) { sint32 insertionIndex = newElement(*it, false, CParticleSystem::EllapsedTime); #ifdef NL_DEBUG nlassert(insertionIndex != -1); #endif if (_Time[insertionIndex] >= 1.f) { #ifdef NL_DEBUG nlassert(CParticleSystem::_ParticleRemoveListIndex[insertionIndex] == -1); #endif CParticleSystem::_ParticleToRemove.push_back(insertionIndex); #ifdef NL_DEBUG nlassert(CParticleSystem::_ParticleToRemove.size() <= _Size); #endif CParticleSystem::_ParticleRemoveListIndex[insertionIndex] = (sint)CParticleSystem::_ParticleToRemove.size() - 1; } } //CParticleSystem::InsideSimLoop = true; } spawns.SpawnInfos.clear(); #ifdef NL_DEBUG CParticleSystem::InsideNewElementsLoop = false; #endif } /// *************************************************************************************** bool CPSLocated::computeBBox(NLMISC::CAABBox &box) const { NL_PS_FUNC(CPSLocated_computeBBox) CHECK_PS_INTEGRITY if (!_Size) return false; // something to compute ? TLocatedBoundCont::const_iterator it; TPSAttribVector::const_iterator it2; // check whether any object bound to us need a bbox for (it = _LocatedBoundCont.begin(); it != _LocatedBoundCont.end(); ++it) { if ((*it)->doesProduceBBox()) { break; } } if (it == _LocatedBoundCont.end()) { return false; } CVector min = _Pos[0], max = _Pos[0]; for (it2 = _Pos.begin(); it2 != _Pos.end(); ++ it2) { const CVector &v = (*it2); min.minof(min, v); max.maxof(max, v); } box.setMinMax(min, max); // we've considered that located had no extent in space // now, we must call each objects that are bound to the located in order // to complete the bbox if they have no null extent NLMISC::CAABBox tmpBox, startBox = box; for (it = _LocatedBoundCont.begin(); it != _LocatedBoundCont.end(); ++it) { if ((*it)->doesProduceBBox()) { tmpBox = startBox; if ((*it)->completeBBox(tmpBox)) { box = NLMISC::CAABBox::computeAABBoxUnion(tmpBox, box); } } } CHECK_PS_INTEGRITY return true; } /// Setup the driver model matrix. It is set accordingly to the basis used for rendering /// *************************************************************************************** void CPSLocated::setupDriverModelMatrix(void) { NL_PS_FUNC(CPSLocated_setupDriverModelMatrix) CHECK_PS_INTEGRITY getDriver()->setupModelMatrix(getLocalToWorldMatrix()); CHECK_PS_INTEGRITY } /// *************************************************************************************** void CPSLocated::queryCollisionInfo(void) { NL_PS_FUNC(CPSLocated_queryCollisionInfo) CHECK_PS_INTEGRITY if (_CollisionInfoNbRef) { ++ _CollisionInfoNbRef; } else { _CollisionNextPos = new TPSAttribVector; _CollisionInfoNbRef = 1; _CollisionNextPos ->resize(_MaxSize); for(uint k = 0; k < _Size; ++k) { _CollisionNextPos->insert(); } } CHECK_PS_INTEGRITY } /// *************************************************************************************** void CPSLocated::releaseCollisionInfo(void) { NL_PS_FUNC(CPSLocated_releaseCollisionInfo) CHECK_PS_INTEGRITY nlassert(_CollisionInfoNbRef); // check whether queryCollisionInfo was called // so the number of refs must not = 0 --_CollisionInfoNbRef; if (_CollisionInfoNbRef == 0) { delete _CollisionNextPos; _CollisionNextPos = NULL; } CHECK_PS_INTEGRITY } /// *************************************************************************************** void CPSLocated::registerIntegrableForce(CPSForce *f) { NL_PS_FUNC(CPSLocated_registerIntegrableForce) CHECK_PS_INTEGRITY nlassert(std::find(_IntegrableForces.begin(), _IntegrableForces.end(), f) == _IntegrableForces.end()); // force registered twice _IntegrableForces.push_back(f); if (getMatrixMode() != f->getOwner()->getMatrixMode()) { ++_NumIntegrableForceWithDifferentBasis; releaseParametricInfos(); } CHECK_PS_INTEGRITY } /// *************************************************************************************** void CPSLocated::unregisterIntegrableForce(CPSForce *f) { NL_PS_FUNC(CPSLocated_unregisterIntegrableForce) CHECK_PS_INTEGRITY nlassert(f->getOwner()); // f must be attached to a located CPSVector::V::iterator it = std::find(_IntegrableForces.begin(), _IntegrableForces.end(), f); nlassert(it != _IntegrableForces.end() ); _IntegrableForces.erase(it); if (getMatrixMode() != f->getOwner()->getMatrixMode()) { --_NumIntegrableForceWithDifferentBasis; } CHECK_PS_INTEGRITY } /// *************************************************************************************** void CPSLocated::addNonIntegrableForceRef(void) { NL_PS_FUNC(CPSLocated_addNonIntegrableForceRef) CHECK_PS_INTEGRITY ++_NonIntegrableForceNbRefs; releaseParametricInfos(); CHECK_PS_INTEGRITY } /// *************************************************************************************** void CPSLocated::releaseNonIntegrableForceRef(void) { NL_PS_FUNC(CPSLocated_releaseNonIntegrableForceRef) CHECK_PS_INTEGRITY nlassert(_NonIntegrableForceNbRefs != 0); --_NonIntegrableForceNbRefs; CHECK_PS_INTEGRITY } /// *************************************************************************************** void CPSLocated::integrableForceBasisChanged(TPSMatrixMode matrixMode) { NL_PS_FUNC(CPSLocated_integrableForceBasisChanged) CHECK_PS_INTEGRITY if (getMatrixMode() != matrixMode) { ++_NumIntegrableForceWithDifferentBasis; releaseParametricInfos(); } else { --_NumIntegrableForceWithDifferentBasis; } CHECK_PS_INTEGRITY } /// *************************************************************************************** CPSLocatedBindable *CPSLocated::unbind(uint index) { NL_PS_FUNC(CPSLocated_unbind) CHECK_PS_INTEGRITY nlassert(index < _LocatedBoundCont.size()); CPSLocatedBindable *lb = _LocatedBoundCont[index]; lb->setOwner(NULL); _LocatedBoundCont.erase(_LocatedBoundCont.begin() + index); return lb; CHECK_PS_INTEGRITY } /// *************************************************************************************** bool CPSLocated::isBound(const CPSLocatedBindable *lb) const { NL_PS_FUNC(CPSLocated_isBound) CHECK_PS_INTEGRITY TLocatedBoundCont::const_iterator it = std::find(_LocatedBoundCont.begin(), _LocatedBoundCont.end(), lb); return it != _LocatedBoundCont.end(); } /// *************************************************************************************** uint CPSLocated::getIndexOf(const CPSLocatedBindable *lb) const { NL_PS_FUNC(CPSLocated_getIndexOf) CHECK_PS_INTEGRITY for(uint k = 0; k < _LocatedBoundCont.size(); ++k) { if (_LocatedBoundCont[k] == lb) return k; } nlassert(0); return 0; } /////////////////////////////////////// // CPSLocatedBindable implementation // /////////////////////////////////////// /// *************************************************************************************** CPSLocatedBindable::CPSLocatedBindable() : _Owner(NULL), _ExternID(0), _LOD(PSLod1n2), _Active(true) { NL_PS_FUNC(CPSLocatedBindable_CPSLocatedBindable) _Owner = NULL; } /// *************************************************************************************** void CPSLocatedBindable::setOwner(CPSLocated *psl) { NL_PS_FUNC(CPSLocatedBindable_setOwner) if (psl == _Owner) return; if (psl == NULL) { releaseAllRef(); if (_Owner) { // empty this located bindable. Need to be empty if it must be rebound to another located. for (uint k = 0; k < _Owner->getSize(); ++k) { deleteElement(0); } } } if (_Owner && _Owner->getOwner()) { _Owner->getOwner()->releaseRefForUserSysCoordInfo(getUserMatrixUsageCount()); } _Owner = psl; if (_Owner && _Owner->getOwner()) { _Owner->getOwner()->addRefForUserSysCoordInfo(getUserMatrixUsageCount()); } } /// *************************************************************************************** void CPSLocatedBindable::finalize(void) { NL_PS_FUNC(CPSLocatedBindable_finalize) if (_Owner && _Owner->getOwner()) { _Owner->getOwner()->releaseRefForUserSysCoordInfo(getUserMatrixUsageCount()); } } /// *************************************************************************************** CPSLocatedBindable::~CPSLocatedBindable() { NL_PS_FUNC(CPSLocatedBindable_CPSLocatedBindableDtor) if (_ExternID) { if (_Owner && _Owner->getOwner()) { _Owner->getOwner()->unregisterLocatedBindableExternID(this); } } } /// *************************************************************************************** void CPSLocatedBindable::notifyTargetRemoved(CPSLocated *ptr) { NL_PS_FUNC(CPSLocatedBindable_notifyTargetRemoved) ptr->unregisterDtorObserver(this); } /// *************************************************************************************** void CPSLocatedBindable::serial(NLMISC::IStream &f) throw(NLMISC::EStream) { NL_PS_FUNC(CPSLocatedBindable_IStream ) sint ver = f.serialVersion(4); f.serialPtr(_Owner); if (ver > 1) f.serialEnum(_LOD); if (ver > 2) { if (f.isReading() && !CParticleSystem::getSerializeIdentifierFlag()) { // just skip the name sint32 len; f.serial(len); f.seek(len, NLMISC::IStream::current); } else { f.serial(_Name); } } if (ver > 3) { if (f.isReading()) { uint32 id; f.serial(id); setExternID(id); } else { f.serial(_ExternID); } } } /// *************************************************************************************** void CPSLocatedBindable::displayIcon2d(const CVector tab[], uint nbSegs, float scale) { NL_PS_FUNC(CPSLocatedBindable_displayIcon2d) uint32 size = _Owner->getSize(); if (!size) return; setupDriverModelMatrix(); const CVector I = computeI(); const CVector K = computeK(); static std::vector lines; lines.clear(); // 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]; for (uint l = 0; l < nbSegs; ++l) { NLMISC::CLine li; li.V0 = p + scale * (tab[l << 1].x * I + tab[l << 1].y * K); li.V1 = p + scale * (tab[(l << 1) + 1].x * I + tab[(l << 1) + 1].y * K); lines.push_back(li); } 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() ); } } /// *************************************************************************************** CFontManager *CPSLocatedBindable::getFontManager(void) { NL_PS_FUNC(CPSLocatedBindable_getFontManager) nlassert(_Owner); return _Owner->getFontManager(); } /// *************************************************************************************** /// Shortcut to get the font manager if one was set (const version) const CFontManager *CPSLocatedBindable::getFontManager(void) const { NL_PS_FUNC(CPSLocatedBindable_getFontManager) nlassert(_Owner); return _Owner->getFontManager(); } /// *************************************************************************************** // Shortcut to get the matrix of the system const NLMISC::CMatrix &CPSLocatedBindable::getSysMat(void) const { NL_PS_FUNC( CPSLocatedBindable_getSysMat) nlassert(_Owner); return _Owner->getOwner()->getSysMat(); } /// *************************************************************************************** /// shortcut to get the inverted matrix of the system const NLMISC::CMatrix &CPSLocatedBindable::getInvertedSysMat(void) const { NL_PS_FUNC(CPSLocatedBindable_getInvertedSysMat) nlassert(_Owner); return _Owner->getOwner()->getInvertedSysMat(); } /// *************************************************************************************** /// shortcut to get the view matrix const NLMISC::CMatrix &CPSLocatedBindable::getViewMat(void) const { NL_PS_FUNC(CPSLocatedBindable_getViewMat) nlassert(_Owner); return _Owner->getOwner()->getViewMat(); } /// *************************************************************************************** /// shortcut to get the inverted view matrix const NLMISC::CMatrix &CPSLocatedBindable::getInvertedViewMat(void) const { NL_PS_FUNC(CPSLocatedBindable_getInvertedViewMat) nlassert(_Owner); return _Owner->getOwner()->getInvertedViewMat(); } /// *************************************************************************************** /// shortcut to setup the model matrix (system basis or world basis) void CPSLocatedBindable::setupDriverModelMatrix(void) { NL_PS_FUNC(CPSLocatedBindable_setupDriverModelMatrix) nlassert(_Owner); _Owner->setupDriverModelMatrix(); } /// *************************************************************************************** void CPSLocatedBindable::setExternID(uint32 id) { NL_PS_FUNC(CPSLocatedBindable_setExternID) if (id == _ExternID) return; CParticleSystem *ps = NULL; if (_Owner && _Owner->getOwner()) { ps = _Owner->getOwner(); } if (ps) { ps->unregisterLocatedBindableExternID(this); _ExternID = 0; } if (id != 0) { if (ps) ps->registerLocatedBindableExternID(id, this); _ExternID = id; } } /// *************************************************************************************** void CPSLocatedBindable::releaseAllRef() { NL_PS_FUNC(CPSLocatedBindable_releaseAllRef) } ///////////////////////////////////////////// // CPSTargetLocatedBindable implementation // ///////////////////////////////////////////// /// *************************************************************************************** void CPSTargetLocatedBindable::serial(NLMISC::IStream &f) throw(NLMISC::EStream) { NL_PS_FUNC(CPSTargetLocatedBindable_serial) (void)f.serialVersion(1); f.serialPtr(_Owner); f.serial(_Name); if (f.isReading()) { // delete previous attached bindables... for (TTargetCont::iterator it = _Targets.begin(); it != _Targets.end(); ++it) { delete (*it); } _Targets.clear(); } f.serialContPolyPtr(_Targets); } /// *************************************************************************************** void CPSTargetLocatedBindable::attachTarget(CPSLocated *ptr) { NL_PS_FUNC(CPSTargetLocatedBindable_attachTarget) // a target can't be shared between different particle systems #ifdef NL_DEBUG if (_Owner) { nlassert(_Owner->getOwner() == ptr->getOwner()); } #endif // see whether this target has not been registered before nlassert(std::find(_Targets.begin(), _Targets.end(), ptr) == _Targets.end()); _Targets.push_back(ptr); // we register us to be notified when the target disappear ptr->registerDtorObserver(this); } /// *************************************************************************************** void CPSTargetLocatedBindable::notifyTargetRemoved(CPSLocated *ptr) { NL_PS_FUNC(CPSTargetLocatedBindable_notifyTargetRemoved) TTargetCont::iterator it = std::find(_Targets.begin(), _Targets.end(), ptr); nlassert(it != _Targets.end()); releaseTargetRsc(*it); _Targets.erase(it); CPSLocatedBindable::notifyTargetRemoved(ptr); } // dtor /// *************************************************************************************** void CPSTargetLocatedBindable::finalize(void) { NL_PS_FUNC(CPSTargetLocatedBindable_finalize) /** Release the collisionInfos we've querried. We can't do it in the dtor, as calls to releaseTargetRsc wouldn't be polymorphics for derived class! * And the behaviour of releaseTergetRsc is implemented in derived class */ for (TTargetCont::iterator it = _Targets.begin(); it != _Targets.end(); ++it) { releaseTargetRsc(*it); } CPSLocatedBindable::finalize(); } /// *************************************************************************************** CPSTargetLocatedBindable::~CPSTargetLocatedBindable() { NL_PS_FUNC(CPSTargetLocatedBindable_CPSTargetLocatedBindable) // we unregister to all the targets for (TTargetCont::iterator it = _Targets.begin(); it != _Targets.end(); ++it) { (*it)->unregisterDtorObserver(this); } } /// *************************************************************************************** void CPSTargetLocatedBindable::releaseRefTo(const CParticleSystemProcess *other) { NL_PS_FUNC(CPSTargetLocatedBindable_releaseRefTo) TTargetCont::iterator it = std::find(_Targets.begin(), _Targets.end(), other); if (it == _Targets.end()) return; releaseTargetRsc(*it); (*it)->unregisterDtorObserver(this); _Targets.erase(it); nlassert(std::find(_Targets.begin(), _Targets.end(), other) == _Targets.end()); } /// *************************************************************************************** void CPSTargetLocatedBindable::releaseAllRef() { NL_PS_FUNC(CPSTargetLocatedBindable_releaseAllRef) for (TTargetCont::iterator it = _Targets.begin(); it != _Targets.end(); ++it) { releaseTargetRsc(*it); (*it)->unregisterDtorObserver(this); } _Targets.clear(); CPSLocatedBindable::releaseAllRef(); } /// *************************************************************************************** uint CPSLocated::getUserMatrixUsageCount() const { NL_PS_FUNC(CPSLocated_getUserMatrixUsageCount) uint count = 0; for(TLocatedBoundCont::const_iterator it = _LocatedBoundCont.begin(); it != _LocatedBoundCont.end(); ++it) { count += (*it)->getUserMatrixUsageCount(); } return count + CParticleSystemProcess::getUserMatrixUsageCount(); } /// *************************************************************************************** void CPSLocated::enumTexs(std::vector > &dest, IDriver &drv) { NL_PS_FUNC(CPSLocated_enumTexs) for(TLocatedBoundCont::const_iterator it = _LocatedBoundCont.begin(); it != _LocatedBoundCont.end(); ++it) { (*it)->enumTexs(dest, drv); } } /// *************************************************************************************** void CPSLocated::setZBias(float value) { NL_PS_FUNC(CPSLocated_setZBias) for(TLocatedBoundCont::const_iterator it = _LocatedBoundCont.begin(); it != _LocatedBoundCont.end(); ++it) { (*it)->setZBias(value); } } /// *************************************************************************************** void CPSLocated::computeCollisions(uint firstInstanceIndex, const NLMISC::CVector *posBefore, const NLMISC::CVector *posAfter) { NL_PS_FUNC(CPSLocated_computeCollisions) for(TDtorObserversVect::iterator it = _DtorObserversVect.begin(); it != _DtorObserversVect.end(); ++it) { if ((*it)->getType() == PSZone) { static_cast(*it)->computeCollisions(*this, firstInstanceIndex, posBefore, posAfter); } } } /// *************************************************************************************** void CPSLocated::computeSpawns(uint firstInstanceIndex, bool includeEmitOnce) { NL_PS_FUNC(CPSLocated_computeSpawns) nlassert(CParticleSystem::InsideSimLoop); for(TLocatedBoundCont::iterator it = _LocatedBoundCont.begin(); it != _LocatedBoundCont.end(); ++it) { if (!(*it)->isActive()) continue; if ((*it)->getType() == PSEmitter) { CPSEmitter *emit = static_cast(*it); emit->updateEmitTrigger(); switch(emit->getEmissionType()) { case CPSEmitter::regular: emit->computeSpawns(firstInstanceIndex); break; case CPSEmitter::once: // if we're at first frame, then do emit for each emitter nlassert(_Owner); if (_Owner->getSystemDate() == 0.f || includeEmitOnce) { // if first pass, then do the emit a single time // if firstInstanceIndex != 0 then we're dealing with newly created particles, so do the spawn too emit->doEmitOnce(firstInstanceIndex); } break; default: break; } } } } /// *************************************************************************************** void CPSLocated::computeForces() { NL_PS_FUNC(CPSLocated_computeForces) nlassert(CParticleSystem::InsideSimLoop); for(TDtorObserversVect::iterator it = _DtorObserversVect.begin(); it != _DtorObserversVect.end(); ++it) { if ((*it)->getType() == PSForce) { CPSForce *force = static_cast(*it); force->computeForces(*this); } } } /// *************************************************************************************** void CPSCollisionInfo::update(const CPSCollisionInfo &other) { NL_PS_FUNC(CPSCollisionInfo_update) if (Dist == -1) { // link collision in the global list of active collisions Next = CPSLocated::_FirstCollision; CPSLocated::_FirstCollision = this; Dist = other.Dist; NewPos = other.NewPos; NewSpeed = other.NewSpeed; CollisionZone = other.CollisionZone; } else if (other.Dist < Dist) // is the new collision better (e.g it happens sooner) ? { Dist = other.Dist; NewPos = other.NewPos; NewSpeed = other.NewSpeed; CollisionZone = other.CollisionZone; } } /// *************************************************************************************** void CPSLocated::checkLife() const { NL_PS_FUNC(CPSLocated_checkLife) if (!getLastForever()) { for(uint k = 0; k < getSize(); ++k) { nlassert(getTime()[k] >= 0.f); nlassert(getTime()[k] <= 1.f); } } } /// *************************************************************************************** void CPSLocated::onShow(bool shown) { for(TLocatedBoundCont::iterator it = _LocatedBoundCont.begin(); it != _LocatedBoundCont.end(); ++it) { (*it)->onShow(shown); } } } // NL3D