Fix "no remove aqua speed" bug when player leaves the water
[ryzomcore.git] / nel / src / sound / clustered_sound.cpp
blob4fef8912822dd6e3885843b95c456ef73e14fca0
1 // NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
2 // Copyright (C) 2010 Winch Gate Property Limited
3 //
4 // This source file has been modified by the following contributors:
5 // Copyright (C) 2010 Matt RAYKOWSKI (sfb) <matt.raykowski@gmail.com>
6 // Copyright (C) 2012-2019 Jan BOON (Kaetemi) <jan.boon@kaetemi.be>
7 //
8 // This program is free software: you can redistribute it and/or modify
9 // it under the terms of the GNU Affero General Public License as
10 // published by the Free Software Foundation, either version 3 of the
11 // License, or (at your option) any later version.
13 // This program is distributed in the hope that it will be useful,
14 // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 // GNU Affero General Public License for more details.
18 // You should have received a copy of the GNU Affero General Public License
19 // along with this program. If not, see <http://www.gnu.org/licenses/>.
21 #include "stdsound.h"
22 #include "nel/misc/string_mapper.h"
23 #include "nel/misc/hierarchical_timer.h"
24 #include "nel/georges/u_form.h"
25 #include "nel/georges/u_form_elm.h"
26 #include "nel/georges/load_form.h"
27 //#include "nel/3d/std3d.h"
28 #include "nel/3d/scene.h"
29 #include "nel/3d/scene_user.h"
30 #include "nel/3d/cluster.h"
31 #include "nel/3d/portal.h"
32 #include "nel/sound/driver/listener.h"
33 #include "nel/sound/audio_mixer_user.h"
34 #include "nel/sound/driver/sound_driver.h"
35 #include "nel/sound/driver/effect.h"
36 #include "nel/sound/clustered_sound.h"
38 using namespace std;
39 using namespace NLMISC;
40 using namespace NL3D;
42 namespace NLSOUND
45 #if EAX_AVAILABLE == 1
46 // An array to report all EAX predefined meterials
47 float EAX_MATERIAL_PARAM[][3] =
49 {EAX_MATERIAL_SINGLEWINDOW}, {EAX_MATERIAL_SINGLEWINDOWLF}, {EAX_MATERIAL_SINGLEWINDOWROOMRATIO},
50 {EAX_MATERIAL_DOUBLEWINDOW}, {EAX_MATERIAL_DOUBLEWINDOWHF}, {EAX_MATERIAL_DOUBLEWINDOWHF},
51 {EAX_MATERIAL_THINDOOR}, {EAX_MATERIAL_THINDOORLF}, {EAX_MATERIAL_THINDOORROOMRATIO},
52 {EAX_MATERIAL_THICKDOOR}, {EAX_MATERIAL_THICKDOORLF}, {EAX_MATERIAL_THICKDOORROOMRTATION},
53 {EAX_MATERIAL_WOODWALL}, {EAX_MATERIAL_WOODWALLLF}, {EAX_MATERIAL_WOODWALLROOMRATIO},
54 {EAX_MATERIAL_BRICKWALL}, {EAX_MATERIAL_BRICKWALLLF}, {EAX_MATERIAL_BRICKWALLROOMRATIO},
55 {EAX_MATERIAL_STONEWALL}, {EAX_MATERIAL_STONEWALLLF}, {EAX_MATERIAL_STONEWALLROOMRATIO},
56 {EAX_MATERIAL_CURTAIN}, {EAX_MATERIAL_CURTAINLF}, {EAX_MATERIAL_CURTAINROOMRATIO}
58 #else // EAX_AVAILABLE
59 // No EAX, just have an array of gain factor to apply for each material type
60 float EAX_MATERIAL_PARAM[] =
62 float(pow((double)10, (double)-2800/2000)),
63 float(pow((double)10, (double)-5000/2000)),
64 float(pow((double)10, (double)-1800/2000)),
65 float(pow((double)10, (double)-4400/2000)),
66 float(pow((double)10, (double)-4000/2000)),
67 float(pow((double)10, (double)-5000/2000)),
68 float(pow((double)10, (double)-6000/2000)),
69 float(pow((double)10, (double)-1200/2000))
71 #endif // EAX_AVAILABLE
73 // An utility class to handle packed sheet loading/saving/updating
74 class CSoundGroupSerializer
76 public:
77 std::vector<std::pair<NLMISC::TStringId, NLMISC::TStringId> > _SoundGroupAssoc;
79 // load the values using the george sheet (called by GEORGE::loadForm)
80 void readGeorges (const NLMISC::CSmartPtr<NLGEORGES::UForm> &form, const std::string &/* name */)
82 try
84 NLGEORGES::UFormElm &root = form->getRootNode();
85 NLGEORGES::UFormElm *items;
86 uint size;
87 root.getNodeByName(&items, ".Items");
88 items->getArraySize(size);
90 for (uint i=0; i<size; ++i)
92 std::string soundGroup;
93 std::string sound;
95 NLGEORGES::UFormElm *item;
97 items->getArrayNode(&item, i);
99 item->getValueByName(soundGroup, ".SoundGroup");
100 item->getValueByName(sound, ".Sound");
102 string::size_type n = sound.rfind(".sound");
104 if (n != string::npos)
106 // remove the tailing .sound
107 sound = sound.substr(0, n);
110 _SoundGroupAssoc.push_back(make_pair(CStringMapper::map(soundGroup), CStringMapper::map(sound)));
113 catch(...)
118 // load/save the values using the serial system (called by GEORGE::loadForm)
119 void serial (NLMISC::IStream &s)
121 uint32 size;
122 if (!s.isReading())
124 size = (uint32)_SoundGroupAssoc.size();
126 s.serial(size);
128 for (uint i=0; i<size; ++i)
130 if (s.isReading())
132 std::string soundGroup;
133 std::string sound;
135 s.serial(soundGroup);
136 s.serial(sound);
138 _SoundGroupAssoc.push_back(make_pair(CStringMapper::map(soundGroup), CStringMapper::map(sound)));
140 else
142 std::string soundGroup;
143 std::string sound;
145 soundGroup = CStringMapper::unmap(_SoundGroupAssoc[i].first);
146 sound = CStringMapper::unmap(_SoundGroupAssoc[i].second);
148 s.serial(soundGroup);
149 s.serial(sound);
154 /** called by GEORGE::loadForm when a sheet read from the packed sheet is no more in
155 * the directories.
157 void removed()
159 // nothing to do
162 // return the version of this class, increments this value when the content of this class changed
163 static uint getVersion () { return 1; }
167 CClusteredSound::CClusteredSound()
168 : _Scene(0),
169 _RootCluster(0),
170 _LastEnv(CStringMapper::emptyId()),
171 _LastEnvSize(-1.0f) // size goes from 0.0f to 100.0f
176 void CClusteredSound::buildSheets(const std::string &packedSheetPath)
178 std::map<std::string, CSoundGroupSerializer> container;
179 ::loadForm("sound_group", packedSheetPath + "sound_groups.packed_sheets", container, true, false);
182 void CClusteredSound::init(NL3D::CScene *scene, float portalInterpolate, float maxEarDist, float minGain)
184 // load the sound_group sheets
185 std::map<std::string, CSoundGroupSerializer> container;
186 ::loadForm("sound_group", CAudioMixerUser::instance()->getPackedSheetPath()+"sound_groups.packed_sheets", container, CAudioMixerUser::instance()->getPackedSheetUpdate(), false);
188 // copy the container data into internal structure
189 std::map<std::string, CSoundGroupSerializer>::iterator first(container.begin()), last(container.end());
190 for (; first != last; ++first)
192 _SoundGroupToSound.insert(first->second._SoundGroupAssoc.begin(), first->second._SoundGroupAssoc.end());
195 // and clear the temporary Container
196 container.clear();
199 _Scene = scene;
200 _PortalInterpolate = portalInterpolate;
201 _MaxEarDistance = maxEarDist;
202 _MinGain = minGain;
203 if(scene != 0)
205 _RootCluster = _Scene->getClipTrav().RootCluster;
207 else
208 _RootCluster = 0;
211 void CClusteredSound::update(const CVector &listenerPos, const CVector &/* view */, const CVector &/* up */)
213 H_AUTO(NLSOUND_ClusteredSoundUpdate)
214 if (_Scene == 0)
216 // hum... what to do ?
217 static bool bDisplayOnce = false;
218 if (!bDisplayOnce)
220 nlinfo("CClusteredSound::update : no scene specified !");
221 bDisplayOnce = true;
223 return;
226 CClipTrav &clipTrav = _Scene->getClipTrav ();
228 // Retreive the list of cluster where the listener is
229 vector<CCluster*> vCluster;
230 clipTrav.fullSearch (vCluster, listenerPos);
233 // reset the audible cluster map
234 _AudibleClusters.clear();
236 // create the initial travesal context
237 CSoundTravContext stc(listenerPos, false, false);
239 // and start the cluster traversal to find out what cluster is audible and how we ear it
240 soundTraverse(vCluster, stc);
242 //-----------------------------------------------------
243 // update the clustered sound (create and stop sound)
244 //-----------------------------------------------------
246 // std::hash_map<uint, CClusterSound> newSources;
247 TClusterSoundCont newSources;
250 // fake the distance for all playing source
251 // std::map<std::string, CClusterSound>::iterator first(_Sources.begin()), last(_Sources.end());
252 TClusterSoundCont::iterator first(_Sources.begin()), last(_Sources.end());
253 for (; first != last; ++first)
255 first->second.Distance = FLT_MAX;
260 TClusterStatusMap::const_iterator first(_AudibleClusters.begin()), last(_AudibleClusters.end());
261 for (; first != last; ++first )
263 static NLMISC::TStringId NO_SOUND_GROUP = CStringMapper::emptyId();
264 const CClusterSoundStatus &css = first->second;
265 CCluster *cluster = first->first;
266 NLMISC::TStringId soundGroup;
268 soundGroup = cluster->getSoundGroupId();
271 if (soundGroup != NO_SOUND_GROUP)
273 // search an associated sound name
274 TClusterSoundCont::iterator it(_Sources.find(soundGroup));
275 if (it != _Sources.end())
277 // the source is already playing, check and replace if needed
278 CClusterSound &cs = it->second;
280 if (cs.Distance >= css.Dist)
282 // this one is better !
283 cs.Distance = css.Dist;
284 cs.Source->setPos(listenerPos + css.Direction * css.Dist + CVector(0,0,2));
285 if (css.DistFactor < 1.0f)
286 cs.Source->setRelativeGain(css.Gain * (1.0f - (css.DistFactor*css.DistFactor*css.DistFactor*css.DistFactor)));
287 else
288 cs.Source->setRelativeGain(css.Gain);
290 newSources.insert(make_pair(soundGroup, cs));
292 else
294 // create a new source
296 // nldebug("Searching sound assoc for group [%s]", CStringMapper::unmap(soundGroup).c_str());
298 TStringStringMap::iterator it2(_SoundGroupToSound.find(soundGroup));
299 if (it2 != _SoundGroupToSound.end())
301 NLMISC::TStringId soundName = it2->second;
302 CClusterSound cs;
304 // nldebug("Found the sound [%s] for sound group [%s]", CStringMapper::unmap(soundName).c_str(), CStringMapper::unmap(soundGroup).c_str());
306 cs.Distance = css.Dist;
307 cs.Source = CAudioMixerUser::instance()->createSource(soundName, false, NULL, NULL, cluster);
308 if (cs.Source != 0)
310 cs.Source->setPos(listenerPos + css.Direction * css.Dist + CVector(0,0,2));
311 if (css.DistFactor < 1.0f)
312 cs.Source->setRelativeGain(css.Gain * (1.0f - (css.DistFactor*css.DistFactor/**css.DistFactor*css.DistFactor*/)));
313 else
314 cs.Source->setRelativeGain(css.Gain);
315 cs.Source->setLooping(true);
316 newSources.insert(make_pair(soundGroup, cs));
322 // check for source to stop
324 TClusterSoundCont oldSources;
325 oldSources.swap(_Sources);
327 TClusterSoundCont::iterator first(newSources.begin()), last(newSources.end());
328 for (; first != last; ++first)
330 _Sources.insert(*first);
331 if (!first->second.Source->isPlaying())
332 first->second.Source->play();
334 oldSources.erase(first->first);
337 while (!oldSources.empty())
339 CClusterSound &cs = oldSources.begin()->second;
340 delete cs.Source;
341 oldSources.erase(oldSources.begin());
345 // update the environment effect (if any)
346 CAudioMixerUser *mixer = CAudioMixerUser::instance();
347 if (mixer->useEnvironmentEffects() && !vCluster.empty())
349 H_AUTO(NLSOUND_ClusteredSound_updateEnvFx)
350 TStringId fxId = vCluster[0]->getEnvironmentFxId();
351 const CAABBox &box = vCluster[0]->getBBox();
352 CVector vsize = box.getHalfSize();
353 float size = NLMISC::minof(vsize.x, vsize.y, vsize.z) * 2;
355 // special case for root cluster (ie, external)
356 if (vCluster[0] == _RootCluster)
358 // this is the root cluster. This cluster have a size of 0 !
359 size = 100.f;
361 else
363 // else, clip the env size to max eax supported size
364 clamp(size, 1.f, 100.f);
367 // only update environment if there is some change.
368 if (fxId != _LastEnv || size != _LastEnvSize)
370 nldebug("AM: CClusteredSound => setEnvironment %s %f", CStringMapper::unmap(fxId).c_str(), size);
371 mixer->setEnvironment(fxId, size);
372 _LastEnv = fxId;
373 _LastEnvSize = size;
378 const CClusteredSound::CClusterSoundStatus *CClusteredSound::getClusterSoundStatus(NL3D::CCluster *cluster)
380 TClusterStatusMap::iterator it(_AudibleClusters.find(cluster));
382 if (it == _AudibleClusters.end())
384 return 0;
386 else
387 return &(it->second);
391 NL3D::CCluster *CClusteredSound::getRootCluster()
393 if (_Scene == 0)
394 return 0;
396 return _Scene->getClipTrav().RootCluster;
400 void CClusteredSound::soundTraverse(const std::vector<CCluster *> &clusters, CSoundTravContext &travContext)
402 H_AUTO(NLSOUND_soundTraverse)
403 // std::map<CCluster*, CSoundTravContext> nextTraverse;
404 std::vector<std::pair<const CCluster*, CSoundTravContext> > curClusters;
405 CVector realListener (travContext.ListenerPos);
407 _AudioPath.clear();
409 // fill the initial cluster liste
410 CClusterSoundStatus css;
411 css.Direction = CVector::Null;
412 css.DistFactor = 0.0f;
413 css.Dist = 0.0f;
414 css.Gain = 1.0f;
415 css.Occlusion = 0;
416 css.OcclusionLFFactor = 1.0f;
417 css.OcclusionRoomRatio = 1.0f;
418 css.Obstruction = 0;
419 css.PosAlpha = 0;
420 // css.Position = CVector::Null;
421 css.Position = realListener;
423 for (uint i=0; i<clusters.size(); ++i)
425 bool valid = true;
426 // eliminate cluster when listener is behind their portals AND inside the other cluster
427 for (uint j=0; j<clusters[i]->getNbPortals(); j++)
429 CPortal *portal = clusters[i]->getPortal(j);
430 const std::vector<CVector> &poly = portal->getPoly();
432 if (poly.size() < 3)
434 // only warn once, avoid log flooding !
435 static std::set<std::string> warned;
436 if (warned.find(clusters[i]->Name) == warned.end())
438 nlwarning("Cluster [%s] contains a portal [%s] with less than 3 vertex !",
439 clusters[i]->Name.c_str(), portal->getName().empty() ? "no name" : portal->getName().c_str());
440 warned.insert(clusters[i]->Name);
442 valid = false;
443 continue;
445 CVector normal = (poly[0] - poly[1]) ^ (poly[2] - poly[1]);
447 float dist = (realListener - poly[0]) * normal;
448 float dist2 = (clusters[i]->getBBox().getCenter() - poly[0]) * normal;
450 if ((dist < 0 && dist2 > 0) || (dist > 0 && dist2 < 0))
452 if (portal->getCluster(0) == clusters[i])
454 if (find(clusters.begin(), clusters.end(), portal->getCluster(1)) != clusters.end())
456 valid = false;
457 continue;
460 else if (find(clusters.begin(), clusters.end(), portal->getCluster(0)) != clusters.end())
462 valid = false;
463 continue;
469 if (portal->getCluster(0) == clusters[i] && dist > 0)
470 // if (!portal->isInFront(realListener))
472 valid = false;
473 continue;
475 else if (portal->getCluster(1) == clusters[i] && dist < 0)
476 // if (portal->isInFront(realListener))
478 valid = false;
479 continue;
483 if( valid)
485 curClusters.push_back(make_pair(clusters[i], travContext));
486 addAudibleCluster(clusters[i], css);
492 // add the next traverse (if any)
493 std::copy(_NextTraversalStep.begin(), _NextTraversalStep.end(), std::back_inserter(curClusters));
494 _NextTraversalStep.clear();
496 while (!curClusters.empty())
498 CCluster * cluster = const_cast<CCluster*>(curClusters.back().first);
499 CSoundTravContext &travContext = curClusters.back().second;
501 CClusterSoundStatus css;
502 css.DistFactor = 0.0f;
503 css.Position = CVector::Null;
504 css.PosAlpha = 0.0f;
505 css.Gain = travContext.Gain;
506 css.Dist = travContext.Dist;
507 css.Direction = travContext.Direction;
508 css.Occlusion = travContext.Occlusion;
509 css.OcclusionLFFactor = travContext.OcclusionLFFactor;
510 css.OcclusionRoomRatio = travContext.OcclusionRoomRatio;
511 css.Obstruction = travContext.Obstruction;
513 // store this cluster and it's parameters
514 _AudibleClusters.insert(make_pair(cluster, css));
516 // 1st, look each portal
517 uint i;
518 for (i=0; i<cluster->getNbPortals(); ++i)
520 CPortal *portal = cluster->getPortal(i);
521 // get the other cluster
522 CCluster *otherCluster = portal->getCluster(0);
523 bool clusterInFront = true;
524 if (otherCluster == cluster)
526 otherCluster = portal->getCluster(1);
527 clusterInFront = false;
529 nlassert(otherCluster != cluster);
531 if (otherCluster && travContext.PreviousCluster != otherCluster) // && (!travContext.FilterUnvisibleChild || otherCluster->AudibleFromFather))
533 const vector<CVector> &poly = portal->getPoly();
535 // a security test
536 if (poly.size() < 3)
538 // only warn once, avoid log flooding !
539 static std::set<std::string> warned;
540 if (warned.find(cluster->Name) == warned.end())
542 nlwarning("Cluster [%s] contains a portal [%s] with less than 3 vertex !",
543 cluster->Name.c_str(), portal->getName().empty() ? "no name" : portal->getName().c_str());
544 warned.insert(cluster->Name);
547 else
550 // Test to skip portal with suface > 40 m2 (aprox)
551 float surface = ((poly[2]-poly[1]) ^ (poly[0]-poly[1])).norm();
552 if (surface > 340 /* && otherCluster->isIn(travContext.ListenerPos, travContext.MaxDist-travContext.Dist)*/)
554 float minDist;
555 CVector nearPos;
556 CAABBox box = otherCluster->getBBox();
558 minDist = getAABoxNearestPos(box, travContext.ListenerPos, nearPos);
560 if (travContext.Dist + minDist < _MaxEarDistance)
562 CVector soundDir = (nearPos - travContext.ListenerPos).normed();
563 CClusterSoundStatus css;
564 css.Gain = travContext.Gain;
565 css.Dist = travContext.Dist + minDist;
566 css.Occlusion = travContext.Occlusion;
567 css.OcclusionLFFactor = travContext.OcclusionLFFactor;
568 css.Obstruction = travContext.Obstruction;
569 css.OcclusionRoomRatio = travContext.OcclusionRoomRatio;
570 css.DistFactor = css.Dist / _MaxEarDistance;
571 css.Direction = travContext.Direction;
573 float alpha = travContext.Alpha;
574 CVector d1(travContext.Direction1), d2;
576 css.Direction = interpolateSourceDirection(travContext, css.Dist+travContext.Dist, nearPos, travContext.ListenerPos /*realListener*/, d1, d2, alpha);
577 css.Position = nearPos + css.Dist * css.Direction;
578 css.PosAlpha = min(1.0f, css.Dist / _PortalInterpolate);
580 if (addAudibleCluster(otherCluster, css))
582 // debugLines.push_back(CLine(travContext.ListenerPos, nearPos));
583 CSoundTravContext stc(travContext);
584 stc.FilterUnvisibleChild = true;
585 stc.Direction1 = d1;
586 stc.Direction2 = d2;
587 stc.Direction = css.Direction;
588 stc.PreviousCluster = cluster;
589 stc.Alpha = alpha;
590 stc.PreviousVector = (nearPos - travContext.ListenerPos).normed();
591 addNextTraverse(otherCluster, stc);
592 _AudioPath.push_back(make_pair(travContext.ListenerPos, nearPos));
596 else
598 // find the nearest point of this portal (either on of the perimeter vertex or a point on the portal surface)
599 float minDist;
600 CVector nearPos;
602 minDist = getPolyNearestPos(poly, travContext.ListenerPos, nearPos);
604 if (travContext.Dist+minDist < _MaxEarDistance)
606 // note: this block of code is a mess and should be cleaned up and commented =)
607 // TODO : compute relative gain according to portal behavior.
608 CClusterSoundStatus css;
609 css.Gain = travContext.Gain;
610 CVector soundDir = (nearPos - travContext.ListenerPos).normed();
611 /* ****** Todo: OpenAL EFX & XAudio2 implementation of Occlusion & Obstruction (not implemented for fmod anyways) !!! ******
612 TStringId occId = portal->getOcclusionModelId();
613 TStringIntMap::iterator it(_IdToMaterial.find(occId));
614 ****** Todo: OpenAL EFX & XAudio2 implementation of Occlusion & Obstruction (not implemented for fmod anyways) !!! ****** */
616 #if EAX_AVAILABLE == 1 // EAX_AVAILABLE no longer used => TODO: implement with EFX and remove when new implementation OK.
617 if (it != _IdToMaterial.end())
619 // found an occlusion material for this portal
620 uint matId = it->second;
621 css.Occlusion = max(sint32(EAXBUFFER_MINOCCLUSION), sint32(travContext.Occlusion + EAX_MATERIAL_PARAM[matId][0])); //- 1800); //EAX_MATERIAL_THINDOOR;
622 css.OcclusionLFFactor = travContext.OcclusionLFFactor * EAX_MATERIAL_PARAM[matId][1]; //EAX_MATERIAL_THICKDOORLF; //0.66f; //0.0f; //min(EAX_MATERIAL_THINDOORLF, travContext.OcclusionLFFactor);
623 css.OcclusionRoomRatio = EAX_MATERIAL_PARAM[matId][2] * travContext.OcclusionRoomRatio;
625 else
627 // the id does not match any know material
628 css.Occlusion = travContext.Occlusion;
629 css.OcclusionLFFactor = travContext.OcclusionLFFactor;
630 css.OcclusionRoomRatio = travContext.OcclusionRoomRatio;
632 #else // EAX_AVAILABLE
633 /* ****** Todo: OpenAL EFX & XAudio2 implementation of Occlusion & Obstruction (not implemented for fmod anyways) !!! ******
634 if (it != _IdToMaterial.end())
636 // found an occlusion material for this portal
637 uint matId = it->second;
638 css.Gain *= EAX_MATERIAL_PARAM[matId];
640 ****** Todo: OpenAL EFX & XAudio2 implementation of Occlusion & Obstruction (not implemented for fmod anyways) !!! ****** */
641 #endif // EAX_AVAILABLE
642 /* if (portal->getOcclusionModel() == "wood door")
644 // css.Gain *= 0.5f;
645 #if EAX_AVAILABLE == 1
646 css.Occlusion = max(EAXBUFFER_MINOCCLUSION, travContext.Occlusion + EAX_MATERIAL_THICKDOOR); //- 1800); //EAX_MATERIAL_THINDOOR;
647 css.OcclusionLFFactor = 0.1f * travContext.OcclusionLFFactor; //EAX_MATERIAL_THICKDOORLF; //0.66f; //0.0f; //min(EAX_MATERIAL_THINDOORLF, travContext.OcclusionLFFactor);
648 css.OcclusionRoomRatio = EAX_MATERIAL_THICKDOORROOMRATION * travContext.OcclusionRoomRatio;
649 #else
650 css.Gain *= 0.5f;
651 #endif
653 else if (portal->getOcclusionModel() == "brick door")
655 #if EAX_AVAILABLE == 1
656 css.Occlusion = max(EAXBUFFER_MINOCCLUSION, travContext.Occlusion + EAX_MATERIAL_BRICKWALL);
657 css.OcclusionLFFactor = min(EAX_MATERIAL_BRICKWALLLF, travContext.OcclusionLFFactor);
658 css.OcclusionRoomRatio = EAX_MATERIAL_BRICKWALLROOMRATIO * travContext.OcclusionRoomRatio;
659 #else
660 css.Gain *= 0.2f;
661 #endif
663 else
665 #if EAX_AVAILABLE == 1
666 css.Occlusion = travContext.Occlusion;
667 css.OcclusionLFFactor = travContext.OcclusionLFFactor;
668 css.OcclusionRoomRatio = travContext.OcclusionRoomRatio;
669 #endif
672 */ // compute obstruction
673 if (travContext.NbPortal >= 1)
675 float h = soundDir * travContext.PreviousVector;
676 float obst;
678 if (h < 0)
680 // obst = float(2000 + asinf(-(soundDir ^ travContext.PreviousVector).norm()) / (Pi/2) * 2000);
681 obst = float(4000 - (soundDir ^ travContext.PreviousVector).norm() * 2000);
683 else
685 // obst = float(asinf((soundDir ^ travContext.PreviousVector).norm()) / (Pi/2) * 2000);
686 obst = float((soundDir ^ travContext.PreviousVector).norm() * 2000);
689 // float sqrdist = (realListener - nearPoint).sqrnorm();
690 if (travContext.Dist < 2.0f) // interpolate a 2 m
691 obst *= travContext.Dist / 2.0f;
692 #if EAX_AVAILABLE == 1 // EAX_AVAILABLE no longer used => TODO: implement with EFX and remove when new implementation OK.
693 css.Obstruction = max(sint32(EAXBUFFER_MINOBSTRUCTION), sint32(travContext.Obstruction - sint32(obst)));
694 css.OcclusionLFFactor = 0.50f * travContext.OcclusionLFFactor;
695 #else
696 css.Gain *= float(pow(10, -(obst/4)/2000));
697 #endif
699 else
700 css.Obstruction = travContext.Obstruction;
701 // css.Dist = travContext.Dist + float(sqrt(minDist));
702 css.Dist = travContext.Dist + minDist;
703 css.DistFactor = css.Dist / _MaxEarDistance;
704 float portalDist = css.Dist;
705 float alpha = travContext.Alpha;
706 CVector d1(travContext.Direction1), d2(travContext.Direction2);
708 css.Direction = interpolateSourceDirection(travContext, portalDist+travContext.Dist, nearPos, travContext.ListenerPos /*realListener*/, d1, d2, alpha);
709 css.Position = nearPos + css.Dist * css.Direction;
710 css.PosAlpha = min(1.0f, css.Dist / _PortalInterpolate);
712 if (addAudibleCluster(otherCluster, css))
714 // debugLines.push_back(CLine(travContext.ListenerPos, nearPoint));
715 CSoundTravContext tc(nearPos, travContext.FilterUnvisibleChild, !cluster->AudibleFromFather);
716 tc.Dist = css.Dist;
717 tc.Gain = css.Gain;
718 tc.Occlusion = css.Occlusion;
719 tc.OcclusionLFFactor = css.OcclusionLFFactor;
720 tc.OcclusionRoomRatio = css.OcclusionRoomRatio;
721 tc.Obstruction = css.Obstruction;
722 tc.Direction1 = d1;
723 tc.Direction2 = d2;
724 tc.NbPortal = travContext.NbPortal+1;
725 tc.Direction = css.Direction;
726 tc.PreviousCluster = cluster;
727 tc.Alpha = alpha;
728 tc.PreviousVector = soundDir;
730 addNextTraverse(otherCluster, tc);
731 _AudioPath.push_back(make_pair(travContext.ListenerPos, nearPos));
739 // 2nd, look each child cluster
740 for (i=0; i<cluster->Children.size(); ++i)
742 CCluster *c = cluster->Children[i];
744 // dont redown into an upstream
745 if (c != travContext.PreviousCluster)
747 // clip on distance.
748 if (c->AudibleFromFather && c->isIn(travContext.ListenerPos, _MaxEarDistance-travContext.Dist))
750 float minDist;
751 CVector nearPos;
752 CAABBox box = c->getBBox();
754 minDist = getAABoxNearestPos(box, travContext.ListenerPos, nearPos);
756 if (travContext.Dist + minDist < _MaxEarDistance)
758 CClusterSoundStatus css;
759 css.Gain = travContext.Gain;
760 css.Dist = travContext.Dist + minDist;
761 css.DistFactor = css.Dist / _MaxEarDistance;
762 css.Occlusion = travContext.Occlusion;
763 css.OcclusionLFFactor = travContext.OcclusionLFFactor;
764 css.OcclusionRoomRatio = travContext.OcclusionRoomRatio;
765 css.Obstruction = travContext.Obstruction;
766 /* if (travContext.NbPortal == 0)
767 css.Direction = (nearPos - travContext.ListenerPos).normed();
768 else
769 css.Direction = travContext.Direction1;
771 float alpha = travContext.Alpha;
772 CVector d1(travContext.Direction1), d2;
774 css.Direction = interpolateSourceDirection(travContext, css.Dist+travContext.Dist, nearPos, travContext.ListenerPos /*realListener*/, d1, d2, alpha);
775 css.Position = nearPos + css.Dist * css.Direction;
776 css.PosAlpha = min(1.0f, css.Dist / _PortalInterpolate);
778 if (addAudibleCluster(c, css))
780 // debugLines.push_back(CLine(travContext.ListenerPos, nearPos));
781 CSoundTravContext stc(travContext);
782 stc.FilterUnvisibleChild = true;
783 stc.Direction1 = d1;
784 stc.Direction2 = d2;
785 stc.Direction = css.Direction;
786 stc.PreviousCluster = cluster;
787 stc.Alpha = alpha;
788 stc.PreviousVector = (nearPos - travContext.ListenerPos).normed();
789 addNextTraverse(c, stc);
790 _AudioPath.push_back(make_pair(travContext.ListenerPos, nearPos));
797 // 3nd, look in father cluster
798 if (cluster->Father && cluster->Father != travContext.PreviousCluster && cluster->FatherAudible)
800 // if (!travContext.FilterUnvisibleFather || ((1.0f-travContext.Alpha) > travContext.MinGain))
802 CCluster *c = cluster->Father;
803 float minDist;
804 CVector nearPos;
805 CAABBox box = c->getBBox();
807 if (c != _RootCluster)
808 minDist = getAABoxNearestPos(box, travContext.ListenerPos, nearPos);
809 else
811 // special case for root cluster coz it have a zero sized box and a zero position.
812 nearPos = travContext.ListenerPos;
813 minDist = 0;
816 CClusterSoundStatus css;
817 css.Gain = travContext.Gain;
818 /* if (travContext.FilterUnvisibleFather)
820 // compute a gain
821 float alpha = 1-(travContext.Dist / _PortalInterpolate);
822 alpha = alpha * alpha * alpha;
823 css.Gain = max(0.0f, alpha);
825 else
826 css.Gain = travContext.Gain;
828 // if (c->Name == "cluster_1")
829 // nldebug("Cluster 1 : gain = %f", css.Gain);
830 float alpha = travContext.Alpha;
831 CVector d1(travContext.Direction1), d2;
833 css.Direction = interpolateSourceDirection(travContext, travContext.Dist, nearPos, travContext.ListenerPos /*realListener*/, d1, d2, alpha);
835 if (css.Gain > _MinGain)
837 css.Dist = travContext.Dist;
838 // css.Direction = CVector::Null;
839 css.DistFactor = css.Dist / _MaxEarDistance;
840 css.Occlusion = travContext.Occlusion;
841 css.OcclusionLFFactor = travContext.OcclusionLFFactor;
842 css.OcclusionRoomRatio = travContext.OcclusionRoomRatio;
843 css.Obstruction = travContext.Obstruction;
844 css.Position = nearPos + css.Dist * css.Direction;
845 css.PosAlpha = min(1.0f, css.Dist / _PortalInterpolate);
847 if (addAudibleCluster(c, css))
849 CSoundTravContext stc(travContext);
850 stc.FilterUnvisibleFather = true;
851 stc.PreviousCluster = cluster;
852 stc.Direction1 = d1;
853 stc.Direction2 = d2;
854 stc.Direction = css.Direction;
855 stc.Alpha = alpha;
856 stc.PreviousVector = (nearPos - travContext.ListenerPos).normed();
857 _NextTraversalStep.insert(make_pair(c, stc));
862 curClusters.pop_back();
865 while (!_NextTraversalStep.empty());
868 void CClusteredSound::addNextTraverse(CCluster *cluster, CSoundTravContext &travContext)
870 std::map<CCluster*, CSoundTravContext>::iterator it = _NextTraversalStep.find(cluster);
872 if (it != _NextTraversalStep.end())
874 if (it->second.Dist > travContext.Dist)
876 it->second = travContext;
879 else
880 _NextTraversalStep.insert(make_pair(cluster, travContext));
884 bool CClusteredSound::addAudibleCluster(CCluster *cluster, CClusterSoundStatus &soundStatus)
886 TClusterStatusMap::iterator it(_AudibleClusters.find(cluster));
887 nlassert(soundStatus.Dist < _MaxEarDistance);
888 nlassert(soundStatus.Direction.norm() <= 1.01f);
890 if (it != _AudibleClusters.end())
892 // get the best one (for now, based on shortest distance)
893 if (soundStatus.Dist < it->second.Dist)
895 it->second = soundStatus;
897 return true;
900 else
902 _AudibleClusters.insert(make_pair(cluster, soundStatus));
903 return true;
906 return false;
909 CVector CClusteredSound::interpolateSourceDirection(const CClusteredSound::CSoundTravContext &context, float portalDist, const CVector &nearPoint, const CVector &realListener, CVector &d1, CVector &/* d2 */, float &alpha)
911 CVector direction;// (context.Direction);
913 if (portalDist > _PortalInterpolate || alpha >= 1.0f)
915 // the portal is too far, no interpolation.
916 if (context.NbPortal == 0)
918 // it's the first portal, compute the initial virtual sound direction
919 direction = d1 = (nearPoint-realListener).normed();
921 else
923 direction = (nearPoint-realListener).normed();
924 direction = (direction * (1-alpha) + d1 * (alpha)).normed();
925 d1 = direction;
927 alpha = 1;
929 else
931 // the portal is near the listener, interpolate the direction
932 if (context.NbPortal == 0)
934 // It's the first portal, compute the initial direction
935 alpha = (portalDist / _PortalInterpolate);
936 // alpha = alpha*alpha*alpha;
937 direction = d1 = (nearPoint-realListener).normed();
939 /* else if (context.NbPortal == 1)
941 float factor = (1-alpha);
942 // factor = factor*factor*factor;
943 direction = (nearPoint-realListener).normed();
944 direction = d1 = (direction * factor + d1 * (1-factor)).normed();
946 // alpha = 1-factor;
947 alpha = factor;
949 */ else
951 // two or more portal
952 float factor = (portalDist / _PortalInterpolate) * (1-alpha);
953 // factor = factor*factor*factor;
954 direction = (nearPoint-realListener).normed();
955 direction = d1 = (direction * factor + d1 * alpha).normed();
957 // alpha = 1-factor;
958 alpha = factor;
962 nlassert(direction.norm() <= 1.01f);
963 return direction;
965 CVector direction (context.Direction);
966 d1 = context.Direction1;
967 d2 = context.Direction2;
970 if (portalDist < _PortalInterpolate)
972 if (context.NbPortal == 0)
974 alpha = (portalDist / _PortalInterpolate);
975 direction = d1 = (nearPoint-realListener).normed() * alpha;
976 direction.normalize();
978 else if (context.NbPortal == 1)
980 alpha = alpha * (portalDist / _PortalInterpolate);
981 // d2 = (nearPoint-realListener).normed() * alpha;
982 d2 = (nearPoint-context.ListenerPos).normed() * (1-alpha);
983 direction = d1 + d2;
984 direction.normalize();
986 else
988 alpha = alpha * (portalDist / _PortalInterpolate);
989 // d1 = d1+d2;
990 d2 = (nearPoint-context.ListenerPos).normed() * (1-alpha);
991 // direction = d1 + d2 + (nearPoint-context.ListenerPos).normed() * (1-alpha);
992 direction = d1 + d2;
993 direction.normalize();
996 else
998 // alpha = 0.0f
999 if (context.NbPortal == 0)
1001 direction = d1 = (nearPoint-realListener).normed();
1003 else if (context.NbPortal == 1)
1005 d2 = (nearPoint-context.ListenerPos).normed() * (1-alpha);
1006 // d2 = d1; //(nearPoint-context.ListenerPos).normed(); // * (1-alpha);
1007 direction = d1+d2;
1008 direction.normalize();
1010 else
1012 // d2 = (nearPoint-context.ListenerPos).normed() * (1-alpha);
1013 d1 = d1+d2;
1014 // d2 = (nearPoint-realListener).normed();
1015 // direction = d1+d2+(nearPoint-context.ListenerPos).normed() * (1-alpha);
1016 direction.normalize();
1020 return direction;
1025 float CClusteredSound::getPolyNearestPos(const std::vector<CVector> &poly, const CVector &pos, CVector &nearPoint)
1027 CPlane plane;
1028 plane.make(poly[0], poly[1], poly[2]);
1029 CVector proj = plane.project(pos);
1030 float minDist = FLT_MAX;
1031 bool projIn = true;
1032 uint nbVertex = (uint)poly.size();
1034 // loop throw all vertex
1035 for (uint j=0; j<nbVertex; ++j)
1037 float d = (pos-poly[j]).sqrnorm();
1038 // check if the vertex is the nearest point
1039 if (d < minDist)
1041 nearPoint = poly[j];
1042 minDist = d;
1044 // if (projIn /*&& j<poly.size()-1*/)
1046 // check each segment
1047 if (plane.getNormal()*((poly[(j+1)%nbVertex] - poly[j]) ^ (proj - poly[j])) < 0)
1049 // the point is not inside the poly surface !
1050 projIn = false;
1051 // check if the nearest point is on this segment
1052 CVector v1 = (poly[(j+1)%nbVertex] - poly[j]);
1053 float v1Len = v1.norm();
1054 v1 = v1 / v1Len;
1055 CVector v2 = proj - poly[j];
1056 // project v2 on v1
1057 float p = v1 * v2;
1058 if (p>=0 && p<=v1Len)
1060 // the nearest point is on the segment!
1061 nearPoint = poly[j] + v1 * p;
1062 minDist = (nearPoint-pos).sqrnorm();
1063 break;
1068 if (projIn)
1070 float d = (proj-pos).sqrnorm();
1071 if (d < minDist)
1073 // the nearest point is on the surface
1074 nearPoint = proj;
1075 minDist = d;
1079 return sqrtf(minDist);
1082 float CClusteredSound::getAABoxNearestPos(const CAABBox &box, const CVector &pos, CVector &nearPos)
1084 CVector vMin, vMax;
1085 box.getMin(vMin);
1086 box.getMax(vMax);
1089 nearPos = pos;
1090 // X
1091 clamp(nearPos.x, vMin.x, vMax.x);
1092 // Y
1093 clamp(nearPos.y, vMin.y, vMax.y);
1094 // Z
1095 clamp(nearPos.z, vMin.z, vMax.z);
1097 return (pos-nearPos).norm();