You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1114 lines
43 KiB
C++
1114 lines
43 KiB
C++
/*
|
|
* Copyright (c) Contributors to the Open 3D Engine Project.
|
|
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
*
|
|
*/
|
|
|
|
|
|
#include <ATLAudioObject.h>
|
|
|
|
#include <AzCore/Casting/numeric_cast.h>
|
|
#include <AzCore/std/chrono/clocks.h>
|
|
|
|
#if !defined(AUDIO_RELEASE)
|
|
#include <AzCore/std/string/conversions.h>
|
|
#endif // !AUDIO_RELEASE
|
|
|
|
#include <MathConversion.h>
|
|
|
|
#include <AudioInternalInterfaces.h>
|
|
#include <SoundCVars.h>
|
|
#include <ATLUtils.h>
|
|
|
|
#include <IRenderer.h>
|
|
#include <IRenderAuxGeom.h>
|
|
|
|
namespace Audio
|
|
{
|
|
extern CAudioLogger g_audioLogger;
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void CATLAudioObjectBase::ReportStartingTriggerInstance(const TAudioTriggerInstanceID audioTriggerInstanceID, const TAudioControlID audioControlID)
|
|
{
|
|
SATLTriggerInstanceState& rTriggerInstState = m_cTriggers.emplace(audioTriggerInstanceID, SATLTriggerInstanceState()).first->second;
|
|
rTriggerInstState.nTriggerID = audioControlID;
|
|
rTriggerInstState.nFlags |= eATS_STARTING;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void CATLAudioObjectBase::ReportStartedTriggerInstance(
|
|
const TAudioTriggerInstanceID audioTriggerInstanceID,
|
|
void* const pOwnerOverride,
|
|
void* const pUserData,
|
|
void* const pUserDataOwner,
|
|
const TATLEnumFlagsType nFlags)
|
|
{
|
|
TObjectTriggerStates::iterator Iter(m_cTriggers.find(audioTriggerInstanceID));
|
|
|
|
if (Iter != m_cTriggers.end())
|
|
{
|
|
SATLTriggerInstanceState& rTriggerInstState = Iter->second;
|
|
if (rTriggerInstState.numPlayingEvents > 0 || rTriggerInstState.numLoadingEvents > 0)
|
|
{
|
|
rTriggerInstState.pOwnerOverride = pOwnerOverride;
|
|
rTriggerInstState.pUserData = pUserData;
|
|
rTriggerInstState.pUserDataOwner = pUserDataOwner;
|
|
rTriggerInstState.nFlags &= ~eATS_STARTING;
|
|
|
|
if ((nFlags & eARF_SYNC_FINISHED_CALLBACK) == 0)
|
|
{
|
|
rTriggerInstState.nFlags |= eATS_CALLBACK_ON_AUDIO_THREAD;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// All of the events have either finished before we got here or never started.
|
|
// So we report this trigger as finished immediately.
|
|
ReportFinishedTriggerInstance(Iter);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
g_audioLogger.Log(eALT_WARNING, "Reported a started instance %u that couldn't be found on an object %u",
|
|
audioTriggerInstanceID, GetID());
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
void CATLAudioObjectBase::ReportStartedEvent(const CATLEvent* const pEvent)
|
|
{
|
|
m_cActiveEvents.insert(pEvent->GetID());
|
|
m_cTriggerImpls.emplace(pEvent->m_nTriggerImplID, SATLTriggerImplState());
|
|
|
|
const TObjectTriggerStates::iterator Iter(m_cTriggers.find(pEvent->m_nTriggerInstanceID));
|
|
|
|
if (Iter != m_cTriggers.end())
|
|
{
|
|
SATLTriggerInstanceState& rTriggerInstState = Iter->second;
|
|
|
|
switch (pEvent->m_audioEventState)
|
|
{
|
|
case eAES_PLAYING:
|
|
{
|
|
++(rTriggerInstState.numPlayingEvents);
|
|
break;
|
|
}
|
|
case eAES_PLAYING_DELAYED:
|
|
{
|
|
AZ_Assert(rTriggerInstState.numLoadingEvents > 0, "Event state is PLAYING_DELAYED but there are no loading events!");
|
|
--(rTriggerInstState.numLoadingEvents);
|
|
++(rTriggerInstState.numPlayingEvents);
|
|
break;
|
|
}
|
|
case eAES_LOADING:
|
|
{
|
|
++(rTriggerInstState.numLoadingEvents);
|
|
break;
|
|
}
|
|
case eAES_UNLOADING:
|
|
{
|
|
// not handled currently
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
AZ_Assert(false, "Unknown event state in ReportStartedEvent (%d)", pEvent->m_audioEventState);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
AZ_Assert(false, "ATL Event must exist and was not found in ReportStartedEvent!");
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void CATLAudioObjectBase::ReportFinishedEvent(const CATLEvent* const pEvent, const bool bSuccess)
|
|
{
|
|
m_cActiveEvents.erase(pEvent->GetID());
|
|
|
|
TObjectTriggerStates::iterator Iter(m_cTriggers.begin());
|
|
|
|
if (FindPlace(m_cTriggers, pEvent->m_nTriggerInstanceID, Iter))
|
|
{
|
|
switch (pEvent->m_audioEventState)
|
|
{
|
|
case eAES_PLAYING:
|
|
case eAES_PLAYING_DELAYED: // intentional fall-through
|
|
{
|
|
SATLTriggerInstanceState& rTriggerInstState = Iter->second;
|
|
AZ_Assert(rTriggerInstState.numPlayingEvents > 0, "ReportFinishedEvent - Trigger instances being decremented too many times!");
|
|
|
|
if (--(rTriggerInstState.numPlayingEvents) == 0 &&
|
|
rTriggerInstState.numLoadingEvents == 0 &&
|
|
(rTriggerInstState.nFlags & eATS_STARTING) == 0)
|
|
{
|
|
ReportFinishedTriggerInstance(Iter);
|
|
}
|
|
|
|
DecrementRefCount();
|
|
break;
|
|
}
|
|
case eAES_LOADING:
|
|
{
|
|
if (bSuccess)
|
|
{
|
|
ReportPrepUnprepTriggerImpl(pEvent->m_nTriggerImplID, true);
|
|
}
|
|
|
|
DecrementRefCount();
|
|
break;
|
|
}
|
|
case eAES_UNLOADING:
|
|
{
|
|
if (bSuccess)
|
|
{
|
|
ReportPrepUnprepTriggerImpl(pEvent->m_nTriggerImplID, false);
|
|
}
|
|
|
|
DecrementRefCount();
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
AZ_Assert(false, "Unknown event state in ReportFinishedEvent (%d)", pEvent->m_audioEventState);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
g_audioLogger.Log(eALT_WARNING, "Reported finished event %u on an inactive trigger %u", pEvent->GetID(), pEvent->m_nTriggerID);
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void CATLAudioObjectBase::ReportPrepUnprepTriggerImpl(const TAudioTriggerImplID nTriggerImplID, const bool bPrepared)
|
|
{
|
|
if (bPrepared)
|
|
{
|
|
m_cTriggerImpls[nTriggerImplID].nFlags |= eATS_PREPARED;
|
|
}
|
|
else
|
|
{
|
|
m_cTriggerImpls[nTriggerImplID].nFlags &= ~eATS_PREPARED;
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void CATLAudioObjectBase::SetSwitchState(const TAudioControlID nSwitchID, const TAudioSwitchStateID nStateID)
|
|
{
|
|
m_cSwitchStates[nSwitchID] = nStateID;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void CATLAudioObjectBase::SetRtpc(const TAudioControlID nRtpcID, const float fValue)
|
|
{
|
|
m_cRtpcs[nRtpcID] = fValue;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void CATLAudioObjectBase::SetEnvironmentAmount(const TAudioEnvironmentID nEnvironmentID, const float fAmount)
|
|
{
|
|
if (fAmount > 0.0f)
|
|
{
|
|
m_cEnvironments[nEnvironmentID] = fAmount;
|
|
}
|
|
else
|
|
{
|
|
m_cEnvironments.erase(nEnvironmentID);
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
const TObjectTriggerImplStates& CATLAudioObjectBase::GetTriggerImpls() const
|
|
{
|
|
return m_cTriggerImpls;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
const TObjectRtpcMap& CATLAudioObjectBase::GetRtpcs() const
|
|
{
|
|
return m_cRtpcs;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
const TObjectEnvironmentMap& CATLAudioObjectBase::GetEnvironments() const
|
|
{
|
|
return m_cEnvironments;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void CATLAudioObjectBase::ClearRtpcs()
|
|
{
|
|
m_cRtpcs.clear();
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void CATLAudioObjectBase::ClearEnvironments()
|
|
{
|
|
m_cEnvironments.clear();
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
bool CATLAudioObjectBase::HasActiveEvents() const
|
|
{
|
|
for (const auto& triggerInstance : m_cTriggers)
|
|
{
|
|
if (triggerInstance.second.numPlayingEvents != 0)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
TObjectTriggerInstanceSet CATLAudioObjectBase::GetTriggerInstancesByOwner(void* const owner) const
|
|
{
|
|
AZ_Assert(owner, "Retrieving a filtered list of trigger instances requires a non-null Owner pointer!");
|
|
TObjectTriggerInstanceSet filteredTriggers;
|
|
|
|
for (auto& triggerInstanceState : m_cTriggers)
|
|
{
|
|
if (triggerInstanceState.second.pOwnerOverride == owner)
|
|
{
|
|
filteredTriggers.insert(triggerInstanceState.first);
|
|
}
|
|
}
|
|
|
|
return filteredTriggers;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void CATLAudioObjectBase::Update([[maybe_unused]] const float fUpdateIntervalMS, [[maybe_unused]] const SATLWorldPosition& rListenerPosition)
|
|
{
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void CATLAudioObjectBase::Clear()
|
|
{
|
|
m_cActiveEvents.clear();
|
|
|
|
m_cTriggers.clear();
|
|
m_cTriggerImpls.clear();
|
|
m_cSwitchStates.clear();
|
|
m_cRtpcs.clear();
|
|
m_cEnvironments.clear();
|
|
|
|
m_nRefCounter = 0;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void CATLAudioObjectBase::ReportFinishedTriggerInstance(TObjectTriggerStates::iterator& iTriggerEntry)
|
|
{
|
|
SATLTriggerInstanceState& rTriggerInstState = iTriggerEntry->second;
|
|
SAudioRequest oRequest;
|
|
SAudioCallbackManagerRequestData<eACMRT_REPORT_FINISHED_TRIGGER_INSTANCE> oRequestData(rTriggerInstState.nTriggerID);
|
|
oRequest.nFlags = (eARF_PRIORITY_HIGH | eARF_THREAD_SAFE_PUSH | eARF_SYNC_CALLBACK);
|
|
oRequest.nAudioObjectID = GetID();
|
|
oRequest.pData = &oRequestData;
|
|
oRequest.pOwner = rTriggerInstState.pOwnerOverride;
|
|
oRequest.pUserData = rTriggerInstState.pUserData;
|
|
oRequest.pUserDataOwner = rTriggerInstState.pUserDataOwner;
|
|
|
|
if ((rTriggerInstState.nFlags & eATS_CALLBACK_ON_AUDIO_THREAD) != 0)
|
|
{
|
|
oRequest.nFlags &= ~eARF_SYNC_CALLBACK;
|
|
}
|
|
|
|
AudioSystemThreadSafeRequestBus::Broadcast(&AudioSystemThreadSafeRequestBus::Events::PushRequestThreadSafe, oRequest);
|
|
|
|
if ((rTriggerInstState.nFlags & eATS_PREPARED) != 0)
|
|
{
|
|
// if the trigger instance was manually prepared -- keep it
|
|
rTriggerInstState.nFlags &= ~eATS_PLAYING;
|
|
}
|
|
else
|
|
{
|
|
//if the trigger instance wasn't prepared -- kill it
|
|
m_cTriggers.erase(iTriggerEntry);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void CATLAudioObject::Clear()
|
|
{
|
|
CATLAudioObjectBase::Clear();
|
|
m_oPosition = SATLWorldPosition();
|
|
m_raycastProcessor.Reset();
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void CATLAudioObject::Update(const float fUpdateIntervalMS, const SATLWorldPosition& rListenerPosition)
|
|
{
|
|
CATLAudioObjectBase::Update(fUpdateIntervalMS, rListenerPosition);
|
|
|
|
if (CanRunRaycasts())
|
|
{
|
|
m_raycastProcessor.Update(fUpdateIntervalMS);
|
|
m_raycastProcessor.Run(rListenerPosition);
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void CATLAudioObject::SetPosition(const SATLWorldPosition& oNewPosition)
|
|
{
|
|
m_oPosition = oNewPosition;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void CATLAudioObject::SetVelocityTracking(const bool bTrackingOn)
|
|
{
|
|
if (bTrackingOn)
|
|
{
|
|
m_oPreviousPosition = m_oPosition;
|
|
m_nFlags |= eAOF_TRACK_VELOCITY;
|
|
}
|
|
else
|
|
{
|
|
m_nFlags &= ~eAOF_TRACK_VELOCITY;
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void CATLAudioObject::UpdateVelocity(float const fUpdateIntervalMS)
|
|
{
|
|
const AZ::Vector3 cPositionDelta = m_oPosition.GetPositionVec() - m_oPreviousPosition.GetPositionVec();
|
|
const float fCurrentVelocity = (1000.0f * cPositionDelta.GetLength()) / fUpdateIntervalMS; // fCurrentVelocity is given in units per second
|
|
|
|
if (AZ::GetAbs(fCurrentVelocity - m_fPreviousVelocity) > Audio::CVars::s_VelocityTrackingThreshold)
|
|
{
|
|
m_fPreviousVelocity = fCurrentVelocity;
|
|
SAudioRequest oRequest;
|
|
SAudioObjectRequestData<eAORT_SET_RTPC_VALUE> oRequestData(ATLInternalControlIDs::ObjectSpeedRtpcID, fCurrentVelocity);
|
|
|
|
oRequest.nAudioObjectID = GetID();
|
|
oRequest.nFlags = eARF_THREAD_SAFE_PUSH;
|
|
oRequest.pData = &oRequestData;
|
|
AudioSystemThreadSafeRequestBus::Broadcast(&AudioSystemThreadSafeRequestBus::Events::PushRequestThreadSafe, oRequest);
|
|
}
|
|
|
|
m_oPreviousPosition = m_oPosition;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void CATLAudioObject::SetRaycastCalcType(const EAudioObjectObstructionCalcType calcType)
|
|
{
|
|
m_raycastProcessor.SetType(calcType);
|
|
switch (calcType)
|
|
{
|
|
case eAOOCT_IGNORE:
|
|
AudioRaycastNotificationBus::Handler::BusDisconnect();
|
|
break;
|
|
case eAOOCT_SINGLE_RAY:
|
|
[[fallthrough]];
|
|
case eAOOCT_MULTI_RAY:
|
|
AudioRaycastNotificationBus::Handler::BusConnect(GetID());
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void CATLAudioObject::RunRaycasts(const SATLWorldPosition& listenerPos)
|
|
{
|
|
m_raycastProcessor.Run(listenerPos);
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
bool CATLAudioObject::CanRunRaycasts() const
|
|
{
|
|
return Audio::CVars::s_EnableRaycasts // This is the CVar to enable/disable audio raycasts.
|
|
&& Audio::CVars::s_RaycastMinDistance < Audio::CVars::s_RaycastMaxDistance
|
|
&& m_raycastProcessor.CanRun();
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void CATLAudioObject::GetObstOccData(SATLSoundPropagationData& data) const
|
|
{
|
|
data.fObstruction = m_raycastProcessor.GetObstruction();
|
|
data.fOcclusion = m_raycastProcessor.GetOcclusion();
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void CATLAudioObject::OnAudioRaycastResults(const AudioRaycastResult& result)
|
|
{
|
|
// Pull in the results to the raycast processor...
|
|
AZ_Assert(result.m_audioObjectId != INVALID_AUDIO_OBJECT_ID, "Audio Raycast Results - audio object id is invalid!\n");
|
|
AZ_Assert(result.m_rayIndex < s_maxRaysPerObject, "Audio Raycast Results - ray index is out of bounds (index: %zu)!\n", result.m_rayIndex);
|
|
AZ_Assert(result.m_result.size() <= s_maxHitResultsPerRaycast, "Audio Raycast Results - too many hits returned (hits: %zu)!\n", result.m_result.size());
|
|
|
|
RaycastInfo& info = m_raycastProcessor.m_rayInfos[result.m_rayIndex];
|
|
if (!info.m_pending)
|
|
{
|
|
// This may mean that an audio object was recycled (reset) and then reused.
|
|
// Need to investigate this further.
|
|
return;
|
|
}
|
|
|
|
info.m_pending = false;
|
|
info.m_hits.clear();
|
|
info.m_numHits = 0;
|
|
|
|
for (auto& hit : result.m_result)
|
|
{
|
|
if (hit.m_distance > 0.f)
|
|
{
|
|
info.m_hits.push_back(hit);
|
|
info.m_numHits++;
|
|
}
|
|
}
|
|
|
|
info.UpdateContribution();
|
|
info.m_cached = true;
|
|
info.m_cacheTimerMs = Audio::CVars::s_RaycastCacheTimeMs;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void RaycastInfo::UpdateContribution()
|
|
{
|
|
// This calculates the contribution of a single raycast. This calculation can be updated
|
|
// as needed to suit a user's needs. This is provided as a first example.
|
|
// Based on the number of hits reported, add values from the sequence: 1/2 + 1/4 + 1/8 + ...
|
|
float newContribution = 0.f;
|
|
|
|
for (AZ::u16 hit = 0; hit < m_numHits; ++hit)
|
|
{
|
|
newContribution += std::pow(2.f, -aznumeric_cast<float>(hit + 1));
|
|
}
|
|
|
|
m_contribution = newContribution;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
float RaycastInfo::GetDistanceScaledContribution() const
|
|
{
|
|
// Max extent is the s_RaycastMaxDistance, and use the distance embedded in the raycast request as a percent (inverse).
|
|
// Objects closer to the listener will have greater contribution amounts.
|
|
// Objects farther away will contribute less obstruction/occlusion, but distance attenuation will be the larger contributing factor.
|
|
const float maxDistance = static_cast<float>(Audio::CVars::s_RaycastMaxDistance);
|
|
float clampedDistance = AZ::GetClamp(m_raycastRequest.m_distance, 0.f, maxDistance);
|
|
float distanceScale = 1.f - (clampedDistance / maxDistance);
|
|
|
|
// Scale the contribution amount by (inverse) distance.
|
|
return distanceScale * m_contribution;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
float RaycastInfo::GetNearestHitDistance() const
|
|
{
|
|
float minDistance = m_raycastRequest.m_distance;
|
|
for (const auto& hit : m_hits)
|
|
{
|
|
minDistance = AZ::GetMin(minDistance, hit.m_distance);
|
|
}
|
|
|
|
return minDistance;
|
|
}
|
|
|
|
|
|
// static
|
|
bool RaycastProcessor::s_raycastsEnabled = false;
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
RaycastProcessor::RaycastProcessor(const TAudioObjectID objectId, const SATLWorldPosition& objectPosition)
|
|
: m_rayInfos(s_maxRaysPerObject, RaycastInfo())
|
|
, m_position(objectPosition)
|
|
, m_obstructionValue(Audio::CVars::s_RaycastSmoothFactor, s_epsilon)
|
|
, m_occlusionValue(Audio::CVars::s_RaycastSmoothFactor, s_epsilon)
|
|
, m_audioObjectId(objectId)
|
|
, m_obstOccType(eAOOCT_IGNORE)
|
|
{
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void RaycastProcessor::Update(float elapsedMs)
|
|
{
|
|
if (m_obstOccType == eAOOCT_SINGLE_RAY || m_obstOccType == eAOOCT_MULTI_RAY)
|
|
{
|
|
// First ray is direct-path obstruction value...
|
|
m_obstructionValue.SetNewTarget(m_rayInfos[0].GetDistanceScaledContribution());
|
|
|
|
if (m_obstOccType == eAOOCT_MULTI_RAY)
|
|
{
|
|
float occlusion = 0.f;
|
|
for (size_t i = 1; i < s_maxRaysPerObject; ++i)
|
|
{
|
|
occlusion += m_rayInfos[i].GetDistanceScaledContribution();
|
|
}
|
|
|
|
// Average of the occlusion rays' contributions...
|
|
occlusion /= aznumeric_cast<float>(s_maxRaysPerObject - 1);
|
|
|
|
m_occlusionValue.SetNewTarget(occlusion);
|
|
}
|
|
|
|
// Tick down the cache timers, when expired mark them dirty...
|
|
for (auto& rayInfo : m_rayInfos)
|
|
{
|
|
if (rayInfo.m_cached)
|
|
{
|
|
rayInfo.m_cacheTimerMs -= elapsedMs;
|
|
rayInfo.m_cached = (rayInfo.m_cacheTimerMs > 0.f);
|
|
}
|
|
}
|
|
}
|
|
|
|
m_obstructionValue.Update(Audio::CVars::s_RaycastSmoothFactor);
|
|
m_occlusionValue.Update(Audio::CVars::s_RaycastSmoothFactor);
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void RaycastProcessor::Reset()
|
|
{
|
|
m_obstructionValue.Reset();
|
|
m_occlusionValue.Reset();
|
|
|
|
for (auto& rayInfo : m_rayInfos)
|
|
{
|
|
rayInfo.Reset();
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void RaycastProcessor::SetType(EAudioObjectObstructionCalcType calcType)
|
|
{
|
|
if (calcType == m_obstOccType)
|
|
{
|
|
// No change to type, no need to reset any data.
|
|
return;
|
|
}
|
|
|
|
if (calcType == eAOOCT_IGNORE)
|
|
{
|
|
// Reset the target values when turning off raycasts (set to IGNORE).
|
|
m_obstructionValue.Reset();
|
|
m_occlusionValue.Reset();
|
|
}
|
|
|
|
// Otherwise, switching to a new type we can allow the obst/occ values from before to smooth
|
|
// to new targets as they become available. Hence no reset.
|
|
|
|
for (auto& rayInfo : m_rayInfos)
|
|
{
|
|
rayInfo.Reset();
|
|
}
|
|
|
|
m_obstOccType = calcType;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
bool RaycastProcessor::CanRun() const
|
|
{
|
|
return s_raycastsEnabled // This enable/disable is set via ISystem events.
|
|
&& m_obstOccType != eAOOCT_IGNORE;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void RaycastProcessor::Run(const SATLWorldPosition& listenerPosition)
|
|
{
|
|
const AZ::Vector3 listener = listenerPosition.GetPositionVec();
|
|
const AZ::Vector3 source = m_position.GetPositionVec();
|
|
const AZ::Vector3 ray = source - listener;
|
|
|
|
const float distance = ray.GetLength();
|
|
|
|
// Prevent raycast when individual sources are not within the allowed distance range...
|
|
if (Audio::CVars::s_RaycastMinDistance >= distance || distance >= Audio::CVars::s_RaycastMaxDistance)
|
|
{
|
|
Reset();
|
|
return;
|
|
}
|
|
|
|
const AZ::Vector3 up = AZ::Vector3::CreateAxisZ();
|
|
const AZ::Vector3 side = ray.GetNormalized().Cross(up);
|
|
|
|
// Spread out the side rays based on the percentage the ray distance is of the maximum distance.
|
|
// The begin of the rays spread by [0.f, 1.f] in the side direction.
|
|
// The end of the rays spread by [1.f, 10.f] in the side direction.
|
|
constexpr float spreadDistanceMinExtent = 1.f;
|
|
constexpr float spreadDistanceMaxExtent = 10.f;
|
|
constexpr float spreadDistanceDelta = spreadDistanceMaxExtent - spreadDistanceMinExtent;
|
|
|
|
const float rayDistancePercent = (distance / Audio::CVars::s_RaycastMaxDistance);
|
|
|
|
const float spreadDist = spreadDistanceMinExtent + rayDistancePercent * spreadDistanceDelta;
|
|
|
|
// Cast ray 0, the direct obstruction ray.
|
|
CastRay(listener, source, 0);
|
|
|
|
if (m_obstOccType == eAOOCT_MULTI_RAY)
|
|
{
|
|
// Cast ray 1, an indirect occlusion ray.
|
|
CastRay(listener, source + up, 1);
|
|
|
|
// Cast ray 2, an indirect occlusion ray.
|
|
CastRay(listener, source - up, 2);
|
|
|
|
// Cast ray 3, an indirect occlusion ray.
|
|
CastRay(listener + side * rayDistancePercent, source + side * spreadDist, 3);
|
|
|
|
// Cast ray 4, an indirect occlusion ray.
|
|
CastRay(listener - side * rayDistancePercent, source - side * spreadDist, 4);
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void RaycastProcessor::CastRay(const AZ::Vector3& origin, const AZ::Vector3& dest, const AZ::u16 rayIndex)
|
|
{
|
|
AZ_Assert(rayIndex < s_maxRaysPerObject, "RaycastProcessor::CastRay - ray index is out of bounds!\n");
|
|
|
|
RaycastInfo& rayInfo = m_rayInfos[rayIndex];
|
|
if (rayInfo.m_pending || rayInfo.m_cached)
|
|
{
|
|
// A raycast is already in flight OR
|
|
// A raycast result was received recently and is still considered valid.
|
|
return;
|
|
}
|
|
|
|
rayInfo.m_raycastRequest.m_start = origin;
|
|
rayInfo.m_raycastRequest.m_direction = dest - origin;
|
|
rayInfo.m_raycastRequest.m_distance = rayInfo.m_raycastRequest.m_direction.NormalizeSafeWithLength();
|
|
rayInfo.m_raycastRequest.m_maxResults = s_maxHitResultsPerRaycast;
|
|
rayInfo.m_raycastRequest.m_reportMultipleHits = true;
|
|
|
|
// Mark as pending
|
|
rayInfo.m_pending = true;
|
|
|
|
AudioRaycastRequest request(rayInfo.m_raycastRequest, m_audioObjectId, rayIndex);
|
|
AudioRaycastRequestBus::Broadcast(&AudioRaycastRequestBus::Events::PushAudioRaycastRequest, request);
|
|
}
|
|
|
|
void RaycastProcessor::SetupTestRay(AZ::u16 rayIndex)
|
|
{
|
|
if (rayIndex < s_maxRaysPerObject)
|
|
{
|
|
// Set the pending flag to true, so the results aren't discarded.
|
|
m_rayInfos[rayIndex].m_pending = true;
|
|
// Set the distance in the request structure so it doesn't have the default.
|
|
m_rayInfos[rayIndex].m_raycastRequest.m_distance = (Audio::CVars::s_RaycastMaxDistance / 4.f);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
#if !defined(AUDIO_RELEASE)
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void CATLAudioObjectBase::CheckBeforeRemoval(const CATLDebugNameStore* const pDebugNameStore)
|
|
{
|
|
// warn if there is activity on an object being cleared
|
|
if (!m_cActiveEvents.empty())
|
|
{
|
|
const char* const sEventString = GetEventIDs("; ").c_str();
|
|
g_audioLogger.Log(eALT_WARNING, "Active events on an object (ID: %u) being released! #Events: %u EventIDs: %s", GetID(), m_cActiveEvents.size(), sEventString);
|
|
}
|
|
|
|
if (!m_cTriggers.empty())
|
|
{
|
|
const char* const sTriggerString = GetTriggerNames("; ", pDebugNameStore).c_str();
|
|
g_audioLogger.Log(eALT_WARNING, "Active triggers on an object (ID: %u) being released! #Triggers: %u TriggerNames: %s", GetID(), m_cTriggers.size(), sTriggerString);
|
|
}
|
|
}
|
|
|
|
|
|
using TTriggerCountMap = ATLMapLookupType<TAudioControlID, size_t>;
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
AZStd::string CATLAudioObjectBase::GetTriggerNames(const char* const sSeparator, const CATLDebugNameStore* const pDebugNameStore)
|
|
{
|
|
AZStd::string triggersString;
|
|
|
|
TTriggerCountMap cTriggerCounts;
|
|
|
|
for (auto& trigger : m_cTriggers)
|
|
{
|
|
++cTriggerCounts[trigger.second.nTriggerID];
|
|
}
|
|
|
|
for (auto& triggerCount : cTriggerCounts)
|
|
{
|
|
const char* const pName = pDebugNameStore->LookupAudioTriggerName(triggerCount.first);
|
|
|
|
if (pName)
|
|
{
|
|
const size_t nInstances = triggerCount.second;
|
|
|
|
if (nInstances == 1)
|
|
{
|
|
triggersString = AZStd::string::format("%s%s%s", triggersString.c_str(), pName, sSeparator);
|
|
}
|
|
else
|
|
{
|
|
triggersString = AZStd::string::format("%s%s(%zu inst.)%s", triggersString.c_str(), pName, nInstances, sSeparator);
|
|
}
|
|
}
|
|
}
|
|
|
|
return triggersString;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
AZStd::string CATLAudioObjectBase::GetEventIDs(const char* const sSeparator)
|
|
{
|
|
AZStd::string eventsString;
|
|
for (auto activeEvent : m_cActiveEvents)
|
|
{
|
|
eventsString = AZStd::string::format("%s%llu%s", eventsString.c_str(), activeEvent, sSeparator);
|
|
}
|
|
|
|
return eventsString;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
const float CATLAudioObjectBase::CStateDebugDrawData::fMinAlpha = 0.5f;
|
|
const float CATLAudioObjectBase::CStateDebugDrawData::fMaxAlpha = 1.0f;
|
|
const int CATLAudioObjectBase::CStateDebugDrawData::nMaxToMinUpdates = 100;
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
CATLAudioObjectBase::CStateDebugDrawData::CStateDebugDrawData(const TAudioSwitchStateID nState)
|
|
: nCurrentState(nState)
|
|
, fCurrentAlpha(fMaxAlpha)
|
|
{
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
CATLAudioObjectBase::CStateDebugDrawData::~CStateDebugDrawData()
|
|
{
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void CATLAudioObjectBase::CStateDebugDrawData::Update(const TAudioSwitchStateID nNewState)
|
|
{
|
|
if ((nNewState == nCurrentState) && (fCurrentAlpha > fMinAlpha))
|
|
{
|
|
fCurrentAlpha -= (fMaxAlpha - fMinAlpha) / nMaxToMinUpdates;
|
|
}
|
|
else if (nNewState != nCurrentState)
|
|
{
|
|
nCurrentState = nNewState;
|
|
fCurrentAlpha = fMaxAlpha;
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void CATLAudioObject::DrawDebugInfo(IRenderAuxGeom& auxGeom, const AZ::Vector3& vListenerPos, const CATLDebugNameStore* const pDebugNameStore) const
|
|
{
|
|
m_raycastProcessor.DrawObstructionRays(auxGeom);
|
|
|
|
if (!m_cTriggers.empty())
|
|
{
|
|
// Inspect triggers and apply filter (if set)...
|
|
TTriggerCountMap cTriggerCounts;
|
|
|
|
auto triggerFilter = static_cast<AZ::CVarFixedString>(Audio::CVars::s_AudioTriggersDebugFilter);
|
|
AZStd::to_lower(triggerFilter.begin(), triggerFilter.end());
|
|
|
|
for (auto& trigger : m_cTriggers)
|
|
{
|
|
AZStd::string triggerName(pDebugNameStore->LookupAudioTriggerName(trigger.second.nTriggerID));
|
|
AZStd::to_lower(triggerName.begin(), triggerName.end());
|
|
|
|
if (AudioDebugDrawFilter(triggerName, triggerFilter))
|
|
{
|
|
++cTriggerCounts[trigger.second.nTriggerID];
|
|
}
|
|
}
|
|
|
|
// Early out for this object if all trigger names were filtered out.
|
|
if (cTriggerCounts.empty())
|
|
{
|
|
return;
|
|
}
|
|
|
|
const AZ::Vector3 vPos(m_oPosition.GetPositionVec());
|
|
AZ::Vector3 vScreenPos(0.f);
|
|
|
|
// ToDo: Update to work with Atom? LYN-3677
|
|
/*{
|
|
float screenProj[3];
|
|
???->ProjectToScreen(vPos.GetX(), vPos.GetY(), vPos.GetZ(), &screenProj[0], &screenProj[1], &screenProj[2]);
|
|
|
|
screenProj[0] *= 0.01f * static_cast<float>(???->GetWidth());
|
|
screenProj[1] *= 0.01f * static_cast<float>(???->GetHeight());
|
|
vScreenPos.Set(screenProj);
|
|
}
|
|
else*/
|
|
{
|
|
vScreenPos.SetZ(-1.0f);
|
|
}
|
|
|
|
if ((0.0f <= vScreenPos.GetZ()) && (vScreenPos.GetZ() <= 1.0f))
|
|
{
|
|
const float fDist = vPos.GetDistance(vListenerPos);
|
|
|
|
if (CVars::s_debugDrawOptions.AreAllFlagsActive(DebugDraw::Options::DrawObjects))
|
|
{
|
|
const SAuxGeomRenderFlags nPreviousRenderFlags = auxGeom.GetRenderFlags();
|
|
SAuxGeomRenderFlags nNewRenderFlags(e_Def3DPublicRenderflags | e_AlphaBlended);
|
|
nNewRenderFlags.SetCullMode(e_CullModeNone);
|
|
auxGeom.SetRenderFlags(nNewRenderFlags);
|
|
const float fRadius = 0.15f;
|
|
const AZ::Color sphereColor(1.f, 0.1f, 0.1f, 1.f);
|
|
auxGeom.DrawSphere(AZVec3ToLYVec3(vPos), fRadius, AZColorToLYColorB(sphereColor));
|
|
auxGeom.SetRenderFlags(nPreviousRenderFlags);
|
|
}
|
|
|
|
const float fFontSize = 1.3f;
|
|
const float fLineHeight = 12.0f;
|
|
|
|
if (CVars::s_debugDrawOptions.AreAllFlagsActive(DebugDraw::Options::ObjectStates))
|
|
{
|
|
AZ::Vector3 vSwitchPos(vScreenPos);
|
|
|
|
for (auto& switchState : m_cSwitchStates)
|
|
{
|
|
const TAudioControlID nSwitchID = switchState.first;
|
|
const TAudioSwitchStateID nStateID = switchState.second;
|
|
|
|
const char* const pSwitchName = pDebugNameStore->LookupAudioSwitchName(nSwitchID);
|
|
const char* const pStateName = pDebugNameStore->LookupAudioSwitchStateName(nSwitchID, nStateID);
|
|
|
|
if (pSwitchName && pStateName)
|
|
{
|
|
CStateDebugDrawData& oDrawData = m_cStateDrawInfoMap[nSwitchID];
|
|
oDrawData.Update(nStateID);
|
|
const AZ::Color switchTextColor(0.8f, 0.8f, 0.8f, oDrawData.fCurrentAlpha);
|
|
|
|
vSwitchPos -= AZ::Vector3(0.f, fLineHeight, 0.f);
|
|
auxGeom.Draw2dLabel(
|
|
vSwitchPos.GetX(),
|
|
vSwitchPos.GetY(),
|
|
fFontSize,
|
|
AZColorToLYColorF(switchTextColor),
|
|
false,
|
|
"%s: %s\n",
|
|
pSwitchName,
|
|
pStateName);
|
|
}
|
|
}
|
|
}
|
|
|
|
const AZ::Color brightTextColor(0.9f, 0.9f, 0.9f, 1.f);
|
|
const AZ::Color normalTextColor(0.75f, 0.75f, 0.75f, 1.f);
|
|
const AZ::Color dimmedTextColor(0.5f, 0.5f, 0.5f, 1.f);
|
|
|
|
if (CVars::s_debugDrawOptions.AreAllFlagsActive(DebugDraw::Options::ObjectLabels))
|
|
{
|
|
const TAudioObjectID nObjectID = GetID();
|
|
auxGeom.Draw2dLabel(
|
|
vScreenPos.GetX(),
|
|
vScreenPos.GetY(),
|
|
fFontSize,
|
|
AZColorToLYColorF(brightTextColor),
|
|
false,
|
|
"%s ID: %llu RefCnt: %2zu Dist: %4.1fm",
|
|
pDebugNameStore->LookupAudioObjectName(nObjectID),
|
|
nObjectID,
|
|
GetRefCount(),
|
|
fDist);
|
|
|
|
SATLSoundPropagationData obstOccData;
|
|
GetObstOccData(obstOccData);
|
|
|
|
auxGeom.Draw2dLabel(
|
|
vScreenPos.GetX(),
|
|
vScreenPos.GetY() + fLineHeight,
|
|
fFontSize,
|
|
AZColorToLYColorF(brightTextColor),
|
|
false,
|
|
"Obst: %.3f Occl: %.3f",
|
|
obstOccData.fObstruction,
|
|
obstOccData.fOcclusion
|
|
);
|
|
}
|
|
|
|
if (CVars::s_debugDrawOptions.AreAllFlagsActive(DebugDraw::Options::ObjectTriggers))
|
|
{
|
|
AZStd::string triggerStringFormatted;
|
|
|
|
for (auto& triggerCount : cTriggerCounts)
|
|
{
|
|
const char* const pName = pDebugNameStore->LookupAudioTriggerName(triggerCount.first);
|
|
if (pName)
|
|
{
|
|
const size_t nInstances = triggerCount.second;
|
|
if (nInstances == 1)
|
|
{
|
|
triggerStringFormatted += pName;
|
|
triggerStringFormatted += "\n";
|
|
}
|
|
else
|
|
{
|
|
triggerStringFormatted += AZStd::string::format("%s: %zu\n", pName, nInstances);
|
|
}
|
|
}
|
|
}
|
|
|
|
auxGeom.Draw2dLabel(
|
|
vScreenPos.GetX(),
|
|
vScreenPos.GetY() + (2.0f * fLineHeight),
|
|
fFontSize,
|
|
AZColorToLYColorF(normalTextColor),
|
|
false,
|
|
"%s",
|
|
triggerStringFormatted.c_str());
|
|
}
|
|
|
|
if (CVars::s_debugDrawOptions.AreAllFlagsActive(DebugDraw::Options::ObjectRtpcs))
|
|
{
|
|
AZ::Vector3 vRtpcPos(vScreenPos);
|
|
|
|
for (auto& rtpc : m_cRtpcs)
|
|
{
|
|
const float fRtpcValue = rtpc.second;
|
|
const char* const pRtpcName = pDebugNameStore->LookupAudioRtpcName(rtpc.first);
|
|
|
|
if (pRtpcName)
|
|
{
|
|
const float xOffset = 5.f;
|
|
|
|
vRtpcPos -= AZ::Vector3(0.f, fLineHeight, 0.f); // list grows up
|
|
auxGeom.Draw2dLabelCustom(
|
|
vRtpcPos.GetX() - xOffset,
|
|
vRtpcPos.GetY(),
|
|
fFontSize,
|
|
AZColorToLYColorF(normalTextColor),
|
|
eDrawText_Right, // right-justified
|
|
"%s: %2.2f\n",
|
|
pRtpcName,
|
|
fRtpcValue);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (CVars::s_debugDrawOptions.AreAllFlagsActive(DebugDraw::Options::ObjectEnvironments))
|
|
{
|
|
AZ::Vector3 vEnvPos(vScreenPos);
|
|
|
|
for (auto& environment : m_cEnvironments)
|
|
{
|
|
const float fEnvValue = environment.second;
|
|
const char* const pEnvName = pDebugNameStore->LookupAudioEnvironmentName(environment.first);
|
|
|
|
if (pEnvName)
|
|
{
|
|
const float xOffset = 5.f;
|
|
|
|
vEnvPos += AZ::Vector3(0.f, fLineHeight, 0.f); // list grows down
|
|
auxGeom.Draw2dLabelCustom(
|
|
vEnvPos.GetX() - xOffset,
|
|
vEnvPos.GetY(),
|
|
fFontSize,
|
|
AZColorToLYColorF(normalTextColor),
|
|
eDrawText_Right, // right-justified
|
|
"%s: %.2f\n",
|
|
pEnvName,
|
|
fEnvValue);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void RaycastProcessor::DrawObstructionRays(IRenderAuxGeom& auxGeom) const
|
|
{
|
|
static const AZ::Color obstructedRayColor(0.8f, 0.08f, 0.f, 1.f);
|
|
static const AZ::Color freeRayColor(0.08f, 0.8f, 0.f, 1.f);
|
|
static const AZ::Color hitSphereColor(1.f, 0.27f, 0.f, 0.8f);
|
|
static const AZ::Color obstructedRayLabelColor(1.f, 0.f, 0.02f, 0.9f);
|
|
static const AZ::Color freeRayLabelColor(0.f, 1.f, 0.02f, 0.9f);
|
|
|
|
static const float hitSphereRadius = 0.02f;
|
|
|
|
if (!CanRun())
|
|
{
|
|
return;
|
|
}
|
|
|
|
const SAuxGeomRenderFlags previousRenderFlags = auxGeom.GetRenderFlags();
|
|
SAuxGeomRenderFlags newRenderFlags(e_Def3DPublicRenderflags | e_AlphaBlended);
|
|
newRenderFlags.SetCullMode(e_CullModeNone);
|
|
auxGeom.SetRenderFlags(newRenderFlags);
|
|
|
|
const bool drawRays = CVars::s_debugDrawOptions.AreAllFlagsActive(DebugDraw::Options::DrawRays);
|
|
// ToDo: Update to work with Atom? LYN-3677
|
|
//const bool drawLabels = CVars::s_debugDrawOptions.AreAllFlagsActive(DebugDraw::Options::RayLabels);
|
|
|
|
size_t numRays = m_obstOccType == eAOOCT_SINGLE_RAY ? 1 : s_maxRaysPerObject;
|
|
for (size_t rayIndex = 0; rayIndex < numRays; ++rayIndex)
|
|
{
|
|
const RaycastInfo& rayInfo = m_rayInfos[rayIndex];
|
|
|
|
const AZ::Vector3 rayEnd = rayInfo.m_raycastRequest.m_start + rayInfo.m_raycastRequest.m_direction * rayInfo.GetNearestHitDistance();
|
|
|
|
if (drawRays)
|
|
{
|
|
const bool rayObstructed = (rayInfo.m_numHits > 0);
|
|
const AZ::Color& rayColor = (rayObstructed ? obstructedRayColor : freeRayColor);
|
|
|
|
if (rayObstructed)
|
|
{
|
|
auxGeom.DrawSphere(
|
|
AZVec3ToLYVec3(rayEnd),
|
|
hitSphereRadius,
|
|
AZColorToLYColorB(hitSphereColor)
|
|
);
|
|
}
|
|
|
|
auxGeom.DrawLine(
|
|
AZVec3ToLYVec3(rayInfo.m_raycastRequest.m_start),
|
|
AZColorToLYColorB(freeRayColor),
|
|
AZVec3ToLYVec3(rayEnd),
|
|
AZColorToLYColorB(rayColor),
|
|
1.f
|
|
);
|
|
}
|
|
|
|
// ToDo: Update to work with Atom? LYN-3677
|
|
/*if (drawLabels)
|
|
{
|
|
float screenProj[3];
|
|
renderer->ProjectToScreen(rayEnd.GetX(), rayEnd.GetY(), rayEnd.GetZ(),
|
|
&screenProj[0], &screenProj[1], &screenProj[2]);
|
|
|
|
screenProj[0] *= 0.01f * aznumeric_cast<float>(renderer->GetWidth());
|
|
screenProj[1] *= 0.01f * aznumeric_cast<float>(renderer->GetHeight());
|
|
|
|
AZ::Vector3 screenPos = AZ::Vector3::CreateFromFloat3(screenProj);
|
|
|
|
if ((0.f <= screenPos.GetZ()) && (screenPos.GetZ() <= 1.f))
|
|
{
|
|
float lerpValue = rayInfo.m_contribution;
|
|
AZ::Color labelColor = freeRayLabelColor.Lerp(obstructedRayLabelColor, lerpValue);
|
|
|
|
auxGeom.Draw2dLabel(
|
|
screenPos.GetX(),
|
|
screenPos.GetY() - 12.f,
|
|
1.6f,
|
|
AZColorToLYColorF(labelColor),
|
|
true,
|
|
(rayIndex == 0) ? "OBST: %.2f" : "OCCL: %.2f",
|
|
rayInfo.GetDistanceScaledContribution()
|
|
);
|
|
}
|
|
}*/
|
|
}
|
|
|
|
auxGeom.SetRenderFlags(previousRenderFlags);
|
|
}
|
|
|
|
#endif // !AUDIO_RELEASE
|
|
|
|
} // namespace Audio
|