// Ryzom - MMORPG Framework // Copyright (C) 2010 Winch Gate Property Limited // // This source file has been modified by the following contributors: // Copyright (C) 2014-2020 Jan BOON (Kaetemi) // // 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 "stdpch.h" #include "timed_fx_manager.h" #include "ig_callback.h" #include "client_sheets/plant_sheet.h" #include "nel/3d/u_transform.h" #include "nel/3d/u_particle_system_instance.h" #include "nel/3d/u_driver.h" #include "nel/3d/u_text_context.h" #include "nel/3d/u_scene.h" #include "nel/3d/u_camera.h" #include "nel/misc/fast_floor.h" #include "misc.h" #include "fx_manager.h" #include "nel/misc/check_fpu.h" #ifdef DEBUG_NEW #define new DEBUG_NEW #endif using namespace std::rel_ops; using namespace NLMISC; extern NL3D::UTextContext *TextContext; extern NL3D::UDriver *Driver; extern NL3D::UScene *Scene; //#define DEBUG_FX #ifdef DEBUG_FX #ifndef NL_DEBUG #error Flag DEBUG_FX can only be used in debug mode #endif #endif #ifdef DEBUG_FX #define CHECK_INTEGRITY checkIntegrity(); #else #define CHECK_INTEGRITY #endif CTimedFXManager &CTimedFXManager::getInstance() { FPU_CHECKER static CTimedFXManager manager; return manager; } const float DEFAULT_SPAWNED_FX_TIMED_OUT = 100.f; H_AUTO_DECL(RZ_TimedFX) // ******************************************************************************************* NLMISC::CMatrix CTimedFX::getInstanceMatrix() const { FPU_CHECKER H_AUTO_USE(RZ_TimedFX) NLMISC::CMatrix result; result.identity(); result.translate(SpawnPosition); if (FXSheet && FXSheet->InheritRot) result.rotate(Rot); if (FXSheet && FXSheet->InheritScale) result.scale(Scale); return result; } // ******************************************************************************************* CTimedFXManager::CTimedFXManager() { FPU_CHECKER H_AUTO_USE(RZ_TimedFX) _Scene = NULL, _DayLength = 24.f, _InitDone = false; _InstanciatedFXs = NULL; _CandidateFXListTouched = false; _SortDistance = 0.f; _MaxNumberOfFXInstances = 0; } // ******************************************************************************************* CTimedFXManager::~CTimedFXManager() { FPU_CHECKER H_AUTO_USE(RZ_TimedFX) reset(); } // ******************************************************************************************* void CTimedFXManager::init(NL3D::UScene *scene, const CClientDate &startDate, float /* dayLength */, float noiseFrequency, uint maxNumberOfFXInstances, float sortDistanceInterval, float maxDist) { FPU_CHECKER H_AUTO_USE(RZ_TimedFX) nlassert(!_InitDone); if (!scene) { nlwarning("Scene is NULL"); } _Scene = scene; _CurrDate = startDate; _Noise.Frequency = noiseFrequency; _Noise.Abs = 0.f; _Noise.Rand = 1.f; CHECK_INTEGRITY _InitDone = true; nlassert(maxDist > 0.f); nlassert(sortDistanceInterval > 0.f); _CandidateFXListSortedByDist.resize((int) ceilf(maxDist / sortDistanceInterval)); _CandidateFXListSortedByDistTmp.resize((int) ceilf(maxDist / sortDistanceInterval)); _MaxNumberOfFXInstances = maxNumberOfFXInstances; _SortDistance = sortDistanceInterval; } // ******************************************************************************************* void CTimedFXManager::reset() { FPU_CHECKER H_AUTO_USE(RZ_TimedFX) if (_InitDone) { nlassert(_InitDone); CHECK_INTEGRITY while (!_FXGroups.empty()) { remove(_FXGroups.begin()); } _Scene = NULL; _CurrDate = CClientDate(); _InitDone = false; _FXManager.reset(); } } // ******************************************************************************************* void CTimedFXManager::setDate(const CClientDate &date) { FPU_CHECKER H_AUTO_USE(RZ_TimedFX) _CurrDate = date; } // ******************************************************************************************* CTimedFXManager::TFXGroupHandle CTimedFXManager::add(const std::vector &fxs, EGSPD::CSeason::TSeason /* season */) { FPU_CHECKER H_AUTO_USE(RZ_TimedFX) nlassert(_InitDone); CHECK_INTEGRITY _FXGroups.push_front(CManagedFXGroup()); #ifdef DEBUG_FX _FXGroups.front().Season = season; #endif TManagedFXGroup &newGroup = _FXGroups.begin()->Group; newGroup.resize(fxs.size()); for(uint k = 0; k < fxs.size(); ++k) { CManagedFX &fi = newGroup[k]; fi.FXSheet = fxs[k].FXSheet; fi.SpawnPosition = fxs[k].SpawnPosition; fi.Scale = fxs[k].Scale; fi.Rot = fxs[k].Rot; fi.Instance = NULL; #if !FINAL_VERSION fi.FromIG = fxs[k].FromIG; #endif #ifdef DEBUG_FX fi.OwnerGroup = &(_FXGroups.front()); #endif // compute start and end hour to see in which list insertion should occurs bool instanciate = false; CClientDate startDate; CClientDate endDate; //sint32 debugDay; if (!(fi.FXSheet && fi.FXSheet->Mode == CSeasonFXSheet::AlwaysStarted)) { if (fi.FXSheet && fi.FXSheet->Mode == CSeasonFXSheet::Spawn) { // compute next spawn date float cycleLength = fi.FXSheet ? fi.FXSheet->CycleDuration : _DayLength; sint32 cycle = dateToCycle(_CurrDate, cycleLength, _DayLength); fi.computeStartHour(cycle - 1, startDate, cycleLength, _DayLength, _Noise); if (startDate < _CurrDate) { fi.computeStartHour(cycle, startDate, cycleLength, _DayLength, _Noise); if (startDate < _CurrDate) { fi.computeStartHour(cycle + 1, startDate, cycleLength, _DayLength, _Noise); } } } else { // the system is not always instanciated, so must check if it currently is. float cycleLength = fi.FXSheet ? fi.FXSheet->CycleDuration : _DayLength; sint32 cycle = dateToCycle(_CurrDate, cycleLength, _DayLength); fi.computeStartHour(cycle, startDate, cycleLength, _DayLength, _Noise); //debugDay = _CurrDate.Day; if (startDate > _CurrDate) { // Let see if it did not start the previous cycle and then ended that cycle fi.computeEndHour(cycle - 1, endDate, cycleLength, _DayLength, _Noise); if (endDate > _CurrDate) { CClientDate prevStartDate; fi.computeStartHour(cycle - 1, prevStartDate, cycleLength, _DayLength, _Noise); if (prevStartDate <= _CurrDate) { instanciate = true; } else { startDate = prevStartDate; //debugDay = _CurrDate.Day - 1; } } } else { fi.computeEndHour(cycle, endDate, cycleLength, _DayLength, _Noise); if (endDate > _CurrDate) { // the fx is currently running instanciate = true; } else { fi.computeStartHour(_CurrDate.Day + 1, endDate, cycleLength, _DayLength, _Noise); // the fx will start only during the next day //debugDay = _CurrDate.Day + 1; } } } } else { if (fi.FXSheet && fi.FXSheet->Mode == CSeasonFXSheet::AlwaysStarted) { fi.State = CManagedFX::Permanent; instanciate = true; } } if (instanciate) { if (!_Scene || !fi.FXSheet) { fi.Instance = NULL; } else { // insert in candidate fx list float dist = (fi.SpawnPosition - _LastCamPos).norm(); linkCandidateFX(_CandidateFXListSortedByDist, dist, &fi); _CandidateFXListTouched = true; } // Add the system to the queue of systems to be removed, only if it is not always started if (!(fi.FXSheet && fi.FXSheet->Mode == CSeasonFXSheet::AlwaysStarted)) { // As the system is currenlty instanciated, we won't deals with it again until it shutdowns, so, we save it in the priority queue. CTimeStampedFX tsf; tsf.Date = endDate; //tsf.DebugDay = 666; tsf.FX = &fi; // yes, we keep a pointer on a vector element, but we will never grow that vector fi.SetHandle = _FXToRemove.insert(tsf).first; fi.State = CManagedFX::InRemoveList; } } else { // The system is not instanciated yet, but will be later, so add to the queue of systems that "will be instanciated later" CTimeStampedFX tsf; tsf.Date = startDate; tsf.FX = &fi; // yes, we keep a pointer on a vector element, but we will never grow that vector //tsf.DebugDay = debugDay; nlassert(fi.State != CManagedFX::InAddList); fi.SetHandle = _FXToAdd.insert(tsf).first; fi.State = CManagedFX::InAddList; #ifdef DEBUG_FX nlinfo("==== added fx %s, start day = %day, start hour = %f, pos=(%f, %f, %f)", fi.FXSheet ? fi.FXSheet->FXName.c_str() : "???", (int) startDate.Day, startDate.Hour, fi.SpawnPosition.x, fi.SpawnPosition.y, fi.SpawnPosition.z); #endif } } CHECK_INTEGRITY return _FXGroups.begin(); } // ******************************************************************************************* void CTimedFXManager::remove(TFXGroupHandle handle) { FPU_CHECKER H_AUTO_USE(RZ_TimedFX) nlassert(_InitDone); CHECK_INTEGRITY TManagedFXGroup &group = handle->Group; for(uint k = 0; k < group.size(); ++k) { CManagedFX &fi = group[k]; if (!fi.Instance.empty()) { nlassert(_Scene); _Scene->deleteInstance(fi.Instance); } switch(fi.State) { case CManagedFX::InAddList: _FXToAdd.erase(fi.SetHandle); break; case CManagedFX::InRemoveList: _FXToRemove.erase(fi.SetHandle); break; default: break; } fi.unlinkFromCandidateFXList(); fi.unlinkFromInstanciatedFXList(); } _FXGroups.erase(handle); CHECK_INTEGRITY } // ******************************************************************************************* void CTimedFXManager::update(const CClientDate &date, EGSPD::CSeason::TSeason /* season */, const NLMISC::CVector &camPos) { FPU_CHECKER H_AUTO_USE(RZ_TimedFX) nlassert(_InitDone); #ifdef DEBUG_FX #ifdef NL_OS_WINDOWS _CrtCheckMemory(); #endif #endif CHECK_INTEGRITY _FXManager.update(); // check for instanciated fxs that should be removed while(!_FXToRemove.empty()) { const CTimeStampedFX &tsf = *_FXToRemove.begin(); #ifdef DEBUG_FX nlassert(tsf.FX->OwnerGroup); //nlassert(tsf.FX->OwnerGroup->Season == season); #endif nlassert(tsf.FX->State == CManagedFX::InRemoveList); if (tsf.Date > date) break; // all following fx will be remove in the future // removes from candidate & instanciated list (this causes fx to be shutdown at the end of the pass) tsf.FX->unlinkFromCandidateFXList(); _CandidateFXListTouched = true; // compute new activation date, and insert in _FXToAdd queue CTimeStampedFX newTsf; newTsf.FX = tsf.FX; float cycleLength = newTsf.FX->FXSheet ? newTsf.FX->FXSheet->CycleDuration : _DayLength; sint32 cycle = dateToCycle(tsf.Date, cycleLength, _DayLength); tsf.FX->computeStartHour(cycle, newTsf.Date, cycleLength, _DayLength, _Noise); if (newTsf.Date <= tsf.Date) { // already started for that day, so compute start hour for next day. tsf.FX->computeStartHour(cycle + 1, newTsf.Date, cycleLength, _DayLength, _Noise); } tsf.FX->SetHandle = _FXToAdd.insert(newTsf).first; tsf.FX->State = CManagedFX::InAddList; // remove from current queue _FXToRemove.erase(_FXToRemove.begin()); } // check for fxs that should be instanciated while (!_FXToAdd.empty()) { const CTimeStampedFX &tsf = *_FXToAdd.begin(); #ifdef DEBUG_FX nlassert(tsf.FX->OwnerGroup); //nlassert(tsf.FX->OwnerGroup->Season == season); #endif nlassert(tsf.FX->State == CManagedFX::InAddList); if (tsf.Date >= date) break; // all following fx will be instancied in the future // activate current fx if (!_Scene || !tsf.FX->FXSheet) { tsf.FX->Instance = NULL; } else { // insert in candidate fx list float dist = (tsf.FX->SpawnPosition - _LastCamPos).norm(); linkCandidateFX(_CandidateFXListSortedByDist, dist, tsf.FX); _CandidateFXListTouched = true; } // compute next shutdown date #ifdef DEBUG_FX nlinfo("===== compute end date for %s, debug day = %d", tsf.FX->FXSheet ? tsf.FX->FXSheet->FXName.c_str() : "???", /*tsf.DebugDay*/ 666); #endif CTimeStampedFX newTsf; newTsf.FX = tsf.FX; if (tsf.FX->FXSheet && tsf.FX->FXSheet->Mode == CSeasonFXSheet::Spawn) { // for a spawned fx, it is shutdown as soon the frame after it is created, so we add it to the remove list with the current date newTsf.Date = date; } else { #ifdef DEBUG_FX nlinfo("start day = %d, start hour = %f", (int) tsf.Date.Day, tsf.Date.Hour); #endif float cycleLength = tsf.FX->FXSheet ? tsf.FX->FXSheet->CycleDuration : _DayLength; sint32 cycle = dateToCycle(tsf.Date, cycleLength, _DayLength); tsf.FX->computeEndHour(cycle - 1, newTsf.Date, cycleLength, _DayLength, _Noise); #ifdef DEBUG_FX nlinfo("try 0 : end day = %d, end hour = %f", (int) newTsf.Date.Day, newTsf.Date.Hour); #endif if (newTsf.Date < tsf.Date) { tsf.FX->computeEndHour(cycle, newTsf.Date, cycleLength, _DayLength, _Noise); #ifdef DEBUG_FX nlinfo("try 1 : end day = %d, end hour = %f", (int) newTsf.Date.Day, newTsf.Date.Hour); #endif if (newTsf.Date < tsf.Date) { tsf.FX->computeEndHour(cycle + 1, newTsf.Date, cycleLength, _DayLength, _Noise); #ifdef DEBUG_FX nlinfo("try 2 : end day = %d, end hour = %f", (int) newTsf.Date.Day, newTsf.Date.Hour); #endif } } } newTsf.FX->SetHandle = _FXToRemove.insert(newTsf).first; newTsf.FX->State = CManagedFX::InRemoveList; // remove from current queue _FXToAdd.erase(_FXToAdd.begin()); } CHECK_INTEGRITY _CurrDate = date; // if cam pos has moved too much, must completely resort fx by distance if ((_CurrDate.Day == 0 && _CurrDate.Hour == 0.f) ||(camPos - _LastCamPos).sqrnorm() > _SortDistance * _SortDistance) { _LastCamPos = camPos; updateCandidateFXListSorting(); _CandidateFXListTouched = true; } // update instanciated fxs updateInstanciatedFXList(); } // ******************************************************************************************* void CTimedFXManager::shutDown(TFXGroupHandle handle) { FPU_CHECKER H_AUTO_USE(RZ_TimedFX) nlassert(_InitDone); CHECK_INTEGRITY TManagedFXGroup &fxGroup = handle->Group; // remove all fxs of that group from both queues, and shutdown those that are instanciated for(uint k = 0; k < fxGroup.size(); ++k) { fxGroup[k].shutDown(_Scene, _FXManager); } CHECK_INTEGRITY remove(handle); } // ******************************************************************************************* void CTimedFXManager::CManagedFX::unlinkFromCandidateFXList() { FPU_CHECKER H_AUTO_USE(RZ_TimedFX) if (_PrevCandidateFX) { *_PrevCandidateFX = _NextCandidateFX; if (_NextCandidateFX) { _NextCandidateFX->_PrevCandidateFX = _PrevCandidateFX; } _NextCandidateFX = NULL; _PrevCandidateFX = NULL; } } // ******************************************************************************************* void CTimedFXManager::CManagedFX::unlinkFromInstanciatedFXList() { FPU_CHECKER H_AUTO_USE(RZ_TimedFX) if (_PrevInstanciatedFX) { *_PrevInstanciatedFX = _NextInstanciatedFX; if (_NextInstanciatedFX) { _NextInstanciatedFX->_PrevInstanciatedFX = _PrevInstanciatedFX; } _NextInstanciatedFX = NULL; _PrevInstanciatedFX = NULL; } } // ******************************************************************************************* void CTimedFXManager::CManagedFX::shutDown(NL3D::UScene *scene, CFXManager &fxManager) { FPU_CHECKER H_AUTO_USE(RZ_TimedFX) if (!Instance.empty()) { // should not be already shutting down // if the fx is spaned, it will end by itself, so we just add it to the shutdown list if (FXSheet && FXSheet->Mode == CSeasonFXSheet::Spawn) { // delegate to fx manager fxManager.addFX(Instance, DEFAULT_SPAWNED_FX_TIMED_OUT); } else { // fx isn't spwaned, so must tell fx to stop its emitters if (Instance.isSystemPresent() && Instance.isValid()) { if (!Instance.removeByID(NELID("main")) && !Instance.removeByID(NELID("STOP"))) { // if a specific emitter has not be tagged, just stop all emitters if there's no better solution Instance.activateEmitters(false); } fxManager.addFX(Instance, FX_MANAGER_DEFAULT_TIMEOUT, true); } else { // the system is not present yet -> delete it immediatly if (scene) scene->deleteInstance(Instance); } } Instance = NULL; } } // ******************************************************************************************* void CTimedFXManager::cycleToDate(sint32 cycle, float hour, float cycleLength, float dayLength, CClientDate &result) { FPU_CHECKER H_AUTO_USE(RZ_TimedFX) if (dayLength == 0.f) { result.Day = 0; result.Hour = 0; } double resultHour = (double) cycle * (double) cycleLength + (double) hour; result.Day = (sint32) floor(resultHour / dayLength); result.Hour = (float) (resultHour - (double) result.Day * (double) dayLength); } // ******************************************************************************************* sint32 CTimedFXManager::dateToCycle(const CClientDate &date, float cycleLength, float dayLength) { FPU_CHECKER H_AUTO_USE(RZ_TimedFX) if (cycleLength == 0.f) return 0; if (dayLength == cycleLength) return date.Day; double hour = (double) date.Day * (double) dayLength + (double) date.Hour; return (sint32) floor(hour / cycleLength); } // ******************************************************************************************* void CTimedFXManager::CManagedFX::computeHour(sint32 cycle, float bias, CClientDate &resultDate, float cycleLength, float dayLength, const NLMISC::CNoiseValue &nv, float minHour, float maxHour) const { FPU_CHECKER H_AUTO_USE(RZ_TimedFX) // add an offset in z to get a different noise value each day CVector pos = SpawnPosition + (float) cycle * CVector::K + bias * nv.Frequency * CVector::I; float delta = computeUniformNoise(nv, pos); if (minHour <= maxHour) { // convert cycle:hour to day:hour (a cycle may be something other than 24 hour) cycleToDate(cycle, minHour + delta * (maxHour - minHour), cycleLength, dayLength, resultDate); } else { float hourDiff = dayLength - minHour + maxHour; float hour = minHour + delta * hourDiff; if (hour >= cycleLength) { cycleToDate(cycle + 1, hour - dayLength, cycleLength, dayLength, resultDate); } else { cycleToDate(cycle, hour, cycleLength, dayLength, resultDate); } } } // ******************************************************************************************* void CTimedFXManager::CManagedFX::computeStartHour(sint32 cycle, CClientDate &resultDate, float cycleLength, float dayLength, const NLMISC::CNoiseValue &nv) const { FPU_CHECKER H_AUTO_USE(RZ_TimedFX) if (!FXSheet) { resultDate = CClientDate(); return; } computeHour(cycle, 0.f, resultDate, cycleLength, dayLength, nv, FXSheet->StartHourMin, FXSheet->StartHourMax); } // ******************************************************************************************* void CTimedFXManager::CManagedFX::computeEndHour(sint32 cycle, CClientDate &resultDate, float cycleLength, float dayLength, const NLMISC::CNoiseValue &nv) const { FPU_CHECKER H_AUTO_USE(RZ_TimedFX) if (!FXSheet) { resultDate = CClientDate(); return; } if (!(FXSheet->Mode == CSeasonFXSheet::UseDuration)) { if (FXSheet->EndHourMax < FXSheet->StartHourMin) { computeHour(cycle + 1, 1.f, resultDate, cycleLength, dayLength, nv, FXSheet->EndHourMin, FXSheet->EndHourMax); } else { computeHour(cycle, 1.f, resultDate, cycleLength, dayLength, nv, FXSheet->EndHourMin, FXSheet->EndHourMax); } } else { // compute start hour and add duration computeHour(cycle, 0.f, resultDate, cycleLength, dayLength, nv, FXSheet->StartHourMin, FXSheet->StartHourMax); #ifdef DEBUG_FX nlinfo("computeEndHour for day %d: start day = %d, start hour = %f, pos=(%f, %f, %f)", (int) day, (int) resultDate.Day, resultDate.Hour, SpawnPosition.x, SpawnPosition.y, SpawnPosition.z); #endif // add duration const float bias = 0.5656f; // dummy bias to get another value CVector pos = SpawnPosition + (float) cycle * CVector::K + bias * nv.Frequency * CVector::I; float delta = computeUniformNoise(nv, pos); resultDate.Hour += delta * (FXSheet->MaxDuration - FXSheet->MinDuration) + FXSheet->MinDuration; if (resultDate.Hour > dayLength) { resultDate.Hour -= dayLength; ++resultDate.Day; } } } // ******************************************************************************************* void CTimedFXManager::dumpFXToAdd() const { FPU_CHECKER H_AUTO_USE(RZ_TimedFX) nlassert(_InitDone); nlinfo("FX to add"); nlinfo("========="); for(TTimeStampedFXPtrSet::const_iterator it = _FXToAdd.begin(); it != _FXToAdd.end(); ++it) { nlinfo("%s : day=%d, hour=%f, pos=(%.1f, %.1f, %.1f)", it->FX->FXSheet ? it->FX->FXSheet->FXName.c_str() : "???", (int) it->Date.Day, it->Date.Hour, it->FX->SpawnPosition.x, it->FX->SpawnPosition.y, it->FX->SpawnPosition.z ); } } // ******************************************************************************************* void CTimedFXManager::dumpFXToRemove() const { FPU_CHECKER H_AUTO_USE(RZ_TimedFX) nlassert(_InitDone); nlinfo("FX to add"); nlinfo("========="); for(TTimeStampedFXPtrSet::const_iterator it = _FXToRemove.begin(); it != _FXToRemove.end(); ++it) { nlinfo("%s : day=%d, hour=%f, pos=(%.1f, %.1f, %.1f)", it->FX->FXSheet ? it->FX->FXSheet->FXName.c_str() : "???", (int) it->Date.Day, it->Date.Hour, it->FX->SpawnPosition.x, it->FX->SpawnPosition.y, it->FX->SpawnPosition.z ); } } // ******************************************************************************************* void CTimedFXManager::dumpFXInfo() const { FPU_CHECKER H_AUTO_USE(RZ_TimedFX) nlassert(_InitDone); nlinfo("FX Infos"); nlinfo("========="); nlinfo("Num FX to add = %d", (int) _FXToAdd.size()); nlinfo("Num FX to remove = %d", (int) _FXToRemove.size()); uint numInstance = 0; for(TManagedFXGroupList::const_iterator it = _FXGroups.begin(); it != _FXGroups.end(); ++it) { const TManagedFXGroup &group = it->Group; for(uint k = 0; k < group.size(); ++k) { if (!group[k].Instance.empty()) ++ numInstance; } } nlinfo("Num Intanciated FX = %d", (int) numInstance); } // ******************************************************************************************* void CTimedFXManager::checkIntegrity() { FPU_CHECKER H_AUTO_USE(RZ_TimedFX) // count number of instances that are not always instanciated uint numInstance = 0; for(TManagedFXGroupList::const_iterator it = _FXGroups.begin(); it != _FXGroups.end(); ++it) { const TManagedFXGroup &group = it->Group; for(uint k = 0; k < group.size(); ++k) { if (!group[k].Instance.empty()) { if (!(group[k].FXSheet && group[k].FXSheet->Mode == CSeasonFXSheet::AlwaysStarted)) { ++ numInstance; } } } } #ifdef NL_DEBUG for(TTimeStampedFXPtrSet::iterator it = _FXToAdd.begin(); it != _FXToAdd.end(); ++it) { nlassert(it->FX->Magic == 0xbaadcafe); } for(TTimeStampedFXPtrSet::iterator it = _FXToRemove.begin(); it != _FXToRemove.end(); ++it) { nlassert(it->FX->Magic == 0xbaadcafe); } #endif } // ******************************************************************************************* void CTimedFXManager::setupUserParams(CManagedFX &fi, uint cycle) { FPU_CHECKER H_AUTO_USE(RZ_TimedFX) if (fi.FXSheet) { for(uint k = 0; k < 4; ++k) { if (fi.FXSheet->UserParamsMax[k] != fi.FXSheet->UserParamsMin[k]) { float delta = computeUniformNoise(_Noise, fi.SpawnPosition + (float) 1.23564f * k * CVector::I + (float) cycle * 2.564848f * CVector::J); fi.Instance.setUserParam(k, fi.FXSheet->UserParamsMin[k] + delta * (fi.FXSheet->UserParamsMax[k] - fi.FXSheet->UserParamsMin[k])); } else { fi.Instance.setUserParam(k, fi.FXSheet->UserParamsMin[k]); } } } else { for(uint k = 0; k < 4; ++k) { fi.Instance.setUserParam(k, 0.f); } } } // ******************************************************************************************* // from a fx mode, get a description string const char *timedFXModeToStr(CSeasonFXSheet::TMode mode) { FPU_CHECKER H_AUTO_USE(RZ_TimedFX) switch(mode) { case CSeasonFXSheet::AlwaysStarted: return "always started"; case CSeasonFXSheet::UseEndHour: return "use end hour"; case CSeasonFXSheet::UseDuration: return "use duration"; case CSeasonFXSheet::Spawn: return "spawn"; default: break; } return "???"; } // ******************************************************************************************* void CTimedFXManager::displayFXBoxes(TDebugDisplayMode displayMode) const { FPU_CHECKER H_AUTO_USE(RZ_TimedFX) Driver->setViewMatrix(Scene->getCam().getMatrix().inverted()); NL3D::CFrustum fr; Scene->getCam().getFrustum(fr.Left, fr.Right, fr.Bottom, fr.Top, fr.Near, fr.Far); fr.Perspective = true; Driver->setFrustum(fr); TextContext->setColor(CRGBA::Blue); TextContext->setShaded(false); TextContext->setShadeOutline(false); TextContext->setFontSize(10); // float size = 0.4f; float textSize = 2.5f; // display fx to add for(TManagedFXGroupList::const_iterator groupIt = _FXGroups.begin(); groupIt != _FXGroups.end(); ++groupIt) { for(uint k = 0; k < groupIt->Group.size(); ++k) { const CManagedFX &mf = groupIt->Group[k]; NLMISC::CMatrix mat = mf.getInstanceMatrix(); Driver->setModelMatrix(mat); NLMISC::CRGBA color = CRGBA::Black; switch(mf.State) { case CManagedFX::Permanent: color = (!mf.Instance.empty()) ? CRGBA::Magenta : CRGBA::Cyan; break; case CManagedFX::InAddList: color = CRGBA::Blue; break; case CManagedFX::InRemoveList: color = (!mf.Instance.empty()) ? CRGBA::Red : CRGBA::Yellow; break; case CManagedFX::Unknown: break; } drawBox(CVector(- size, - size, - size), CVector(size, size, size), color); std::string textToDisplay; switch(displayMode) { case NoText: break; case PSName: textToDisplay = mf.FXSheet->FXName; break; case SpawnDate: switch(mf.State) { case CManagedFX::Permanent: textToDisplay = NLMISC::toString("PERMANENT"); break; case CManagedFX::InAddList: case CManagedFX::InRemoveList: textToDisplay = NLMISC::toString("day:%d, hour:%d:%d", (int) mf.SetHandle->Date.Day, (int) mf.SetHandle->Date.Hour, (int) (60.f * fmodf(mf.SetHandle->Date.Hour, 1.f))); break; default: break; } break; default: nlassert(0); break; } #if !FINAL_VERSION if (mf.FromIG) { textToDisplay = "*" + textToDisplay; } #endif if (!textToDisplay.empty()) { TextContext->setColor(color); mat.identity(); mat.setRot(Scene->getCam().getRotQuat()); mat.setPos(mf.SpawnPosition + 2.5f * size * CVector::K); CVector distPos = mf.SpawnPosition - Scene->getCam().getPos(); mat.scale(textSize * distPos.norm()); TextContext->render3D(mat, textToDisplay); } } } } // ******************************************************************************************* void CTimedFXManager::linkCandidateFX(TCandidateFXListSortedByDist &targetList, float dist, CManagedFX *fx) { FPU_CHECKER H_AUTO_USE(RZ_TimedFX) if (!fx) return; uint rank = (uint) (dist / _SortDistance); fx->unlinkFromCandidateFXList(); // unlink from previous list rank = std::min(rank, (uint) (_CandidateFXListSortedByDist.size() - 1)); fx->_NextCandidateFX = targetList[rank]; if (targetList[rank] != NULL) { targetList[rank]->_PrevCandidateFX = &fx->_NextCandidateFX; } fx->_PrevCandidateFX = &targetList[rank]; // NB the vector won't grow so no prb targetList[rank] = fx; } // ******************************************************************************************* void CTimedFXManager::linkInstanciatedFX(CManagedFX *&listHead, CManagedFX *fx) { FPU_CHECKER H_AUTO_USE(RZ_TimedFX) if (!fx) return; fx->unlinkFromInstanciatedFXList(); fx->_NextInstanciatedFX = listHead; if (listHead) { listHead->_PrevInstanciatedFX = &fx->_NextInstanciatedFX; } fx->_PrevInstanciatedFX = &listHead; listHead = fx; } // ******************************************************************************************* void CTimedFXManager::updateInstanciatedFXList() { FPU_CHECKER H_AUTO_USE(RZ_TimedFX) nlassert(_InitDone); // if the list has not been modified since last time, no-op if (!_CandidateFXListTouched) return; // algo : we take at most '_MaxNumberOfFXInstances' from the start of the updated list of candidate fxs (they are roughly sorted by distance) // these fx are inserted in new list of instanciated fxs. All fx that haven't been inserted (they remains in the previous list) // are discarded. At the end the new list replaces the previous one CManagedFX *toInstanciateListHead = NULL; CManagedFX *alreadyInstanciatedListHead = NULL; uint numInstances = 0; uint numDistanceRanges = (uint)_CandidateFXListSortedByDist.size(); sint maxNumPossibleInstance = (sint) (_MaxNumberOfFXInstances - _FXManager.getNumFXtoRemove()); if (maxNumPossibleInstance > 0) { // NB: Ideally _CandidateFXListSortedByDist is small as a vector, so it's ok to traverse it in its entirety. for(uint k = 0; k < numDistanceRanges; ++k) { CManagedFX *currCandidate = _CandidateFXListSortedByDist[k]; while(currCandidate) { nlassert(currCandidate->_PrevCandidateFX); // if fx already instanciated, put in special list if (currCandidate->_PrevInstanciatedFX != NULL) { linkInstanciatedFX(alreadyInstanciatedListHead, currCandidate); } else { // not already instanciated linkInstanciatedFX(toInstanciateListHead, currCandidate); } ++ numInstances; if (numInstances == (uint) maxNumPossibleInstance) break; // max number of instances reached currCandidate = currCandidate->_NextCandidateFX; } if (numInstances == (uint) maxNumPossibleInstance) break; // max number of instances reached } } // removes old instances (those that were instanciated at previous frame, but are not anymore) CManagedFX *currFXToRemove = _InstanciatedFXs; while (currFXToRemove) { CManagedFX *tmp = currFXToRemove; nlassert(tmp->_PrevInstanciatedFX); currFXToRemove = currFXToRemove->_NextInstanciatedFX; // shut the fx down tmp->shutDown(_Scene, _FXManager); // fast unlink (all instance are removed from that list) tmp->_PrevInstanciatedFX = NULL; tmp->_NextInstanciatedFX = NULL; } // create new instances CManagedFX *prevFXToInstanciate = NULL; CManagedFX *currFXToInstanciate = toInstanciateListHead; if (maxNumPossibleInstance > 0) { while (currFXToInstanciate) { nlassert(currFXToInstanciate->_PrevInstanciatedFX); bool oktoCreate = true; // special case : if the fx must be spawned, then it must be a valid candidate at the frame it is inserted, otherwise // it is not created at all (this is to avoid it to spawn at a later date, when it's too late) if (currFXToInstanciate->FXSheet && currFXToInstanciate->FXSheet->Mode == CSeasonFXSheet::Spawn) { if (currFXToInstanciate->SetHandle->Date != _CurrDate) { oktoCreate = false; // nb : the fx will be removed from the 'instanciated list' at next pass, because spawned fx are shutdown one frame after their creation (see CTimedFXManager::update) } } if (oktoCreate) { nlassert(currFXToInstanciate->Instance.empty()); // if fx was in shutting down state, delete the instance NL3D::UInstance ts = _Scene->createInstance(currFXToInstanciate->FXSheet->FXName); if (ts.empty()) { currFXToInstanciate->Instance.detach(); } else { currFXToInstanciate->Instance.cast (ts); if (currFXToInstanciate->Instance.empty()) { // bad type, removes the shape _Scene->deleteInstance(ts); } else { currFXToInstanciate->Instance.setTransformMode(NL3D::UTransform::DirectMatrix); currFXToInstanciate->Instance.setMatrix(currFXToInstanciate->getInstanceMatrix()); if (currFXToInstanciate->State == CManagedFX::Permanent) { setupUserParams(*currFXToInstanciate,0); } else { setupUserParams(*currFXToInstanciate, currFXToInstanciate->SetHandle->Date.Day); } currFXToInstanciate->Instance.freezeHRC(); } } } prevFXToInstanciate = currFXToInstanciate; currFXToInstanciate = currFXToInstanciate->_NextInstanciatedFX; } } // merge toInstanciateListHead & alreadyInstanciatedListHead together // this becomes the new list of instanciated fxs if (prevFXToInstanciate) { nlassert(prevFXToInstanciate->_NextInstanciatedFX == NULL); prevFXToInstanciate->_NextInstanciatedFX = alreadyInstanciatedListHead; if (alreadyInstanciatedListHead) { alreadyInstanciatedListHead->_PrevInstanciatedFX = &prevFXToInstanciate->_NextInstanciatedFX; } // set new list _InstanciatedFXs = toInstanciateListHead; toInstanciateListHead->_PrevInstanciatedFX = &_InstanciatedFXs; } else { // set new list _InstanciatedFXs = alreadyInstanciatedListHead; if (alreadyInstanciatedListHead) { alreadyInstanciatedListHead->_PrevInstanciatedFX = &_InstanciatedFXs; } } _CandidateFXListTouched = false; } // ******************************************************************************************* void CTimedFXManager::updateCandidateFXListSorting() { FPU_CHECKER H_AUTO_USE(RZ_TimedFX) // re-sort all instances by distance nlassert(_CandidateFXListSortedByDist.size() == _CandidateFXListSortedByDistTmp.size()); uint numDistanceRanges = (uint)_CandidateFXListSortedByDist.size(); for(uint k = 0; k < numDistanceRanges; ++k) { CManagedFX *currCandidate = _CandidateFXListSortedByDist[k]; while(currCandidate) { CManagedFX *tmp = currCandidate; currCandidate = currCandidate->_NextCandidateFX; float dist = (tmp->SpawnPosition - _LastCamPos).norm(); // unlink & relink in new list linkCandidateFX(_CandidateFXListSortedByDistTmp, dist, tmp); } } // swap the 2 list _CandidateFXListSortedByDist.swap(_CandidateFXListSortedByDistTmp); } // ******************************************************************************************* void CTimedFXManager::setMaxNumFXInstances(uint maxNumInstances) { FPU_CHECKER H_AUTO_USE(RZ_TimedFX) _MaxNumberOfFXInstances = maxNumInstances; _CandidateFXListTouched = true; } // ******************************************************************************************* // temp, for debug NLMISC_COMMAND(dumpFXToAdd, "dumpFXToAdd", "<>") { if (!args.empty()) return false; CTimedFXManager::getInstance().dumpFXToAdd(); return true; } // ******************************************************************************************* // temp, for debug NLMISC_COMMAND(dumpFXToRemove, "dumpFXToRemove", "<>") { if (!args.empty()) return false; CTimedFXManager::getInstance().dumpFXToRemove(); return true; } // ******************************************************************************************* // temp, for debug NLMISC_COMMAND(dumpFXInfo, "dumpFXInfo", "<>") { if (!args.empty()) return false; CTimedFXManager::getInstance().dumpFXInfo(); return true; } extern class CIGCallback *IGCallbacks; // ******************************************************************************************* // temp, for debug NLMISC_COMMAND(updateSeason, "updateSeason", "<>") { if (!args.empty()) return false; if (IGCallbacks) IGCallbacks->changeSeason(); return true; } // ******************************************************************************************* // temp, for debug NLMISC_COMMAND(setMaxNumTimedFXs, "set the max number of timed fx that are visible at a time", "") { if (args.size() != 1) return false; uint maxNumInstances; fromString(args[0], maxNumInstances); CTimedFXManager::getInstance().setMaxNumFXInstances(maxNumInstances); return true; }