sfxEmitter.cpp

Engine/source/T3D/sfx/sfxEmitter.cpp

More...

Public Variables

bool

For frame signal.

Public Functions

ConsoleDocClass(SFXEmitter , "@brief An invisible 3D object that emits <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">sound.\n\n</a>" "Sound emitters are used <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> place sounds in the level. They are full 3D objects with their own position and orientation and " "when assigned 3D sounds, the transform and velocity of the sound emitter object will be applied <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> the 3D <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">sound.\n\n</a>" "Sound emitters can be set up of in either of two <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">ways:\n</a>" "< ul >\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "< li >< p >By assigning an existing <a href="/coding/class/classsfxtrack/">SFXTrack</a> <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> the emitter 's #track property.</p >\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "< p >In this case the general sound setup(3D, streaming, looping, etc.) will be taken from #track. However, the emitter 's " "own properties will still override their corresponding properties in the #track 's SFXDescription.</p ></li >\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "< li >< p >By directly assigning <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> sound <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a702945180aa732857b380a007a7e2a21">file</a> <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> the emitter 's #fileName property.</p >\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "< p >In this case, the sound <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a702945180aa732857b380a007a7e2a21">file</a> will be set up <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> playback according <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> the properties defined on the emitter.</p >\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "</ul >\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n\n</a>" "Using #playOnAdd emitters can be configured <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> start playing immediately when they are added <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> the system(e.g. when the level " "objects are loaded from the mission <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a702945180aa732857b380a007a7e2a21">file</a>).\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n\n</a>" " @note A sound emitter need not necessarily emit <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> 3D sound. Instead, sound emitters may also be used <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> play " "non-positional sounds. For placing background audio <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> level, however , it is usually easier <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> use <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">LevelInfo::soundAmbience.\n\n</a>" " @section SFXEmitter_networking Sound Emitters and <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">Networking\n\n</a>" "It is important <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> be aware of the fact that sounds will only play client-side whereas <a href="/coding/class/classsfxemitter/">SFXEmitter</a> objects are server-side " "entities. This means that <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> server-side object has no connection <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> the actual sound playing on the client. It is thus " "not possible <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> the server-object <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> perform queries about playback status and other <a href="/coding/file/pointer_8h/#pointer_8h_1adb82dfe18535e9a30aa97d275f82bd55">source</a>-related properties as these " "may in fact differ from client <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">client.\n\n</a>" " @ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">SFX\n</a>" )
DefineEngineMethod(SFXEmitter , getSource , SFXSource * , () , "Get the sound <a href="/coding/file/pointer_8h/#pointer_8h_1adb82dfe18535e9a30aa97d275f82bd55">source</a> object from the <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">emitter.\n\n</a>" "@return The sound <a href="/coding/file/pointer_8h/#pointer_8h_1adb82dfe18535e9a30aa97d275f82bd55">source</a> used by the emitter or null." "@note This method will return null when called on the server-side <a href="/coding/class/classsfxemitter/">SFXEmitter</a> object. Only client-side ghosts " "actually hold on <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> %<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">SFXSources.\n\n</a>" )
DefineEngineMethod(SFXEmitter , play , void , () , "Manually start playback of the emitter's <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">sound.\n</a>" "If this is called on the server-side object, the play command will be related <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> all client-side <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">ghosts.\n</a>" )
DefineEngineMethod(SFXEmitter , stop , void , () , "Manually stop playback of the emitter's <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">sound.\n</a>" "If this is called on the server-side object, the stop command will be related <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> all client-side <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">ghosts.\n</a>" )

Detailed Description

Public Variables

bool gEditingMission 

For frame signal.

Public Functions

ConsoleDocClass(SFXEmitter , "@brief An invisible 3D object that emits <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">sound.\n\n</a>" "Sound emitters are used <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> place sounds in the level. They are full 3D objects with their own position and orientation and " "when assigned 3D sounds, the transform and velocity of the sound emitter object will be applied <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> the 3D <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">sound.\n\n</a>" "Sound emitters can be set up of in either of two <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">ways:\n</a>" "< ul >\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "< li >< p >By assigning an existing <a href="/coding/class/classsfxtrack/">SFXTrack</a> <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> the emitter 's #track property.</p >\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "< p >In this case the general sound setup(3D, streaming, looping, etc.) will be taken from #track. However, the emitter 's " "own properties will still override their corresponding properties in the #track 's SFXDescription.</p ></li >\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "< li >< p >By directly assigning <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> sound <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a702945180aa732857b380a007a7e2a21">file</a> <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> the emitter 's #fileName property.</p >\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "< p >In this case, the sound <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a702945180aa732857b380a007a7e2a21">file</a> will be set up <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> playback according <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> the properties defined on the emitter.</p >\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "</ul >\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n\n</a>" "Using #playOnAdd emitters can be configured <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> start playing immediately when they are added <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> the system(e.g. when the level " "objects are loaded from the mission <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a702945180aa732857b380a007a7e2a21">file</a>).\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n\n</a>" " @note A sound emitter need not necessarily emit <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> 3D sound. Instead, sound emitters may also be used <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> play " "non-positional sounds. For placing background audio <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> level, however , it is usually easier <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> use <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">LevelInfo::soundAmbience.\n\n</a>" " @section SFXEmitter_networking Sound Emitters and <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">Networking\n\n</a>" "It is important <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> be aware of the fact that sounds will only play client-side whereas <a href="/coding/class/classsfxemitter/">SFXEmitter</a> objects are server-side " "entities. This means that <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> server-side object has no connection <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> the actual sound playing on the client. It is thus " "not possible <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> the server-object <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> perform queries about playback status and other <a href="/coding/file/pointer_8h/#pointer_8h_1adb82dfe18535e9a30aa97d275f82bd55">source</a>-related properties as these " "may in fact differ from client <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">client.\n\n</a>" " @ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">SFX\n</a>" )

DefineEngineMethod(SFXEmitter , getSource , SFXSource * , () , "Get the sound <a href="/coding/file/pointer_8h/#pointer_8h_1adb82dfe18535e9a30aa97d275f82bd55">source</a> object from the <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">emitter.\n\n</a>" "@return The sound <a href="/coding/file/pointer_8h/#pointer_8h_1adb82dfe18535e9a30aa97d275f82bd55">source</a> used by the emitter or null." "@note This method will return null when called on the server-side <a href="/coding/class/classsfxemitter/">SFXEmitter</a> object. Only client-side ghosts " "actually hold on <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> %<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">SFXSources.\n\n</a>" )

DefineEngineMethod(SFXEmitter , play , void , () , "Manually start playback of the emitter's <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">sound.\n</a>" "If this is called on the server-side object, the play command will be related <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> all client-side <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">ghosts.\n</a>" )

DefineEngineMethod(SFXEmitter , stop , void , () , "Manually stop playback of the emitter's <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">sound.\n</a>" "If this is called on the server-side object, the stop command will be related <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> all client-side <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">ghosts.\n</a>" )

IMPLEMENT_CO_NETOBJECT_V1(SFXEmitter )

   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#include "platform/platform.h"
  25#include "T3D/sfx/sfxEmitter.h"
  26#include "sfx/sfxSystem.h"
  27#include "sfx/sfxTrack.h"
  28#include "sfx/sfxSource.h"
  29#include "sfx/sfxTypes.h"
  30#include "scene/sceneRenderState.h"
  31#include "core/stream/bitStream.h"
  32#include "sim/netConnection.h"
  33#include "math/mathIO.h"
  34#include "math/mQuat.h"
  35#include "renderInstance/renderPassManager.h"
  36#include "gfx/gfxTransformSaver.h"
  37#include "gfx/gfxDrawUtil.h"
  38#include "gfx/primBuilder.h"
  39#include "console/engineAPI.h"
  40
  41
  42IMPLEMENT_CO_NETOBJECT_V1( SFXEmitter );
  43
  44ConsoleDocClass( SFXEmitter,
  45   "@brief An invisible 3D object that emits sound.\n\n"
  46   
  47   "Sound emitters are used to place sounds in the level.  They are full 3D objects with their own position and orientation and "
  48   "when assigned 3D sounds, the transform and velocity of the sound emitter object will be applied to the 3D sound.\n\n"
  49   
  50   "Sound emitters can be set up of in either of two ways:\n"
  51   
  52   "<ul>\n"
  53   "<li><p>By assigning an existing SFXTrack to the emitter's #track property.</p>\n"
  54   "<p>In this case the general sound setup (3D, streaming, looping, etc.) will be taken from #track.  However, the emitter's "
  55      "own properties will still override their corresponding properties in the #track's SFXDescription.</p></li>\n"
  56   "<li><p>By directly assigning a sound file to the emitter's #fileName property.</p>\n"
  57   "<p>In this case, the sound file will be set up for playback according to the properties defined on the emitter.</p>\n"
  58   "</ul>\n\n"
  59   
  60   "Using #playOnAdd emitters can be configured to start playing immediately when they are added to the system (e.g. when the level "
  61   "objects are loaded from the mission file).\n\n"
  62      
  63   "@note A sound emitter need not necessarily emit a 3D sound.  Instead, sound emitters may also be used to play "
  64      "non-positional sounds.  For placing background audio to a level, however, it is usually easier to use LevelInfo::soundAmbience.\n\n"
  65      
  66   "@section SFXEmitter_networking Sound Emitters and Networking\n\n"
  67   
  68   "It is important to be aware of the fact that sounds will only play client-side whereas SFXEmitter objects are server-side "
  69   "entities.  This means that a server-side object has no connection to the actual sound playing on the client.  It is thus "
  70   "not possible for the server-object to perform queries about playback status and other source-related properties as these "
  71   "may in fact differ from client to client.\n\n"
  72   
  73   "@ingroup SFX\n"
  74);
  75
  76
  77extern bool gEditingMission;
  78bool SFXEmitter::smRenderEmitters;
  79F32 SFXEmitter::smRenderPointSize = 0.8f;
  80F32 SFXEmitter::smRenderRadialIncrements = 5.f;
  81F32 SFXEmitter::smRenderSweepIncrements = 5.f;
  82F32 SFXEmitter::smRenderPointDistance = 5.f;
  83ColorI SFXEmitter::smRenderColorPlayingInRange( 50, 255, 50, 255 );
  84ColorI SFXEmitter::smRenderColorPlayingOutOfRange( 50, 128, 50, 255 );
  85ColorI SFXEmitter::smRenderColorStoppedInRange( 0, 0, 0, 255 );
  86ColorI SFXEmitter::smRenderColorStoppedOutOfRange( 128, 128, 128, 255 );
  87ColorI SFXEmitter::smRenderColorInnerCone( 0, 0, 255, 255 );
  88ColorI SFXEmitter::smRenderColorOuterCone( 255, 0, 255, 255 );
  89ColorI SFXEmitter::smRenderColorOutsideVolume( 255, 0, 0, 255 );
  90ColorI SFXEmitter::smRenderColorRangeSphere( 200, 0, 0, 90 );
  91
  92
  93//-----------------------------------------------------------------------------
  94
  95SFXEmitter::SFXEmitter()
  96   :  SceneObject(),
  97      mSource( NULL ),
  98      mTrack( NULL ),
  99      mUseTrackDescriptionOnly( false ),
 100      mLocalProfile( &mDescription ),
 101      mPlayOnAdd( true )
 102{
 103   mTypeMask |= MarkerObjectType;
 104   mNetFlags.set( Ghostable | ScopeAlways );
 105
 106   mDescription.mIs3D = true;
 107   mDescription.mIsLooping = true;
 108   mDescription.mIsStreaming = false;
 109   mDescription.mFadeInTime = -1.f;
 110   mDescription.mFadeOutTime = -1.f;
 111   
 112   mLocalProfile._registerSignals();
 113
 114   mObjBox.minExtents.set( -1.f, -1.f, -1.f );
 115   mObjBox.maxExtents.set( 1.f, 1.f, 1.f );
 116}
 117
 118//-----------------------------------------------------------------------------
 119
 120SFXEmitter::~SFXEmitter()
 121{
 122   mLocalProfile._unregisterSignals();
 123   
 124   SFX_DELETE( mSource );
 125}
 126
 127//-----------------------------------------------------------------------------
 128
 129void SFXEmitter::consoleInit()
 130{
 131   Con::addVariable( "$SFXEmitter::renderEmitters", TypeBool, &smRenderEmitters,
 132      "Whether to render enhanced range feedback in the editor on all emitters regardless of selection state.\n"
 133     "@ingroup SFX\n");
 134      
 135   //TODO: not implemented ATM
 136   //Con::addVariable( "$SFXEmitter::renderPointSize", TypeF32, &smRenderPointSize );
 137   
 138   Con::addVariable( "$SFXEmitter::renderPointDistance", TypeF32, &smRenderPointDistance,
 139      "The distance between individual points in the sound emitter rendering in the editor as the points move from the emitter's center away to maxDistance.\n"
 140     "@ingroup SFX\n");
 141   Con::addVariable( "$SFXEmitter::renderRadialIncrements", TypeF32, &smRenderRadialIncrements,
 142      "The stepping (in degrees) for the radial sweep along the axis of the XY plane sweep for sound emitter rendering in the editor.\n"
 143     "@ingroup SFX\n");
 144   Con::addVariable( "$SFXEmitter::renderSweepIncrements", TypeF32, &smRenderSweepIncrements,
 145      "The stepping (in degrees) for the radial sweep on the XY plane for sound emitter rendering in the editor.\n"
 146     "@ingroup SFX\n");
 147   Con::addVariable( "$SFXEmitter::renderColorPlayingInRange", TypeColorI, &smRenderColorPlayingInRange,
 148      "The color with which to render a sound emitter's marker cube in the editor when the emitter's sound is playing and in range of the listener.\n"
 149     "@ingroup SFX\n" );
 150   Con::addVariable( "$SFXEmitter::renderColorPlayingOutOfRange", TypeColorI, &smRenderColorPlayingOutOfRange,
 151      "The color with which to render a sound emitter's marker cube in the editor when the emitter's sound is playing but out of the range of the listener.\n"
 152     "@ingroup SFX\n" );
 153   Con::addVariable( "$SFXEmitter::renderColorStoppedInRange", TypeColorI, &smRenderColorStoppedInRange,
 154      "The color with which to render a sound emitter's marker cube in the editor when the emitter's sound is not playing but the emitter is in range of the listener.\n"
 155     "@ingroup SFX\n" );
 156   Con::addVariable( "$SFXEmitter::renderColorStoppedOutOfRange", TypeColorI, &smRenderColorStoppedOutOfRange,
 157      "The color with which to render a sound emitter's marker cube in the editor when the emitter's sound is not playing and the emitter is out of range of the listener.\n"
 158     "@ingroup SFX\n" );
 159   Con::addVariable( "$SFXEmitter::renderColorInnerCone", TypeColorI, &smRenderColorInnerCone,
 160      "The color with which to render dots in the inner sound cone (Editor only).\n"
 161     "@ingroup SFX\n");
 162   Con::addVariable( "$SFXEmitter::renderColorOuterCone", TypeColorI, &smRenderColorOuterCone,
 163      "The color with which to render dots in the outer sound cone (Editor only).\n"
 164     "@ingroup SFX\n" );
 165   Con::addVariable( "$SFXEmitter::renderColorOutsideVolume", TypeColorI, &smRenderColorOutsideVolume,
 166      "The color with which to render dots outside of the outer sound cone (Editor only).\n"
 167     "@ingroup SFX\n" );
 168   Con::addVariable( "$SFXEmitter::renderColorRangeSphere", TypeColorI, &smRenderColorRangeSphere,
 169      "The color of the range sphere with which to render sound emitters in the editor.\n"
 170     "@ingroup SFX\n" );
 171}
 172
 173//-----------------------------------------------------------------------------
 174
 175void SFXEmitter::initPersistFields()
 176{
 177   addGroup( "Media" );
 178   
 179      addField( "track",               TypeSFXTrackName,          Offset( mTrack, SFXEmitter),
 180         "The track which the emitter should play.\n"
 181         "@note If assigned, this field will take precedence over a #fileName that may also be assigned to the "
 182            "emitter." );
 183      addField( "fileName",            TypeStringFilename,        Offset( mLocalProfile.mFilename, SFXEmitter),
 184         "The sound file to play.\n"
 185         "Use @b either this property @b or #track.  If both are assigned, #track takes precendence.  The primary purpose of this "
 186         "field is to avoid the need for the user to define SFXTrack datablocks for all sounds used in a level." );
 187   
 188   endGroup( "Media");
 189
 190   addGroup( "Sound" );
 191
 192      addField( "playOnAdd",           TypeBool,      Offset( mPlayOnAdd, SFXEmitter ),
 193         "Whether playback of the emitter's sound should start as soon as the emitter object is added to the level.\n"
 194         "If this is true, the emitter will immediately start to play when the level is loaded." );
 195      addField( "useTrackDescriptionOnly", TypeBool,  Offset( mUseTrackDescriptionOnly, SFXEmitter ),
 196         "If this is true, all fields except for #playOnAdd and #track are ignored on the emitter object.\n"
 197         "This is useful to prevent fields in the #track's description from being overridden by emitter fields." );
 198      addField( "isLooping",           TypeBool,      Offset( mDescription.mIsLooping, SFXEmitter ),
 199         "Whether to play #fileName in an infinite loop.\n"
 200         "If a #track is assigned, the value of this field is ignored.\n"
 201         "@see SFXDescription::isLooping" );
 202      addField( "isStreaming",         TypeBool,      Offset( mDescription.mIsStreaming, SFXEmitter ),
 203         "Whether to use streamed playback for #fileName.\n"
 204         "If a #track is assigned, the value of this field is ignored.\n"
 205         "@see SFXDescription::isStreaming\n\n"
 206         "@ref SFX_streaming" );
 207      addField( "sourceGroup",         TypeSFXSourceName, Offset( mDescription.mSourceGroup, SFXEmitter ),
 208         "The SFXSource to which to assign the sound of this emitter as a child.\n"
 209         "@note This field is ignored if #useTrackDescriptionOnly is true.\n\n"
 210         "@see SFXDescription::sourceGroup" );
 211      addField( "volume",              TypeF32,       Offset( mDescription.mVolume, SFXEmitter ),
 212         "Volume level to apply to the sound.\n"
 213         "@note This field is ignored if #useTrackDescriptionOnly is true.\n\n"
 214         "@see SFXDescription::volume" );
 215      addField( "pitch",               TypeF32,       Offset( mDescription.mPitch, SFXEmitter ),
 216         "Pitch shift to apply to the sound.  Default is 1 = play at normal speed.\n"
 217         "@note This field is ignored if #useTrackDescriptionOnly is true.\n\n"
 218         "@see SFXDescription::pitch" );
 219      addField( "fadeInTime",          TypeF32,       Offset( mDescription.mFadeInTime, SFXEmitter ),
 220         "Number of seconds to gradually fade in volume from zero when playback starts.\n"
 221         "@note This field is ignored if #useTrackDescriptionOnly is true.\n\n"
 222         "@see SFXDescription::fadeInTime" );
 223      addField( "fadeOutTime",         TypeF32,       Offset( mDescription.mFadeOutTime, SFXEmitter ),
 224         "Number of seconds to gradually fade out volume down to zero when playback is stopped or paused.\n"
 225         "@note This field is ignored if #useTrackDescriptionOnly is true.\n\n"
 226         "@see SFXDescription::fadeOutTime" );
 227
 228   endGroup( "Sound");
 229
 230   addGroup( "3D Sound" );
 231   
 232      addField( "is3D",                TypeBool,      Offset( mDescription.mIs3D, SFXEmitter ),
 233         "Whether to play #fileName as a positional (3D) sound or not.\n"
 234         "If a #track is assigned, the value of this field is ignored.\n\n"
 235         "@see SFXDescription::is3D" );
 236      addField( "referenceDistance",   TypeF32,       Offset( mDescription.mMinDistance, SFXEmitter ),
 237         "Distance at which to start volume attenuation of the 3D sound.\n"
 238         "@note This field is ignored if #useTrackDescriptionOnly is true.\n\n"
 239         "@see SFXDescription::referenceDistance" );
 240      addField( "maxDistance",         TypeF32,       Offset( mDescription.mMaxDistance, SFXEmitter ),
 241         "Distance at which to stop volume attenuation of the 3D sound.\n"
 242         "@note This field is ignored if #useTrackDescriptionOnly is true.\n\n"
 243         "@see SFXDescription::maxDistance" );
 244      addField( "scatterDistance",     TypePoint3F,   Offset( mDescription.mScatterDistance, SFXEmitter ),
 245         "Bounds on random offset to apply to initial 3D sound position.\n"
 246         "@note This field is ignored if #useTrackDescriptionOnly is true.\n\n"
 247         "@see SFXDescription::scatterDistance" );
 248      addField( "coneInsideAngle",     TypeS32,       Offset( mDescription.mConeInsideAngle, SFXEmitter ),
 249         "Angle of inner volume cone of 3D sound in degrees.\n"
 250         "@note This field is ignored if #useTrackDescriptionOnly is true.\n\n"
 251         "@see SFXDescription::coneInsideAngle" );
 252      addField( "coneOutsideAngle",    TypeS32,       Offset( mDescription.mConeOutsideAngle, SFXEmitter ),
 253         "Angle of outer volume cone of 3D sound in degrees\n"
 254         "@note This field is ignored if #useTrackDescriptionOnly is true.\n\n"
 255         "@see SFXDescription::coneOutsideAngle" );
 256      addField( "coneOutsideVolume",   TypeF32,       Offset( mDescription.mConeOutsideVolume, SFXEmitter ),
 257         "Volume scale factor of outside of outer volume 3D sound cone.\n"
 258         "@note This field is ignored if #useTrackDescriptionOnly is true.\n\n"
 259         "@see SFXDescription::coneOutsideVolume" );
 260   
 261   endGroup( "3D Sound" );
 262
 263   Parent::initPersistFields();
 264}
 265
 266//-----------------------------------------------------------------------------
 267
 268U32 SFXEmitter::packUpdate( NetConnection *con, U32 mask, BitStream *stream )
 269{
 270   U32 retMask = Parent::packUpdate( con, mask, stream );
 271
 272   if( stream->writeFlag( mask & InitialUpdateMask ) )
 273   {
 274      // If this is the initial update then all the source
 275      // values are dirty and must be transmitted.
 276      mask |= TransformUpdateMask;
 277      mDirty = AllDirtyMask;
 278
 279      // Clear the source masks... they are not
 280      // used during an initial update!
 281      mask &= ~<a href="/coding/class/classsfxemitter/#classsfxemitter_1ad82ad6be1a2de7b6efeccd42a03d7a6ca126c17c650b28cac6bd1d0d0ec929edf">AllSourceMasks</a>;
 282   }
 283
 284   stream->writeFlag( mPlayOnAdd );
 285
 286   // transform
 287   if( stream->writeFlag( mask & TransformUpdateMask ) )
 288      stream->writeAffineTransform( mObjToWorld );
 289
 290   // track
 291   if( stream->writeFlag( mDirty.test( Track ) ) )
 292      sfxWrite( stream, mTrack );
 293
 294   // filename
 295   if( stream->writeFlag( mDirty.test( Filename ) ) )
 296      stream->writeString( mLocalProfile.mFilename );
 297
 298   // volume
 299   if( stream->writeFlag( mDirty.test( Volume ) ) )
 300      stream->write( mDescription.mVolume );
 301      
 302   // pitch
 303   if( stream->writeFlag( mDirty.test( Pitch ) ) )
 304      stream->write( mDescription.mPitch );
 305
 306   // islooping
 307   if( stream->writeFlag( mDirty.test( IsLooping ) ) )
 308      stream->writeFlag( mDescription.mIsLooping );
 309      
 310   // isStreaming
 311   if( stream->writeFlag( mDirty.test( IsStreaming ) ) )
 312      stream->writeFlag( mDescription.mIsStreaming );
 313
 314   // is3d
 315   if( stream->writeFlag( mDirty.test( Is3D ) ) )
 316      stream->writeFlag( mDescription.mIs3D );
 317
 318   // minDistance
 319   if( stream->writeFlag( mDirty.test( MinDistance ) ) )
 320      stream->write( mDescription.mMinDistance );
 321
 322   // maxdistance
 323   if( stream->writeFlag( mDirty.test( MaxDistance) ) )
 324      stream->write( mDescription.mMaxDistance );
 325
 326   // coneinsideangle
 327   if( stream->writeFlag( mDirty.test( ConeInsideAngle ) ) )
 328      stream->write( mDescription.mConeInsideAngle );
 329
 330   // coneoutsideangle
 331   if( stream->writeFlag( mDirty.test( ConeOutsideAngle ) ) )
 332      stream->write( mDescription.mConeOutsideAngle );
 333
 334   // coneoutsidevolume
 335   if( stream->writeFlag( mDirty.test( ConeOutsideVolume ) ) )
 336      stream->write( mDescription.mConeOutsideVolume );
 337
 338   // sourcegroup
 339   if( stream->writeFlag( mDirty.test( SourceGroup ) ) )
 340      sfxWrite( stream, mDescription.mSourceGroup );
 341      
 342   // fadein
 343   if( stream->writeFlag( mDirty.test( FadeInTime ) ) )
 344      stream->write( mDescription.mFadeInTime );
 345      
 346   // fadeout
 347   if( stream->writeFlag( mDirty.test( FadeOutTime ) ) )
 348      stream->write( mDescription.mFadeOutTime );
 349      
 350   // scatterdistance
 351   if( stream->writeFlag( mDirty.test( ScatterDistance ) ) )
 352      mathWrite( *stream, mDescription.mScatterDistance );
 353
 354   mDirty.clear();
 355   
 356   stream->writeFlag( mUseTrackDescriptionOnly );
 357
 358   // We should never have both source masks 
 359   // enabled at the same time!
 360   AssertFatal( ( mask & AllSourceMasks ) != AllSourceMasks, 
 361      "SFXEmitter::packUpdate() - Bad source mask!" );
 362
 363   // Write the source playback state.
 364   stream->writeFlag( mask & SourcePlayMask );
 365   stream->writeFlag( mask & SourceStopMask );
 366
 367   return retMask;
 368}
 369
 370//-----------------------------------------------------------------------------
 371
 372bool SFXEmitter::_readDirtyFlag( BitStream* stream, U32 mask )
 373{
 374   bool flag = stream->readFlag();
 375   if ( flag )
 376      mDirty.set( mask );
 377
 378   return flag;
 379}
 380
 381//-----------------------------------------------------------------------------
 382
 383void SFXEmitter::unpackUpdate( NetConnection *conn, BitStream *stream )
 384{
 385   Parent::unpackUpdate( conn, stream );
 386
 387   // initial update?
 388   bool initialUpdate = stream->readFlag();
 389
 390   mPlayOnAdd = stream->readFlag();
 391
 392   // transform
 393   if ( _readDirtyFlag( stream, Transform ) )
 394   {
 395      MatrixF mat;
 396      stream->readAffineTransform(&mat);
 397      Parent::setTransform(mat);
 398   }
 399
 400   // track
 401   if ( _readDirtyFlag( stream, Track ) )
 402   {
 403      String errorStr;
 404      if( !sfxReadAndResolve( stream, &mTrack, errorStr ) )
 405         Con::errorf( "%s", errorStr.c_str() );
 406   }
 407
 408   // filename
 409   if ( _readDirtyFlag( stream, Filename ) )
 410      mLocalProfile.mFilename = stream->readSTString();
 411
 412   // volume
 413   if ( _readDirtyFlag( stream, Volume ) )
 414      stream->read( &mDescription.mVolume );
 415      
 416   // pitch
 417   if( _readDirtyFlag( stream, Pitch ) )
 418      stream->read( &mDescription.mPitch );
 419
 420   // islooping
 421   if ( _readDirtyFlag( stream, IsLooping ) )
 422      mDescription.mIsLooping = stream->readFlag();
 423      
 424   if( _readDirtyFlag( stream, IsStreaming ) )
 425      mDescription.mIsStreaming = stream->readFlag();
 426
 427   // is3d
 428   if ( _readDirtyFlag( stream, Is3D ) )
 429      mDescription.mIs3D = stream->readFlag();
 430
 431   // mindistance
 432   if ( _readDirtyFlag( stream, MinDistance ) )
 433      stream->read( &mDescription.mMinDistance );
 434
 435   // maxdistance
 436   if ( _readDirtyFlag( stream, MaxDistance ) )
 437   {
 438      stream->read( &mDescription.mMaxDistance );
 439      mObjScale.set( mDescription.mMaxDistance, mDescription.mMaxDistance, mDescription.mMaxDistance );
 440   }
 441
 442   // coneinsideangle
 443   if ( _readDirtyFlag( stream, ConeInsideAngle ) )
 444      stream->read( &mDescription.mConeInsideAngle );
 445
 446   // coneoutsideangle
 447   if ( _readDirtyFlag( stream, ConeOutsideAngle ) )
 448      stream->read( &mDescription.mConeOutsideAngle );
 449
 450   // coneoutsidevolume
 451   if ( _readDirtyFlag( stream, ConeOutsideVolume ) )
 452      stream->read( &mDescription.mConeOutsideVolume );
 453
 454   // sourcegroup
 455   if ( _readDirtyFlag( stream, SourceGroup ) )
 456   {
 457      String errorStr;
 458      if( !sfxReadAndResolve( stream, &mDescription.mSourceGroup, errorStr ) )
 459         Con::errorf( "%s", errorStr.c_str() );
 460   }
 461      
 462   // fadein
 463   if ( _readDirtyFlag( stream, FadeInTime ) )
 464      stream->read( &mDescription.mFadeInTime );
 465      
 466   // fadeout
 467   if( _readDirtyFlag( stream, FadeOutTime ) )
 468      stream->read( &mDescription.mFadeOutTime );
 469      
 470   // scatterdistance
 471   if( _readDirtyFlag( stream, ScatterDistance ) )
 472      mathRead( *stream, &mDescription.mScatterDistance );
 473      
 474   mUseTrackDescriptionOnly = stream->readFlag();
 475
 476   // update the emitter now?
 477   if ( !initialUpdate )
 478      _update();
 479
 480   // Check the source playback masks.
 481   if ( stream->readFlag() ) // SourcePlayMask
 482      play();
 483   if ( stream->readFlag() ) // SourceStopMask
 484      stop();
 485}
 486
 487//-----------------------------------------------------------------------------
 488
 489void SFXEmitter::onStaticModified( const char* slotName, const char* newValue )
 490{
 491   // NOTE: The signature for this function is very 
 492   // misleading... slotName is a StringTableEntry.
 493
 494   // We don't check for changes on the client side.
 495   if ( isClientObject() )
 496      return;
 497
 498   // Lookup and store the property names once here
 499   // and we can then just do pointer compares. 
 500   static StringTableEntry slotPosition   = StringTable->lookup( "position" );
 501   static StringTableEntry slotRotation   = StringTable->lookup( "rotation" );
 502   static StringTableEntry slotScale      = StringTable->lookup( "scale" );
 503   static StringTableEntry slotTrack      = StringTable->lookup( "track" );
 504   static StringTableEntry slotFilename   = StringTable->lookup( "fileName" );
 505   static StringTableEntry slotVolume     = StringTable->lookup( "volume" );
 506   static StringTableEntry slotPitch      = StringTable->lookup( "pitch" );
 507   static StringTableEntry slotIsLooping  = StringTable->lookup( "isLooping" );
 508   static StringTableEntry slotIsStreaming= StringTable->lookup( "isStreaming" );
 509   static StringTableEntry slotIs3D       = StringTable->lookup( "is3D" );
 510   static StringTableEntry slotRefDist    = StringTable->lookup( "referenceDistance" );
 511   static StringTableEntry slotMaxDist    = StringTable->lookup( "maxDistance" );
 512   static StringTableEntry slotConeInAng  = StringTable->lookup( "coneInsideAngle" );
 513   static StringTableEntry slotConeOutAng = StringTable->lookup( "coneOutsideAngle" );
 514   static StringTableEntry slotConeOutVol = StringTable->lookup( "coneOutsideVolume" );
 515   static StringTableEntry slotFadeInTime = StringTable->lookup( "fadeInTime" );
 516   static StringTableEntry slotFadeOutTime= StringTable->lookup( "fadeOutTime" );
 517   static StringTableEntry slotScatterDistance = StringTable->lookup( "scatterDistance" );
 518   static StringTableEntry slotSourceGroup= StringTable->lookup( "sourceGroup" );
 519   static StringTableEntry slotUseTrackDescriptionOnly = StringTable->lookup( "useTrackDescriptionOnly" );
 520
 521   // Set the dirty flags.
 522   mDirty.clear();
 523   if(  slotName == slotPosition ||
 524         slotName == slotRotation ||
 525         slotName == slotScale )
 526      mDirty.set( Transform );
 527
 528   else if( slotName == slotTrack )
 529      mDirty.set( Track );
 530
 531   else if( slotName == slotFilename )
 532      mDirty.set( Filename );
 533
 534   else if( slotName == slotVolume )
 535      mDirty.set( Volume );
 536      
 537   else if( slotName == slotPitch )
 538      mDirty.set( Pitch );
 539
 540   else if( slotName == slotIsLooping )
 541      mDirty.set( IsLooping );
 542      
 543   else if( slotName == slotIsStreaming )
 544      mDirty.set( IsStreaming );
 545
 546   else if( slotName == slotIs3D )
 547      mDirty.set( Is3D );
 548
 549   else if( slotName == slotRefDist )
 550      mDirty.set( MinDistance );
 551
 552   else if( slotName == slotMaxDist )
 553      mDirty.set( MaxDistance );
 554
 555   else if( slotName == slotConeInAng )
 556      mDirty.set( ConeInsideAngle );
 557
 558   else if( slotName == slotConeOutAng )
 559      mDirty.set( ConeOutsideAngle );
 560
 561   else if( slotName == slotConeOutVol )
 562      mDirty.set( ConeOutsideVolume );
 563      
 564   else if( slotName == slotFadeInTime )
 565      mDirty.set( FadeInTime );
 566      
 567   else if( slotName == slotFadeOutTime )
 568      mDirty.set( FadeOutTime );
 569      
 570   else if( slotName == slotScatterDistance )
 571      mDirty.set( ScatterDistance );
 572      
 573   else if( slotName == slotSourceGroup )
 574      mDirty.set( SourceGroup );
 575      
 576   else if( slotName == slotUseTrackDescriptionOnly )
 577      mDirty.set( TrackOnly );
 578
 579   if( mDirty )
 580      setMaskBits( DirtyUpdateMask );
 581}
 582
 583//-----------------------------------------------------------------------------
 584
 585void SFXEmitter::inspectPostApply()
 586{
 587   // Parent will call setScale so sync up scale with distance.
 588   
 589   F32 maxDistance = mDescription.mMaxDistance;
 590   if( mUseTrackDescriptionOnly && mTrack )
 591      maxDistance = mTrack->getDescription()->mMaxDistance;
 592      
 593   mObjScale.set( maxDistance, maxDistance, maxDistance );
 594   
 595   Parent::inspectPostApply();
 596}
 597
 598//-----------------------------------------------------------------------------
 599
 600bool SFXEmitter::onAdd()
 601{
 602   if( !Parent::onAdd() )
 603      return false;
 604
 605   if( isServerObject() )
 606   {
 607      // Validate the data we'll be passing across
 608      // the network to the client.
 609      mDescription.validate();
 610      
 611      // Read an old 'profile' field for backwards-compatibility.
 612      
 613      if( !mTrack )
 614      {
 615         static const char* sProfile = StringTable->insert( "profile" );
 616         const char* profileName = getDataField( sProfile, NULL );
 617         if( profileName &&  profileName[ 0 ] )
 618         {
 619            if( !Sim::findObject( profileName, mTrack ) )
 620               Con::errorf( "SFXEmitter::onAdd - No SFXTrack '%s' in SFXEmitter '%i' (%s)", profileName, getId(), getName() );
 621            else
 622            {
 623               // Remove the old 'profile' field.
 624               setDataField( sProfile, NULL, "" );
 625            }
 626         }
 627      }
 628
 629      // Convert a legacy 'channel' field, if we have one.
 630      
 631      static const char* sChannel = StringTable->insert( "channel" );
 632      const char* channelValue = getDataField( sChannel, NULL );
 633      if( channelValue && channelValue[ 0 ] )
 634      {
 635         const char* group = Con::evaluatef( "return sfxOldChannelToGroup( %s );", channelValue );
 636         SFXSource* sourceGroup;
 637         if( !Sim::findObject( group, sourceGroup ) )
 638            Con::errorf( "SFXEmitter::onAdd - could not resolve channel '%s' to SFXSource", channelValue );
 639         else
 640         {
 641            static const char* sSourceGroup = StringTable->insert( "sourceGroup" );
 642            setDataField( sSourceGroup, NULL, sourceGroup->getIdString() );
 643            
 644            // Remove the old 'channel' field.
 645            setDataField( sChannel, NULL, "" );
 646         }
 647      }
 648   }
 649   else
 650   {
 651      _update();
 652
 653      // Do we need to start playback?
 654      if( mPlayOnAdd && mSource )
 655         mSource->play();
 656   }
 657   
 658   // Setup the bounds.
 659
 660   mObjScale.set( mDescription.mMaxDistance, mDescription.mMaxDistance, mDescription.mMaxDistance );
 661   resetWorldBox();
 662
 663   addToScene();
 664   return true;
 665}
 666
 667//-----------------------------------------------------------------------------
 668
 669void SFXEmitter::onRemove()
 670{
 671   SFX_DELETE( mSource );
 672
 673   removeFromScene();
 674   Parent::onRemove();
 675}
 676
 677//-----------------------------------------------------------------------------
 678
 679void SFXEmitter::_update()
 680{
 681   AssertFatal( isClientObject(), "SFXEmitter::_update() - This shouldn't happen on the server!" );
 682
 683   // Store the playback status so we
 684   // we can restore it.
 685   SFXStatus prevState = mSource ? mSource->getStatus() : SFXStatusNull;
 686
 687   // Make sure all the settings are valid.
 688   mDescription.validate();
 689
 690   const MatrixF& transform = getTransform();
 691   const VectorF& velocity = getVelocity();
 692   
 693   // Did we change the source?
 694   if( mDirty.test( Track | Filename | Is3D | IsLooping | IsStreaming | TrackOnly ) )
 695   {
 696      SFX_DELETE( mSource );
 697
 698      // Do we have a track?
 699      if( mTrack )
 700      {
 701         mSource = SFX->createSource( mTrack, &transform, &velocity );
 702         if( !mSource )
 703            Con::errorf( "SFXEmitter::_update() - failed to create sound for track %i (%s)",
 704               mTrack->getId(), mTrack->getName() );
 705
 706         // If we're supposed to play when the emitter is 
 707         // added to the scene then also restart playback 
 708         // when the profile changes.
 709         prevState = mPlayOnAdd ? SFXStatusPlaying : prevState;
 710         
 711         // Force an update of properties set on the local description.
 712         
 713         mDirty.set( AllDirtyMask );
 714      }
 715      
 716      // Else take the local profile
 717      else
 718      {
 719         // Clear the resource and buffer on profile
 720         // to force reload.
 721
 722         mLocalProfile.mResource = NULL;
 723         mLocalProfile.mBuffer = NULL;
 724
 725         if( !mLocalProfile.mFilename.isEmpty() )
 726         {
 727            mSource = SFX->createSource( &mLocalProfile, &transform, &velocity );
 728            if( !mSource )
 729               Con::errorf( "SFXEmitter::_update() - failed to create sound for: %s",
 730                  mLocalProfile.mFilename.c_str() );
 731            
 732            prevState = mPlayOnAdd ? SFXStatusPlaying : prevState;
 733         }
 734      }
 735      
 736      mDirty.clear( Track | Filename | Is3D | IsLooping | IsStreaming | TrackOnly );
 737   }
 738
 739   // Cheat if the editor is open and the looping state
 740   // is toggled on a local profile sound.  It makes the
 741   // editor feel responsive and that things are working.
 742   if(  gEditingMission &&
 743        !mTrack && 
 744        mPlayOnAdd && 
 745        mDirty.test( IsLooping ) )
 746      prevState = SFXStatusPlaying;
 747      
 748   bool useTrackDescriptionOnly = ( mUseTrackDescriptionOnly && mTrack );
 749
 750   // The rest only applies if we have a source.
 751   if( mSource )
 752   {
 753      // Set the volume irrespective of the profile.
 754      if( mDirty.test( Volume ) && !useTrackDescriptionOnly )
 755         mSource->setVolume( mDescription.mVolume );
 756         
 757      if( mDirty.test( Pitch ) && !useTrackDescriptionOnly )
 758         mSource->setPitch( mDescription.mPitch );
 759         
 760      if( mDirty.test( FadeInTime | FadeOutTime ) && !useTrackDescriptionOnly )
 761         mSource->setFadeTimes( mDescription.mFadeInTime, mDescription.mFadeOutTime );
 762         
 763      if( mDirty.test( SourceGroup ) && mDescription.mSourceGroup && !useTrackDescriptionOnly )
 764         mDescription.mSourceGroup->addObject( mSource );
 765
 766      // Skip these 3d only settings.
 767      if( mDescription.mIs3D )
 768      {
 769         if( mDirty.test( Transform ) )
 770         {
 771            mSource->setTransform( transform );
 772            mSource->setVelocity( velocity );
 773         }
 774         
 775         if( mDirty.test( MinDistance | MaxDistance ) && !useTrackDescriptionOnly )
 776         {
 777            mSource->setMinMaxDistance(   mDescription.mMinDistance,
 778                                          mDescription.mMaxDistance );
 779         }
 780
 781         if( mDirty.test( ConeInsideAngle | ConeOutsideAngle | ConeOutsideVolume ) && !useTrackDescriptionOnly )
 782         {
 783            mSource->setCone( F32( mDescription.mConeInsideAngle ),
 784                              F32( mDescription.mConeOutsideAngle ),
 785                              mDescription.mConeOutsideVolume );
 786         }
 787         
 788         mDirty.clear( Transform | MinDistance | MaxDistance | ConeInsideAngle | ConeOutsideAngle | ConeOutsideVolume );
 789      }     
 790
 791      // Restore the pre-update playback state.
 792      if( prevState == SFXStatusPlaying )
 793         mSource->play();
 794         
 795      mDirty.clear( Volume | Pitch | Transform | FadeInTime | FadeOutTime | SourceGroup );
 796   }
 797}
 798
 799//-----------------------------------------------------------------------------
 800
 801void SFXEmitter::prepRenderImage( SceneRenderState* state )
 802{
 803   // Only render in editor.
 804   if( !gEditingMission )
 805      return;
 806
 807   ObjectRenderInst* ri = state->getRenderPass()->allocInst< ObjectRenderInst >();
 808
 809   ri->renderDelegate.bind( this, &SFXEmitter::_renderObject );
 810   ri->type = RenderPassManager::RIT_Editor;
 811   ri->defaultKey = 0;
 812   ri->defaultKey2 = 0;
 813
 814   state->getRenderPass()->addInst( ri );
 815}
 816
 817//-----------------------------------------------------------------------------
 818
 819void SFXEmitter::_renderObject( ObjectRenderInst* ri, SceneRenderState* state, BaseMatInstance* overrideMat )
 820{   
 821   // Check to see if the emitter is in range and playing
 822   // and assign a proper color depending on this.
 823
 824   ColorI color;
 825   if( _getPlaybackStatus() == SFXStatusPlaying )
 826   {
 827      if( isInRange() )
 828         color = smRenderColorPlayingInRange;
 829      else
 830         color = smRenderColorPlayingOutOfRange;
 831   }
 832   else
 833   {
 834      if( isInRange() )
 835         color = smRenderColorStoppedInRange;
 836      else
 837         color = smRenderColorStoppedOutOfRange;
 838   }
 839
 840   // Draw the cube.
 841   
 842   GFXStateBlockDesc desc;
 843   desc.setZReadWrite( true, false );
 844   desc.setBlend( true );
 845   desc.setCullMode( GFXCullNone );
 846
 847   GFXDrawUtil *drawer = GFX->getDrawUtil();
 848   drawer->drawCube( desc,  Point3F( 0.5f, 0.5f, 0.5f ), getBoxCenter(), color );
 849   
 850   // Render visual feedback for 3D sounds.
 851   
 852   if( ( smRenderEmitters || isSelected() ) && is3D() )
 853      _render3DVisualFeedback();
 854}
 855
 856//-----------------------------------------------------------------------------
 857
 858void SFXEmitter::_render3DVisualFeedback()
 859{
 860   GFXTransformSaver saver;
 861   
 862   GFX->multWorld( getRenderTransform() );
 863   
 864   GFXStateBlockDesc desc;
 865   desc.setZReadWrite( true, false );
 866   desc.setBlend( true );
 867   desc.setCullMode( GFXCullNone );
 868
 869   if( mRenderSB == NULL )
 870      mRenderSB = GFX->createStateBlock( desc );
 871   
 872   GFX->setStateBlock( mRenderSB );
 873   
 874   // Render the max range sphere.
 875   
 876   if( smRenderColorRangeSphere.alpha > 0 )
 877      GFX->getDrawUtil()->drawSphere( desc, mDescription.mMaxDistance, Point3F( 0.f, 0.f, 0.f ), smRenderColorRangeSphere );
 878   
 879   //TODO: some point size support in GFX would be nice
 880
 881   // Prepare primitive list.  Make sure we stay within limits.
 882   
 883   F32 radialIncrements = smRenderRadialIncrements;
 884   F32 sweepIncrements = smRenderSweepIncrements;
 885   F32 pointDistance = smRenderPointDistance;
 886   
 887   F32 numPoints;
 888   while( 1 )
 889   {
 890      numPoints = mCeil( 360.f / radialIncrements ) *
 891                  mCeil( 360.f / sweepIncrements ) *
 892                  ( mDescription.mMaxDistance / pointDistance );
 893                  
 894      if( numPoints < 65536 )
 895         break;
 896         
 897      radialIncrements *= 1.1f;
 898      sweepIncrements *= 1.1f;
 899      pointDistance *= 1.5;
 900   }
 901                           
 902   PrimBuild::begin( GFXPointList, numPoints );
 903
 904   // Render inner cone.
 905   
 906   _renderCone(
 907      radialIncrements,
 908      sweepIncrements,
 909      pointDistance,
 910      mDescription.mConeInsideAngle, 0.f,
 911      mDescription.mVolume, mDescription.mVolume,
 912      smRenderColorInnerCone );
 913
 914   // Outer Cone and Outside volume only get rendered if mConeOutsideVolume > 0
 915   
 916   if( mDescription.mConeOutsideVolume > 0.f )
 917   {
 918      const F32 outsideVolume = mDescription.mVolume * mDescription.mConeOutsideVolume;
 919      
 920      // Render outer cone.
 921      
 922      _renderCone(
 923         radialIncrements,
 924         sweepIncrements,
 925         pointDistance,
 926         mDescription.mConeOutsideAngle, mDescription.mConeInsideAngle,
 927         outsideVolume, mDescription.mVolume,
 928         smRenderColorOuterCone );
 929      
 930      // Render outside volume.
 931      
 932      _renderCone(
 933         radialIncrements,
 934         sweepIncrements,
 935         pointDistance,
 936         360.f, mDescription.mConeOutsideAngle,
 937         outsideVolume, outsideVolume,
 938         smRenderColorOutsideVolume );
 939   }
 940   
 941   // Commit primitive list.
 942   
 943   PrimBuild::end();
 944}
 945
 946//-----------------------------------------------------------------------------
 947
 948void SFXEmitter::_renderCone( F32 radialIncrements, F32 sweepIncrements,
 949                              F32 pointDistance,
 950                              F32 startAngle, F32 stopAngle,
 951                              F32 startVolume, F32 stopVolume,
 952                              const ColorI& color )
 953{
 954   if( startAngle == stopAngle )
 955      return;
 956      
 957   const F32 startAngleRadians = mDegToRad( startAngle );
 958   const F32 stopAngleRadians = mDegToRad( stopAngle );
 959   const F32 radialIncrementsRadians = mDegToRad( radialIncrements );
 960      
 961   // Unit quaternions representing the start and end angle so we
 962   // can interpolate between the two without flipping.
 963   
 964   QuatF rotateZStart( EulerF( 0.f, 0.f, startAngleRadians / 2.f ) );
 965   QuatF rotateZEnd( EulerF( 0.f, 0.f, stopAngleRadians / 2.f ) );
 966   
 967   // Do an angular sweep on one side of our XY disc.  Since we do a full 360 radial sweep
 968   // around Y for each angle, we only need to sweep over one side.
 969   
 970   const F32 increment = 1.f / ( ( ( startAngle / 2.f ) - ( stopAngle / 2.f ) ) / sweepIncrements );
 971   for( F32 t = 0.f; t < 1.0f; t += increment )
 972   {
 973      // Quaternion to rotate point into place on XY disc.
 974      QuatF rotateZ;
 975      rotateZ.interpolate( rotateZStart, rotateZEnd, t );
 976      
 977      // Quaternion to rotate one position around Y axis.  Used for radial sweep.
 978      QuatF rotateYOne( EulerF( 0.f, radialIncrementsRadians, 0.f ) );
 979
 980      // Do a radial sweep each step along the distance axis.  For each step, volume is
 981      // the same for any point on the sweep circle.
 982
 983      for( F32 y = pointDistance; y <= mDescription.mMaxDistance; y += pointDistance )
 984      {
 985         ColorI c = color;
 986         
 987         // Compute volume at current point.  First off, find the interpolated volume
 988         // in the cone.  Only for the outer cone will this actually result in
 989         // interpolation.  For the remaining angles, the cone volume is constant.
 990
 991         F32 volume = mLerp( startVolume, stopVolume, t );
 992         if( volume == 0.f )
 993            c.alpha = 0;
 994         else
 995         {         
 996            // Apply distance attenuation.
 997            
 998            F32 attenuatedVolume = SFXDistanceAttenuation(
 999               SFX->getDistanceModel(),
1000               mDescription.mMinDistance,
1001               mDescription.mMaxDistance,
1002               y,
1003               volume,
1004               SFX->getRolloffFactor() ); //RDTODO
1005
1006            // Fade alpha according to how much volume we
1007            // have left at the current point.
1008            
1009            c.alpha = F32( c.alpha ) * ( attenuatedVolume / 1.f );
1010         }
1011         
1012         PrimBuild::color( c );
1013         
1014         // Create points by doing a full 360 degree radial sweep around Y.
1015
1016         Point3F p( 0.f, y, 0.f );
1017         rotateZ.mulP( p, &p );
1018         
1019         for( F32 radialAngle = 0.f; radialAngle < 360.f; radialAngle += radialIncrements )
1020         {
1021            PrimBuild::vertex3f( p.x, p.y, p.z );
1022            rotateYOne.mulP( p, &p );
1023         }
1024      }
1025   }
1026}
1027
1028//-----------------------------------------------------------------------------
1029
1030void SFXEmitter::play()
1031{
1032   if( mSource )
1033      mSource->play();
1034   else
1035   {
1036      // By clearing the playback masks first we
1037      // ensure the last playback command called 
1038      // within a single tick is the one obeyed.
1039      clearMaskBits( AllSourceMasks );
1040
1041      setMaskBits( SourcePlayMask );
1042   }
1043}
1044
1045//-----------------------------------------------------------------------------
1046
1047void SFXEmitter::stop()
1048{
1049   if ( mSource )
1050      mSource->stop();
1051   else
1052   {
1053      // By clearing the playback masks first we
1054      // ensure the last playback command called 
1055      // within a single tick is the one obeyed.
1056      clearMaskBits( AllSourceMasks );
1057
1058      setMaskBits( SourceStopMask );
1059   }
1060}
1061
1062//-----------------------------------------------------------------------------
1063
1064SFXStatus SFXEmitter::_getPlaybackStatus() const
1065{
1066   const SFXEmitter* emitter = this;
1067
1068   // We only have a source playing on client objects, so if this is a server
1069   // object, we want to know the playback status on the local client connection's
1070   // version of this emitter.
1071   
1072   if( isServerObject() )
1073   {
1074      S32 index = NetConnection::getLocalClientConnection()->getGhostIndex( ( NetObject* ) this );
1075      if( index != -1 )
1076         emitter = dynamic_cast< SFXEmitter* >( NetConnection::getConnectionToServer()->resolveGhost( index ) );
1077      else
1078         emitter = NULL;
1079   }
1080   
1081   if( emitter && emitter->mSource )
1082      return emitter->mSource->getStatus();
1083      
1084   return SFXStatusNull;
1085}
1086
1087//-----------------------------------------------------------------------------
1088
1089bool SFXEmitter::is3D() const
1090{
1091   if( mTrack != NULL )
1092      return mTrack->getDescription()->mIs3D;
1093   else
1094      return mDescription.mIs3D;
1095}
1096
1097//-----------------------------------------------------------------------------
1098
1099bool SFXEmitter::isInRange() const
1100{
1101   if( !mDescription.mIs3D )
1102      return false;
1103   
1104   const SFXListenerProperties& listener = SFX->getListener();
1105   const Point3F listenerPos = listener.getTransform().getPosition();
1106   const Point3F emitterPos = getPosition();
1107   const F32 dist = mDescription.mMaxDistance;
1108   
1109   return ( ( emitterPos - listenerPos ).len() <= dist );
1110}
1111
1112//-----------------------------------------------------------------------------
1113
1114void SFXEmitter::setTransform( const MatrixF &mat )
1115{
1116   // Set the transform directly from the 
1117   // matrix created by inspector.
1118   Parent::setTransform( mat );
1119   setMaskBits( TransformUpdateMask );
1120}
1121
1122//-----------------------------------------------------------------------------
1123
1124void SFXEmitter::setScale( const VectorF &scale )
1125{
1126   F32 maxDistance;
1127   
1128   if( mUseTrackDescriptionOnly && mTrack )
1129      maxDistance = mTrack->getDescription()->mMaxDistance;
1130   else
1131   {
1132      // Use the average of the three coords.
1133      maxDistance = ( scale.x + scale.y + scale.z ) / 3.0f;
1134      maxDistance = getMax( maxDistance, mDescription.mMinDistance );
1135      
1136      mDescription.mMaxDistance = maxDistance;
1137      
1138      mDirty.set( MaxDistance );
1139      setMaskBits( DirtyUpdateMask );
1140   }
1141
1142   Parent::setScale( VectorF( maxDistance, maxDistance, maxDistance ) );
1143}
1144
1145//=============================================================================
1146//    Console Methods.
1147//=============================================================================
1148// MARK: ---- Console Methods ----
1149
1150//-----------------------------------------------------------------------------
1151
1152DefineEngineMethod( SFXEmitter, play, void, (),,
1153   "Manually start playback of the emitter's sound.\n"
1154   "If this is called on the server-side object, the play command will be related to all client-side ghosts.\n" )
1155{
1156   object->play();
1157}
1158
1159//-----------------------------------------------------------------------------
1160
1161DefineEngineMethod( SFXEmitter, stop, void, (),,
1162   "Manually stop playback of the emitter's sound.\n"
1163   "If this is called on the server-side object, the stop command will be related to all client-side ghosts.\n" )
1164{
1165   object->stop();
1166}
1167
1168//-----------------------------------------------------------------------------
1169
1170DefineEngineMethod( SFXEmitter, getSource, SFXSource*, (),,
1171   "Get the sound source object from the emitter.\n\n"
1172   "@return The sound source used by the emitter or null."
1173   "@note This method will return null when called on the server-side SFXEmitter object.  Only client-side ghosts "
1174      "actually hold on to %SFXSources.\n\n" )
1175{
1176   return object->getSource();
1177}
1178