khanat-opennel-code/code/nel/src/sound/clustered_sound.cpp
2014-06-12 19:27:09 +02:00

1082 lines
34 KiB
C++

// NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
// Copyright (C) 2010 Winch Gate Property Limited
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "stdsound.h"
#include "nel/misc/string_mapper.h"
#include "nel/misc/hierarchical_timer.h"
#include "nel/georges/u_form.h"
#include "nel/georges/u_form_elm.h"
#include "nel/georges/load_form.h"
//#include "nel/3d/std3d.h"
#include "nel/3d/scene.h"
#include "nel/3d/scene_user.h"
#include "nel/3d/cluster.h"
#include "nel/3d/portal.h"
#include "nel/sound/driver/listener.h"
#include "nel/sound/audio_mixer_user.h"
#include "nel/sound/driver/sound_driver.h"
#include "nel/sound/driver/effect.h"
#include "nel/sound/clustered_sound.h"
using namespace std;
using namespace NLMISC;
using namespace NL3D;
namespace NLSOUND
{
#if EAX_AVAILABLE == 1
// An array to report all EAX predefined meterials
float EAX_MATERIAL_PARAM[][3] =
{
{EAX_MATERIAL_SINGLEWINDOW}, {EAX_MATERIAL_SINGLEWINDOWLF}, {EAX_MATERIAL_SINGLEWINDOWROOMRATIO},
{EAX_MATERIAL_DOUBLEWINDOW}, {EAX_MATERIAL_DOUBLEWINDOWHF}, {EAX_MATERIAL_DOUBLEWINDOWHF},
{EAX_MATERIAL_THINDOOR}, {EAX_MATERIAL_THINDOORLF}, {EAX_MATERIAL_THINDOORROOMRATIO},
{EAX_MATERIAL_THICKDOOR}, {EAX_MATERIAL_THICKDOORLF}, {EAX_MATERIAL_THICKDOORROOMRTATION},
{EAX_MATERIAL_WOODWALL}, {EAX_MATERIAL_WOODWALLLF}, {EAX_MATERIAL_WOODWALLROOMRATIO},
{EAX_MATERIAL_BRICKWALL}, {EAX_MATERIAL_BRICKWALLLF}, {EAX_MATERIAL_BRICKWALLROOMRATIO},
{EAX_MATERIAL_STONEWALL}, {EAX_MATERIAL_STONEWALLLF}, {EAX_MATERIAL_STONEWALLROOMRATIO},
{EAX_MATERIAL_CURTAIN}, {EAX_MATERIAL_CURTAINLF}, {EAX_MATERIAL_CURTAINROOMRATIO}
};
#else // EAX_AVAILABLE
// No EAX, just have an array of gain factor to apply for each material type
float EAX_MATERIAL_PARAM[] =
{
float(pow((double)10, (double)-2800/2000)),
float(pow((double)10, (double)-5000/2000)),
float(pow((double)10, (double)-1800/2000)),
float(pow((double)10, (double)-4400/2000)),
float(pow((double)10, (double)-4000/2000)),
float(pow((double)10, (double)-5000/2000)),
float(pow((double)10, (double)-6000/2000)),
float(pow((double)10, (double)-1200/2000))
};
#endif // EAX_AVAILABLE
// An utility class to handle packed sheet loading/saving/updating
class CSoundGroupSerializer
{
public:
std::vector<std::pair<NLMISC::TStringId, NLMISC::CSheetId> > _SoundGroupAssoc;
// load the values using the george sheet (called by GEORGE::loadForm)
void readGeorges (const NLMISC::CSmartPtr<NLGEORGES::UForm> &form, const std::string &/* name */)
{
try
{
NLGEORGES::UFormElm &root = form->getRootNode();
NLGEORGES::UFormElm *items;
uint size;
root.getNodeByName(&items, ".Items");
items->getArraySize(size);
for (uint i=0; i<size; ++i)
{
std::string soundGroup;
std::string sound;
NLGEORGES::UFormElm *item;
items->getArrayNode(&item, i);
item->getValueByName(soundGroup, ".SoundGroup");
item->getValueByName(sound, ".Sound");
nlassert(sound.find(".sound") != std::string::npos);
_SoundGroupAssoc.push_back(make_pair(CStringMapper::map(soundGroup), CSheetId(sound)));
}
}
catch(...)
{
}
}
// load/save the values using the serial system (called by GEORGE::loadForm)
void serial (NLMISC::IStream &s)
{
uint32 size;
if (!s.isReading())
{
size = (uint32)_SoundGroupAssoc.size();
}
s.serial(size);
for (uint i=0; i<size; ++i)
{
if (s.isReading())
{
TStringId soundGroup;
CSheetId sound;
CStringMapper::serialString(s, soundGroup);
sound.serialString(s, "sound");
_SoundGroupAssoc.push_back(make_pair(soundGroup, sound));
}
else
{
CStringMapper::serialString(s, _SoundGroupAssoc[i].first);
_SoundGroupAssoc[i].second.serialString(s, "sound");
}
}
}
/** called by GEORGE::loadForm when a sheet read from the packed sheet is no more in
* the directories.
*/
void removed()
{
// nothing to do
}
// return the version of this class, increments this value when the content of this class changed
static uint getVersion () { return 1; }
};
// this structure is fill by the loadForm() function and will contain all you need
std::map<std::string, CSoundGroupSerializer> Container;
CClusteredSound::CClusteredSound()
: _Scene(0),
_RootCluster(0),
_LastEnv(CStringMapper::emptyId()),
_LastEnvSize(-1.0f) // size goes from 0.0f to 100.0f
{
}
void CClusteredSound::init(NL3D::CScene *scene, float portalInterpolate, float maxEarDist, float minGain)
{
// load the sound_group sheets
::loadForm("sound_group", CAudioMixerUser::instance()->getPackedSheetPath()+"sound_groups.packed_sheets", Container, CAudioMixerUser::instance()->getPackedSheetUpdate(), false);
// copy the container data into internal structure
std::map<std::string, CSoundGroupSerializer>::iterator first(Container.begin()), last(Container.end());
for (; first != last; ++first)
{
_SoundGroupToSound.insert(first->second._SoundGroupAssoc.begin(), first->second._SoundGroupAssoc.end());
}
// and clear the temporary Container
Container.clear();
_Scene = scene;
_PortalInterpolate = portalInterpolate;
_MaxEarDistance = maxEarDist;
_MinGain = minGain;
if(scene != 0)
{
_RootCluster = _Scene->getClipTrav().RootCluster;
}
else
_RootCluster = 0;
}
void CClusteredSound::update(const CVector &listenerPos, const CVector &/* view */, const CVector &/* up */)
{
H_AUTO(NLSOUND_ClusteredSoundUpdate)
if (_Scene == 0)
{
// hum... what to do ?
static bool bDisplayOnce = false;
if (!bDisplayOnce)
{
nlinfo("CClusteredSound::update : no scene specified !");
bDisplayOnce = true;
}
return;
}
CClipTrav &clipTrav = _Scene->getClipTrav ();
// Retreive the list of cluster where the listener is
vector<CCluster*> vCluster;
clipTrav.fullSearch (vCluster, listenerPos);
// reset the audible cluster map
_AudibleClusters.clear();
// create the initial travesal context
CSoundTravContext stc(listenerPos, false, false);
// and start the cluster traversal to find out what cluster is audible and how we ear it
soundTraverse(vCluster, stc);
//-----------------------------------------------------
// update the clustered sound (create and stop sound)
//-----------------------------------------------------
// std::hash_map<uint, CClusterSound> newSources;
TClusterSoundCont newSources;
{
// fake the distance for all playing source
// std::map<std::string, CClusterSound>::iterator first(_Sources.begin()), last(_Sources.end());
TClusterSoundCont::iterator first(_Sources.begin()), last(_Sources.end());
for (; first != last; ++first)
{
first->second.Distance = FLT_MAX;
}
}
TClusterStatusMap::const_iterator first(_AudibleClusters.begin()), last(_AudibleClusters.end());
for (; first != last; ++first )
{
static NLMISC::TStringId NO_SOUND_GROUP = CStringMapper::emptyId();
const CClusterSoundStatus &css = first->second;
CCluster *cluster = first->first;
NLMISC::TStringId soundGroup;
soundGroup = cluster->getSoundGroupId();
if (soundGroup != NO_SOUND_GROUP)
{
// search an associated sound name
TClusterSoundCont::iterator it(_Sources.find(soundGroup));
if (it != _Sources.end())
{
// the source is already playing, check and replace if needed
CClusterSound &cs = it->second;
if (cs.Distance >= css.Dist)
{
// this one is better !
cs.Distance = css.Dist;
cs.Source->setPos(listenerPos + css.Direction * css.Dist + CVector(0,0,2));
if (css.DistFactor < 1.0f)
cs.Source->setRelativeGain(css.Gain * (1.0f - (css.DistFactor*css.DistFactor*css.DistFactor*css.DistFactor)));
else
cs.Source->setRelativeGain(css.Gain);
}
newSources.insert(make_pair(soundGroup, cs));
}
else
{
// create a new source
// nldebug("Searching sound assoc for group [%s]", CStringMapper::unmap(soundGroup).c_str());
TStringSheetMap::iterator it2(_SoundGroupToSound.find(soundGroup));
if (it2 != _SoundGroupToSound.end())
{
NLMISC::CSheetId soundName = it2->second;
CClusterSound cs;
// nldebug("Found the sound [%s] for sound group [%s]", CStringMapper::unmap(soundName).c_str(), CStringMapper::unmap(soundGroup).c_str());
cs.Distance = css.Dist;
cs.Source = CAudioMixerUser::instance()->createSource(soundName, false, NULL, NULL, cluster);
if (cs.Source != 0)
{
cs.Source->setPos(listenerPos + css.Direction * css.Dist + CVector(0,0,2));
if (css.DistFactor < 1.0f)
cs.Source->setRelativeGain(css.Gain * (1.0f - (css.DistFactor*css.DistFactor/**css.DistFactor*css.DistFactor*/)));
else
cs.Source->setRelativeGain(css.Gain);
cs.Source->setLooping(true);
newSources.insert(make_pair(soundGroup, cs));
}
}
}
}
}
// check for source to stop
{
TClusterSoundCont oldSources;
oldSources.swap(_Sources);
TClusterSoundCont::iterator first(newSources.begin()), last(newSources.end());
for (; first != last; ++first)
{
_Sources.insert(*first);
if (!first->second.Source->isPlaying())
first->second.Source->play();
oldSources.erase(first->first);
}
while (!oldSources.empty())
{
CClusterSound &cs = oldSources.begin()->second;
delete cs.Source;
oldSources.erase(oldSources.begin());
}
}
// update the environment effect (if any)
CAudioMixerUser *mixer = CAudioMixerUser::instance();
if (mixer->useEnvironmentEffects() && !vCluster.empty())
{
H_AUTO(NLSOUND_ClusteredSound_updateEnvFx)
TStringId fxId = vCluster[0]->getEnvironmentFxId();
const CAABBox &box = vCluster[0]->getBBox();
CVector vsize = box.getHalfSize();
float size = NLMISC::minof(vsize.x, vsize.y, vsize.z) * 2;
// special case for root cluster (ie, external)
if (vCluster[0] == _RootCluster)
{
// this is the root cluster. This cluster have a size of 0 !
size = 100.f;
}
else
{
// else, clip the env size to max eax supported size
clamp(size, 1.f, 100.f);
}
// only update environment if there is some change.
if (fxId != _LastEnv || size != _LastEnvSize)
{
nldebug("AM: CClusteredSound => setEnvironment %s %f", CStringMapper::unmap(fxId).c_str(), size);
mixer->setEnvironment(fxId, size);
_LastEnv = fxId;
_LastEnvSize = size;
}
}
}
const CClusteredSound::CClusterSoundStatus *CClusteredSound::getClusterSoundStatus(NL3D::CCluster *cluster)
{
TClusterStatusMap::iterator it(_AudibleClusters.find(cluster));
if (it == _AudibleClusters.end())
{
return 0;
}
else
return &(it->second);
}
NL3D::CCluster *CClusteredSound::getRootCluster()
{
if (_Scene == 0)
return 0;
return _Scene->getClipTrav().RootCluster;
}
void CClusteredSound::soundTraverse(const std::vector<CCluster *> &clusters, CSoundTravContext &travContext)
{
H_AUTO(NLSOUND_soundTraverse)
// std::map<CCluster*, CSoundTravContext> nextTraverse;
std::vector<std::pair<const CCluster*, CSoundTravContext> > curClusters;
CVector realListener (travContext.ListenerPos);
_AudioPath.clear();
// fill the initial cluster liste
CClusterSoundStatus css;
css.Direction = CVector::Null;
css.DistFactor = 0.0f;
css.Dist = 0.0f;
css.Gain = 1.0f;
css.Occlusion = 0;
css.OcclusionLFFactor = 1.0f;
css.OcclusionRoomRatio = 1.0f;
css.Obstruction = 0;
css.PosAlpha = 0;
// css.Position = CVector::Null;
css.Position = realListener;
for (uint i=0; i<clusters.size(); ++i)
{
bool valid = true;
// eliminate cluster when listener is behind their portals AND inside the other cluster
for (uint j=0; j<clusters[i]->getNbPortals(); j++)
{
CPortal *portal = clusters[i]->getPortal(j);
const std::vector<CVector> &poly = portal->getPoly();
if (poly.size() < 3)
{
// only warn once, avoid log flooding !
static std::set<std::string> warned;
if (warned.find(clusters[i]->Name) == warned.end())
{
nlwarning("Cluster [%s] contains a portal [%s] with less than 3 vertex !",
clusters[i]->Name.c_str(), portal->getName().empty() ? "no name" : portal->getName().c_str());
warned.insert(clusters[i]->Name);
}
valid = false;
continue;
}
CVector normal = (poly[0] - poly[1]) ^ (poly[2] - poly[1]);
float dist = (realListener - poly[0]) * normal;
float dist2 = (clusters[i]->getBBox().getCenter() - poly[0]) * normal;
if ((dist < 0 && dist2 > 0) || (dist > 0 && dist2 < 0))
{
if (portal->getCluster(0) == clusters[i])
{
if (find(clusters.begin(), clusters.end(), portal->getCluster(1)) != clusters.end())
{
valid = false;
continue;
}
}
else if (find(clusters.begin(), clusters.end(), portal->getCluster(0)) != clusters.end())
{
valid = false;
continue;
}
}
/*
if (portal->getCluster(0) == clusters[i] && dist > 0)
// if (!portal->isInFront(realListener))
{
valid = false;
continue;
}
else if (portal->getCluster(1) == clusters[i] && dist < 0)
// if (portal->isInFront(realListener))
{
valid = false;
continue;
}
*/
}
if( valid)
{
curClusters.push_back(make_pair(clusters[i], travContext));
addAudibleCluster(clusters[i], css);
}
}
do
{
// add the next traverse (if any)
std::copy(_NextTraversalStep.begin(), _NextTraversalStep.end(), std::back_inserter(curClusters));
_NextTraversalStep.clear();
while (!curClusters.empty())
{
CCluster * cluster = const_cast<CCluster*>(curClusters.back().first);
CSoundTravContext &travContext = curClusters.back().second;
CClusterSoundStatus css;
css.DistFactor = 0.0f;
css.Position = CVector::Null;
css.PosAlpha = 0.0f;
css.Gain = travContext.Gain;
css.Dist = travContext.Dist;
css.Direction = travContext.Direction;
css.Occlusion = travContext.Occlusion;
css.OcclusionLFFactor = travContext.OcclusionLFFactor;
css.OcclusionRoomRatio = travContext.OcclusionRoomRatio;
css.Obstruction = travContext.Obstruction;
// store this cluster and it's parameters
_AudibleClusters.insert(make_pair(cluster, css));
// 1st, look each portal
uint i;
for (i=0; i<cluster->getNbPortals(); ++i)
{
CPortal *portal = cluster->getPortal(i);
// get the other cluster
CCluster *otherCluster = portal->getCluster(0);
bool clusterInFront = true;
if (otherCluster == cluster)
{
otherCluster = portal->getCluster(1);
clusterInFront = false;
}
nlassert(otherCluster != cluster);
if (otherCluster && travContext.PreviousCluster != otherCluster) // && (!travContext.FilterUnvisibleChild || otherCluster->AudibleFromFather))
{
const vector<CVector> &poly = portal->getPoly();
// a security test
if (poly.size() < 3)
{
// only warn once, avoid log flooding !
static std::set<std::string> warned;
if (warned.find(cluster->Name) == warned.end())
{
nlwarning("Cluster [%s] contains a portal [%s] with less than 3 vertex !",
cluster->Name.c_str(), portal->getName().empty() ? "no name" : portal->getName().c_str());
warned.insert(cluster->Name);
}
}
else
{
// Test to skip portal with suface > 40 m2 (aprox)
float surface = ((poly[2]-poly[1]) ^ (poly[0]-poly[1])).norm();
if (surface > 340 /* && otherCluster->isIn(travContext.ListenerPos, travContext.MaxDist-travContext.Dist)*/)
{
float minDist;
CVector nearPos;
CAABBox box = otherCluster->getBBox();
minDist = getAABoxNearestPos(box, travContext.ListenerPos, nearPos);
if (travContext.Dist + minDist < _MaxEarDistance)
{
CVector soundDir = (nearPos - travContext.ListenerPos).normed();
CClusterSoundStatus css;
css.Gain = travContext.Gain;
css.Dist = travContext.Dist + minDist;
css.Occlusion = travContext.Occlusion;
css.OcclusionLFFactor = travContext.OcclusionLFFactor;
css.Obstruction = travContext.Obstruction;
css.OcclusionRoomRatio = travContext.OcclusionRoomRatio;
css.DistFactor = css.Dist / _MaxEarDistance;
css.Direction = travContext.Direction;
float alpha = travContext.Alpha;
CVector d1(travContext.Direction1), d2;
css.Direction = interpolateSourceDirection(travContext, css.Dist+travContext.Dist, nearPos, travContext.ListenerPos /*realListener*/, d1, d2, alpha);
css.Position = nearPos + css.Dist * css.Direction;
css.PosAlpha = min(1.0f, css.Dist / _PortalInterpolate);
if (addAudibleCluster(otherCluster, css))
{
// debugLines.push_back(CLine(travContext.ListenerPos, nearPos));
CSoundTravContext stc(travContext);
stc.FilterUnvisibleChild = true;
stc.Direction1 = d1;
stc.Direction2 = d2;
stc.Direction = css.Direction;
stc.PreviousCluster = cluster;
stc.Alpha = alpha;
stc.PreviousVector = (nearPos - travContext.ListenerPos).normed();
addNextTraverse(otherCluster, stc);
_AudioPath.push_back(make_pair(travContext.ListenerPos, nearPos));
}
}
}
else
{
// find the nearest point of this portal (either on of the perimeter vertex or a point on the portal surface)
float minDist;
CVector nearPos;
minDist = getPolyNearestPos(poly, travContext.ListenerPos, nearPos);
if (travContext.Dist+minDist < _MaxEarDistance)
{
// note: this block of code is a mess and should be cleaned up and commented =)
// TODO : compute relative gain according to portal behavior.
CClusterSoundStatus css;
css.Gain = travContext.Gain;
CVector soundDir = (nearPos - travContext.ListenerPos).normed();
/* ****** Todo: OpenAL EFX & XAudio2 implementation of Occlusion & Obstruction (not implemented for fmod anyways) !!! ******
TStringId occId = portal->getOcclusionModelId();
TStringIntMap::iterator it(_IdToMaterial.find(occId));
****** Todo: OpenAL EFX & XAudio2 implementation of Occlusion & Obstruction (not implemented for fmod anyways) !!! ****** */
#if EAX_AVAILABLE == 1 // EAX_AVAILABLE no longer used => TODO: implement with EFX and remove when new implementation OK.
if (it != _IdToMaterial.end())
{
// found an occlusion material for this portal
uint matId = it->second;
css.Occlusion = max(sint32(EAXBUFFER_MINOCCLUSION), sint32(travContext.Occlusion + EAX_MATERIAL_PARAM[matId][0])); //- 1800); //EAX_MATERIAL_THINDOOR;
css.OcclusionLFFactor = travContext.OcclusionLFFactor * EAX_MATERIAL_PARAM[matId][1]; //EAX_MATERIAL_THICKDOORLF; //0.66f; //0.0f; //min(EAX_MATERIAL_THINDOORLF, travContext.OcclusionLFFactor);
css.OcclusionRoomRatio = EAX_MATERIAL_PARAM[matId][2] * travContext.OcclusionRoomRatio;
}
else
{
// the id does not match any know material
css.Occlusion = travContext.Occlusion;
css.OcclusionLFFactor = travContext.OcclusionLFFactor;
css.OcclusionRoomRatio = travContext.OcclusionRoomRatio;
}
#else // EAX_AVAILABLE
/* ****** Todo: OpenAL EFX & XAudio2 implementation of Occlusion & Obstruction (not implemented for fmod anyways) !!! ******
if (it != _IdToMaterial.end())
{
// found an occlusion material for this portal
uint matId = it->second;
css.Gain *= EAX_MATERIAL_PARAM[matId];
}
****** Todo: OpenAL EFX & XAudio2 implementation of Occlusion & Obstruction (not implemented for fmod anyways) !!! ****** */
#endif // EAX_AVAILABLE
/* if (portal->getOcclusionModel() == "wood door")
{
// css.Gain *= 0.5f;
#if EAX_AVAILABLE == 1
css.Occlusion = max(EAXBUFFER_MINOCCLUSION, travContext.Occlusion + EAX_MATERIAL_THICKDOOR); //- 1800); //EAX_MATERIAL_THINDOOR;
css.OcclusionLFFactor = 0.1f * travContext.OcclusionLFFactor; //EAX_MATERIAL_THICKDOORLF; //0.66f; //0.0f; //min(EAX_MATERIAL_THINDOORLF, travContext.OcclusionLFFactor);
css.OcclusionRoomRatio = EAX_MATERIAL_THICKDOORROOMRATION * travContext.OcclusionRoomRatio;
#else
css.Gain *= 0.5f;
#endif
}
else if (portal->getOcclusionModel() == "brick door")
{
#if EAX_AVAILABLE == 1
css.Occlusion = max(EAXBUFFER_MINOCCLUSION, travContext.Occlusion + EAX_MATERIAL_BRICKWALL);
css.OcclusionLFFactor = min(EAX_MATERIAL_BRICKWALLLF, travContext.OcclusionLFFactor);
css.OcclusionRoomRatio = EAX_MATERIAL_BRICKWALLROOMRATIO * travContext.OcclusionRoomRatio;
#else
css.Gain *= 0.2f;
#endif
}
else
{
#if EAX_AVAILABLE == 1
css.Occlusion = travContext.Occlusion;
css.OcclusionLFFactor = travContext.OcclusionLFFactor;
css.OcclusionRoomRatio = travContext.OcclusionRoomRatio;
#endif
}
*/ // compute obstruction
if (travContext.NbPortal >= 1)
{
float h = soundDir * travContext.PreviousVector;
float obst;
if (h < 0)
{
// obst = float(2000 + asinf(-(soundDir ^ travContext.PreviousVector).norm()) / (Pi/2) * 2000);
obst = float(4000 - (soundDir ^ travContext.PreviousVector).norm() * 2000);
}
else
{
// obst = float(asinf((soundDir ^ travContext.PreviousVector).norm()) / (Pi/2) * 2000);
obst = float((soundDir ^ travContext.PreviousVector).norm() * 2000);
}
// float sqrdist = (realListener - nearPoint).sqrnorm();
if (travContext.Dist < 2.0f) // interpolate a 2 m
obst *= travContext.Dist / 2.0f;
#if EAX_AVAILABLE == 1 // EAX_AVAILABLE no longer used => TODO: implement with EFX and remove when new implementation OK.
css.Obstruction = max(sint32(EAXBUFFER_MINOBSTRUCTION), sint32(travContext.Obstruction - sint32(obst)));
css.OcclusionLFFactor = 0.50f * travContext.OcclusionLFFactor;
#else
css.Gain *= float(pow(10, -(obst/4)/2000));
#endif
}
else
css.Obstruction = travContext.Obstruction;
// css.Dist = travContext.Dist + float(sqrt(minDist));
css.Dist = travContext.Dist + minDist;
css.DistFactor = css.Dist / _MaxEarDistance;
float portalDist = css.Dist;
float alpha = travContext.Alpha;
CVector d1(travContext.Direction1), d2(travContext.Direction2);
css.Direction = interpolateSourceDirection(travContext, portalDist+travContext.Dist, nearPos, travContext.ListenerPos /*realListener*/, d1, d2, alpha);
css.Position = nearPos + css.Dist * css.Direction;
css.PosAlpha = min(1.0f, css.Dist / _PortalInterpolate);
if (addAudibleCluster(otherCluster, css))
{
// debugLines.push_back(CLine(travContext.ListenerPos, nearPoint));
CSoundTravContext tc(nearPos, travContext.FilterUnvisibleChild, !cluster->AudibleFromFather);
tc.Dist = css.Dist;
tc.Gain = css.Gain;
tc.Occlusion = css.Occlusion;
tc.OcclusionLFFactor = css.OcclusionLFFactor;
tc.OcclusionRoomRatio = css.OcclusionRoomRatio;
tc.Obstruction = css.Obstruction;
tc.Direction1 = d1;
tc.Direction2 = d2;
tc.NbPortal = travContext.NbPortal+1;
tc.Direction = css.Direction;
tc.PreviousCluster = cluster;
tc.Alpha = alpha;
tc.PreviousVector = soundDir;
addNextTraverse(otherCluster, tc);
_AudioPath.push_back(make_pair(travContext.ListenerPos, nearPos));
}
}
}
}
}
}
// 2nd, look each child cluster
for (i=0; i<cluster->Children.size(); ++i)
{
CCluster *c = cluster->Children[i];
// dont redown into an upstream
if (c != travContext.PreviousCluster)
{
// clip on distance.
if (c->AudibleFromFather && c->isIn(travContext.ListenerPos, _MaxEarDistance-travContext.Dist))
{
float minDist;
CVector nearPos;
CAABBox box = c->getBBox();
minDist = getAABoxNearestPos(box, travContext.ListenerPos, nearPos);
if (travContext.Dist + minDist < _MaxEarDistance)
{
CClusterSoundStatus css;
css.Gain = travContext.Gain;
css.Dist = travContext.Dist + minDist;
css.DistFactor = css.Dist / _MaxEarDistance;
css.Occlusion = travContext.Occlusion;
css.OcclusionLFFactor = travContext.OcclusionLFFactor;
css.OcclusionRoomRatio = travContext.OcclusionRoomRatio;
css.Obstruction = travContext.Obstruction;
/* if (travContext.NbPortal == 0)
css.Direction = (nearPos - travContext.ListenerPos).normed();
else
css.Direction = travContext.Direction1;
*/
float alpha = travContext.Alpha;
CVector d1(travContext.Direction1), d2;
css.Direction = interpolateSourceDirection(travContext, css.Dist+travContext.Dist, nearPos, travContext.ListenerPos /*realListener*/, d1, d2, alpha);
css.Position = nearPos + css.Dist * css.Direction;
css.PosAlpha = min(1.0f, css.Dist / _PortalInterpolate);
if (addAudibleCluster(c, css))
{
// debugLines.push_back(CLine(travContext.ListenerPos, nearPos));
CSoundTravContext stc(travContext);
stc.FilterUnvisibleChild = true;
stc.Direction1 = d1;
stc.Direction2 = d2;
stc.Direction = css.Direction;
stc.PreviousCluster = cluster;
stc.Alpha = alpha;
stc.PreviousVector = (nearPos - travContext.ListenerPos).normed();
addNextTraverse(c, stc);
_AudioPath.push_back(make_pair(travContext.ListenerPos, nearPos));
}
}
}
}
}
// 3nd, look in father cluster
if (cluster->Father && cluster->Father != travContext.PreviousCluster && cluster->FatherAudible)
{
// if (!travContext.FilterUnvisibleFather || ((1.0f-travContext.Alpha) > travContext.MinGain))
{
CCluster *c = cluster->Father;
float minDist;
CVector nearPos;
CAABBox box = c->getBBox();
if (c != _RootCluster)
minDist = getAABoxNearestPos(box, travContext.ListenerPos, nearPos);
else
{
// special case for root cluster coz it have a zero sized box and a zero position.
nearPos = travContext.ListenerPos;
minDist = 0;
}
CClusterSoundStatus css;
css.Gain = travContext.Gain;
/* if (travContext.FilterUnvisibleFather)
{
// compute a gain
float alpha = 1-(travContext.Dist / _PortalInterpolate);
alpha = alpha * alpha * alpha;
css.Gain = max(0.0f, alpha);
}
else
css.Gain = travContext.Gain;
*/
// if (c->Name == "cluster_1")
// nldebug("Cluster 1 : gain = %f", css.Gain);
float alpha = travContext.Alpha;
CVector d1(travContext.Direction1), d2;
css.Direction = interpolateSourceDirection(travContext, travContext.Dist, nearPos, travContext.ListenerPos /*realListener*/, d1, d2, alpha);
if (css.Gain > _MinGain)
{
css.Dist = travContext.Dist;
// css.Direction = CVector::Null;
css.DistFactor = css.Dist / _MaxEarDistance;
css.Occlusion = travContext.Occlusion;
css.OcclusionLFFactor = travContext.OcclusionLFFactor;
css.OcclusionRoomRatio = travContext.OcclusionRoomRatio;
css.Obstruction = travContext.Obstruction;
css.Position = nearPos + css.Dist * css.Direction;
css.PosAlpha = min(1.0f, css.Dist / _PortalInterpolate);
if (addAudibleCluster(c, css))
{
CSoundTravContext stc(travContext);
stc.FilterUnvisibleFather = true;
stc.PreviousCluster = cluster;
stc.Direction1 = d1;
stc.Direction2 = d2;
stc.Direction = css.Direction;
stc.Alpha = alpha;
stc.PreviousVector = (nearPos - travContext.ListenerPos).normed();
_NextTraversalStep.insert(make_pair(c, stc));
}
}
}
}
curClusters.pop_back();
}
}
while (!_NextTraversalStep.empty());
}
void CClusteredSound::addNextTraverse(CCluster *cluster, CSoundTravContext &travContext)
{
std::map<CCluster*, CSoundTravContext>::iterator it = _NextTraversalStep.find(cluster);
if (it != _NextTraversalStep.end())
{
if (it->second.Dist > travContext.Dist)
{
it->second = travContext;
}
}
else
_NextTraversalStep.insert(make_pair(cluster, travContext));
}
bool CClusteredSound::addAudibleCluster(CCluster *cluster, CClusterSoundStatus &soundStatus)
{
TClusterStatusMap::iterator it(_AudibleClusters.find(cluster));
nlassert(soundStatus.Dist < _MaxEarDistance);
nlassert(soundStatus.Direction.norm() <= 1.01f);
if (it != _AudibleClusters.end())
{
// get the best one (for now, based on shortest distance)
if (soundStatus.Dist < it->second.Dist)
{
it->second = soundStatus;
return true;
}
}
else
{
_AudibleClusters.insert(make_pair(cluster, soundStatus));
return true;
}
return false;
}
CVector CClusteredSound::interpolateSourceDirection(const CClusteredSound::CSoundTravContext &context, float portalDist, const CVector &nearPoint, const CVector &realListener, CVector &d1, CVector &/* d2 */, float &alpha)
{
CVector direction;// (context.Direction);
if (portalDist > _PortalInterpolate || alpha >= 1.0f)
{
// the portal is too far, no interpolation.
if (context.NbPortal == 0)
{
// it's the first portal, compute the initial virtual sound direction
direction = d1 = (nearPoint-realListener).normed();
}
else
{
direction = (nearPoint-realListener).normed();
direction = (direction * (1-alpha) + d1 * (alpha)).normed();
d1 = direction;
}
alpha = 1;
}
else
{
// the portal is near the listener, interpolate the direction
if (context.NbPortal == 0)
{
// It's the first portal, compute the initial direction
alpha = (portalDist / _PortalInterpolate);
// alpha = alpha*alpha*alpha;
direction = d1 = (nearPoint-realListener).normed();
}
/* else if (context.NbPortal == 1)
{
float factor = (1-alpha);
// factor = factor*factor*factor;
direction = (nearPoint-realListener).normed();
direction = d1 = (direction * factor + d1 * (1-factor)).normed();
// alpha = 1-factor;
alpha = factor;
}
*/ else
{
// two or more portal
float factor = (portalDist / _PortalInterpolate) * (1-alpha);
// factor = factor*factor*factor;
direction = (nearPoint-realListener).normed();
direction = d1 = (direction * factor + d1 * alpha).normed();
// alpha = 1-factor;
alpha = factor;
}
}
nlassert(direction.norm() <= 1.01f);
return direction;
/*
CVector direction (context.Direction);
d1 = context.Direction1;
d2 = context.Direction2;
if (portalDist < _PortalInterpolate)
{
if (context.NbPortal == 0)
{
alpha = (portalDist / _PortalInterpolate);
direction = d1 = (nearPoint-realListener).normed() * alpha;
direction.normalize();
}
else if (context.NbPortal == 1)
{
alpha = alpha * (portalDist / _PortalInterpolate);
// d2 = (nearPoint-realListener).normed() * alpha;
d2 = (nearPoint-context.ListenerPos).normed() * (1-alpha);
direction = d1 + d2;
direction.normalize();
}
else
{
alpha = alpha * (portalDist / _PortalInterpolate);
// d1 = d1+d2;
d2 = (nearPoint-context.ListenerPos).normed() * (1-alpha);
// direction = d1 + d2 + (nearPoint-context.ListenerPos).normed() * (1-alpha);
direction = d1 + d2;
direction.normalize();
}
}
else
{
// alpha = 0.0f
if (context.NbPortal == 0)
{
direction = d1 = (nearPoint-realListener).normed();
}
else if (context.NbPortal == 1)
{
d2 = (nearPoint-context.ListenerPos).normed() * (1-alpha);
// d2 = d1; //(nearPoint-context.ListenerPos).normed(); // * (1-alpha);
direction = d1+d2;
direction.normalize();
}
else
{
// d2 = (nearPoint-context.ListenerPos).normed() * (1-alpha);
d1 = d1+d2;
// d2 = (nearPoint-realListener).normed();
// direction = d1+d2+(nearPoint-context.ListenerPos).normed() * (1-alpha);
direction.normalize();
}
}
return direction;
*/
}
float CClusteredSound::getPolyNearestPos(const std::vector<CVector> &poly, const CVector &pos, CVector &nearPoint)
{
CPlane plane;
plane.make(poly[0], poly[1], poly[2]);
CVector proj = plane.project(pos);
float minDist = FLT_MAX;
bool projIn = true;
uint nbVertex = (uint)poly.size();
// loop throw all vertex
for (uint j=0; j<nbVertex; ++j)
{
float d = (pos-poly[j]).sqrnorm();
// check if the vertex is the nearest point
if (d < minDist)
{
nearPoint = poly[j];
minDist = d;
}
// if (projIn /*&& j<poly.size()-1*/)
{
// check each segment
if (plane.getNormal()*((poly[(j+1)%nbVertex] - poly[j]) ^ (proj - poly[j])) < 0)
{
// the point is not inside the poly surface !
projIn = false;
// check if the nearest point is on this segment
CVector v1 = (poly[(j+1)%nbVertex] - poly[j]);
float v1Len = v1.norm();
v1 = v1 / v1Len;
CVector v2 = proj - poly[j];
// project v2 on v1
float p = v1 * v2;
if (p>=0 && p<=v1Len)
{
// the nearest point is on the segment!
nearPoint = poly[j] + v1 * p;
minDist = (nearPoint-pos).sqrnorm();
break;
}
}
}
}
if (projIn)
{
float d = (proj-pos).sqrnorm();
if (d < minDist)
{
// the nearest point is on the surface
nearPoint = proj;
minDist = d;
}
}
return sqrtf(minDist);
}
float CClusteredSound::getAABoxNearestPos(const CAABBox &box, const CVector &pos, CVector &nearPos)
{
CVector vMin, vMax;
box.getMin(vMin);
box.getMax(vMax);
nearPos = pos;
// X
clamp(nearPos.x, vMin.x, vMax.x);
// Y
clamp(nearPos.y, vMin.y, vMax.y);
// Z
clamp(nearPos.z, vMin.z, vMax.z);
return (pos-nearPos).norm();
}
}