mirror of
https://github.com/TorqueGameEngines/Torque3D.git
synced 2026-01-20 04:34:48 +00:00
422 lines
13 KiB
C++
422 lines
13 KiB
C++
//-----------------------------------------------------------------------------
|
|
// Copyright (c) 2012 GarageGames, LLC
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
// of this software and associated documentation files (the "Software"), to
|
|
// deal in the Software without restriction, including without limitation the
|
|
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
|
// sell copies of the Software, and to permit persons to whom the Software is
|
|
// furnished to do so, subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included in
|
|
// all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
// IN THE SOFTWARE.
|
|
//-----------------------------------------------------------------------------
|
|
#include "T3D/components/audio/SoundComponent.h"
|
|
#include "core/stream/bitStream.h"
|
|
#include "sim/netConnection.h"
|
|
|
|
#include "sfx/sfxSystem.h"
|
|
#include "sfx/sfxSource.h"
|
|
#include "sfx/sfxTrack.h"
|
|
#include "sfx/sfxDescription.h"
|
|
#include "T3D/sfx/sfx3DWorld.h"
|
|
|
|
#include "sfx/sfxTrack.h"
|
|
#include "sfx/sfxTypes.h"
|
|
|
|
#include "renderInstance/renderPassManager.h"
|
|
#include "gfx/gfxDrawUtil.h"
|
|
|
|
// Timeout for non-looping sounds on a channel
|
|
static SimTime sAudioTimeout = 500;
|
|
|
|
extern bool gEditingMission;
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// Constructor/Destructor
|
|
//////////////////////////////////////////////////////////////////////////
|
|
SoundComponent::SoundComponent() : Component()
|
|
{
|
|
//These flags inform that, in this particular component, we network down to the client, which enables the pack/unpackData functions to operate
|
|
mNetworked = true;
|
|
|
|
mFriendlyName = "Sound(Component)";
|
|
mComponentType = "Sound";
|
|
mDescription = getDescriptionText("Stores up to 4 sounds for playback.");
|
|
|
|
for (U32 slotNum = 0; slotNum < MaxSoundThreads; slotNum++) {
|
|
mSoundThread[slotNum].play = false;
|
|
mSoundThread[slotNum].profile = 0;
|
|
mSoundThread[slotNum].sound = 0;
|
|
|
|
mSoundFile[slotNum] = NULL;
|
|
mPreviewSound[slotNum] = false;
|
|
mPlay[slotNum] = false;
|
|
}
|
|
}
|
|
|
|
SoundComponent::~SoundComponent()
|
|
{
|
|
}
|
|
|
|
IMPLEMENT_CO_NETOBJECT_V1(SoundComponent);
|
|
|
|
//Standard onAdd function, for when the component is created
|
|
bool SoundComponent::onAdd()
|
|
{
|
|
if (!Parent::onAdd())
|
|
return false;
|
|
|
|
for (U32 slotNum = 0; slotNum < MaxSoundThreads; slotNum++)
|
|
mPreviewSound[slotNum] = false;
|
|
|
|
return true;
|
|
}
|
|
|
|
//Standard onRemove function, when the component object is deleted
|
|
void SoundComponent::onRemove()
|
|
{
|
|
Parent::onRemove();
|
|
}
|
|
|
|
//This is called when the component has been added to an entity
|
|
void SoundComponent::onComponentAdd()
|
|
{
|
|
Parent::onComponentAdd();
|
|
|
|
Con::printf("We were added to an entity! SoundComponent reporting in for owner entity %i", mOwner->getId());
|
|
}
|
|
|
|
//This is called when the component has been removed from an entity
|
|
void SoundComponent::onComponentRemove()
|
|
{
|
|
Con::printf("We were removed from our entity! SoundComponent signing off for owner entity %i", mOwner->getId());
|
|
Parent::onComponentRemove();
|
|
}
|
|
|
|
//This is called any time a component is added to an entity. Every component currently owned by the entity is informed of the event.
|
|
//This allows you to do dependency behavior, like collisions being aware of a mesh component, etc
|
|
void SoundComponent::componentAddedToOwner(Component *comp)
|
|
{
|
|
for (S32 slotNum = 0; slotNum < MaxSoundThreads; slotNum++)
|
|
{
|
|
if (mPlay[slotNum])
|
|
{
|
|
playAudio(slotNum, mSoundFile[slotNum]);
|
|
}
|
|
}
|
|
Con::printf("Our owner entity has a new component being added! SoundComponent welcomes component %i of type %s", comp->getId(), comp->getClassRep()->getNameSpace());
|
|
}
|
|
|
|
//This is called any time a component is removed from an entity. Every component current owned by the entity is informed of the event.
|
|
//This allows cleanup and dependency management.
|
|
void SoundComponent::componentRemovedFromOwner(Component *comp)
|
|
{
|
|
Con::printf("Our owner entity has a removed a component! SoundComponent waves farewell to component %i of type %s", comp->getId(), comp->getClassRep()->getNameSpace());
|
|
}
|
|
|
|
//Regular init persist fields function to set up static fields.
|
|
void SoundComponent::initPersistFields()
|
|
{
|
|
//addArray("Sounds", MaxSoundThreads);
|
|
addField("mSoundFile", TypeSFXTrackName, Offset(mSoundFile, SoundComponent), MaxSoundThreads, "If the text will not fit in the control, the deniedSound is played.");
|
|
addProtectedField("mPreviewSound", TypeBool, Offset(mPreviewSound, SoundComponent),
|
|
&_previewSound, &defaultProtectedGetFn, MaxSoundThreads, "Preview Sound", AbstractClassRep::FieldFlags::FIELD_ComponentInspectors);
|
|
addProtectedField("play", TypeBool, Offset(mPlay, SoundComponent),
|
|
&_autoplay, &defaultProtectedGetFn, MaxSoundThreads, "Whether playback of the emitter's sound should start as soon as the emitter object is added to the level.\n"
|
|
"If this is true, the emitter will immediately start to play when the level is loaded.");
|
|
//endArray("Sounds");
|
|
Parent::initPersistFields();
|
|
}
|
|
|
|
bool SoundComponent::_previewSound(void *object, const char *index, const char *data)
|
|
{
|
|
U32 slotNum = (index != NULL) ? dAtoui(index) : 0;
|
|
SoundComponent* component = reinterpret_cast< SoundComponent* >(object);
|
|
if (!component->mPreviewSound[slotNum])
|
|
component->playAudio(slotNum, component->mSoundFile[slotNum]);
|
|
else
|
|
component->stopAudio(slotNum);
|
|
component->mPreviewSound[slotNum] = !component->mPreviewSound[slotNum];
|
|
|
|
return false;
|
|
}
|
|
|
|
bool SoundComponent::_autoplay(void *object, const char *index, const char *data)
|
|
{
|
|
U32 slotNum = (index != NULL) ? dAtoui(index) : 0;
|
|
SoundComponent* component = reinterpret_cast< SoundComponent* >(object);
|
|
component->mPlay[slotNum] = dAtoui(data);
|
|
if (component->mPlay[slotNum])
|
|
component->playAudio(slotNum, component->mSoundFile[slotNum]);
|
|
else
|
|
component->stopAudio(slotNum);
|
|
|
|
return false;
|
|
}
|
|
|
|
U32 SoundComponent::packUpdate(NetConnection *con, U32 mask, BitStream *stream)
|
|
{
|
|
U32 retMask = Parent::packUpdate(con, mask, stream);
|
|
|
|
if (mask & InitialUpdateMask)
|
|
{
|
|
// mask off sounds that aren't playing
|
|
S32 slotNum;
|
|
for (slotNum = 0; slotNum < MaxSoundThreads; slotNum++)
|
|
if (!mSoundThread[slotNum].play)
|
|
mask &= ~(SoundMaskN << slotNum);
|
|
}
|
|
|
|
for (S32 slotNum = 0; slotNum < MaxSoundThreads; slotNum++)
|
|
stream->writeFlag(mPreviewSound[slotNum]);
|
|
|
|
if (stream->writeFlag(mask & SoundMask))
|
|
{
|
|
for (S32 slotNum = 0; slotNum < MaxSoundThreads; slotNum++)
|
|
{
|
|
Sound& st = mSoundThread[slotNum];
|
|
|
|
if (stream->writeFlag(mask & (SoundMaskN << slotNum)))
|
|
{
|
|
if (stream->writeFlag(st.play))
|
|
//stream->writeRangedU32(st.profile->getId(), DataBlockObjectIdFirst,
|
|
// DataBlockObjectIdLast);
|
|
stream->writeString(st.profile->getName());
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
return retMask;
|
|
}
|
|
|
|
void SoundComponent::unpackUpdate(NetConnection *con, BitStream *stream)
|
|
{
|
|
Parent::unpackUpdate(con, stream);
|
|
|
|
for (S32 slotNum = 0; slotNum < MaxSoundThreads; slotNum++)
|
|
mPreviewSound[slotNum] = stream->readFlag();
|
|
|
|
if (stream->readFlag())
|
|
{
|
|
for (S32 slotNum = 0; slotNum < MaxSoundThreads; slotNum++)
|
|
{
|
|
if (stream->readFlag())
|
|
{
|
|
Sound& st = mSoundThread[slotNum];
|
|
st.play = stream->readFlag();
|
|
if (st.play)
|
|
{
|
|
//st.profile = (SFXTrack*)stream->readRangedU32(DataBlockObjectIdFirst,
|
|
// DataBlockObjectIdLast);
|
|
char profileName[255];
|
|
stream->readString(profileName);
|
|
|
|
if (!Sim::findObject(profileName, st.profile))
|
|
Con::errorf("Could not find SFXTrack");
|
|
}
|
|
|
|
//if (isProperlyAdded())
|
|
updateAudioState(st);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//This allows custom behavior in the event the owner is being edited
|
|
void SoundComponent::onInspect()
|
|
{
|
|
}
|
|
|
|
//This allows cleanup of the custom editor behavior if our owner stopped being edited
|
|
void SoundComponent::onEndInspect()
|
|
{
|
|
}
|
|
|
|
//Process tick update function, natch
|
|
void SoundComponent::processTick()
|
|
{
|
|
Parent::processTick();
|
|
}
|
|
|
|
//Client-side advance function
|
|
void SoundComponent::advanceTime(F32 dt)
|
|
{
|
|
|
|
}
|
|
|
|
//Client-side interpolation function
|
|
void SoundComponent::interpolateTick(F32 delta)
|
|
{
|
|
|
|
}
|
|
|
|
void SoundComponent::prepRenderImage(SceneRenderState *state)
|
|
{
|
|
if (!mEnabled || !mOwner || !gEditingMission)
|
|
return;
|
|
ObjectRenderInst* ri = state->getRenderPass()->allocInst< ObjectRenderInst >();
|
|
|
|
ri->renderDelegate.bind(this, &SoundComponent::_renderObject);
|
|
ri->type = RenderPassManager::RIT_Editor;
|
|
ri->defaultKey = 0;
|
|
ri->defaultKey2 = 0;
|
|
|
|
state->getRenderPass()->addInst(ri);
|
|
}
|
|
|
|
void SoundComponent::_renderObject(ObjectRenderInst *ri,
|
|
SceneRenderState *state,
|
|
BaseMatInstance *overrideMat)
|
|
{
|
|
if (overrideMat)
|
|
return;
|
|
|
|
GFXStateBlockDesc desc;
|
|
desc.setBlend(true);
|
|
|
|
MatrixF camera = GFX->getWorldMatrix();
|
|
camera.inverse();
|
|
Point3F pos = mOwner->getPosition();
|
|
|
|
for (S32 slotNum = 0; slotNum < MaxSoundThreads; slotNum++)
|
|
{
|
|
if (mPreviewSound[slotNum])
|
|
{
|
|
Sound& st = mSoundThread[slotNum];
|
|
if (st.sound && st.sound->getDescription())
|
|
{
|
|
F32 minRad = st.sound->getDescription()->mMinDistance;
|
|
F32 falloffRad = st.sound->getDescription()->mMaxDistance;
|
|
SphereF sphere(pos, falloffRad);
|
|
if (sphere.isContained(camera.getPosition()))
|
|
desc.setCullMode(GFXCullNone);
|
|
|
|
GFX->getDrawUtil()->drawSphere(desc, minRad, pos, ColorI(255, 0, 255, 64));
|
|
GFX->getDrawUtil()->drawSphere(desc, falloffRad, pos, ColorI(128, 0, 128, 64));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void SoundComponent::playAudio(U32 slotNum, SFXTrack* _profile)
|
|
{
|
|
AssertFatal(slotNum < MaxSoundThreads, "ShapeBase::playAudio() bad slot index");
|
|
SFXTrack* profile = (_profile != NULL) ? _profile : mSoundFile[slotNum];
|
|
Sound& st = mSoundThread[slotNum];
|
|
if (profile && (!st.play || st.profile != profile))
|
|
{
|
|
setMaskBits(SoundMaskN << slotNum);
|
|
st.play = true;
|
|
st.profile = profile;
|
|
updateAudioState(st);
|
|
}
|
|
}
|
|
|
|
void SoundComponent::stopAudio(U32 slotNum)
|
|
{
|
|
AssertFatal(slotNum < MaxSoundThreads, "ShapeBase::stopAudio() bad slot index");
|
|
|
|
Sound& st = mSoundThread[slotNum];
|
|
if (st.play)
|
|
{
|
|
st.play = false;
|
|
setMaskBits(SoundMaskN << slotNum);
|
|
updateAudioState(st);
|
|
}
|
|
}
|
|
|
|
void SoundComponent::updateServerAudio()
|
|
{
|
|
// Timeout non-looping sounds
|
|
for (S32 slotNum = 0; slotNum < MaxSoundThreads; slotNum++)
|
|
{
|
|
Sound& st = mSoundThread[slotNum];
|
|
if (st.play && st.timeout && st.timeout < Sim::getCurrentTime())
|
|
{
|
|
//clearMaskBits(SoundMaskN << slotNum);
|
|
st.play = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
void SoundComponent::updateAudioState(Sound& st)
|
|
{
|
|
SFX_DELETE(st.sound);
|
|
|
|
if (st.play && st.profile)
|
|
{
|
|
if (isClientObject())
|
|
{
|
|
//if (Sim::findObject(SimObjectId((uintptr_t)st.profile), st.profile))
|
|
// {
|
|
MatrixF transform = mOwner->getTransform();
|
|
st.sound = SFX->createSource(st.profile, &transform);
|
|
if (st.sound)
|
|
st.sound->play();
|
|
//}
|
|
else
|
|
st.play = false;
|
|
}
|
|
else
|
|
{
|
|
// Non-looping sounds timeout on the server
|
|
st.timeout = 0;
|
|
if (!st.profile->getDescription()->mIsLooping)
|
|
st.timeout = Sim::getCurrentTime() + sAudioTimeout;
|
|
}
|
|
}
|
|
else
|
|
st.play = false;
|
|
}
|
|
|
|
void SoundComponent::updateAudioPos()
|
|
{
|
|
for (S32 slotNum = 0; slotNum < MaxSoundThreads; slotNum++)
|
|
{
|
|
SFXSource* source = mSoundThread[slotNum].sound;
|
|
if (source)
|
|
source->setTransform(mOwner->getTransform());
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
DefineEngineMethod(SoundComponent, playAudio, bool, (S32 slot, SFXTrack* track), (0, nullAsType<SFXTrack*>()),
|
|
"@brief Attach a sound to this shape and start playing it.\n\n"
|
|
|
|
"@param slot Audio slot index for the sound (valid range is 0 - 3)\n" // 3 = ShapeBase::MaxSoundThreads-1
|
|
"@param track SFXTrack to play\n"
|
|
"@return true if the sound was attached successfully, false if failed\n\n"
|
|
|
|
"@see stopAudio()\n")
|
|
{
|
|
if (track && slot >= 0 && slot < SoundComponent::MaxSoundThreads) {
|
|
object->playAudio(slot, track);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
DefineEngineMethod(SoundComponent, stopAudio, bool, (S32 slot), ,
|
|
"@brief Stop a sound started with playAudio.\n\n"
|
|
|
|
"@param slot audio slot index (started with playAudio)\n"
|
|
"@return true if the sound was stopped successfully, false if failed\n\n"
|
|
|
|
"@see playAudio()\n")
|
|
{
|
|
if (slot >= 0 && slot < SoundComponent::MaxSoundThreads) {
|
|
object->stopAudio(slot);
|
|
return true;
|
|
}
|
|
return false;
|
|
} |