1 // NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
2 // Copyright (C) 2010 Winch Gate Property Limited
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>
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/>.
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"
39 using namespace NLMISC
;
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
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 */)
84 NLGEORGES::UFormElm
&root
= form
->getRootNode();
85 NLGEORGES::UFormElm
*items
;
87 root
.getNodeByName(&items
, ".Items");
88 items
->getArraySize(size
);
90 for (uint i
=0; i
<size
; ++i
)
92 std::string soundGroup
;
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
)));
118 // load/save the values using the serial system (called by GEORGE::loadForm)
119 void serial (NLMISC::IStream
&s
)
124 size
= (uint32
)_SoundGroupAssoc
.size();
128 for (uint i
=0; i
<size
; ++i
)
132 std::string soundGroup
;
135 s
.serial(soundGroup
);
138 _SoundGroupAssoc
.push_back(make_pair(CStringMapper::map(soundGroup
), CStringMapper::map(sound
)));
142 std::string soundGroup
;
145 soundGroup
= CStringMapper::unmap(_SoundGroupAssoc
[i
].first
);
146 sound
= CStringMapper::unmap(_SoundGroupAssoc
[i
].second
);
148 s
.serial(soundGroup
);
154 /** called by GEORGE::loadForm when a sheet read from the packed sheet is no more in
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()
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
200 _PortalInterpolate
= portalInterpolate
;
201 _MaxEarDistance
= maxEarDist
;
205 _RootCluster
= _Scene
->getClipTrav().RootCluster
;
211 void CClusteredSound::update(const CVector
&listenerPos
, const CVector
&/* view */, const CVector
&/* up */)
213 H_AUTO(NLSOUND_ClusteredSoundUpdate
)
216 // hum... what to do ?
217 static bool bDisplayOnce
= false;
220 nlinfo("CClusteredSound::update : no scene specified !");
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
)));
288 cs
.Source
->setRelativeGain(css
.Gain
);
290 newSources
.insert(make_pair(soundGroup
, cs
));
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
;
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
);
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*/)));
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
;
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 !
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
);
378 const CClusteredSound::CClusterSoundStatus
*CClusteredSound::getClusterSoundStatus(NL3D::CCluster
*cluster
)
380 TClusterStatusMap::iterator
it(_AudibleClusters
.find(cluster
));
382 if (it
== _AudibleClusters
.end())
387 return &(it
->second
);
391 NL3D::CCluster
*CClusteredSound::getRootCluster()
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
);
409 // fill the initial cluster liste
410 CClusterSoundStatus css
;
411 css
.Direction
= CVector::Null
;
412 css
.DistFactor
= 0.0f
;
416 css
.OcclusionLFFactor
= 1.0f
;
417 css
.OcclusionRoomRatio
= 1.0f
;
420 // css.Position = CVector::Null;
421 css
.Position
= realListener
;
423 for (uint i
=0; i
<clusters
.size(); ++i
)
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();
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
);
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())
460 else if (find(clusters
.begin(), clusters
.end(), portal
->getCluster(0)) != clusters
.end())
469 if (portal->getCluster(0) == clusters[i] && dist > 0)
470 // if (!portal->isInFront(realListener))
475 else if (portal->getCluster(1) == clusters[i] && dist < 0)
476 // if (portal->isInFront(realListener))
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
;
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
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();
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
);
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)*/)
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;
587 stc
.Direction
= css
.Direction
;
588 stc
.PreviousCluster
= cluster
;
590 stc
.PreviousVector
= (nearPos
- travContext
.ListenerPos
).normed();
591 addNextTraverse(otherCluster
, stc
);
592 _AudioPath
.push_back(make_pair(travContext
.ListenerPos
, nearPos
));
598 // find the nearest point of this portal (either on of the perimeter vertex or a point on the portal surface)
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
;
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")
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;
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;
665 #if EAX_AVAILABLE == 1
666 css.Occlusion = travContext.Occlusion;
667 css.OcclusionLFFactor = travContext.OcclusionLFFactor;
668 css.OcclusionRoomRatio = travContext.OcclusionRoomRatio;
672 */ // compute obstruction
673 if (travContext
.NbPortal
>= 1)
675 float h
= soundDir
* travContext
.PreviousVector
;
680 // obst = float(2000 + asinf(-(soundDir ^ travContext.PreviousVector).norm()) / (Pi/2) * 2000);
681 obst
= float(4000 - (soundDir
^ travContext
.PreviousVector
).norm() * 2000);
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
;
696 css
.Gain
*= float(pow(10, -(obst
/4)/2000));
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
);
718 tc
.Occlusion
= css
.Occlusion
;
719 tc
.OcclusionLFFactor
= css
.OcclusionLFFactor
;
720 tc
.OcclusionRoomRatio
= css
.OcclusionRoomRatio
;
721 tc
.Obstruction
= css
.Obstruction
;
724 tc
.NbPortal
= travContext
.NbPortal
+1;
725 tc
.Direction
= css
.Direction
;
726 tc
.PreviousCluster
= cluster
;
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
)
748 if (c
->AudibleFromFather
&& c
->isIn(travContext
.ListenerPos
, _MaxEarDistance
-travContext
.Dist
))
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();
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;
785 stc
.Direction
= css
.Direction
;
786 stc
.PreviousCluster
= cluster
;
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
;
805 CAABBox box
= c
->getBBox();
807 if (c
!= _RootCluster
)
808 minDist
= getAABoxNearestPos(box
, travContext
.ListenerPos
, nearPos
);
811 // special case for root cluster coz it have a zero sized box and a zero position.
812 nearPos
= travContext
.ListenerPos
;
816 CClusterSoundStatus css
;
817 css
.Gain
= travContext
.Gain
;
818 /* if (travContext.FilterUnvisibleFather)
821 float alpha = 1-(travContext.Dist / _PortalInterpolate);
822 alpha = alpha * alpha * alpha;
823 css.Gain = max(0.0f, alpha);
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
;
854 stc
.Direction
= css
.Direction
;
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
;
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
;
902 _AudibleClusters
.insert(make_pair(cluster
, soundStatus
));
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();
923 direction
= (nearPoint
-realListener
).normed();
924 direction
= (direction
* (1-alpha
) + d1
* (alpha
)).normed();
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();
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();
962 nlassert(direction
.norm() <= 1.01f
);
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);
984 direction.normalize();
988 alpha = alpha * (portalDist / _PortalInterpolate);
990 d2 = (nearPoint-context.ListenerPos).normed() * (1-alpha);
991 // direction = d1 + d2 + (nearPoint-context.ListenerPos).normed() * (1-alpha);
993 direction.normalize();
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);
1008 direction.normalize();
1012 // d2 = (nearPoint-context.ListenerPos).normed() * (1-alpha);
1014 // d2 = (nearPoint-realListener).normed();
1015 // direction = d1+d2+(nearPoint-context.ListenerPos).normed() * (1-alpha);
1016 direction.normalize();
1025 float CClusteredSound::getPolyNearestPos(const std::vector
<CVector
> &poly
, const CVector
&pos
, CVector
&nearPoint
)
1028 plane
.make(poly
[0], poly
[1], poly
[2]);
1029 CVector proj
= plane
.project(pos
);
1030 float minDist
= FLT_MAX
;
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
1041 nearPoint
= poly
[j
];
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 !
1051 // check if the nearest point is on this segment
1052 CVector v1
= (poly
[(j
+1)%nbVertex
] - poly
[j
]);
1053 float v1Len
= v1
.norm();
1055 CVector v2
= proj
- poly
[j
];
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();
1070 float d
= (proj
-pos
).sqrnorm();
1073 // the nearest point is on the surface
1079 return sqrtf(minDist
);
1082 float CClusteredSound::getAABoxNearestPos(const CAABBox
&box
, const CVector
&pos
, CVector
&nearPos
)
1091 clamp(nearPos
.x
, vMin
.x
, vMax
.x
);
1093 clamp(nearPos
.y
, vMin
.y
, vMax
.y
);
1095 clamp(nearPos
.z
, vMin
.z
, vMax
.z
);
1097 return (pos
-nearPos
).norm();