SoundSource3D.cpp 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. // Copyright (c) 2008-2023 the Urho3D project
  2. // License: MIT
  3. #include "../Precompiled.h"
  4. #include "../Audio/Audio.h"
  5. #include "../Audio/Sound.h"
  6. #include "../Audio/SoundListener.h"
  7. #include "../Audio/SoundSource3D.h"
  8. #include "../Core/Context.h"
  9. #include "../Graphics/DebugRenderer.h"
  10. #include "../Scene/Node.h"
  11. namespace Urho3D
  12. {
  13. static const float DEFAULT_NEARDISTANCE = 0.0f;
  14. static const float DEFAULT_FARDISTANCE = 100.0f;
  15. static const float DEFAULT_ROLLOFF = 2.0f;
  16. static const float DEFAULT_ANGLE = 360.0f;
  17. static const float MIN_ROLLOFF = 0.1f;
  18. static const Color INNER_COLOR(1.0f, 0.5f, 1.0f);
  19. static const Color OUTER_COLOR(1.0f, 0.0f, 1.0f);
  20. extern const char* AUDIO_CATEGORY;
  21. SoundSource3D::SoundSource3D(Context* context) :
  22. SoundSource(context),
  23. nearDistance_(DEFAULT_NEARDISTANCE),
  24. farDistance_(DEFAULT_FARDISTANCE),
  25. innerAngle_(DEFAULT_ANGLE),
  26. outerAngle_(DEFAULT_ANGLE),
  27. rolloffFactor_(DEFAULT_ROLLOFF)
  28. {
  29. // Start from zero volume until attenuation properly calculated
  30. attenuation_ = 0.0f;
  31. }
  32. void SoundSource3D::RegisterObject(Context* context)
  33. {
  34. context->RegisterFactory<SoundSource3D>(AUDIO_CATEGORY);
  35. URHO3D_COPY_BASE_ATTRIBUTES(SoundSource);
  36. // Remove Attenuation and Panning as attribute as they are constantly being updated
  37. URHO3D_REMOVE_ATTRIBUTE("Attenuation");
  38. URHO3D_REMOVE_ATTRIBUTE("Panning");
  39. URHO3D_ATTRIBUTE("Near Distance", nearDistance_, DEFAULT_NEARDISTANCE, AM_DEFAULT);
  40. URHO3D_ATTRIBUTE("Far Distance", farDistance_, DEFAULT_FARDISTANCE, AM_DEFAULT);
  41. URHO3D_ATTRIBUTE("Inner Angle", innerAngle_, DEFAULT_ANGLE, AM_DEFAULT);
  42. URHO3D_ATTRIBUTE("Outer Angle", outerAngle_, DEFAULT_ANGLE, AM_DEFAULT);
  43. URHO3D_ATTRIBUTE("Rolloff Factor", rolloffFactor_, DEFAULT_ROLLOFF, AM_DEFAULT);
  44. }
  45. void SoundSource3D::DrawDebugGeometry(DebugRenderer* debug, bool depthTest)
  46. {
  47. if (!debug || !node_ || !IsEnabledEffective())
  48. return;
  49. const Matrix3x4& worldTransform = node_->GetWorldTransform();
  50. Vector3 worldPosition = worldTransform.Translation();
  51. Quaternion worldRotation = worldTransform.Rotation();
  52. // Draw cones for directional sounds, or spheres for non-directional
  53. if (innerAngle_ < DEFAULT_ANGLE && outerAngle_ > 0.0f)
  54. {
  55. const Quaternion rotation = worldRotation * Quaternion(Vector3::UP, Vector3::FORWARD);
  56. debug->AddSphereSector(Sphere(worldPosition, nearDistance_), rotation, innerAngle_, false, INNER_COLOR, depthTest);
  57. debug->AddSphereSector(Sphere(worldPosition, nearDistance_), rotation, outerAngle_, false, OUTER_COLOR, depthTest);
  58. debug->AddSphereSector(Sphere(worldPosition, farDistance_), rotation, innerAngle_, true, INNER_COLOR, depthTest);
  59. debug->AddSphereSector(Sphere(worldPosition, farDistance_), rotation, outerAngle_, true, OUTER_COLOR, depthTest);
  60. }
  61. else
  62. {
  63. debug->AddSphere(Sphere(worldPosition, nearDistance_), INNER_COLOR, depthTest);
  64. debug->AddSphere(Sphere(worldPosition, farDistance_), OUTER_COLOR, depthTest);
  65. }
  66. }
  67. void SoundSource3D::Update(float timeStep)
  68. {
  69. CalculateAttenuation();
  70. SoundSource::Update(timeStep);
  71. }
  72. void SoundSource3D::SetDistanceAttenuation(float nearDistance, float farDistance, float rolloffFactor)
  73. {
  74. nearDistance_ = Max(nearDistance, 0.0f);
  75. farDistance_ = Max(farDistance, 0.0f);
  76. rolloffFactor_ = Max(rolloffFactor, MIN_ROLLOFF);
  77. MarkNetworkUpdate();
  78. }
  79. void SoundSource3D::SetAngleAttenuation(float innerAngle, float outerAngle)
  80. {
  81. innerAngle_ = Clamp(innerAngle, 0.0f, DEFAULT_ANGLE);
  82. outerAngle_ = Clamp(outerAngle, 0.0f, DEFAULT_ANGLE);
  83. MarkNetworkUpdate();
  84. }
  85. void SoundSource3D::SetFarDistance(float distance)
  86. {
  87. farDistance_ = Max(distance, 0.0f);
  88. MarkNetworkUpdate();
  89. }
  90. void SoundSource3D::SetNearDistance(float distance)
  91. {
  92. nearDistance_ = Max(distance, 0.0f);
  93. MarkNetworkUpdate();
  94. }
  95. void SoundSource3D::SetInnerAngle(float angle)
  96. {
  97. innerAngle_ = Clamp(angle, 0.0f, DEFAULT_ANGLE);
  98. MarkNetworkUpdate();
  99. }
  100. void SoundSource3D::SetOuterAngle(float angle)
  101. {
  102. outerAngle_ = Clamp(angle, 0.0f, DEFAULT_ANGLE);
  103. MarkNetworkUpdate();
  104. }
  105. void SoundSource3D::SetRolloffFactor(float factor)
  106. {
  107. rolloffFactor_ = Max(factor, MIN_ROLLOFF);
  108. MarkNetworkUpdate();
  109. }
  110. void SoundSource3D::CalculateAttenuation()
  111. {
  112. if (!audio_)
  113. return;
  114. float interval = farDistance_ - nearDistance_;
  115. if (node_)
  116. {
  117. SoundListener* listener = audio_->GetListener();
  118. // Listener must either be sceneless or in the same scene, else attenuate sound to silence
  119. if (listener && listener->IsEnabledEffective() && (!listener->GetScene() || listener->GetScene() == GetScene()))
  120. {
  121. Node* listenerNode = listener->GetNode();
  122. Vector3 relativePos
  123. (listenerNode->GetWorldRotation().Inverse() * (node_->GetWorldPosition() - listenerNode->GetWorldPosition()));
  124. float distance = relativePos.Length();
  125. // Distance attenuation
  126. if (interval > 0.0f)
  127. attenuation_ = powf(1.0f - Clamp(distance - nearDistance_, 0.0f, interval) / interval, rolloffFactor_);
  128. else
  129. attenuation_ = distance <= nearDistance_ ? 1.0f : 0.0f;
  130. // Panning
  131. panning_ = relativePos.Normalized().x_;
  132. // Angle attenuation
  133. if (innerAngle_ < DEFAULT_ANGLE && outerAngle_ > 0.0f)
  134. {
  135. Vector3 listenerRelativePos
  136. (node_->GetWorldRotation().Inverse() * (listenerNode->GetWorldPosition() - node_->GetWorldPosition()));
  137. float listenerDot = Vector3::FORWARD.DotProduct(listenerRelativePos.Normalized());
  138. float listenerAngle = acosf(listenerDot) * M_RADTODEG * 2.0f;
  139. float angleInterval = Max(outerAngle_ - innerAngle_, 0.0f);
  140. float angleAttenuation = 1.0f;
  141. if (angleInterval > 0.0f)
  142. {
  143. if (listenerAngle > innerAngle_)
  144. {
  145. angleAttenuation = powf(1.0f - Clamp(listenerAngle - innerAngle_, 0.0f, angleInterval) / angleInterval,
  146. rolloffFactor_);
  147. }
  148. }
  149. else
  150. angleAttenuation = listenerAngle <= innerAngle_ ? 1.0f : 0.0f;
  151. attenuation_ *= angleAttenuation;
  152. }
  153. }
  154. else
  155. attenuation_ = 0.0f;
  156. }
  157. else
  158. attenuation_ = 0.0f;
  159. }
  160. }