SoundSource3D.cpp 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. //
  2. // Copyright (c) 2008-2014 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 "../Core/Context.h"
  25. #include "../Graphics/DebugRenderer.h"
  26. #include "../Scene/Node.h"
  27. #include "../Audio/Sound.h"
  28. #include "../Audio/SoundListener.h"
  29. #include "../Audio/SoundSource3D.h"
  30. #include "../Container/DebugNew.h"
  31. namespace Urho3D
  32. {
  33. static const float DEFAULT_NEARDISTANCE = 0.0f;
  34. static const float DEFAULT_FARDISTANCE = 100.0f;
  35. static const float DEFAULT_ROLLOFF = 2.0f;
  36. static const float DEFAULT_ANGLE = 360.0f;
  37. static const float MIN_ROLLOFF = 0.1f;
  38. static const Color INNER_COLOR(1.0f, 0.5f, 1.0f);
  39. static const Color OUTER_COLOR(1.0f, 0.0f, 1.0f);
  40. extern const char* AUDIO_CATEGORY;
  41. static Vector3 PointOnSphere(float radius, float theta, float phi)
  42. {
  43. // Zero angles point toward positive Z axis
  44. phi += 90.0f;
  45. return Vector3(
  46. radius * Sin(theta) * Sin(phi),
  47. radius * Cos(phi),
  48. radius * Cos(theta) * Sin(phi)
  49. );
  50. }
  51. static void DrawDebugArc(const Vector3& worldPosition, const Quaternion& worldRotation, float angle, float distance, bool drawLines,
  52. const Color& color, DebugRenderer* debug, bool depthTest)
  53. {
  54. if (angle <= 0.f)
  55. return;
  56. else if (angle >= 360.0f)
  57. {
  58. debug->AddSphere(Sphere(worldPosition, distance), color, depthTest);
  59. return;
  60. }
  61. unsigned uintColor = color.ToUInt();
  62. float halfAngle = 0.5f * angle;
  63. if (drawLines)
  64. {
  65. debug->AddLine(worldPosition, worldPosition + worldRotation * PointOnSphere(distance, halfAngle, halfAngle),
  66. uintColor);
  67. debug->AddLine(worldPosition, worldPosition + worldRotation * PointOnSphere(distance, -halfAngle, halfAngle),
  68. uintColor);
  69. debug->AddLine(worldPosition, worldPosition + worldRotation * PointOnSphere(distance, halfAngle, -halfAngle),
  70. uintColor);
  71. debug->AddLine(worldPosition, worldPosition + worldRotation * PointOnSphere(distance, -halfAngle, -halfAngle),
  72. uintColor);
  73. }
  74. const float step = 0.5f;
  75. for (float x = -1.0f; x < 1.0f; x += step)
  76. {
  77. debug->AddLine(worldPosition + worldRotation * PointOnSphere(distance, x * halfAngle, halfAngle),
  78. worldPosition + worldRotation * PointOnSphere(distance, (x + step) * halfAngle, halfAngle),
  79. uintColor);
  80. debug->AddLine(worldPosition + worldRotation * PointOnSphere(distance, x * halfAngle, -halfAngle),
  81. worldPosition + worldRotation * PointOnSphere(distance, (x + step) * halfAngle, -halfAngle),
  82. uintColor);
  83. debug->AddLine(worldPosition + worldRotation * PointOnSphere(distance, halfAngle, x * halfAngle),
  84. worldPosition + worldRotation * PointOnSphere(distance, halfAngle, (x + step) * halfAngle),
  85. uintColor);
  86. debug->AddLine(worldPosition + worldRotation * PointOnSphere(distance, -halfAngle, x * halfAngle),
  87. worldPosition + worldRotation * PointOnSphere(distance, -halfAngle, (x + step) * halfAngle),
  88. uintColor);
  89. }
  90. }
  91. SoundSource3D::SoundSource3D(Context* context) :
  92. SoundSource(context),
  93. nearDistance_(DEFAULT_NEARDISTANCE),
  94. farDistance_(DEFAULT_FARDISTANCE),
  95. innerAngle_(DEFAULT_ANGLE),
  96. outerAngle_(DEFAULT_ANGLE),
  97. rolloffFactor_(DEFAULT_ROLLOFF)
  98. {
  99. // Start from zero volume until attenuation properly calculated
  100. attenuation_ = 0.0f;
  101. }
  102. void SoundSource3D::RegisterObject(Context* context)
  103. {
  104. context->RegisterFactory<SoundSource3D>(AUDIO_CATEGORY);
  105. COPY_BASE_ATTRIBUTES(SoundSource);
  106. // Remove Attenuation and Panning as attribute as they are constantly being updated
  107. REMOVE_ATTRIBUTE("Attenuation");
  108. REMOVE_ATTRIBUTE("Panning");
  109. ATTRIBUTE("Near Distance", float, nearDistance_, DEFAULT_NEARDISTANCE, AM_DEFAULT);
  110. ATTRIBUTE("Far Distance", float, farDistance_, DEFAULT_FARDISTANCE, AM_DEFAULT);
  111. ATTRIBUTE("Inner Angle", float, innerAngle_, DEFAULT_ANGLE, AM_DEFAULT);
  112. ATTRIBUTE("Outer Angle", float, outerAngle_, DEFAULT_ANGLE, AM_DEFAULT);
  113. ATTRIBUTE("Rolloff Factor", float, rolloffFactor_, DEFAULT_ROLLOFF, AM_DEFAULT);
  114. }
  115. void SoundSource3D::DrawDebugGeometry(DebugRenderer* debug, bool depthTest)
  116. {
  117. if (!debug || !node_ || !IsEnabledEffective())
  118. return;
  119. const Matrix3x4& worldTransform = node_->GetWorldTransform();
  120. Vector3 worldPosition = worldTransform.Translation();
  121. Quaternion worldRotation = worldTransform.Rotation();
  122. // Draw cones for directional sounds, or spheres for non-directional
  123. if (innerAngle_ < DEFAULT_ANGLE && outerAngle_ > 0.0f)
  124. {
  125. DrawDebugArc(worldPosition, worldRotation, innerAngle_, nearDistance_, false, INNER_COLOR, debug, depthTest);
  126. DrawDebugArc(worldPosition, worldRotation, outerAngle_, nearDistance_, false, OUTER_COLOR, debug, depthTest);
  127. DrawDebugArc(worldPosition, worldRotation, innerAngle_, farDistance_, true, INNER_COLOR, debug, depthTest);
  128. DrawDebugArc(worldPosition, worldRotation, outerAngle_, farDistance_, true, OUTER_COLOR, debug, depthTest);
  129. }
  130. else
  131. {
  132. debug->AddSphere(Sphere(worldPosition, nearDistance_), INNER_COLOR, depthTest);
  133. debug->AddSphere(Sphere(worldPosition, farDistance_), OUTER_COLOR, depthTest);
  134. }
  135. }
  136. void SoundSource3D::Update(float timeStep)
  137. {
  138. CalculateAttenuation();
  139. SoundSource::Update(timeStep);
  140. }
  141. void SoundSource3D::SetDistanceAttenuation(float nearDistance, float farDistance, float rolloffFactor)
  142. {
  143. nearDistance_ = Max(nearDistance, 0.0f);
  144. farDistance_ = Max(farDistance, 0.0f);
  145. rolloffFactor_ = Max(rolloffFactor, MIN_ROLLOFF);
  146. MarkNetworkUpdate();
  147. }
  148. void SoundSource3D::SetAngleAttenuation(float innerAngle, float outerAngle)
  149. {
  150. innerAngle_ = Clamp(innerAngle, 0.0f, DEFAULT_ANGLE);
  151. outerAngle_ = Clamp(outerAngle, 0.0f, DEFAULT_ANGLE);
  152. MarkNetworkUpdate();
  153. }
  154. void SoundSource3D::SetFarDistance(float distance)
  155. {
  156. farDistance_ = Max(distance, 0.0f);
  157. MarkNetworkUpdate();
  158. }
  159. void SoundSource3D::SetNearDistance(float distance)
  160. {
  161. nearDistance_ = Max(distance, 0.0f);
  162. MarkNetworkUpdate();
  163. }
  164. void SoundSource3D::SetInnerAngle(float angle)
  165. {
  166. innerAngle_ = Clamp(angle, 0.0f, DEFAULT_ANGLE);
  167. MarkNetworkUpdate();
  168. }
  169. void SoundSource3D::SetOuterAngle(float angle)
  170. {
  171. outerAngle_ = Clamp(angle, 0.0f, DEFAULT_ANGLE);
  172. MarkNetworkUpdate();
  173. }
  174. void SoundSource3D::SetRolloffFactor(float factor)
  175. {
  176. rolloffFactor_ = Max(factor, MIN_ROLLOFF);
  177. MarkNetworkUpdate();
  178. }
  179. void SoundSource3D::CalculateAttenuation()
  180. {
  181. if (!audio_)
  182. return;
  183. float interval = farDistance_ - nearDistance_;
  184. if (node_)
  185. {
  186. SoundListener* listener = audio_->GetListener();
  187. // Listener must either be sceneless or in the same scene, else attenuate sound to silence
  188. if (listener && listener->IsEnabledEffective() && (!listener->GetScene() || listener->GetScene() == GetScene()))
  189. {
  190. Node* listenerNode = listener->GetNode();
  191. Vector3 relativePos(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(node_->GetWorldRotation().Inverse() * (listenerNode->GetWorldPosition() -
  204. 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. }