PositionalChannel.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308
  1. //
  2. // Urho3D Engine
  3. // Copyright (c) 2008-2011 Lasse Öörni
  4. //
  5. // Permission is hereby granted, free of charge, to any person obtaining a copy
  6. // of this software and associated documentation files (the "Software"), to deal
  7. // in the Software without restriction, including without limitation the rights
  8. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  9. // copies of the Software, and to permit persons to whom the Software is
  10. // furnished to do so, subject to the following conditions:
  11. //
  12. // The above copyright notice and this permission notice shall be included in
  13. // all copies or substantial portions of the Software.
  14. //
  15. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  16. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  17. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  18. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  19. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  20. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  21. // THE SOFTWARE.
  22. //
  23. #include "Precompiled.h"
  24. #include "Audio.h"
  25. #include "Entity.h"
  26. #include "PositionalChannel.h"
  27. #include "ReplicationUtils.h"
  28. #include "ResourceCache.h"
  29. #include "Sound.h"
  30. #include "StringUtils.h"
  31. #include "XMLElement.h"
  32. #include "DebugNew.h"
  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 std::string typeNames[] =
  37. {
  38. "master",
  39. "effect",
  40. "music",
  41. "voice"
  42. };
  43. PositionalChannel::PositionalChannel(Audio* audio, const std::string& name) :
  44. Channel(audio),
  45. Node(NODE_POSITIONALCHANNEL, name),
  46. mNearDistance(DEFAULT_NEARDISTANCE),
  47. mFarDistance(DEFAULT_FARDISTANCE),
  48. mRolloffFactor(DEFAULT_ROLLOFF),
  49. mAutoRemoveTimer(0.0f),
  50. mAutoRemove(false)
  51. {
  52. // Start from zero volume until attenuation properly calculated
  53. mAttenuation = 0.0f;
  54. }
  55. void PositionalChannel::save(Serializer& dest)
  56. {
  57. // Write node properties
  58. Node::save(dest);
  59. // Write channel properties
  60. dest.writeUByte((unsigned char)mChannelType);
  61. dest.writeFloat(mFrequency);
  62. dest.writeFloat(mGain);
  63. dest.writeFloat(mAttenuation);
  64. dest.writeFloat(mPanning);
  65. dest.writeBool(mAutoRemove);
  66. // Write currently playing sound and its offset (if any)
  67. Sound* sound = getSound();
  68. dest.writeStringHash(getResourceHash(sound));
  69. if (sound)
  70. dest.writeUInt((unsigned)(getPlayPosition() - sound->getStart()));
  71. // Write positional audio properties
  72. dest.writeFloat(mNearDistance);
  73. dest.writeFloat(mFarDistance);
  74. dest.writeFloat(mRolloffFactor);
  75. }
  76. void PositionalChannel::load(Deserializer& source, ResourceCache* cache)
  77. {
  78. // Read node properties
  79. Node::load(source, cache);
  80. // Read channel properties
  81. mChannelType = (ChannelType)source.readUByte();
  82. mFrequency = source.readFloat();
  83. mGain = source.readFloat();
  84. mAttenuation = source.readFloat();
  85. mPanning = source.readFloat();
  86. mAutoRemove = source.readBool();
  87. // Read currently playing sound (if any)
  88. StringHash soundHash = source.readStringHash();
  89. if (soundHash)
  90. {
  91. Sound* sound = cache->getResource<Sound>(soundHash);
  92. unsigned offset = source.readUInt();
  93. play(sound);
  94. setPlayPosition(sound->getStart() + offset);
  95. }
  96. // Read positional audio properties
  97. mNearDistance = source.readFloat();
  98. mFarDistance = source.readFloat();
  99. mRolloffFactor = source.readFloat();
  100. }
  101. void PositionalChannel::saveXML(XMLElement& dest)
  102. {
  103. // Write node properties
  104. Node::saveXML(dest);
  105. // Write channel properties
  106. XMLElement channelElem = dest.createChildElement("channel");
  107. channelElem.setString("type", typeNames[mChannelType]);
  108. channelElem.setFloat("frequency", mFrequency);
  109. channelElem.setFloat("gain", mGain);
  110. channelElem.setFloat("attenuation", mAttenuation);
  111. channelElem.setFloat("panning", mPanning);
  112. channelElem.setBool("autoremove", mAutoRemove);
  113. // Write currently playing sound and its offset (if any)
  114. Sound* sound = getSound();
  115. if (sound)
  116. {
  117. XMLElement soundElem = dest.createChildElement("sound");
  118. soundElem.setString("name", getResourceName(sound));
  119. soundElem.setInt("offset", (unsigned)(getPlayPosition() - sound->getStart()));
  120. }
  121. // Write positional audio properties
  122. XMLElement rolloffElem = dest.createChildElement("rolloff");
  123. rolloffElem.setFloat("neardist", mNearDistance);
  124. rolloffElem.setFloat("fardist", mFarDistance);
  125. rolloffElem.setFloat("factor", mRolloffFactor);
  126. }
  127. void PositionalChannel::loadXML(const XMLElement& source, ResourceCache* cache)
  128. {
  129. // Read node properties
  130. Node::loadXML(source, cache);
  131. // Read channel properties
  132. XMLElement channelElem = source.getChildElement("channel");
  133. mChannelType = (ChannelType)getIndexFromStringList(channelElem.getStringLower("type"), typeNames, 4, 1);
  134. mFrequency = channelElem.getFloat("frequency");
  135. mGain = channelElem.getFloat("gain");
  136. mAttenuation = channelElem.getFloat("attenuation");
  137. mPanning = channelElem.getFloat("panning");
  138. mAutoRemove = channelElem.getBool("autoremove");
  139. // Read currently playing sound (if any)
  140. XMLElement soundElem = source.getChildElement("sound");
  141. if (soundElem)
  142. {
  143. Sound* sound = cache->getResource<Sound>(soundElem.getString("name"));
  144. unsigned offset = soundElem.getInt("offset");
  145. calculateAttenuation();
  146. play(sound);
  147. setPlayPosition(sound->getStart() + offset);
  148. }
  149. // Read positional audio properties
  150. XMLElement rolloffElem = source.getChildElement("rolloff");
  151. mNearDistance = rolloffElem.getFloat("neardist");
  152. mFarDistance = rolloffElem.getFloat("fardist");
  153. mRolloffFactor = rolloffElem.getFloat("factor");
  154. }
  155. bool PositionalChannel::writeNetUpdate(Serializer& dest, Serializer& destRevision, Deserializer& baseRevision, const NetUpdateInfo& info)
  156. {
  157. // Write Node properties and see if there were any changes
  158. bool prevBits = Node::writeNetUpdate(dest, destRevision, baseRevision, info);
  159. Sound* sound = getSound();
  160. // Build bitmask of changed properties
  161. unsigned char bits = 0;
  162. checkUByte((unsigned char)mChannelType, (unsigned char)CHANNEL_EFFECT, baseRevision, bits, 1);
  163. checkFloat(mFrequency, baseRevision, bits, 2);
  164. checkUByte((unsigned char)(mGain * 255.0f), 255, baseRevision, bits, 4);
  165. // Attenuation and panning will be re-calculated according to relative position
  166. checkStringHash(getResourceHash(sound), StringHash(), baseRevision, bits, 8);
  167. checkVLE((unsigned)mNearDistance, (unsigned)DEFAULT_NEARDISTANCE, baseRevision, bits, 16);
  168. checkVLE((unsigned)mFarDistance, (unsigned)DEFAULT_FARDISTANCE, baseRevision, bits, 16);
  169. checkUByte((unsigned char)(mRolloffFactor * 16.0f), (unsigned char)(DEFAULT_ROLLOFF * 16.0f), baseRevision, bits, 32);
  170. // Update replication state fully, and network stream by delta
  171. dest.writeUByte(bits);
  172. writeUByteDelta((unsigned char)mChannelType, dest, destRevision, bits & 1);
  173. writeFloatDelta(mFrequency, dest, destRevision, bits & 2);
  174. writeUByteDelta((unsigned char)(mGain * 255.0f), dest, destRevision, bits & 4);
  175. writeStringHashDelta(getResourceHash(sound), dest, destRevision, bits & 8);
  176. writeVLEDelta((unsigned)mNearDistance, dest, destRevision, bits & 16);
  177. writeVLEDelta((unsigned)mFarDistance, dest, destRevision, bits & 16);
  178. writeUByteDelta((unsigned char)(mRolloffFactor * 16.0f), dest, destRevision, bits & 32);
  179. return prevBits || (bits != 0);
  180. }
  181. void PositionalChannel::readNetUpdate(Deserializer& source, ResourceCache* cache, const NetUpdateInfo& info)
  182. {
  183. Node::readNetUpdate(source, cache, info);
  184. StringHash soundHash;
  185. unsigned char bits = source.readUByte();
  186. if (bits & 1)
  187. mChannelType = (ChannelType)source.readUByte();
  188. if (bits & 2)
  189. mFrequency = source.readFloat();
  190. if (bits & 4)
  191. mGain = source.readUByte() / 255.0f;
  192. if (bits & 8)
  193. soundHash = source.readStringHash();
  194. if (bits & 16)
  195. {
  196. mNearDistance = (float)source.readVLE();
  197. mFarDistance = (float)source.readVLE();
  198. }
  199. if (bits & 32)
  200. mRolloffFactor = source.readUByte() / 16.0f;;
  201. // Trigger sound only if not yet playing (we might get multiple copies of the same update)
  202. if ((soundHash) && (!isPlaying()))
  203. {
  204. Sound* sound = cache->getResource<Sound>(soundHash);
  205. calculateAttenuation();
  206. play(sound);
  207. }
  208. }
  209. void PositionalChannel::update(float timeStep)
  210. {
  211. if (!mAudio)
  212. return;
  213. Channel::update(timeStep);
  214. calculateAttenuation();
  215. // Check automatic remove. Never do autoremove for client proxy components, because this could lead in exceptions
  216. // as the server could still be sending updates to a removed component
  217. if ((mAutoRemove) && (!isProxy()) && (mEntity))
  218. {
  219. if (!isPlaying())
  220. {
  221. mAutoRemoveTimer += timeStep;
  222. if (mAutoRemoveTimer > CHANNEL_AUTOREMOVE_DELAY)
  223. {
  224. mEntity->removeComponent(this);
  225. return;
  226. }
  227. }
  228. else
  229. mAutoRemoveTimer = 0.0f;
  230. }
  231. }
  232. void PositionalChannel::setDistanceAttenuation(float nearDistance, float farDistance, float rolloffFactor)
  233. {
  234. mNearDistance = max(nearDistance, 0.0f);
  235. mFarDistance = max(farDistance, 0.0f);
  236. mRolloffFactor = max(rolloffFactor, CHANNEL_MIN_ROLLOFF);
  237. }
  238. void PositionalChannel::setFarDistance(float distance)
  239. {
  240. mFarDistance = max(distance, 0.0f);
  241. }
  242. void PositionalChannel::setNearDistance(float distance)
  243. {
  244. mNearDistance = max(distance, 0.0f);
  245. }
  246. void PositionalChannel::setRolloffFactor(float factor)
  247. {
  248. mRolloffFactor = max(factor, CHANNEL_MIN_ROLLOFF);
  249. }
  250. void PositionalChannel::setAutoRemove(bool enable)
  251. {
  252. mAutoRemove = enable;
  253. }
  254. void PositionalChannel::calculateAttenuation()
  255. {
  256. if (!mAudio)
  257. return;
  258. float interval = mFarDistance - mNearDistance;
  259. if (interval > 0.0f)
  260. {
  261. Vector3 relativePos(mAudio->getListenerRotation().getInverse() * (getWorldPosition() - mAudio->getListenerPosition()));
  262. float distance = clamp(relativePos.getLength() - mNearDistance, 0.0f, interval);
  263. mAttenuation = powf(1.0f - distance / interval, mRolloffFactor);
  264. mPanning = relativePos.getNormalized().mX;
  265. }
  266. }