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() :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