// Ryzom - 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 "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"
#if defined(NL_DEBUG) && defined(NL_OS_WINDOWS)
#include
#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->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->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;
}
// *******************************************************************************************
// 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;
}