sfxEmitter.cpp 46 KB

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