sfxWorld.h 14 KB

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