// NeL - MMORPG Framework // Copyright (C) 2010 Winch Gate Property Limited // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as // published by the Free Software Foundation, either version 3 of the // License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . #include "std3d.h" #include "nel/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::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((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((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((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((IShape *) Shape); if (shape->isShared()) { if (numRefs == 1) { // release allocator in the shape shape->Allocator.release(); } } #endif } ///===================================================================================== void CParticleSystemModel::refreshRscDeletion(const std::vector &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((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 copyVect; copyVect.resize(_Observers.size()); std::copy(_Observers.begin(), _Observers.end(), copyVect.begin()); for (std::vector::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((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((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 &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((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((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;itraverseClip(); 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((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 &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((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((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