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