sfxWorld.h

Engine/source/sfx/sfxWorld.h

System for representing and tracking changes to the SFX world of sounds, sound spaces, occluders, and receivers.

More...

Classes:

class

Information about an object in the SFX world.

class

SFXWorld tracks an N-dimensional world of SFXObjects.

class

Record for objects that are currently in scope.

Public Enumerations

enum
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() {}
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      
366   // Insert the object into the stack.
367   
368   mScopeStack.insert( iter, Scope( sortValue, object ) );
369}
370
371//-----------------------------------------------------------------------------
372
373template< S32 NUM_DIMENSIONS, class Object >
374void SFXWorld< NUM_DIMENSIONS, Object >::_onScopeOut( Object object )
375{
376   #ifdef DEBUG_SPEW
377   Platform::outputDebugString( "[SFXWorld] Object 0x%x out of scope now", object );
378   #endif
379   
380   // Find the object on the stack.
381   
382   S32 index = _findScope( object );
383   if( index == -1 )
384      return;
385      
386   // Remove its soundscape.
387   
388   Scope& scope = mScopeStack[ index ];
389   if( scope.mSoundscape )
390      SFX->getSoundscapeManager()->removeSoundscape( scope.mSoundscape );
391      
392   mScopeStack.erase( index );
393}
394
395//-----------------------------------------------------------------------------
396
397template< S32 NUM_DIMENSIONS, class Object >
398F32 SFXWorld< NUM_DIMENSIONS, Object >::_getSortValue( Object object )
399{
400   //RDTODO: probably need to work with the overlap here instead of the full volumes
401   
402   // Get the real object bounds (without the surround zone).
403   
404   F32 minBounds[ NUM_DIMENSIONS ], maxBounds[ NUM_DIMENSIONS ];
405   Deref( object ).getRealBounds( minBounds, maxBounds );
406   
407   // Get the minimum extent.
408   
409   F32 minExtent = maxBounds[ 0 ] - minBounds[ 0 ];
410   for( U32 n = 1; n < NUM_DIMENSIONS; ++ n )
411      minExtent = getMin( minExtent, maxBounds[ n ] - minBounds[ n ] );
412      
413   return minExtent;
414}
415
416//-----------------------------------------------------------------------------
417
418template< S32 NUM_DIMENSIONS, class Object >
419S32 SFXWorld< NUM_DIMENSIONS, Object >::_findScope( Object object )
420{
421   for( U32 i = 0; i < mScopeStack.size(); ++ i )
422      if( mScopeStack[ i ].mObject == object )
423         return i;
424         
425   return -1;
426}
427
428#endif // !_SFXWORLD_H_
429