sfxWorld.h
System for representing and tracking changes to the SFX world of sounds, sound spaces, occluders, and receivers.
Classes:
Information about an object in the SFX world.
Record for objects that are currently in scope.
Public Enumerations
SFXObjectFlags { SFXObjectOccluder = BIT( 0 ) SFXObjectPortal = BIT( 1 ) SFXObjectZone = BIT( 2 ) SFXObjectListener = BIT( 3 ) SFXObjectEmitter = BIT( 4 ) }
Property flags for SFXObjects.
Detailed Description
System for representing and tracking changes to the SFX world of sounds, sound spaces, occluders, and receivers.
This is system is abstracted over the number of dimensions it will work with, so it is usable for both 2D and 3D.
To put it to use, it has to be connected to the engine's scene graph structure by deriving suitable types from SFXObject.
Public Enumerations
SFXObjectFlags
Enumerator
- SFXObjectOccluder = BIT( 0 )
Object occludes sound.
- SFXObjectPortal = BIT( 1 )
Object connects two ambient zones.
Results in a continuous blend between the two ambient zones as the listener travels through the portal.
- SFXObjectZone = BIT( 2 )
- SFXObjectListener = BIT( 3 )
Object is currently used as listener.
note:
An object used as a listener will automatically have all its other sound-related functionality (occlusion, zoning) ignored.
- SFXObjectEmitter = BIT( 4 )
Property flags for SFXObjects.
1 2//----------------------------------------------------------------------------- 3// Copyright (c) 2012 GarageGames, LLC 4// 5// Permission is hereby granted, free of charge, to any person obtaining a copy 6// of this software and associated documentation files (the "Software"), to 7// deal in the Software without restriction, including without limitation the 8// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 9// sell copies of the Software, and to permit persons to whom the Software is 10// furnished to do so, subject to the following conditions: 11// 12// The above copyright notice and this permission notice shall be included in 13// all copies or substantial portions of the Software. 14// 15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 20// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 21// IN THE SOFTWARE. 22//----------------------------------------------------------------------------- 23 24#ifndef _SFXWORLD_H_ 25#define _SFXWORLD_H_ 26 27#ifndef _SCOPETRACKER_H_ 28 #include "util/scopeTracker.h" 29#endif 30#ifndef _TVECTOR_H_ 31 #include "core/util/tVector.h" 32#endif 33#ifndef _SFXSYSTEM_H_ 34 #include "sfx/sfxSystem.h" 35#endif 36#ifndef _SFXSOUNDSCAPE_H_ 37 #include "sfx/sfxSoundscape.h" 38#endif 39#ifndef _SFXAMBIENCE_H_ 40 #include "sfx/sfxAmbience.h" 41#endif 42 43// Disable warning about unreferenced 44// local function on VC. 45#ifdef TORQUE_COMPILER_VISUALC 46 #pragma warning( disable: 4505 ) 47#endif 48 49 50//#define DEBUG_SPEW 51 52 53/// @file 54/// System for representing and tracking changes to the SFX world of 55/// sounds, sound spaces, occluders, and receivers. 56/// 57/// This is system is abstracted over the number of dimensions it will 58/// work with, so it is usable for both 2D and 3D. 59/// 60/// To put it to use, it has to be connected to the engine's scene 61/// graph structure by deriving suitable types from SFXObject. 62 63 64class SFXAmbience; 65 66 67/// Property flags for SFXObjects. 68enum SFXObjectFlags 69{ 70 /// Object occludes sound. 71 SFXObjectOccluder = BIT( 0 ), 72 73 /// Object connects two ambient zones. Results in a continuous blend 74 /// between the two ambient zones as the listener travels through the 75 /// portal. 76 SFXObjectPortal = BIT( 1 ), 77 78 /// 79 SFXObjectZone = BIT( 2 ), 80 81 /// Object is currently used as listener. 82 /// 83 /// @note An object used as a listener will automatically have all 84 /// its other sound-related functionality (occlusion, zoning) ignored. 85 SFXObjectListener = BIT( 3 ), 86 87 /// 88 SFXObjectEmitter = BIT( 4 ), 89}; 90 91 92/// Information about an object in the SFX world. 93/// 94/// SFXObjects are: 95/// 96/// - Occluders: blocking sound 97/// - Zones: ambient sound spaces 98/// - Portals: connectors between zones 99/// - Listeners: receiving sound 100/// 101/// Note that emitters are not managed by the SFX world. Instead, the set of 102/// 3D voices active on the device at any one point is defined as the set of 103/// current sound sources. 104/// 105template< S32 NUM_DIMENSIONS > 106class SFXObject : public ScopeTrackerObject< NUM_DIMENSIONS > 107{ 108 public: 109 110 typedef ScopeTrackerObject< NUM_DIMENSIONS> Parent; 111 112 protected: 113 114 /// 115 BitSet32 mFlags; 116 117 public: 118 119 /// 120 SFXObject() 121 : Parent() {} 122 123 /// Return true if this object is only a sound occluder without any more ambient 124 /// sound properties. These kind of objects are not put into the tracking system. 125 bool isOccluderOnly() const { return ( isOccluder() && !isZone() ); } 126 127 /// Return true if this object is occluding sound. 128 bool isOccluder() const { return mFlags.test( SFXObjectOccluder ); } 129 130 /// Return true if this object connects two ambient spaces. 131 bool isPortal() const { return mFlags.test( SFXObjectPortal ); } 132 133 /// 134 bool isZone() const { return mFlags.test( SFXObjectZone ); } 135 136 /// Return true if this object is currently being used as the listener. 137 /// @note All other sound-related properties (occlusion, ambience, etc.) will be ignored 138 /// on the listener object. 139 bool isListener() const { return mFlags.test( SFXObjectListener ); } 140 141 /// 142 bool isEmitter() const { return mFlags.test( SFXObjectEmitter ); } 143 144 /// 145 void setFlags( U32 bits ) { mFlags.set( bits ); } 146 147 /// 148 void clearFlags( U32 bits ) { mFlags.clear( bits ); } 149 150 /// @name Implementor Interface 151 /// 152 /// The following methods must be implemented by the client. They are defined here 153 /// just for reference. If you don't override them, you'll get link errors. 154 /// 155 /// @{ 156 157 /// Return the world-space position of the ears on this object. 158 /// This will only be called on objects that are made listeners. 159 void getReferenceCenter( F32 pos[ NUM_DIMENSIONS ] ) const; 160 161 /// Return the object's bounding box in world-space including its surround zone. 162 void getBounds( F32 minBounds[ NUM_DIMENSIONS ], F32 maxBounds[ NUM_DIMENSIONS ] ) const; 163 164 /// Return the object's bounding box in world-space excluding its surround zone. 165 void getRealBounds( F32 minBounds[ NUM_DIMENSIONS ], F32 maxBounds[ NUM_DIMENSIONS ] ) const; 166 167 /// Return true if the given point is contained in the object's volume. 168 bool containsPoint( const F32 point[ NUM_DIMENSIONS ] ) const; 169 170 /// Return the ambient space active within this object. 171 /// @note Portals cannot have their own ambient spaces. 172 SFXAmbience* getAmbience() const; 173 174 /// 175 String describeSelf() const; 176 177 /// @} 178}; 179 180 181/// SFXWorld tracks an N-dimensional world of SFXObjects. 182/// 183/// 184/// This class uses two systems as back-ends: occlusion handling is passed on to the 185/// occlusion manager installed on the system and tracking the listener traveling through 186/// the ambient spaces is 187/// 188template< S32 NUM_DIMENSIONS, typename Object > 189class SFXWorld : public ScopeTracker< NUM_DIMENSIONS, Object > 190{ 191 public: 192 193 typedef ScopeTracker< NUM_DIMENSIONS, Object> Parent; 194 195 protected: 196 197 /// Record for objects that are currently in scope. 198 struct Scope 199 { 200 /// Sort key on scope stack. This is used to establish an ordering between 201 /// the ambient spaces that the listener is in concurrently. 202 F32 mSortValue; 203 204 /// The soundscape instance. Only objects for which the listener actually 205 /// is in one of the sound zones, will have an associated soundscape. 206 SFXSoundscape* mSoundscape; 207 208 /// The object defining this scope. If this is a portal, we transition 209 /// between this space and the space above us in the stack. 210 Object mObject; 211 212 Scope() :mSortValue(0), mSoundscape(NULL) {} 213 Scope( F32 sortValue, Object object ) 214 : mSortValue( sortValue ), 215 mSoundscape( NULL ), 216 mObject( object ) {} 217 }; 218 219 /// A top-down ordering of all objects that are currently in scope. 220 Vector< Scope> mScopeStack; 221 222 /// Return the index on the scope stack that is tied to the given object or -1 if 223 /// the object is not on the stack. 224 S32 _findScope( Object object ); 225 226 /// 227 void _sortScopes(); 228 229 /// 230 F32 _getSortValue( Object object ); 231 232 // ScopeTracker. 233 virtual void _onScopeIn( Object object ); 234 virtual void _onScopeOut( Object object ); 235 236 public: 237 238 /// 239 SFXWorld(); 240 241 /// Update the state of the SFX world. 242 /// 243 /// @note This method may potentially be costly; don't update every frame. 244 void update(); 245 246 // ScopeTracker. 247 void notifyChanged( Object object ); 248}; 249 250 251 252//----------------------------------------------------------------------------- 253 254template< S32 NUM_DIMENSIONS, class Object > 255SFXWorld< NUM_DIMENSIONS, Object >::SFXWorld() 256{ 257 VECTOR_SET_ASSOCIATION( mScopeStack ); 258} 259 260//----------------------------------------------------------------------------- 261 262template< S32 NUM_DIMENSIONS, class Object > 263void SFXWorld< NUM_DIMENSIONS, Object >::update() 264{ 265 if( !this->mReferenceObject ) 266 return; 267 268 // Get the reference center (listener) pos. 269 270 F32 listenerPos[ NUM_DIMENSIONS ]; 271 for( U32 n = 0; n < NUM_DIMENSIONS; n ++ ) 272 listenerPos[ n ] = Deref( this->mReferenceObject ).getMinTrackingNode( n )->getPosition(); 273 274 // Check all current scopes. 275 276 SFXSoundscapeManager* soundscapeMgr = SFX->getSoundscapeManager(); 277 for( U32 i = 0; i < mScopeStack.size(); ++ i ) 278 { 279 Scope& scope = mScopeStack[ i ]; 280 281 if( Deref( scope.mObject ).containsPoint( listenerPos ) ) 282 { 283 // Listener is in the object. 284 285 if( !scope.mSoundscape ) 286 { 287 // Add the object's ambient space to the soundscape mix. 288 289 SFXAmbience* ambience = Deref( scope.mObject ).getAmbience(); 290 AssertFatal( ambience != NULL, "SFXWorld::update() - object on stack that does not have an ambient space!" ); 291 292 // Find the index at which to insert the ambient space. 0 is always 293 // the global space and each active soundscape lower down our stack 294 // increments by one. 295 296 U32 index = 1; 297 for( U32 j = 0; j < i; ++ j ) 298 if( mScopeStack[ j ].mSoundscape ) 299 ++ index; 300 301 scope.mSoundscape = soundscapeMgr->insertSoundscape( index, ambience ); 302 } 303 } 304 else 305 { 306 SFXAmbience* ambience = Deref( scope.mObject ).getAmbience(); 307 AssertFatal( ambience != NULL, "SFXWorld::update() - object on stack that does not have an ambient space!" ); 308 309 // Listener is neither inside object nor in its 310 // proximity zone. Kill its ambient soundscape if it 311 // has one. 312 313 if( scope.mSoundscape ) 314 { 315 soundscapeMgr->removeSoundscape( scope.mSoundscape ); 316 scope.mSoundscape = NULL; 317 } 318 } 319 } 320} 321 322//----------------------------------------------------------------------------- 323 324template< S32 NUM_DIMENSIONS, class Object > 325void SFXWorld< NUM_DIMENSIONS, Object >::notifyChanged( Object object ) 326{ 327 SFXAmbience* ambience = Deref( object ).getAmbience(); 328 if( !ambience ) 329 return; 330 331 for( U32 i = 0; i < mScopeStack.size(); ++ i ) 332 { 333 Scope& scope = mScopeStack[ i ]; 334 if( scope.mObject == object ) 335 { 336 if( scope.mSoundscape ) 337 scope.mSoundscape->setAmbience( ambience ); 338 break; 339 } 340 } 341} 342 343//----------------------------------------------------------------------------- 344 345template< int NUM_DIMENSIONS, class Object > 346void SFXWorld< NUM_DIMENSIONS, Object >::_onScopeIn( Object object ) 347{ 348 #ifdef DEBUG_SPEW 349 Platform::outputDebugString( "[SFXWorld] Object 0x%x in scope now", object ); 350 #endif 351 352 // Get the object's ambience properties. If it has 353 // none, ignore the object. 354 355 SFXAmbience* ambience = Deref( object ).getAmbience(); 356 if( !ambience ) 357 return; 358 359 // Find where to insert the object into the stack. 360 361 typename Vector< Scope >::iterator iter = mScopeStack.begin(); 362 F32 sortValue = _getSortValue( object ); 363 while( iter != mScopeStack.end() && iter->mSortValue >= sortValue ) 364 ++ iter; 365 // If the object is already on the scope stack, do not add it again 366 S32 index = _findScope(object); 367 if (index != -1) 368 return; 369 // Insert the object into the stack. 370 mScopeStack.insert( iter, Scope( sortValue, object ) ); 371} 372 373//----------------------------------------------------------------------------- 374 375template< S32 NUM_DIMENSIONS, class Object > 376void SFXWorld< NUM_DIMENSIONS, Object >::_onScopeOut( Object object ) 377{ 378 #ifdef DEBUG_SPEW 379 Platform::outputDebugString( "[SFXWorld] Object 0x%x out of scope now", object ); 380 #endif 381 382 // Find the object on the stack. 383 384 S32 index = _findScope( object ); 385 if( index == -1 ) 386 return; 387 388 // Remove its soundscape. 389 390 Scope& scope = mScopeStack[ index ]; 391 if( scope.mSoundscape ) 392 SFX->getSoundscapeManager()->removeSoundscape( scope.mSoundscape ); 393 394 mScopeStack.erase( index ); 395} 396 397//----------------------------------------------------------------------------- 398 399template< S32 NUM_DIMENSIONS, class Object > 400F32 SFXWorld< NUM_DIMENSIONS, Object >::_getSortValue( Object object ) 401{ 402 //RDTODO: probably need to work with the overlap here instead of the full volumes 403 404 // Get the real object bounds (without the surround zone). 405 406 F32 minBounds[ NUM_DIMENSIONS ], maxBounds[ NUM_DIMENSIONS ]; 407 Deref( object ).getRealBounds( minBounds, maxBounds ); 408 409 // Get the minimum extent. 410 411 F32 minExtent = maxBounds[ 0 ] - minBounds[ 0 ]; 412 for( U32 n = 1; n < NUM_DIMENSIONS; ++ n ) 413 minExtent = getMin( minExtent, maxBounds[ n ] - minBounds[ n ] ); 414 415 return minExtent; 416} 417 418//----------------------------------------------------------------------------- 419 420template< S32 NUM_DIMENSIONS, class Object > 421S32 SFXWorld< NUM_DIMENSIONS, Object >::_findScope( Object object ) 422{ 423 for( U32 i = 0; i < mScopeStack.size(); ++ i ) 424 if( mScopeStack[ i ].mObject == object ) 425 return i; 426 427 return -1; 428} 429 430#endif // !_SFXWORLD_H_ 431