SoundSource3D.cpp 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. //
  2. // Copyright (c) 2008-2017 the Urho3D project.
  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 deal
  6. // in the Software without restriction, including without limitation the rights
  7. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  8. // 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 FROM,
  19. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  20. // THE SOFTWARE.
  21. //
  22. #include "../Precompiled.h"
  23. #include "../Audio/Audio.h"
  24. #include "../Audio/Sound.h"
  25. #include "../Audio/SoundListener.h"
  26. #include "../Audio/SoundSource3D.h"
  27. #include "../Core/Context.h"
  28. #include "../Graphics/DebugRenderer.h"
  29. #include "../Scene/Node.h"
  30. namespace Urho3D
  31. {
  32. static const float DEFAULT_NEARDISTANCE = 0.0f;
  33. static const float DEFAULT_FARDISTANCE = 100.0f;
  34. static const float DEFAULT_ROLLOFF = 2.0f;
  35. static const float DEFAULT_ANGLE = 360.0f;
  36. static const float MIN_ROLLOFF = 0.1f;
  37. static const Color INNER_COLOR(1.0f, 0.5f, 1.0f);
  38. static const Color OUTER_COLOR(1.0f, 0.0f, 1.0f);
  39. extern const char* AUDIO_CATEGORY;
  40. static Vector3 PointOnSphere(float radius, float theta, float phi)
  41. {
  42. // Zero angles point toward positive Z axis
  43. phi += 90.0f;
  44. return Vector3(
  45. radius * Sin(theta) * Sin(phi),
  46. radius * Cos(phi),
  47. radius * Cos(theta) * Sin(phi)
  48. );
  49. }
  50. static void DrawDebugArc(const Vector3& worldPosition, const Quaternion& worldRotation, float angle, float distance, bool drawLines,
  51. const Color& color, DebugRenderer* debug, bool depthTest)
  52. {
  53. if (angle <= 0.f)
  54. return;
  55. else if (angle >= 360.0f)
  56. {
  57. debug->AddSphere(Sphere(worldPosition, distance), color, depthTest);
  58. return;
  59. }
  60. unsigned uintColor = color.ToUInt();
  61. float halfAngle = 0.5f * angle;
  62. if (drawLines)
  63. {
  64. debug->AddLine(worldPosition, worldPosition + worldRotation * PointOnSphere(distance, halfAngle, halfAngle),
  65. uintColor);
  66. debug->AddLine(worldPosition, worldPosition + worldRotation * PointOnSphere(distance, -halfAngle, halfAngle),
  67. uintColor);
  68. debug->AddLine(worldPosition, worldPosition + worldRotation * PointOnSphere(distance, halfAngle, -halfAngle),
  69. uintColor);
  70. debug->AddLine(worldPosition, worldPosition + worldRotation * PointOnSphere(distance, -halfAngle, -halfAngle),
  71. uintColor);
  72. }
  73. const float step = 0.5f;
  74. for (float x = -1.0f; x < 1.0f; x += step)
  75. {
  76. debug->AddLine(worldPosition + worldRotation * PointOnSphere(distance, x * halfAngle, halfAngle),
  77. worldPosition + worldRotation * PointOnSphere(distance, (x + step) * halfAngle, halfAngle),
  78. uintColor);
  79. debug->AddLine(worldPosition + worldRotation * PointOnSphere(distance, x * halfAngle, -halfAngle),
  80. worldPosition + worldRotation * PointOnSphere(distance, (x + step) * halfAngle, -halfAngle),
  81. uintColor);
  82. debug->AddLine(worldPosition + worldRotation * PointOnSphere(distance, halfAngle, x * halfAngle),
  83. worldPosition + worldRotation * PointOnSphere(distance, halfAngle, (x + step) * halfAngle),
  84. uintColor);
  85. debug->AddLine(worldPosition + worldRotation * PointOnSphere(distance, -halfAngle, x * halfAngle),
  86. worldPosition + worldRotation * PointOnSphere(distance, -halfAngle, (x + step) * halfAngle),
  87. uintColor);
  88. }
  89. }
  90. SoundSource3D::SoundSource3D(Context* context) :
  91. SoundSource(context),
  92. nearDistance_(DEFAULT_NEARDISTANCE),
  93. farDistance_(DEFAULT_FARDISTANCE),
  94. innerAngle_(DEFAULT_ANGLE),
  95. outerAngle_(DEFAULT_ANGLE),
  96. rolloffFactor_(DEFAULT_ROLLOFF)
  97. {
  98. // Start from zero volume until attenuation properly calculated
  99. attenuation_ = 0.0f;
  100. }
  101. void SoundSource3D::RegisterObject(Context* context)
  102. {
  103. context->RegisterFactory<SoundSource3D>(AUDIO_CATEGORY);
  104. URHO3D_COPY_BASE_ATTRIBUTES(SoundSource);
  105. // Remove Attenuation and Panning as attribute as they are constantly being updated
  106. URHO3D_REMOVE_ATTRIBUTE("Attenuation");
  107. URHO3D_REMOVE_ATTRIBUTE("Panning");
  108. URHO3D_ATTRIBUTE("Near Distance", float, nearDistance_, DEFAULT_NEARDISTANCE, AM_DEFAULT);
  109. URHO3D_ATTRIBUTE("Far Distance", float, farDistance_, DEFAULT_FARDISTANCE, AM_DEFAULT);
  110. URHO3D_ATTRIBUTE("Inner Angle", float, innerAngle_, DEFAULT_ANGLE, AM_DEFAULT);
  111. URHO3D_ATTRIBUTE("Outer Angle", float, outerAngle_, DEFAULT_ANGLE, AM_DEFAULT);
  112. URHO3D_ATTRIBUTE("Rolloff Factor", float, rolloffFactor_, DEFAULT_ROLLOFF, AM_DEFAULT);
  113. }
  114. void SoundSource3D::DrawDebugGeometry(DebugRenderer* debug, bool depthTest)
  115. {
  116. if (!debug || !node_ || !IsEnabledEffective())
  117. return;
  118. const Matrix3x4& worldTransform = node_->GetWorldTransform();
  119. Vector3 worldPosition = worldTransform.Translation();
  120. Quaternion worldRotation = worldTransform.Rotation();
  121. // Draw cones for directional sounds, or spheres for non-directional
  122. if (innerAngle_ < DEFAULT_ANGLE && outerAngle_ > 0.0f)
  123. {
  124. DrawDebugArc(worldPosition, worldRotation, innerAngle_, nearDistance_, false, INNER_COLOR, debug, depthTest);
  125. DrawDebugArc(worldPosition, worldRotation, outerAngle_, nearDistance_, false, OUTER_COLOR, debug, depthTest);
  126. DrawDebugArc(worldPosition, worldRotation, innerAngle_, farDistance_, true, INNER_COLOR, debug, depthTest);
  127. DrawDebugArc(worldPosition, worldRotation, outerAngle_, farDistance_, true, OUTER_COLOR, debug, depthTest);
  128. }
  129. else
  130. {
  131. debug->AddSphere(Sphere(worldPosition, nearDistance_), INNER_COLOR, depthTest);
  132. debug->AddSphere(Sphere(worldPosition, farDistance_), OUTER_COLOR, depthTest);
  133. }
  134. }
  135. void SoundSource3D::Update(float timeStep)
  136. {
  137. CalculateAttenuation();
  138. SoundSource::Update(timeStep);
  139. }
  140. void SoundSource3D::SetDistanceAttenuation(float nearDistance, float farDistance, float rolloffFactor)
  141. {
  142. nearDistance_ = Max(nearDistance, 0.0f);
  143. farDistance_ = Max(farDistance, 0.0f);
  144. rolloffFactor_ = Max(rolloffFactor, MIN_ROLLOFF);
  145. MarkNetworkUpdate();
  146. }
  147. void SoundSource3D::SetAngleAttenuation(float innerAngle, float outerAngle)
  148. {
  149. innerAngle_ = Clamp(innerAngle, 0.0f, DEFAULT_ANGLE);
  150. outerAngle_ = Clamp(outerAngle, 0.0f, DEFAULT_ANGLE);
  151. MarkNetworkUpdate();
  152. }
  153. void SoundSource3D::SetFarDistance(float distance)
  154. {
  155. farDistance_ = Max(distance, 0.0f);
  156. MarkNetworkUpdate();
  157. }
  158. void SoundSource3D::SetNearDistance(float distance)
  159. {
  160. nearDistance_ = Max(distance, 0.0f);
  161. MarkNetworkUpdate();
  162. }
  163. void SoundSource3D::SetInnerAngle(float angle)
  164. {
  165. innerAngle_ = Clamp(angle, 0.0f, DEFAULT_ANGLE);
  166. MarkNetworkUpdate();
  167. }
  168. void SoundSource3D::SetOuterAngle(float angle)
  169. {
  170. outerAngle_ = Clamp(angle, 0.0f, DEFAULT_ANGLE);
  171. MarkNetworkUpdate();
  172. }
  173. void SoundSource3D::SetRolloffFactor(float factor)
  174. {
  175. rolloffFactor_ = Max(factor, MIN_ROLLOFF);
  176. MarkNetworkUpdate();
  177. }
  178. void SoundSource3D::CalculateAttenuation()
  179. {
  180. if (!audio_)
  181. return;
  182. float interval = farDistance_ - nearDistance_;
  183. if (node_)
  184. {
  185. SoundListener* listener = audio_->GetListener();
  186. // Listener must either be sceneless or in the same scene, else attenuate sound to silence
  187. if (listener && listener->IsEnabledEffective() && (!listener->GetScene() || listener->GetScene() == GetScene()))
  188. {
  189. Node* listenerNode = listener->GetNode();
  190. Vector3 relativePos
  191. (listenerNode->GetWorldRotation().Inverse() * (node_->GetWorldPosition() - listenerNode->GetWorldPosition()));
  192. float distance = relativePos.Length();
  193. // Distance attenuation
  194. if (interval > 0.0f)
  195. attenuation_ = powf(1.0f - Clamp(distance - nearDistance_, 0.0f, interval) / interval, rolloffFactor_);
  196. else
  197. attenuation_ = distance <= nearDistance_ ? 1.0f : 0.0f;
  198. // Panning
  199. panning_ = relativePos.Normalized().x_;
  200. // Angle attenuation
  201. if (innerAngle_ < DEFAULT_ANGLE && outerAngle_ > 0.0f)
  202. {
  203. Vector3 listenerRelativePos
  204. (node_->GetWorldRotation().Inverse() * (listenerNode->GetWorldPosition() - node_->GetWorldPosition()));
  205. float listenerDot = Vector3::FORWARD.DotProduct(listenerRelativePos.Normalized());
  206. float listenerAngle = acosf(listenerDot) * M_RADTODEG * 2.0f;
  207. float angleInterval = Max(outerAngle_ - innerAngle_, 0.0f);
  208. float angleAttenuation = 1.0f;
  209. if (angleInterval > 0.0f)
  210. {
  211. if (listenerAngle > innerAngle_)
  212. {
  213. angleAttenuation = powf(1.0f - Clamp(listenerAngle - innerAngle_, 0.0f, angleInterval) / angleInterval,
  214. rolloffFactor_);
  215. }
  216. }
  217. else
  218. angleAttenuation = listenerAngle <= innerAngle_ ? 1.0f : 0.0f;
  219. attenuation_ *= angleAttenuation;
  220. }
  221. }
  222. else
  223. attenuation_ = 0.0f;
  224. }
  225. else
  226. attenuation_ = 0.0f;
  227. }
  228. }