1131 lines
32 KiB
C++
1131 lines
32 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/misc/debug.h"
|
|
#include "nel/misc/common.h"
|
|
#include "nel/misc/hierarchical_timer.h"
|
|
#include "nel/3d/particle_system_model.h"
|
|
#include "nel/3d/particle_system_shape.h"
|
|
#include "nel/3d/particle_system.h"
|
|
#include "nel/3d/scene.h"
|
|
#include "nel/3d/anim_detail_trav.h"
|
|
#include "nel/3d/clip_trav.h"
|
|
#include "nel/3d/render_trav.h"
|
|
#include "nel/3d/skeleton_model.h"
|
|
|
|
#include "nel/3d/cluster.h" // ask trap
|
|
|
|
|
|
|
|
|
|
namespace NL3D {
|
|
|
|
|
|
uint64 PSStatsRegisterPSModelObserver = 0;
|
|
uint64 PSStatsRemovePSModelObserver = 0;
|
|
uint64 PSStatsUpdateOpacityInfos = 0;
|
|
uint64 PSStatsUpdateLightingInfos = 0;
|
|
uint64 PSStatsGetAABBox = 0;
|
|
uint64 PSStatsReallocRsc = 0;
|
|
uint64 PSStatsReleasePSPointer = 0;
|
|
uint64 PSStatsRefreshRscDeletion = 0;
|
|
uint64 PSStatsReleaseRsc = 0;
|
|
uint64 PSStatsReleaseRscAndInvalidate = 0;
|
|
uint64 PSStatsGetNumTriangles = 0;
|
|
uint64 PSStatsCheckAgainstPyramid = 0;
|
|
uint64 PSStatsTraverseAnimDetail = 0;
|
|
uint64 PSStatsDoAnimate = 0;
|
|
uint64 PSStatsDoAnimatePart1 = 0;
|
|
uint64 PSStatsDoAnimatePart2 = 0;
|
|
uint64 PSStatsDoAnimatePart3 = 0;
|
|
uint64 PSStatsTraverseRender = 0;
|
|
uint64 PSStatsTraverseClip = 0;
|
|
uint64 PSStatsClipSystemInstanciated = 0;
|
|
uint64 PSStatsClipSystemNotInstanciated = 0;
|
|
uint64 PSStatsClipSystemCheckAgainstPyramid = 0;
|
|
uint64 PSStatsInsertInVisibleList = 0;
|
|
uint64 PSStatsCheckDestroyCondition = 0;
|
|
uint64 PSStatsForceInstanciate = 0;
|
|
uint64 PSStatsTraverseAnimDetailPart1 = 0;
|
|
uint64 PSStatsTraverseAnimDetailPart2 = 0;
|
|
uint64 PSStatsTraverseAnimDetailPart3 = 0;
|
|
uint64 PSStatsTraverseAnimDetailPart4 = 0;
|
|
//
|
|
uint64 PSAnim1 = 0;
|
|
uint64 PSAnim2 = 0;
|
|
uint64 PSAnim3 = 0;
|
|
uint64 PSAnim4 = 0;
|
|
uint64 PSAnim5 = 0;
|
|
uint64 PSAnim6 = 0;
|
|
uint64 PSAnim7 = 0;
|
|
uint64 PSAnim8 = 0;
|
|
uint64 PSAnim9 = 0;
|
|
uint64 PSAnim10 = 0;
|
|
uint64 PSAnim11 = 0;
|
|
//
|
|
uint PSStatsNumDoAnimateCalls = 0;
|
|
float PSMaxET = 0.f;
|
|
uint PSMaxNBPass = 0;
|
|
//
|
|
uint64 PSStatsZonePlane = 0;
|
|
uint64 PSStatsZoneSphere = 0;
|
|
uint64 PSStatsZoneDisc = 0;
|
|
uint64 PSStatsZoneRectangle = 0;
|
|
uint64 PSStatsZoneCylinder = 0;
|
|
//
|
|
uint64 PSMotion1 = 0;
|
|
uint64 PSMotion2 = 0;
|
|
uint64 PSMotion3 = 0;
|
|
uint64 PSMotion4 = 0;
|
|
uint64 PSStatCollision = 0;
|
|
uint64 PSStatEmit = 0;
|
|
uint64 PSStatRender = 0;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
///=====================================================================================
|
|
/// ctor
|
|
CParticleSystemModel::CParticleSystemModel() : _ParticleSystem(NULL),
|
|
_Scene(NULL),
|
|
_EllapsedTime(0.01f),
|
|
_EllapsedTimeRatio(1.f),
|
|
_AnimType(CParticleSystem::AnimVisible),
|
|
_AutoGetEllapsedTime(true),
|
|
_ToolDisplayEnabled(false),
|
|
_TransparencyStateTouched(true),
|
|
_LightableStateTouched(true),
|
|
_EditionMode(false),
|
|
_Invalidated(false),
|
|
_InsertedInVisibleList(false),
|
|
_InClusterAndVisible(false),
|
|
_EmitterActive(true),
|
|
_SoundActive(true),
|
|
_BypassGlobalUserParam(0),
|
|
_UserColor(CRGBA::White),
|
|
_ZBias(0.f),
|
|
_LastVisibility(CHrcTrav::Show)
|
|
{
|
|
setOpacity(false);
|
|
setTransparency(true);
|
|
IAnimatable::resize(AnimValueLast);
|
|
_TriggerAnimatedValue.Value = true;
|
|
|
|
// AnimDetail behavior: Must be traversed in AnimDetail, even if no channel mixer registered
|
|
CTransform::setIsForceAnimDetail(true);
|
|
|
|
for(uint k = 0; k < MaxPSUserParam; ++k)
|
|
{
|
|
_UserParam[k].Value = 0.f;
|
|
}
|
|
|
|
// RenderFilter: We are a Landscape
|
|
_RenderFilterType= UScene::FilterPS;
|
|
}
|
|
|
|
///=====================================================================================
|
|
void CParticleSystemModel::setEditionMode(bool enable /*= true*/)
|
|
{
|
|
if (enable)
|
|
{
|
|
/// we need to have the system resources instanciated if we want to work with it
|
|
if (!_ParticleSystem)
|
|
{
|
|
nlassert(_Scene);
|
|
nlassert(Shape);
|
|
reallocRsc();
|
|
}
|
|
}
|
|
_EditionMode = enable;
|
|
}
|
|
|
|
///=====================================================================================
|
|
void CParticleSystemModel::registerPSModelObserver(IPSModelObserver *obs)
|
|
{
|
|
MINI_TIMER(PSStatsRegisterPSModelObserver)
|
|
nlassert(!isPSModelObserver(obs)); // this observer has already been registered
|
|
_Observers.push_back(obs);
|
|
}
|
|
|
|
///=====================================================================================
|
|
void CParticleSystemModel::removePSModelObserver(IPSModelObserver *obs)
|
|
{
|
|
MINI_TIMER(PSStatsRemovePSModelObserver);
|
|
nlassert(isPSModelObserver(obs)); // the observer must have been registered
|
|
std::vector<IPSModelObserver *>::iterator it = std::find(_Observers.begin(), _Observers.end(), obs);
|
|
_Observers.erase(it);
|
|
}
|
|
|
|
|
|
///=====================================================================================
|
|
bool CParticleSystemModel::isPSModelObserver(IPSModelObserver *obs)
|
|
{
|
|
return std::find(_Observers.begin(), _Observers.end(), obs) != _Observers.end();
|
|
}
|
|
|
|
|
|
///=====================================================================================
|
|
void CParticleSystemModel::registerBasic()
|
|
{
|
|
// register the model
|
|
CScene::registerModel(ParticleSystemModelId, TransformShapeId, CParticleSystemModel::creator);
|
|
}
|
|
|
|
///=====================================================================================
|
|
void CParticleSystemModel::updateOpacityInfos(void)
|
|
{
|
|
MINI_TIMER(PSStatsUpdateOpacityInfos);
|
|
nlassert(_ParticleSystem);
|
|
if (!_TransparencyStateTouched) return;
|
|
nlassert(_ParticleSystem);
|
|
setOpacity(_ParticleSystem->hasOpaqueObjects() || _ToolDisplayEnabled);
|
|
setTransparency(_ParticleSystem->hasTransparentObjects());
|
|
_TransparencyStateTouched = false;
|
|
}
|
|
|
|
///=====================================================================================
|
|
void CParticleSystemModel::updateLightingInfos(void)
|
|
{
|
|
MINI_TIMER(PSStatsUpdateLightingInfos)
|
|
nlassert(_ParticleSystem);
|
|
if (!_LightableStateTouched) return;
|
|
CTransform::setIsLightable(_ParticleSystem->hasLightableObjects());
|
|
_LightableStateTouched = false;
|
|
}
|
|
|
|
///=====================================================================================
|
|
void CParticleSystemModel::getAABBox(NLMISC::CAABBox &bbox) const
|
|
{
|
|
MINI_TIMER(PSStatsGetAABBox)
|
|
if (_ParticleSystem)
|
|
{
|
|
_ParticleSystem->computeBBox(bbox);
|
|
}
|
|
else
|
|
{
|
|
NLMISC::safe_cast<CParticleSystemShape *>((IShape *) Shape)->getAABBox(bbox);
|
|
}
|
|
}
|
|
|
|
///=====================================================================================
|
|
CParticleSystemModel::~CParticleSystemModel()
|
|
{
|
|
nlassert(_Scene);
|
|
releaseRsc();
|
|
// Auto detach me from skeleton. Must do it here, not in ~CTransform().
|
|
if(_FatherSkeletonModel)
|
|
{
|
|
// detach me from the skeleton.
|
|
// clip and hrc hierarchy is modified.
|
|
_FatherSkeletonModel->detachSkeletonSon(this);
|
|
nlassert(_FatherSkeletonModel==NULL);
|
|
}
|
|
}
|
|
|
|
|
|
///=====================================================================================
|
|
/// Called when the resource (attached system) for this system must be reallocated
|
|
void CParticleSystemModel::reallocRsc()
|
|
{
|
|
//MINI_TIMER(PSStatsReallocRsc)
|
|
nlassert(_ParticleSystem == NULL);
|
|
#ifdef PS_FAST_ALLOC
|
|
CParticleSystemShape *shape = NLMISC::safe_cast<CParticleSystemShape *>((IShape *) Shape);
|
|
if (shape->isShared())
|
|
{
|
|
// there's a single CparticleSystemInstance even if there are several models
|
|
_ParticleSystem = shape->instanciatePS(*_Scene, &shape->Allocator);
|
|
}
|
|
else
|
|
{
|
|
_ParticleSystem = shape->instanciatePS(*_Scene, &_Allocator);
|
|
}
|
|
#else
|
|
_ParticleSystem = NLMISC::safe_cast<CParticleSystemShape *>((IShape *) Shape)->instanciatePS(*_Scene);
|
|
#endif
|
|
nlassert(_ParticleSystem);
|
|
nlassert(_Scene);
|
|
CParticleSystemManager &psmgt = _Scene->getParticleSystemManager();
|
|
_ModelHandle = psmgt.addSystemModel(this);
|
|
_AnimType = _ParticleSystem->getAnimType();
|
|
if (_ParticleSystem->getAnimType() == CParticleSystem::AnimAlways)
|
|
{
|
|
_AnimatedModelHandle = psmgt.addPermanentlyAnimatedSystem(this);
|
|
}
|
|
// touch user params animated value. If the system rsc have been released before, this force to restore them
|
|
for (uint k = 0; k < MaxPSUserParam; ++k)
|
|
{
|
|
touch((uint)CParticleSystemModel::PSParam0 + k, OwnerBit);
|
|
}
|
|
_ParticleSystem->setUserColor(_UserColor);
|
|
//
|
|
if (!_EmitterActive) _ParticleSystem->activateEmitters(false);
|
|
if (!_SoundActive) _ParticleSystem->stopSound();
|
|
//
|
|
if (_ZBias != 0.f) _ParticleSystem->setZBias(_ZBias);
|
|
}
|
|
|
|
///=====================================================================================
|
|
void CParticleSystemModel::releasePSPointer()
|
|
{
|
|
MINI_TIMER(PSStatsReleasePSPointer)
|
|
nlassert(_ParticleSystem != NULL);
|
|
sint numRefs = _ParticleSystem.getNbRef();
|
|
if (numRefs == 1)
|
|
{
|
|
// Backup user params (in animated value) so that they will be restored when the system is recreated
|
|
for (uint k = 0; k < MaxPSUserParam; ++k)
|
|
{
|
|
_UserParam[k].Value = _ParticleSystem->getUserParam(k);
|
|
}
|
|
}
|
|
//
|
|
nlassert(_Scene);
|
|
_Scene->getParticleSystemManager().removeSystemModel(_ModelHandle);
|
|
if (_ParticleSystem->getAnimType() == CParticleSystem::AnimAlways)
|
|
{
|
|
if (_AnimatedModelHandle.Valid)
|
|
{
|
|
_Scene->getParticleSystemManager().removePermanentlyAnimatedSystem(_AnimatedModelHandle);
|
|
}
|
|
}
|
|
//
|
|
_ParticleSystem = NULL; // one less ref with the smart ptr
|
|
#ifdef PS_FAST_ALLOC
|
|
CParticleSystemShape *shape = NLMISC::safe_cast<CParticleSystemShape *>((IShape *) Shape);
|
|
if (shape->isShared())
|
|
{
|
|
if (numRefs == 1)
|
|
{
|
|
// release allocator in the shape
|
|
shape->Allocator.release();
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
///=====================================================================================
|
|
void CParticleSystemModel::refreshRscDeletion(const std::vector<CPlane> &worldFrustumPyramid, const NLMISC::CVector &viewerPos)
|
|
{
|
|
MINI_TIMER(PSStatsRefreshRscDeletion)
|
|
if (_EditionMode) return;
|
|
/** Here we test whether the system has not gone out of scope.
|
|
* Why do we test this here addtionnaly to the clip traversal ?
|
|
* Simply because the clip traversal is not called if the cluster it is inserted in is not parsed.
|
|
* This is not good, because we want to keep few CParticleSystem instance.
|
|
* This method solve that problem. This is called by the particle system manager when each scene has rendered
|
|
*/
|
|
|
|
|
|
nlassert(_ParticleSystem);
|
|
CParticleSystemShape *shape = NLMISC::safe_cast<CParticleSystemShape *>((IShape *) Shape);
|
|
|
|
/* NLMISC::CVector sysPos = getTransformMode() == DirectMatrix ?
|
|
getMatrix().getPos() :
|
|
getPos(); */
|
|
|
|
NLMISC::CVector sysPos = getWorldMatrix().getPos();
|
|
|
|
NLMISC::CVector v = sysPos - viewerPos;
|
|
/// test if not too far
|
|
const float dist2 = v * v;
|
|
|
|
if (dist2 > shape->_MaxViewDist * shape->_MaxViewDist) // too far ?
|
|
{
|
|
releasePSPointer();
|
|
if (shape->_DestroyModelWhenOutOfRange)
|
|
{
|
|
_Invalidated = true;
|
|
}
|
|
return;
|
|
}
|
|
|
|
/// frustum test
|
|
if (shape->_DestroyWhenOutOfFrustum)
|
|
{
|
|
if (checkAgainstPyramid(worldFrustumPyramid) == false)
|
|
{
|
|
if (shape->_DestroyModelWhenOutOfRange)
|
|
{
|
|
_Invalidated = true;
|
|
}
|
|
releasePSPointer();
|
|
return;
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
///=====================================================================================
|
|
void CParticleSystemModel::releaseRsc()
|
|
{
|
|
MINI_TIMER(PSStatsReleaseRsc)
|
|
if (!_ParticleSystem) return;
|
|
releasePSPointer();
|
|
}
|
|
|
|
///=====================================================================================
|
|
void CParticleSystemModel::releaseRscAndInvalidate()
|
|
{
|
|
MINI_TIMER(PSStatsReleaseRscAndInvalidate)
|
|
if (!_ParticleSystem) return;
|
|
releasePSPointer();
|
|
_Invalidated = true;
|
|
|
|
static std::vector<IPSModelObserver *> copyVect;
|
|
copyVect.resize(_Observers.size());
|
|
std::copy(_Observers.begin(), _Observers.end(), copyVect.begin());
|
|
|
|
for (std::vector<IPSModelObserver *>::iterator it = copyVect.begin(); it != copyVect.end(); ++it)
|
|
{
|
|
(*it)->invalidPS(this); // if this crash, then you forgot to call removePSModelObserver !
|
|
}
|
|
#ifdef PS_FAST_ALLOC
|
|
CParticleSystemShape *shape = NLMISC::safe_cast<CParticleSystemShape *>((IShape *) Shape);
|
|
if (!shape->isShared())
|
|
{
|
|
_Allocator.release();
|
|
}
|
|
// else ..
|
|
// if system if shared, the allocator is placed in the shape, so no-op there
|
|
#endif
|
|
}
|
|
|
|
///=====================================================================================
|
|
IAnimatedValue* CParticleSystemModel::getValue (uint valueId)
|
|
{
|
|
nlassert(valueId < AnimValueLast);
|
|
if (valueId < OwnerBit) return CTransformShape::getValue(valueId);
|
|
if (valueId < PSTrigger)
|
|
{
|
|
|
|
return &_UserParam[valueId - (uint) PSParam0];
|
|
}
|
|
return &_TriggerAnimatedValue;
|
|
}
|
|
|
|
///=====================================================================================
|
|
const char *CParticleSystemModel::getPSParamName (uint valueId)
|
|
{
|
|
nlassert(valueId < AnimValueLast);
|
|
const char *name[] = { "PSParam0", "PSParam1", "PSParam2", "PSParam3" };
|
|
return name[valueId - (uint) PSParam0];
|
|
}
|
|
|
|
///=====================================================================================
|
|
const char *CParticleSystemModel::getValueName (uint valueId) const
|
|
{
|
|
nlassert(valueId < AnimValueLast);
|
|
if (valueId < OwnerBit) return CTransformShape::getValueName(valueId);
|
|
if (valueId < PSTrigger) return getPSParamName(valueId);
|
|
return "PSTrigger";
|
|
}
|
|
|
|
///=====================================================================================
|
|
ITrack* CParticleSystemModel::getDefaultTrack (uint valueId)
|
|
{
|
|
nlassert(valueId < AnimValueLast);
|
|
nlassert(Shape);
|
|
|
|
CParticleSystemShape *pss = NLMISC::safe_cast<CParticleSystemShape *>((IShape *) Shape);
|
|
|
|
switch (valueId)
|
|
{
|
|
case PosValue: return pss->getDefaultPos();
|
|
case RotQuatValue: return pss->getDefaultRotQuat();
|
|
case ScaleValue: return pss->getDefaultScale();
|
|
}
|
|
if (valueId < OwnerBit) return CTransformShape::getDefaultTrack(valueId); // delegate to parent
|
|
|
|
// this value belong to us
|
|
if (valueId < PSTrigger)
|
|
{
|
|
return pss->getUserParamDefaultTrack(valueId - (uint) PSParam0);
|
|
}
|
|
return pss->getDefaultTriggerTrack();
|
|
}
|
|
|
|
///=====================================================================================
|
|
void CParticleSystemModel::registerToChannelMixer(CChannelMixer *chanMixer, const std::string &prefix /* =std::string() */)
|
|
{
|
|
CTransformShape::registerToChannelMixer(chanMixer, prefix);
|
|
addValue(chanMixer, PSParam0, OwnerBit, prefix, true);
|
|
addValue(chanMixer, PSParam1, OwnerBit, prefix, true);
|
|
addValue(chanMixer, PSParam2, OwnerBit, prefix, true);
|
|
addValue(chanMixer, PSParam3, OwnerBit, prefix, true);
|
|
addValue(chanMixer, PSTrigger, OwnerBit, prefix, true);
|
|
}
|
|
|
|
|
|
///=====================================================================================
|
|
float CParticleSystemModel::getNumTriangles (float distance)
|
|
{
|
|
MINI_TIMER(PSStatsGetNumTriangles)
|
|
if (!_ParticleSystem) return 0;
|
|
if (!_InsertedInVisibleList) return 0;
|
|
return (float) _ParticleSystem->getWantedNumTris(distance);
|
|
}
|
|
|
|
///=========================================================================================
|
|
bool CParticleSystemModel::checkAgainstPyramid(const std::vector<CPlane> &pyramid) const
|
|
{
|
|
MINI_TIMER(PSStatsCheckAgainstPyramid)
|
|
nlassert(_ParticleSystem);
|
|
NLMISC::CAABBox bbox;
|
|
_ParticleSystem->computeBBox(bbox);
|
|
const CMatrix &mat = getWorldMatrix();
|
|
|
|
// Transform the pyramid in Object space.
|
|
for(sint i=0; i < (sint) pyramid.size(); i++)
|
|
{
|
|
// test whether the bbox is entirely in the neg side of the plane
|
|
if (!bbox.clipBack(pyramid[i] * mat ))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
|
|
}
|
|
|
|
//////////////////////////////////////////////
|
|
// CParticleSystem AnimDetail implementation //
|
|
//////////////////////////////////////////////
|
|
|
|
///=====================================================================================
|
|
void CParticleSystemModel::traverseAnimDetail()
|
|
{
|
|
MINI_TIMER(PSStatsTraverseAnimDetail)
|
|
CTransformShape::traverseAnimDetail();
|
|
CParticleSystem *ps = getPS();
|
|
if (!_WorldVis) return;
|
|
if (_Invalidated) return;
|
|
if (getVisibility() == CHrcTrav::Hide) return;
|
|
|
|
{
|
|
MINI_TIMER(PSStatsTraverseAnimDetailPart1)
|
|
if (!_EditionMode && !_InClusterAndVisible)
|
|
{
|
|
CParticleSystemShape *pss = NLMISC::safe_cast<CParticleSystemShape *>((IShape *)Shape);
|
|
if (pss->_DestroyWhenOutOfFrustum)
|
|
{
|
|
if (pss->_DestroyModelWhenOutOfRange)
|
|
{
|
|
releaseRscAndInvalidate();
|
|
}
|
|
else // remove rsc but do not invalidate the system
|
|
{
|
|
releaseRsc();
|
|
}
|
|
return;
|
|
}
|
|
if (!ps) return;
|
|
}
|
|
|
|
// check for trigger. If the trigger is false, and there is a system instanciated, we delete it.
|
|
if (!_EditionMode)
|
|
{
|
|
if (!_TriggerAnimatedValue.Value)
|
|
{
|
|
// system is off, or hasn't been instanciated now...
|
|
if (ps)
|
|
{
|
|
releaseRsc();
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
{
|
|
MINI_TIMER(PSStatsTraverseAnimDetailPart2)
|
|
|
|
// the system or its center is in the view frustum, but it may not have been instanciated from its shape now
|
|
if (!ps)
|
|
{
|
|
nlassert(_Scene);
|
|
nlassert(Shape);
|
|
reallocRsc();
|
|
ps = _ParticleSystem;
|
|
}
|
|
}
|
|
|
|
CClipTrav &clipTrav= getOwnerScene()->getClipTrav();
|
|
|
|
{
|
|
MINI_TIMER(PSStatsTraverseAnimDetailPart3)
|
|
if (_InClusterAndVisible || ps->getAnimType() == CParticleSystem::AnimInCluster)
|
|
{
|
|
bool animate = true;
|
|
if (ps->isSharingEnabled()) /// with shared system, we only animate one version!
|
|
{
|
|
if (ps->_LastUpdateDate == clipTrav.CurrentDate)
|
|
{
|
|
animate = false;
|
|
}
|
|
else
|
|
{
|
|
ps->_LastUpdateDate = clipTrav.CurrentDate;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ps->_LastUpdateDate = clipTrav.CurrentDate;
|
|
}
|
|
ps->_LastUpdateDate = clipTrav.CurrentDate;
|
|
if (animate)
|
|
{
|
|
if (ps->getAnimType() != CParticleSystem::AnimAlways) // if the animation is always perfomred,
|
|
// then animation is done by the particle system manager
|
|
// just before the render trav
|
|
{
|
|
doAnimate();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
{
|
|
MINI_TIMER(PSStatsTraverseAnimDetailPart4)
|
|
// add a render model if in cluster & not hidden
|
|
if (_InClusterAndVisible)
|
|
{
|
|
getOwnerScene()->getRenderTrav().addRenderModel(this);
|
|
}
|
|
}
|
|
}
|
|
|
|
///=====================================================================================
|
|
void CParticleSystemModel::doAnimate()
|
|
{
|
|
++ PSStatsNumDoAnimateCalls;
|
|
MINI_TIMER(PSStatsDoAnimate)
|
|
nlassert(!_Invalidated);
|
|
CParticleSystem *ps = getPS();
|
|
CClipTrav &clipTrav= getOwnerScene()->getClipTrav();
|
|
const CMatrix &mat= getWorldMatrix();
|
|
//
|
|
{
|
|
MINI_TIMER(PSStatsDoAnimatePart1)
|
|
|
|
// Set the 'hide' flag. This prevent trails from being created is the system is hidden, moved, and then showed in the next frame.
|
|
ps->hide(!this->isHrcVisible());
|
|
ps->setSysMat(&mat);
|
|
ps->setUserMatrix(&_UserMatrix);
|
|
ps->setViewMat(clipTrav.ViewMatrix);
|
|
updateOpacityInfos();
|
|
updateLightingInfos();
|
|
}
|
|
//ps->setSysMat(getWorldMatrix());
|
|
nlassert(ps->getScene());
|
|
|
|
|
|
{
|
|
MINI_TIMER(PSStatsDoAnimatePart2)
|
|
|
|
// setup the number of faces we allow
|
|
ps->setNumTris((uint) getNumTrianglesAfterLoadBalancing());
|
|
|
|
|
|
// set the global user param that are bypassed
|
|
nlctassert(MaxPSUserParam < 8); // there should be less than 8 parameters because of mask stored in a byte
|
|
ps->_BypassGlobalUserParam = _BypassGlobalUserParam;
|
|
|
|
// setup system user parameters for parameters that have been touched
|
|
for (uint k = 0; k < MaxPSUserParam; ++k)
|
|
{
|
|
if (isTouched((uint)CParticleSystemModel::PSParam0 + k))
|
|
{
|
|
ps->setUserParam(k, _UserParam[k].Value);
|
|
clearFlag((uint)CParticleSystemModel::PSParam0 + k);
|
|
}
|
|
}
|
|
if (isAutoGetEllapsedTimeEnabled())
|
|
{
|
|
setEllapsedTime(ps->getScene()->getEllapsedTime() * getEllapsedTimeRatio());
|
|
}
|
|
}
|
|
{
|
|
MINI_TIMER(PSStatsDoAnimatePart3)
|
|
TAnimationTime delay = getEllapsedTime();
|
|
// animate particles
|
|
CParticleSystemShape *pss= NLMISC::safe_cast<CParticleSystemShape *>((IShape *)Shape);
|
|
if (_EditionMode)
|
|
{
|
|
pss->_ProcessOrder.clear(); // force to eval each frame because ps could be modified
|
|
}
|
|
ps->step(CParticleSystem::Anim, delay, *pss, *this);
|
|
}
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////
|
|
// CParticleSystem Render implementation //
|
|
//////////////////////////////////////////////
|
|
void CParticleSystemModel::traverseRender()
|
|
{
|
|
MINI_TIMER(PSStatsTraverseRender)
|
|
/*
|
|
if (!_OutOfFrustum)
|
|
{*/
|
|
if (_ParticleSystem)
|
|
{
|
|
if (CTransform::isLightable())
|
|
{
|
|
// affect global lighting color
|
|
const CLightContribution &lc = getLightContribution();
|
|
NLMISC::CRGBA lighting(0, 0, 0, 255);
|
|
for(uint k = 0; k < NL3D_MAX_LIGHT_CONTRIBUTION; ++k)
|
|
{
|
|
if (lc.PointLight[k] == NULL) break;
|
|
NLMISC::CRGBA currLightContrib;
|
|
currLightContrib.modulateFromui(lc.PointLight[k]->getDiffuse(), lc.AttFactor[k]);
|
|
lighting.add(lighting, currLightContrib);
|
|
}
|
|
// add local ambient
|
|
//lighting.add(lighting, lc.LocalAmbient);
|
|
//lighting.add(lighting,lc.MergedPointLight);
|
|
// add sun diffuse
|
|
nlassert(_Scene);
|
|
NLMISC::CRGBA sunDiffuse;
|
|
sunDiffuse.modulateFromui(_Scene->getSunDiffuse(), lc.SunContribution);
|
|
lighting.add(lighting, sunDiffuse);
|
|
NLMISC::CRGBA sunAmbient;
|
|
sunAmbient.modulateFromui(_Scene->getSunAmbient(), lc.SunContribution);
|
|
lighting.add(lighting, sunAmbient);
|
|
_ParticleSystem->setLightingColor(lighting);
|
|
}
|
|
CTransformShape::traverseRender();
|
|
}
|
|
//}
|
|
}
|
|
|
|
|
|
/*
|
|
* CParticleSystem Clip implementation
|
|
* IMPORTANT : the _Visible attribute is interpreted as 'in traversed clusters'. We need this because we want
|
|
* to know when a p.s is in clusters, but not visible. As a matter of fact we may need to have system that are animated
|
|
* as long as in cluster, but not visible.
|
|
*/
|
|
|
|
void CParticleSystemModel::traverseClip()
|
|
{
|
|
MINI_TIMER(PSStatsTraverseClip)
|
|
// disable H_AUTO, because slowdown when lot of models (eg 1000-2000 tested in forest)
|
|
//H_AUTO ( NL3D_Particles_Clip );
|
|
|
|
// CTransformShape::traverseClip();
|
|
// Traverse the Clip sons.
|
|
uint numClipChildren= clipGetNumChildren();
|
|
for(uint i=0;i<numClipChildren;i++)
|
|
clipGetChild(i)->traverseClip();
|
|
|
|
if (!_WorldVis) return;
|
|
if (_Invalidated) return;
|
|
CClipTrav &clipTrav= getOwnerScene()->getClipTrav();
|
|
|
|
|
|
if (_ClipDate != clipTrav.CurrentDate)
|
|
{
|
|
_InsertedInVisibleList = false;
|
|
_InClusterAndVisible = false;
|
|
_ClipDate = clipTrav.CurrentDate;
|
|
}
|
|
if (_InClusterAndVisible) return; // already visible
|
|
|
|
|
|
CParticleSystem *ps = _ParticleSystem;
|
|
|
|
|
|
if (ps) // system instanciated
|
|
{
|
|
MINI_TIMER(PSStatsClipSystemInstanciated)
|
|
// if there are no more particles, no need to even clip..
|
|
if (checkDestroyCondition(ps)) return;
|
|
// check for anim mode change
|
|
if (_AnimType != ps->getAnimType())
|
|
{
|
|
CParticleSystemManager &psmgt = _Scene->getParticleSystemManager();
|
|
if (_AnimType == CParticleSystem::AnimAlways) // was previously always animated ?
|
|
{
|
|
if (_AnimatedModelHandle.Valid)
|
|
{
|
|
psmgt.removePermanentlyAnimatedSystem(_AnimatedModelHandle);
|
|
}
|
|
}
|
|
_AnimType = ps->getAnimType();
|
|
if (_AnimType == CParticleSystem::AnimAlways)
|
|
{
|
|
_AnimatedModelHandle = psmgt.addPermanentlyAnimatedSystem(this);
|
|
}
|
|
}
|
|
}
|
|
|
|
// check whether display filtered or not
|
|
if( !(_Scene->getFilterRenderFlags() & _RenderFilterType) )
|
|
{
|
|
_Visible = false;
|
|
return;
|
|
}
|
|
|
|
// special case : system sticked to a skeleton
|
|
if( _AncestorSkeletonModel!=NULL )
|
|
{
|
|
bool visible = _AncestorSkeletonModel->isClipVisible();
|
|
// Special test: if we are sticked to a skeletonModel, and if we are still visible, maybe we don't have to
|
|
if(_Visible && _FatherSkeletonModel)
|
|
{
|
|
// if our skeletonModel father is displayed with a Lod, maybe we are not to be displayed
|
|
if(_FatherSkeletonModel->isDisplayedAsLodCharacter())
|
|
{
|
|
// We are visible only if we where sticked to the skeleton with forceCLod==true.
|
|
// This is also true if we are actually a skeletonModel
|
|
if(!getShowWhenLODSticked())
|
|
// otherWise we are not visible. eg: this is the case of skins and some sticked object
|
|
visible = false;
|
|
}
|
|
}
|
|
//
|
|
if (visible)
|
|
{
|
|
{
|
|
MINI_TIMER(PSStatsInsertInVisibleList)
|
|
insertInVisibleList();
|
|
}
|
|
_InClusterAndVisible = true;
|
|
return;
|
|
}
|
|
else // not visible, may need animation however..
|
|
{
|
|
if (!ps) // no resc allocated
|
|
{
|
|
CParticleSystemShape *pss= NLMISC::safe_cast<CParticleSystemShape *>((IShape *)Shape);
|
|
nlassert(pss);
|
|
// invalidate the system if too far
|
|
const CVector pos = _AncestorSkeletonModel->getWorldMatrix().getPos();
|
|
const CVector d = pos - clipTrav.CamPos;
|
|
if (d * d > pss->_MaxViewDist * pss->_MaxViewDist)
|
|
{
|
|
_Visible = false;
|
|
if (pss->_DestroyModelWhenOutOfRange)
|
|
{
|
|
_Invalidated = true;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// NB : The test to see whether the system is not too far is performed by the particle system manager
|
|
if (!_EditionMode)
|
|
{
|
|
{
|
|
MINI_TIMER(PSStatsInsertInVisibleList)
|
|
insertInVisibleList();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
|
|
//
|
|
const std::vector<CPlane> &pyramid= clipTrav.WorldPyramid;
|
|
/** traverse the sons
|
|
* we must do this before us, because this object may delete himself from the scene
|
|
*/
|
|
|
|
// now the pyramid is directly expressed in the world
|
|
const CMatrix &mat= getWorldMatrix();
|
|
|
|
|
|
// Transform the pyramid in Object space.
|
|
|
|
|
|
if(!ps) ///====================== system resource not allocated, test if it entered the scope
|
|
{
|
|
MINI_TIMER(PSStatsClipSystemNotInstanciated)
|
|
CParticleSystemShape *pss= NLMISC::safe_cast<CParticleSystemShape *>((IShape *)Shape);
|
|
nlassert(pss);
|
|
|
|
// the system wasn't present the last time, we use its center to see if it's back in the view frustum,
|
|
// or if it is near enough.
|
|
// if this is the case, we say it isn't clipped, so it will be reinstanciated from the shape
|
|
// during the DetailAnimTraversal
|
|
|
|
const CVector pos = getWorldMatrix().getPos();
|
|
|
|
const CVector d = pos - clipTrav.CamPos;
|
|
|
|
|
|
// check whether system not too far
|
|
if (d * d > pss->_MaxViewDist * pss->_MaxViewDist)
|
|
{
|
|
_Visible = false;
|
|
if (pss->_DestroyModelWhenOutOfRange)
|
|
{
|
|
_Invalidated = true;
|
|
}
|
|
return;
|
|
}
|
|
|
|
// test the shape to see whether we have a precomputed bbox
|
|
if (!pss->_UsePrecomputedBBox)
|
|
{
|
|
///============================= the system has no precomputed bbox
|
|
/// frustum test
|
|
for(sint i=0; i < (sint)pyramid.size(); i++)
|
|
{
|
|
if ( (pyramid[i] * mat ).d > 0.0f ) // in its basis, the system is at the center
|
|
|
|
{
|
|
{
|
|
MINI_TIMER(PSStatsInsertInVisibleList)
|
|
insertInVisibleList();
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
{
|
|
MINI_TIMER(PSStatsInsertInVisibleList)
|
|
insertInVisibleList();
|
|
}
|
|
_InClusterAndVisible = true;
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
///============================= the system has a precomputed bbox
|
|
/// frustum test
|
|
for(sint i=0; i < (sint)pyramid.size(); i++)
|
|
{
|
|
if ( !pss->_PrecomputedBBox.clipBack(pyramid[i] * mat ) )
|
|
{
|
|
{
|
|
MINI_TIMER(PSStatsInsertInVisibleList)
|
|
insertInVisibleList();
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
{
|
|
MINI_TIMER(PSStatsInsertInVisibleList)
|
|
insertInVisibleList();
|
|
}
|
|
_InClusterAndVisible = true;
|
|
return;
|
|
|
|
}
|
|
}
|
|
|
|
//=========================================================================================================
|
|
// the system is already instanciated
|
|
|
|
nlassert(ps);
|
|
/// Pyramid test. IMPORTANT : The test to see whether the system is not too far is performed by the particle system manager
|
|
// In edition mode, it isn't done by the manager (system never removed), so we do it here in this case
|
|
if (_EditionMode)
|
|
{
|
|
CParticleSystemShape *pss= NLMISC::safe_cast<CParticleSystemShape *>((IShape *)Shape);
|
|
nlassert(pss);
|
|
const CVector pos = getWorldMatrix().getPos();
|
|
const CVector d = pos - clipTrav.CamPos;
|
|
// check whether system not too far
|
|
if (d * d > ps->getMaxViewDist() * ps->getMaxViewDist())
|
|
{
|
|
return; // not visible
|
|
}
|
|
}
|
|
if (checkAgainstPyramid(pyramid) == false)
|
|
{
|
|
MINI_TIMER(PSStatsClipSystemCheckAgainstPyramid)
|
|
if (!_EditionMode)
|
|
{
|
|
// system near, but maybe not in cluster..
|
|
{
|
|
MINI_TIMER(PSStatsInsertInVisibleList)
|
|
insertInVisibleList();
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
{
|
|
{
|
|
MINI_TIMER(PSStatsInsertInVisibleList)
|
|
insertInVisibleList();
|
|
}
|
|
}
|
|
_InClusterAndVisible = true;
|
|
}
|
|
|
|
|
|
//===================================================================
|
|
bool CParticleSystemModel::clip()
|
|
{
|
|
// no-op clip() because all done in special traverse()
|
|
return true;
|
|
}
|
|
|
|
|
|
//===================================================================
|
|
bool CParticleSystemModel::checkDestroyCondition(CParticleSystem *ps)
|
|
{
|
|
MINI_TIMER(PSStatsCheckDestroyCondition)
|
|
nlassert(ps);
|
|
if (!_EditionMode)
|
|
{
|
|
/** NB : we don't do this test here for always animated system, as it is done
|
|
* by the CParticleSystemManager, because this code is not sure to be executed if the system has been clipped by a cluster
|
|
*/
|
|
if (ps->getAnimType() != CParticleSystem::AnimAlways)
|
|
{
|
|
if (ps->isDestroyConditionVerified())
|
|
{
|
|
releaseRscAndInvalidate();
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//===================================================================
|
|
void CParticleSystemModel::bypassGlobalUserParamValue(uint userParamIndex,bool byPass /*=true*/)
|
|
{
|
|
nlctassert(MaxPSUserParam < 8); // there should be less than 8 parameters because of mask stored in a byte
|
|
nlassert(userParamIndex < MaxPSUserParam);
|
|
if (byPass) _BypassGlobalUserParam |= (1 << userParamIndex);
|
|
else _BypassGlobalUserParam &= ~(1 << userParamIndex);
|
|
}
|
|
|
|
//===================================================================
|
|
bool CParticleSystemModel::isGlobalUserParamValueBypassed(uint userParamIndex) const
|
|
{
|
|
nlctassert(MaxPSUserParam < 8); // there should be less than 8 parameters because of mask stored in a byte
|
|
nlassert(userParamIndex < MaxPSUserParam);
|
|
return (_BypassGlobalUserParam & (1 << userParamIndex)) != 0;
|
|
}
|
|
|
|
//===================================================================
|
|
void CParticleSystemModel::enableDisplayTools(bool enable /*=true*/)
|
|
{
|
|
_ToolDisplayEnabled = enable;
|
|
touchTransparencyState();
|
|
touchLightableState();
|
|
}
|
|
|
|
//===================================================================
|
|
void CParticleSystemModel::invalidateAutoAnimatedHandle()
|
|
{
|
|
_AnimatedModelHandle.Valid = false;
|
|
}
|
|
|
|
//===================================================================
|
|
void CParticleSystemModel::activateEmitters(bool active)
|
|
{
|
|
if (active == _EmitterActive) return;
|
|
_EmitterActive = active;
|
|
if (_ParticleSystem) _ParticleSystem->activateEmitters(active);
|
|
}
|
|
|
|
//===================================================================
|
|
bool CParticleSystemModel::hasActiveEmitters() const
|
|
{
|
|
#ifdef NL_DEBUG
|
|
if (_ParticleSystem)
|
|
{
|
|
if (_ParticleSystem->hasEmittersTemplates())
|
|
{
|
|
nlassert(_ParticleSystem->hasActiveEmitters() == _EmitterActive);
|
|
}
|
|
}
|
|
#endif
|
|
return _EmitterActive;
|
|
}
|
|
|
|
//===================================================================
|
|
void CParticleSystemModel::setUserColor(NLMISC::CRGBA userColor)
|
|
{
|
|
if (_ParticleSystem) _ParticleSystem->setUserColor(userColor);
|
|
_UserColor = userColor;
|
|
}
|
|
|
|
//===================================================================
|
|
void CParticleSystemModel::forceInstanciate()
|
|
{
|
|
MINI_TIMER(PSStatsForceInstanciate)
|
|
if (_Invalidated) return;
|
|
if (_ParticleSystem) return;
|
|
reallocRsc();
|
|
}
|
|
|
|
//===================================================================
|
|
void CParticleSystemModel::setZBias(float value)
|
|
{
|
|
if (value == _ZBias) return;
|
|
_ZBias = value;
|
|
if (_ParticleSystem) _ParticleSystem->setZBias(_ZBias);
|
|
}
|
|
|
|
//===================================================================
|
|
void CParticleSystemModel::forceSetUserMatrix(const NLMISC::CMatrix &userMatrix)
|
|
{
|
|
_UserMatrix = userMatrix;
|
|
if (getPS())
|
|
{
|
|
getPS()->setUserMatrix(&_UserMatrix);
|
|
}
|
|
}
|
|
|
|
//===================================================================
|
|
void CParticleSystemModel::stopSound()
|
|
{
|
|
if (!_SoundActive) return;
|
|
if (_ParticleSystem) _ParticleSystem->stopSound();
|
|
_SoundActive = false;
|
|
}
|
|
|
|
//===================================================================
|
|
void CParticleSystemModel::reactivateSound()
|
|
{
|
|
if (_SoundActive) return;
|
|
if (_ParticleSystem) _ParticleSystem->reactivateSound();
|
|
_SoundActive = true;
|
|
}
|
|
|
|
//===================================================================
|
|
void CParticleSystemModel::update()
|
|
{
|
|
CTransformShape::update();
|
|
if (_LocalVis != _LastVisibility)
|
|
{
|
|
if (_ParticleSystem)
|
|
{
|
|
_ParticleSystem->onShow(_LocalVis == CHrcTrav::Show);
|
|
}
|
|
_LastVisibility = _LocalVis;
|
|
}
|
|
}
|
|
|
|
|
|
} // NL3D
|