Merge branch '164-crash-on-patching-and-possibly-right-after-login' into main/gingo...
[ryzomcore.git] / ryzom / client / src / timed_fx_manager.cpp
blobc42928127131adb0679b947c15079923961d3571
1 // Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
2 // Copyright (C) 2010 Winch Gate Property Limited
3 //
4 // This source file has been modified by the following contributors:
5 // Copyright (C) 2014-2020 Jan BOON (Kaetemi) <jan.boon@kaetemi.be>
6 //
7 // This program is free software: you can redistribute it and/or modify
8 // it under the terms of the GNU Affero General Public License as
9 // published by the Free Software Foundation, either version 3 of the
10 // License, or (at your option) any later version.
12 // This program is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 // GNU Affero General Public License for more details.
17 // You should have received a copy of the GNU Affero General Public License
18 // along with this program. If not, see <http://www.gnu.org/licenses/>.
22 #include "stdpch.h"
23 #include "timed_fx_manager.h"
24 #include "ig_callback.h"
25 #include "client_sheets/plant_sheet.h"
26 #include "nel/3d/u_transform.h"
27 #include "nel/3d/u_particle_system_instance.h"
28 #include "nel/3d/u_driver.h"
29 #include "nel/3d/u_text_context.h"
30 #include "nel/3d/u_scene.h"
31 #include "nel/3d/u_camera.h"
32 #include "nel/misc/fast_floor.h"
33 #include "misc.h"
34 #include "fx_manager.h"
35 #include "nel/misc/check_fpu.h"
38 #ifdef DEBUG_NEW
39 #define new DEBUG_NEW
40 #endif
42 using namespace std::rel_ops;
43 using namespace NLMISC;
45 extern NL3D::UTextContext *TextContext;
46 extern NL3D::UDriver *Driver;
47 extern NL3D::UScene *Scene;
51 //#define DEBUG_FX
53 #ifdef DEBUG_FX
54 #ifndef NL_DEBUG
55 #error Flag DEBUG_FX can only be used in debug mode
56 #endif
57 #endif
59 #ifdef DEBUG_FX
60 #define CHECK_INTEGRITY checkIntegrity();
61 #else
62 #define CHECK_INTEGRITY
63 #endif
65 CTimedFXManager &CTimedFXManager::getInstance()
67 FPU_CHECKER
68 static CTimedFXManager manager;
69 return manager;
72 const float DEFAULT_SPAWNED_FX_TIMED_OUT = 100.f;
74 H_AUTO_DECL(RZ_TimedFX)
77 // *******************************************************************************************
78 NLMISC::CMatrix CTimedFX::getInstanceMatrix() const
80 FPU_CHECKER
81 H_AUTO_USE(RZ_TimedFX)
82 NLMISC::CMatrix result;
83 result.identity();
84 result.translate(SpawnPosition);
85 if (FXSheet && FXSheet->InheritRot) result.rotate(Rot);
86 if (FXSheet && FXSheet->InheritScale) result.scale(Scale);
87 return result;
91 // *******************************************************************************************
92 CTimedFXManager::CTimedFXManager()
94 FPU_CHECKER
95 H_AUTO_USE(RZ_TimedFX)
96 _Scene = NULL,
97 _DayLength = 24.f,
98 _InitDone = false;
99 _InstanciatedFXs = NULL;
100 _CandidateFXListTouched = false;
101 _SortDistance = 0.f;
102 _MaxNumberOfFXInstances = 0;
105 // *******************************************************************************************
106 CTimedFXManager::~CTimedFXManager()
108 FPU_CHECKER
109 H_AUTO_USE(RZ_TimedFX)
110 reset();
113 // *******************************************************************************************
114 void CTimedFXManager::init(NL3D::UScene *scene,
115 const CClientDate &startDate,
116 float /* dayLength */,
117 float noiseFrequency,
118 uint maxNumberOfFXInstances,
119 float sortDistanceInterval,
120 float maxDist)
122 FPU_CHECKER
123 H_AUTO_USE(RZ_TimedFX)
124 nlassert(!_InitDone);
125 if (!scene)
127 nlwarning("Scene is NULL");
129 _Scene = scene;
130 _CurrDate = startDate;
131 _Noise.Frequency = noiseFrequency;
132 _Noise.Abs = 0.f;
133 _Noise.Rand = 1.f;
134 CHECK_INTEGRITY
135 _InitDone = true;
136 nlassert(maxDist > 0.f);
137 nlassert(sortDistanceInterval > 0.f);
138 _CandidateFXListSortedByDist.resize((int) ceilf(maxDist / sortDistanceInterval));
139 _CandidateFXListSortedByDistTmp.resize((int) ceilf(maxDist / sortDistanceInterval));
140 _MaxNumberOfFXInstances = maxNumberOfFXInstances;
141 _SortDistance = sortDistanceInterval;
145 // *******************************************************************************************
146 void CTimedFXManager::reset()
148 FPU_CHECKER
149 H_AUTO_USE(RZ_TimedFX)
150 if (_InitDone)
152 nlassert(_InitDone);
153 CHECK_INTEGRITY
154 while (!_FXGroups.empty())
156 remove(_FXGroups.begin());
158 _Scene = NULL;
159 _CurrDate = CClientDate();
160 _InitDone = false;
161 _FXManager.reset();
165 // *******************************************************************************************
166 void CTimedFXManager::setDate(const CClientDate &date)
168 FPU_CHECKER
169 H_AUTO_USE(RZ_TimedFX)
170 _CurrDate = date;
173 // *******************************************************************************************
174 CTimedFXManager::TFXGroupHandle CTimedFXManager::add(const std::vector<CTimedFX> &fxs, EGSPD::CSeason::TSeason /* season */)
176 FPU_CHECKER
177 H_AUTO_USE(RZ_TimedFX)
178 nlassert(_InitDone);
179 CHECK_INTEGRITY
180 _FXGroups.push_front(CManagedFXGroup());
181 #ifdef DEBUG_FX
182 _FXGroups.front().Season = season;
183 #endif
184 TManagedFXGroup &newGroup = _FXGroups.begin()->Group;
185 newGroup.resize(fxs.size());
186 for(uint k = 0; k < fxs.size(); ++k)
188 CManagedFX &fi = newGroup[k];
189 fi.FXSheet = fxs[k].FXSheet;
190 fi.SpawnPosition = fxs[k].SpawnPosition;
191 fi.Scale = fxs[k].Scale;
192 fi.Rot = fxs[k].Rot;
193 fi.Instance = NULL;
194 #if !FINAL_VERSION
195 fi.FromIG = fxs[k].FromIG;
196 #endif
197 #ifdef DEBUG_FX
198 fi.OwnerGroup = &(_FXGroups.front());
199 #endif
200 // compute start and end hour to see in which list insertion should occurs
201 bool instanciate = false;
202 CClientDate startDate;
203 CClientDate endDate;
204 //sint32 debugDay;
205 if (!(fi.FXSheet && fi.FXSheet->Mode == CSeasonFXSheet::AlwaysStarted))
207 if (fi.FXSheet && fi.FXSheet->Mode == CSeasonFXSheet::Spawn)
209 // compute next spawn date
210 float cycleLength = fi.FXSheet ? fi.FXSheet->CycleDuration : _DayLength;
211 sint32 cycle = dateToCycle(_CurrDate, cycleLength, _DayLength);
212 fi.computeStartHour(cycle - 1, startDate, cycleLength, _DayLength, _Noise);
213 if (startDate < _CurrDate)
215 fi.computeStartHour(cycle, startDate, cycleLength, _DayLength, _Noise);
216 if (startDate < _CurrDate)
218 fi.computeStartHour(cycle + 1, startDate, cycleLength, _DayLength, _Noise);
222 else
224 // the system is not always instanciated, so must check if it currently is.
225 float cycleLength = fi.FXSheet ? fi.FXSheet->CycleDuration : _DayLength;
226 sint32 cycle = dateToCycle(_CurrDate, cycleLength, _DayLength);
227 fi.computeStartHour(cycle, startDate, cycleLength, _DayLength, _Noise);
228 //debugDay = _CurrDate.Day;
229 if (startDate > _CurrDate)
231 // Let see if it did not start the previous cycle and then ended that cycle
232 fi.computeEndHour(cycle - 1, endDate, cycleLength, _DayLength, _Noise);
233 if (endDate > _CurrDate)
235 CClientDate prevStartDate;
236 fi.computeStartHour(cycle - 1, prevStartDate, cycleLength, _DayLength, _Noise);
237 if (prevStartDate <= _CurrDate)
239 instanciate = true;
241 else
243 startDate = prevStartDate;
244 //debugDay = _CurrDate.Day - 1;
248 else
250 fi.computeEndHour(cycle, endDate, cycleLength, _DayLength, _Noise);
251 if (endDate > _CurrDate)
253 // the fx is currently running
254 instanciate = true;
256 else
258 fi.computeStartHour(_CurrDate.Day + 1, endDate, cycleLength, _DayLength, _Noise);
259 // the fx will start only during the next day
260 //debugDay = _CurrDate.Day + 1;
265 else
267 if (fi.FXSheet && fi.FXSheet->Mode == CSeasonFXSheet::AlwaysStarted)
269 fi.State = CManagedFX::Permanent;
270 instanciate = true;
273 if (instanciate)
275 if (!_Scene || !fi.FXSheet)
277 fi.Instance = NULL;
279 else
281 // insert in candidate fx list
282 float dist = (fi.SpawnPosition - _LastCamPos).norm();
283 linkCandidateFX(_CandidateFXListSortedByDist, dist, &fi);
284 _CandidateFXListTouched = true;
286 // Add the system to the queue of systems to be removed, only if it is not always started
287 if (!(fi.FXSheet && fi.FXSheet->Mode == CSeasonFXSheet::AlwaysStarted))
289 // As the system is currenlty instanciated, we won't deals with it again until it shutdowns, so, we save it in the priority queue.
290 CTimeStampedFX tsf;
291 tsf.Date = endDate;
292 //tsf.DebugDay = 666;
293 tsf.FX = &fi; // yes, we keep a pointer on a vector element, but we will never grow that vector
294 fi.SetHandle = _FXToRemove.insert(tsf).first;
295 fi.State = CManagedFX::InRemoveList;
298 else
300 // The system is not instanciated yet, but will be later, so add to the queue of systems that "will be instanciated later"
301 CTimeStampedFX tsf;
302 tsf.Date = startDate;
303 tsf.FX = &fi; // yes, we keep a pointer on a vector element, but we will never grow that vector
304 //tsf.DebugDay = debugDay;
305 nlassert(fi.State != CManagedFX::InAddList);
306 fi.SetHandle = _FXToAdd.insert(tsf).first;
307 fi.State = CManagedFX::InAddList;
308 #ifdef DEBUG_FX
309 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,
310 fi.SpawnPosition.x, fi.SpawnPosition.y, fi.SpawnPosition.z);
311 #endif
314 CHECK_INTEGRITY
315 return _FXGroups.begin();
320 // *******************************************************************************************
321 void CTimedFXManager::remove(TFXGroupHandle handle)
323 FPU_CHECKER
324 H_AUTO_USE(RZ_TimedFX)
325 nlassert(_InitDone);
326 CHECK_INTEGRITY
327 TManagedFXGroup &group = handle->Group;
328 for(uint k = 0; k < group.size(); ++k)
330 CManagedFX &fi = group[k];
331 if (!fi.Instance.empty())
333 nlassert(_Scene);
334 _Scene->deleteInstance(fi.Instance);
336 switch(fi.State)
338 case CManagedFX::InAddList: _FXToAdd.erase(fi.SetHandle); break;
339 case CManagedFX::InRemoveList: _FXToRemove.erase(fi.SetHandle); break;
340 default: break;
342 fi.unlinkFromCandidateFXList();
343 fi.unlinkFromInstanciatedFXList();
345 _FXGroups.erase(handle);
346 CHECK_INTEGRITY
350 // *******************************************************************************************
351 void CTimedFXManager::update(const CClientDate &date, EGSPD::CSeason::TSeason /* season */, const NLMISC::CVector &camPos)
353 FPU_CHECKER
354 H_AUTO_USE(RZ_TimedFX)
355 nlassert(_InitDone);
356 #ifdef DEBUG_FX
357 #ifdef NL_OS_WINDOWS
358 _CrtCheckMemory();
359 #endif
360 #endif
361 CHECK_INTEGRITY
363 _FXManager.update();
365 // check for instanciated fxs that should be removed
366 while(!_FXToRemove.empty())
369 const CTimeStampedFX &tsf = *_FXToRemove.begin();
370 #ifdef DEBUG_FX
371 nlassert(tsf.FX->OwnerGroup);
372 //nlassert(tsf.FX->OwnerGroup->Season == season);
373 #endif
374 nlassert(tsf.FX->State == CManagedFX::InRemoveList);
375 if (tsf.Date > date) break; // all following fx will be remove in the future
376 // removes from candidate & instanciated list (this causes fx to be shutdown at the end of the pass)
377 tsf.FX->unlinkFromCandidateFXList();
378 _CandidateFXListTouched = true;
380 // compute new activation date, and insert in _FXToAdd queue
381 CTimeStampedFX newTsf;
382 newTsf.FX = tsf.FX;
383 float cycleLength = newTsf.FX->FXSheet ? newTsf.FX->FXSheet->CycleDuration : _DayLength;
384 sint32 cycle = dateToCycle(tsf.Date, cycleLength, _DayLength);
385 tsf.FX->computeStartHour(cycle, newTsf.Date, cycleLength, _DayLength, _Noise);
386 if (newTsf.Date <= tsf.Date)
388 // already started for that day, so compute start hour for next day.
389 tsf.FX->computeStartHour(cycle + 1, newTsf.Date, cycleLength, _DayLength, _Noise);
391 tsf.FX->SetHandle = _FXToAdd.insert(newTsf).first;
392 tsf.FX->State = CManagedFX::InAddList;
393 // remove from current queue
394 _FXToRemove.erase(_FXToRemove.begin());
397 // check for fxs that should be instanciated
398 while (!_FXToAdd.empty())
401 const CTimeStampedFX &tsf = *_FXToAdd.begin();
402 #ifdef DEBUG_FX
403 nlassert(tsf.FX->OwnerGroup);
404 //nlassert(tsf.FX->OwnerGroup->Season == season);
405 #endif
406 nlassert(tsf.FX->State == CManagedFX::InAddList);
407 if (tsf.Date >= date) break; // all following fx will be instancied in the future
408 // activate current fx
409 if (!_Scene || !tsf.FX->FXSheet)
411 tsf.FX->Instance = NULL;
413 else
415 // insert in candidate fx list
416 float dist = (tsf.FX->SpawnPosition - _LastCamPos).norm();
417 linkCandidateFX(_CandidateFXListSortedByDist, dist, tsf.FX);
418 _CandidateFXListTouched = true;
420 // compute next shutdown date
421 #ifdef DEBUG_FX
422 nlinfo("===== compute end date for %s, debug day = %d", tsf.FX->FXSheet ? tsf.FX->FXSheet->FXName.c_str() : "???", /*tsf.DebugDay*/ 666);
423 #endif
424 CTimeStampedFX newTsf;
425 newTsf.FX = tsf.FX;
426 if (tsf.FX->FXSheet && tsf.FX->FXSheet->Mode == CSeasonFXSheet::Spawn)
428 // 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
429 newTsf.Date = date;
431 else
433 #ifdef DEBUG_FX
434 nlinfo("start day = %d, start hour = %f", (int) tsf.Date.Day, tsf.Date.Hour);
435 #endif
436 float cycleLength = tsf.FX->FXSheet ? tsf.FX->FXSheet->CycleDuration : _DayLength;
437 sint32 cycle = dateToCycle(tsf.Date, cycleLength, _DayLength);
438 tsf.FX->computeEndHour(cycle - 1, newTsf.Date, cycleLength, _DayLength, _Noise);
439 #ifdef DEBUG_FX
440 nlinfo("try 0 : end day = %d, end hour = %f", (int) newTsf.Date.Day, newTsf.Date.Hour);
441 #endif
442 if (newTsf.Date < tsf.Date)
444 tsf.FX->computeEndHour(cycle, newTsf.Date, cycleLength, _DayLength, _Noise);
445 #ifdef DEBUG_FX
446 nlinfo("try 1 : end day = %d, end hour = %f", (int) newTsf.Date.Day, newTsf.Date.Hour);
447 #endif
448 if (newTsf.Date < tsf.Date)
450 tsf.FX->computeEndHour(cycle + 1, newTsf.Date, cycleLength, _DayLength, _Noise);
451 #ifdef DEBUG_FX
452 nlinfo("try 2 : end day = %d, end hour = %f", (int) newTsf.Date.Day, newTsf.Date.Hour);
453 #endif
457 newTsf.FX->SetHandle = _FXToRemove.insert(newTsf).first;
458 newTsf.FX->State = CManagedFX::InRemoveList;
459 // remove from current queue
460 _FXToAdd.erase(_FXToAdd.begin());
464 CHECK_INTEGRITY
465 _CurrDate = date;
467 // if cam pos has moved too much, must completely resort fx by distance
468 if ((_CurrDate.Day == 0 && _CurrDate.Hour == 0.f) ||(camPos - _LastCamPos).sqrnorm() > _SortDistance * _SortDistance)
470 _LastCamPos = camPos;
471 updateCandidateFXListSorting();
472 _CandidateFXListTouched = true;
474 // update instanciated fxs
475 updateInstanciatedFXList();
479 // *******************************************************************************************
480 void CTimedFXManager::shutDown(TFXGroupHandle handle)
482 FPU_CHECKER
483 H_AUTO_USE(RZ_TimedFX)
484 nlassert(_InitDone);
485 CHECK_INTEGRITY
486 TManagedFXGroup &fxGroup = handle->Group;
487 // remove all fxs of that group from both queues, and shutdown those that are instanciated
488 for(uint k = 0; k < fxGroup.size(); ++k)
490 fxGroup[k].shutDown(_Scene, _FXManager);
492 CHECK_INTEGRITY
493 remove(handle);
497 // *******************************************************************************************
498 void CTimedFXManager::CManagedFX::unlinkFromCandidateFXList()
500 FPU_CHECKER
501 H_AUTO_USE(RZ_TimedFX)
502 if (_PrevCandidateFX)
504 *_PrevCandidateFX = _NextCandidateFX;
505 if (_NextCandidateFX)
507 _NextCandidateFX->_PrevCandidateFX = _PrevCandidateFX;
509 _NextCandidateFX = NULL;
510 _PrevCandidateFX = NULL;
514 // *******************************************************************************************
515 void CTimedFXManager::CManagedFX::unlinkFromInstanciatedFXList()
517 FPU_CHECKER
518 H_AUTO_USE(RZ_TimedFX)
519 if (_PrevInstanciatedFX)
521 *_PrevInstanciatedFX = _NextInstanciatedFX;
522 if (_NextInstanciatedFX)
524 _NextInstanciatedFX->_PrevInstanciatedFX = _PrevInstanciatedFX;
526 _NextInstanciatedFX = NULL;
527 _PrevInstanciatedFX = NULL;
531 // *******************************************************************************************
532 void CTimedFXManager::CManagedFX::shutDown(NL3D::UScene *scene, CFXManager &fxManager)
534 FPU_CHECKER
535 H_AUTO_USE(RZ_TimedFX)
536 if (!Instance.empty())
538 // should not be already shutting down
539 // if the fx is spaned, it will end by itself, so we just add it to the shutdown list
540 if (FXSheet && FXSheet->Mode == CSeasonFXSheet::Spawn)
542 // delegate to fx manager
543 fxManager.addFX(Instance, DEFAULT_SPAWNED_FX_TIMED_OUT);
545 else
547 // fx isn't spwaned, so must tell fx to stop its emitters
548 if (Instance.isSystemPresent() && Instance.isValid())
550 if (!Instance.removeByID(NELID("main")) && !Instance.removeByID(NELID("STOP")))
552 // if a specific emitter has not be tagged, just stop all emitters if there's no better solution
553 Instance.activateEmitters(false);
555 fxManager.addFX(Instance, FX_MANAGER_DEFAULT_TIMEOUT, true);
557 else
559 // the system is not present yet -> delete it immediatly
560 if (scene) scene->deleteInstance(Instance);
563 Instance = NULL;
567 // *******************************************************************************************
568 void CTimedFXManager::cycleToDate(sint32 cycle, float hour, float cycleLength, float dayLength, CClientDate &result)
570 FPU_CHECKER
571 H_AUTO_USE(RZ_TimedFX)
572 if (dayLength == 0.f)
574 result.Day = 0;
575 result.Hour = 0;
577 double resultHour = (double) cycle * (double) cycleLength + (double) hour;
578 result.Day = (sint32) floor(resultHour / dayLength);
579 result.Hour = (float) (resultHour - (double) result.Day * (double) dayLength);
582 // *******************************************************************************************
583 sint32 CTimedFXManager::dateToCycle(const CClientDate &date, float cycleLength, float dayLength)
585 FPU_CHECKER
586 H_AUTO_USE(RZ_TimedFX)
587 if (cycleLength == 0.f) return 0;
588 if (dayLength == cycleLength) return date.Day;
589 double hour = (double) date.Day * (double) dayLength + (double) date.Hour;
590 return (sint32) floor(hour / cycleLength);
594 // *******************************************************************************************
595 void CTimedFXManager::CManagedFX::computeHour(sint32 cycle, float bias, CClientDate &resultDate, float cycleLength, float dayLength, const NLMISC::CNoiseValue &nv, float minHour, float maxHour) const
597 FPU_CHECKER
598 H_AUTO_USE(RZ_TimedFX)
599 // add an offset in z to get a different noise value each day
600 CVector pos = SpawnPosition + (float) cycle * CVector::K + bias * nv.Frequency * CVector::I;
601 float delta = computeUniformNoise(nv, pos);
602 if (minHour <= maxHour)
604 // convert cycle:hour to day:hour (a cycle may be something other than 24 hour)
605 cycleToDate(cycle, minHour + delta * (maxHour - minHour), cycleLength, dayLength, resultDate);
607 else
609 float hourDiff = dayLength - minHour + maxHour;
610 float hour = minHour + delta * hourDiff;
611 if (hour >= cycleLength)
613 cycleToDate(cycle + 1, hour - dayLength, cycleLength, dayLength, resultDate);
615 else
617 cycleToDate(cycle, hour, cycleLength, dayLength, resultDate);
622 // *******************************************************************************************
623 void CTimedFXManager::CManagedFX::computeStartHour(sint32 cycle, CClientDate &resultDate, float cycleLength, float dayLength, const NLMISC::CNoiseValue &nv) const
625 FPU_CHECKER
626 H_AUTO_USE(RZ_TimedFX)
627 if (!FXSheet)
629 resultDate = CClientDate();
630 return;
632 computeHour(cycle, 0.f, resultDate, cycleLength, dayLength, nv, FXSheet->StartHourMin, FXSheet->StartHourMax);
635 // *******************************************************************************************
636 void CTimedFXManager::CManagedFX::computeEndHour(sint32 cycle, CClientDate &resultDate, float cycleLength, float dayLength, const NLMISC::CNoiseValue &nv) const
638 FPU_CHECKER
639 H_AUTO_USE(RZ_TimedFX)
640 if (!FXSheet)
642 resultDate = CClientDate();
643 return;
645 if (!(FXSheet->Mode == CSeasonFXSheet::UseDuration))
647 if (FXSheet->EndHourMax < FXSheet->StartHourMin)
649 computeHour(cycle + 1, 1.f, resultDate, cycleLength, dayLength, nv, FXSheet->EndHourMin, FXSheet->EndHourMax);
651 else
653 computeHour(cycle, 1.f, resultDate, cycleLength, dayLength, nv, FXSheet->EndHourMin, FXSheet->EndHourMax);
656 else
658 // compute start hour and add duration
659 computeHour(cycle, 0.f, resultDate, cycleLength, dayLength, nv, FXSheet->StartHourMin, FXSheet->StartHourMax);
660 #ifdef DEBUG_FX
661 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);
662 #endif
663 // add duration
664 const float bias = 0.5656f; // dummy bias to get another value
665 CVector pos = SpawnPosition + (float) cycle * CVector::K + bias * nv.Frequency * CVector::I;
666 float delta = computeUniformNoise(nv, pos);
667 resultDate.Hour += delta * (FXSheet->MaxDuration - FXSheet->MinDuration) + FXSheet->MinDuration;
668 if (resultDate.Hour > dayLength)
670 resultDate.Hour -= dayLength;
671 ++resultDate.Day;
676 // *******************************************************************************************
677 void CTimedFXManager::dumpFXToAdd() const
679 FPU_CHECKER
680 H_AUTO_USE(RZ_TimedFX)
681 nlassert(_InitDone);
682 nlinfo("FX to add");
683 nlinfo("=========");
684 for(TTimeStampedFXPtrSet::const_iterator it = _FXToAdd.begin(); it != _FXToAdd.end(); ++it)
686 nlinfo("%s : day=%d, hour=%f, pos=(%.1f, %.1f, %.1f)",
687 it->FX->FXSheet ? it->FX->FXSheet->FXName.c_str() : "???",
688 (int) it->Date.Day,
689 it->Date.Hour,
690 it->FX->SpawnPosition.x,
691 it->FX->SpawnPosition.y,
692 it->FX->SpawnPosition.z
697 // *******************************************************************************************
698 void CTimedFXManager::dumpFXToRemove() const
700 FPU_CHECKER
701 H_AUTO_USE(RZ_TimedFX)
702 nlassert(_InitDone);
703 nlinfo("FX to add");
704 nlinfo("=========");
705 for(TTimeStampedFXPtrSet::const_iterator it = _FXToRemove.begin(); it != _FXToRemove.end(); ++it)
707 nlinfo("%s : day=%d, hour=%f, pos=(%.1f, %.1f, %.1f)",
708 it->FX->FXSheet ? it->FX->FXSheet->FXName.c_str() : "???",
709 (int) it->Date.Day,
710 it->Date.Hour,
711 it->FX->SpawnPosition.x,
712 it->FX->SpawnPosition.y,
713 it->FX->SpawnPosition.z
718 // *******************************************************************************************
719 void CTimedFXManager::dumpFXInfo() const
721 FPU_CHECKER
722 H_AUTO_USE(RZ_TimedFX)
723 nlassert(_InitDone);
724 nlinfo("FX Infos");
725 nlinfo("=========");
726 nlinfo("Num FX to add = %d", (int) _FXToAdd.size());
727 nlinfo("Num FX to remove = %d", (int) _FXToRemove.size());
728 uint numInstance = 0;
729 for(TManagedFXGroupList::const_iterator it = _FXGroups.begin(); it != _FXGroups.end(); ++it)
731 const TManagedFXGroup &group = it->Group;
732 for(uint k = 0; k < group.size(); ++k)
734 if (!group[k].Instance.empty()) ++ numInstance;
738 nlinfo("Num Intanciated FX = %d", (int) numInstance);
741 // *******************************************************************************************
742 void CTimedFXManager::checkIntegrity()
744 FPU_CHECKER
745 H_AUTO_USE(RZ_TimedFX)
747 // count number of instances that are not always instanciated
748 uint numInstance = 0;
749 for(TManagedFXGroupList::const_iterator it = _FXGroups.begin(); it != _FXGroups.end(); ++it)
751 const TManagedFXGroup &group = it->Group;
752 for(uint k = 0; k < group.size(); ++k)
754 if (!group[k].Instance.empty())
756 if (!(group[k].FXSheet && group[k].FXSheet->Mode == CSeasonFXSheet::AlwaysStarted))
758 ++ numInstance;
763 #ifdef NL_DEBUG
764 for(TTimeStampedFXPtrSet::iterator it = _FXToAdd.begin(); it != _FXToAdd.end(); ++it)
766 nlassert(it->FX->Magic == 0xbaadcafe);
768 for(TTimeStampedFXPtrSet::iterator it = _FXToRemove.begin(); it != _FXToRemove.end(); ++it)
770 nlassert(it->FX->Magic == 0xbaadcafe);
772 #endif
776 // *******************************************************************************************
777 void CTimedFXManager::setupUserParams(CManagedFX &fi, uint cycle)
779 FPU_CHECKER
780 H_AUTO_USE(RZ_TimedFX)
781 if (fi.FXSheet)
783 for(uint k = 0; k < 4; ++k)
785 if (fi.FXSheet->UserParamsMax[k] != fi.FXSheet->UserParamsMin[k])
787 float delta = computeUniformNoise(_Noise, fi.SpawnPosition + (float) 1.23564f * k * CVector::I + (float) cycle * 2.564848f * CVector::J);
788 fi.Instance.setUserParam(k, fi.FXSheet->UserParamsMin[k] + delta * (fi.FXSheet->UserParamsMax[k] - fi.FXSheet->UserParamsMin[k]));
790 else
792 fi.Instance.setUserParam(k, fi.FXSheet->UserParamsMin[k]);
796 else
798 for(uint k = 0; k < 4; ++k)
800 fi.Instance.setUserParam(k, 0.f);
806 // *******************************************************************************************
807 // from a fx mode, get a description string
808 const char *timedFXModeToStr(CSeasonFXSheet::TMode mode)
810 FPU_CHECKER
811 H_AUTO_USE(RZ_TimedFX)
812 switch(mode)
814 case CSeasonFXSheet::AlwaysStarted: return "always started";
815 case CSeasonFXSheet::UseEndHour: return "use end hour";
816 case CSeasonFXSheet::UseDuration: return "use duration";
817 case CSeasonFXSheet::Spawn: return "spawn";
818 default: break;
820 return "???";
823 // *******************************************************************************************
824 void CTimedFXManager::displayFXBoxes(TDebugDisplayMode displayMode) const
826 FPU_CHECKER
827 H_AUTO_USE(RZ_TimedFX)
828 Driver->setViewMatrix(Scene->getCam().getMatrix().inverted());
829 NL3D::CFrustum fr;
830 Scene->getCam().getFrustum(fr.Left, fr.Right, fr.Bottom, fr.Top, fr.Near, fr.Far);
831 fr.Perspective = true;
832 Driver->setFrustum(fr);
833 TextContext->setColor(CRGBA::Blue);
834 TextContext->setShaded(false);
835 TextContext->setShadeOutline(false);
836 TextContext->setFontSize(10);
838 float size = 0.4f;
839 float textSize = 2.5f;
840 // display fx to add
841 for(TManagedFXGroupList::const_iterator groupIt = _FXGroups.begin(); groupIt != _FXGroups.end(); ++groupIt)
843 for(uint k = 0; k < groupIt->Group.size(); ++k)
845 const CManagedFX &mf = groupIt->Group[k];
846 NLMISC::CMatrix mat = mf.getInstanceMatrix();
847 Driver->setModelMatrix(mat);
848 NLMISC::CRGBA color = CRGBA::Black;
849 switch(mf.State)
851 case CManagedFX::Permanent: color = (!mf.Instance.empty()) ? CRGBA::Magenta : CRGBA::Cyan; break;
852 case CManagedFX::InAddList: color = CRGBA::Blue; break;
853 case CManagedFX::InRemoveList: color = (!mf.Instance.empty()) ? CRGBA::Red : CRGBA::Yellow; break;
854 case CManagedFX::Unknown: break;
856 drawBox(CVector(- size, - size, - size),
857 CVector(size, size, size),
858 color);
859 std::string textToDisplay;
860 switch(displayMode)
862 case NoText:
863 break;
864 case PSName:
865 textToDisplay = mf.FXSheet->FXName;
866 break;
867 case SpawnDate:
868 switch(mf.State)
870 case CManagedFX::Permanent: textToDisplay = NLMISC::toString("PERMANENT"); break;
871 case CManagedFX::InAddList:
872 case CManagedFX::InRemoveList:
873 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)));
874 break;
875 default:
876 break;
878 break;
879 default:
880 nlassert(0);
881 break;
883 #if !FINAL_VERSION
884 if (mf.FromIG)
886 textToDisplay = "*" + textToDisplay;
888 #endif
889 if (!textToDisplay.empty())
891 TextContext->setColor(color);
892 mat.identity();
893 mat.setRot(Scene->getCam().getRotQuat());
894 mat.setPos(mf.SpawnPosition + 2.5f * size * CVector::K);
895 CVector distPos = mf.SpawnPosition - Scene->getCam().getPos();
896 mat.scale(textSize * distPos.norm());
897 TextContext->render3D(mat, textToDisplay);
904 // *******************************************************************************************
905 void CTimedFXManager::linkCandidateFX(TCandidateFXListSortedByDist &targetList, float dist, CManagedFX *fx)
907 FPU_CHECKER
908 H_AUTO_USE(RZ_TimedFX)
909 if (!fx) return;
910 uint rank = (uint) (dist / _SortDistance);
911 fx->unlinkFromCandidateFXList(); // unlink from previous list
912 rank = std::min(rank, (uint) (_CandidateFXListSortedByDist.size() - 1));
913 fx->_NextCandidateFX = targetList[rank];
914 if (targetList[rank] != NULL)
916 targetList[rank]->_PrevCandidateFX = &fx->_NextCandidateFX;
918 fx->_PrevCandidateFX = &targetList[rank]; // NB the vector won't grow so no prb
919 targetList[rank] = fx;
922 // *******************************************************************************************
923 void CTimedFXManager::linkInstanciatedFX(CManagedFX *&listHead, CManagedFX *fx)
925 FPU_CHECKER
926 H_AUTO_USE(RZ_TimedFX)
927 if (!fx) return;
928 fx->unlinkFromInstanciatedFXList();
929 fx->_NextInstanciatedFX = listHead;
930 if (listHead)
932 listHead->_PrevInstanciatedFX = &fx->_NextInstanciatedFX;
934 fx->_PrevInstanciatedFX = &listHead;
935 listHead = fx;
938 // *******************************************************************************************
939 void CTimedFXManager::updateInstanciatedFXList()
941 FPU_CHECKER
942 H_AUTO_USE(RZ_TimedFX)
943 nlassert(_InitDone);
944 // if the list has not been modified since last time, no-op
945 if (!_CandidateFXListTouched) return;
946 // algo : we take at most '_MaxNumberOfFXInstances' from the start of the updated list of candidate fxs (they are roughly sorted by distance)
947 // these fx are inserted in new list of instanciated fxs. All fx that haven't been inserted (they remains in the previous list)
948 // are discarded. At the end the new list replaces the previous one
949 CManagedFX *toInstanciateListHead = NULL;
950 CManagedFX *alreadyInstanciatedListHead = NULL;
951 uint numInstances = 0;
952 uint numDistanceRanges = (uint)_CandidateFXListSortedByDist.size();
953 sint maxNumPossibleInstance = (sint) (_MaxNumberOfFXInstances - _FXManager.getNumFXtoRemove());
954 if (maxNumPossibleInstance > 0)
956 // NB: Ideally _CandidateFXListSortedByDist is small as a vector, so it's ok to traverse it in its entirety.
957 for(uint k = 0; k < numDistanceRanges; ++k)
959 CManagedFX *currCandidate = _CandidateFXListSortedByDist[k];
960 while(currCandidate)
962 nlassert(currCandidate->_PrevCandidateFX);
963 // if fx already instanciated, put in special list
964 if (currCandidate->_PrevInstanciatedFX != NULL)
966 linkInstanciatedFX(alreadyInstanciatedListHead, currCandidate);
968 else
970 // not already instanciated
971 linkInstanciatedFX(toInstanciateListHead, currCandidate);
973 ++ numInstances;
974 if (numInstances == (uint) maxNumPossibleInstance) break; // max number of instances reached
975 currCandidate = currCandidate->_NextCandidateFX;
977 if (numInstances == (uint) maxNumPossibleInstance) break; // max number of instances reached
980 // removes old instances (those that were instanciated at previous frame, but are not anymore)
981 CManagedFX *currFXToRemove = _InstanciatedFXs;
982 while (currFXToRemove)
984 CManagedFX *tmp = currFXToRemove;
985 nlassert(tmp->_PrevInstanciatedFX);
986 currFXToRemove = currFXToRemove->_NextInstanciatedFX;
988 // shut the fx down
989 tmp->shutDown(_Scene, _FXManager);
991 // fast unlink (all instance are removed from that list)
992 tmp->_PrevInstanciatedFX = NULL;
993 tmp->_NextInstanciatedFX = NULL;
995 // create new instances
996 CManagedFX *prevFXToInstanciate = NULL;
997 CManagedFX *currFXToInstanciate = toInstanciateListHead;
998 if (maxNumPossibleInstance > 0)
1000 while (currFXToInstanciate)
1002 nlassert(currFXToInstanciate->_PrevInstanciatedFX);
1003 bool oktoCreate = true;
1004 // special case : if the fx must be spawned, then it must be a valid candidate at the frame it is inserted, otherwise
1005 // it is not created at all (this is to avoid it to spawn at a later date, when it's too late)
1006 if (currFXToInstanciate->FXSheet && currFXToInstanciate->FXSheet->Mode == CSeasonFXSheet::Spawn)
1008 if (currFXToInstanciate->SetHandle->Date != _CurrDate)
1010 oktoCreate = false;
1011 // 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)
1014 if (oktoCreate)
1016 nlassert(currFXToInstanciate->Instance.empty());
1017 // if fx was in shutting down state, delete the instance
1018 NL3D::UInstance ts = _Scene->createInstance(currFXToInstanciate->FXSheet->FXName);
1019 if (ts.empty())
1021 currFXToInstanciate->Instance.detach();
1023 else
1025 currFXToInstanciate->Instance.cast (ts);
1026 if (currFXToInstanciate->Instance.empty())
1028 // bad type, removes the shape
1029 _Scene->deleteInstance(ts);
1031 else
1033 currFXToInstanciate->Instance.setTransformMode(NL3D::UTransform::DirectMatrix);
1034 currFXToInstanciate->Instance.setMatrix(currFXToInstanciate->getInstanceMatrix());
1035 if (currFXToInstanciate->State == CManagedFX::Permanent)
1037 setupUserParams(*currFXToInstanciate,0);
1039 else
1041 setupUserParams(*currFXToInstanciate, currFXToInstanciate->SetHandle->Date.Day);
1043 currFXToInstanciate->Instance.freezeHRC();
1047 prevFXToInstanciate = currFXToInstanciate;
1048 currFXToInstanciate = currFXToInstanciate->_NextInstanciatedFX;
1051 // merge toInstanciateListHead & alreadyInstanciatedListHead together
1052 // this becomes the new list of instanciated fxs
1053 if (prevFXToInstanciate)
1055 nlassert(prevFXToInstanciate->_NextInstanciatedFX == NULL);
1056 prevFXToInstanciate->_NextInstanciatedFX = alreadyInstanciatedListHead;
1057 if (alreadyInstanciatedListHead)
1059 alreadyInstanciatedListHead->_PrevInstanciatedFX = &prevFXToInstanciate->_NextInstanciatedFX;
1061 // set new list
1062 _InstanciatedFXs = toInstanciateListHead;
1063 toInstanciateListHead->_PrevInstanciatedFX = &_InstanciatedFXs;
1065 else
1067 // set new list
1068 _InstanciatedFXs = alreadyInstanciatedListHead;
1069 if (alreadyInstanciatedListHead)
1071 alreadyInstanciatedListHead->_PrevInstanciatedFX = &_InstanciatedFXs;
1074 _CandidateFXListTouched = false;
1077 // *******************************************************************************************
1078 void CTimedFXManager::updateCandidateFXListSorting()
1080 FPU_CHECKER
1081 H_AUTO_USE(RZ_TimedFX)
1082 // re-sort all instances by distance
1083 nlassert(_CandidateFXListSortedByDist.size() == _CandidateFXListSortedByDistTmp.size());
1084 uint numDistanceRanges = (uint)_CandidateFXListSortedByDist.size();
1085 for(uint k = 0; k < numDistanceRanges; ++k)
1087 CManagedFX *currCandidate = _CandidateFXListSortedByDist[k];
1088 while(currCandidate)
1090 CManagedFX *tmp = currCandidate;
1091 currCandidate = currCandidate->_NextCandidateFX;
1092 float dist = (tmp->SpawnPosition - _LastCamPos).norm();
1093 // unlink & relink in new list
1094 linkCandidateFX(_CandidateFXListSortedByDistTmp, dist, tmp);
1097 // swap the 2 list
1098 _CandidateFXListSortedByDist.swap(_CandidateFXListSortedByDistTmp);
1101 // *******************************************************************************************
1102 void CTimedFXManager::setMaxNumFXInstances(uint maxNumInstances)
1104 FPU_CHECKER
1105 H_AUTO_USE(RZ_TimedFX)
1106 _MaxNumberOfFXInstances = maxNumInstances;
1107 _CandidateFXListTouched = true;
1110 // *******************************************************************************************
1111 // temp, for debug
1112 NLMISC_COMMAND(dumpFXToAdd, "dumpFXToAdd", "<>")
1114 if (!args.empty()) return false;
1115 CTimedFXManager::getInstance().dumpFXToAdd();
1116 return true;
1119 // *******************************************************************************************
1120 // temp, for debug
1121 NLMISC_COMMAND(dumpFXToRemove, "dumpFXToRemove", "<>")
1123 if (!args.empty()) return false;
1124 CTimedFXManager::getInstance().dumpFXToRemove();
1125 return true;
1129 // *******************************************************************************************
1130 // temp, for debug
1131 NLMISC_COMMAND(dumpFXInfo, "dumpFXInfo", "<>")
1133 if (!args.empty()) return false;
1134 CTimedFXManager::getInstance().dumpFXInfo();
1135 return true;
1139 extern class CIGCallback *IGCallbacks;
1141 // *******************************************************************************************
1142 // temp, for debug
1143 NLMISC_COMMAND(updateSeason, "updateSeason", "<>")
1145 if (!args.empty()) return false;
1146 if (IGCallbacks) IGCallbacks->changeSeason();
1147 return true;
1150 // *******************************************************************************************
1151 // temp, for debug
1152 NLMISC_COMMAND(setMaxNumTimedFXs, "set the max number of timed fx that are visible at a time", "<numInst>")
1154 if (args.size() != 1) return false;
1155 uint maxNumInstances;
1156 fromString(args[0], maxNumInstances);
1157 CTimedFXManager::getInstance().setMaxNumFXInstances(maxNumInstances);
1158 return true;