sfxEmitter.cpp
Engine/source/T3D/sfx/sfxEmitter.cpp
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