PositionalChannel.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
  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::getResourceRefs(std::vector<Resource*>& dest)
  210. {
  211. Sound* sound = getSound();
  212. if (sound)
  213. dest.push_back(sound);
  214. }
  215. void PositionalChannel::update(float timeStep)
  216. {
  217. if (!mAudio)
  218. return;
  219. Channel::update(timeStep);
  220. calculateAttenuation();
  221. // Check automatic remove. Never do autoremove for client proxy components, because this could lead in exceptions
  222. // as the server could still be sending updates to a removed component
  223. if ((mAutoRemove) && (!isProxy()) && (mEntity))
  224. {
  225. if (!isPlaying())
  226. {
  227. mAutoRemoveTimer += timeStep;
  228. if (mAutoRemoveTimer > CHANNEL_AUTOREMOVE_DELAY)
  229. {
  230. mEntity->removeComponent(this);
  231. return;
  232. }
  233. }
  234. else
  235. mAutoRemoveTimer = 0.0f;
  236. }
  237. }
  238. void PositionalChannel::setDistanceAttenuation(float nearDistance, float farDistance, float rolloffFactor)
  239. {
  240. mNearDistance = max(nearDistance, 0.0f);
  241. mFarDistance = max(farDistance, 0.0f);
  242. mRolloffFactor = max(rolloffFactor, CHANNEL_MIN_ROLLOFF);
  243. }
  244. void PositionalChannel::setFarDistance(float distance)
  245. {
  246. mFarDistance = max(distance, 0.0f);
  247. }
  248. void PositionalChannel::setNearDistance(float distance)
  249. {
  250. mNearDistance = max(distance, 0.0f);
  251. }
  252. void PositionalChannel::setRolloffFactor(float factor)
  253. {
  254. mRolloffFactor = max(factor, CHANNEL_MIN_ROLLOFF);
  255. }
  256. void PositionalChannel::setAutoRemove(bool enable)
  257. {
  258. mAutoRemove = enable;
  259. }
  260. void PositionalChannel::calculateAttenuation()
  261. {
  262. if (!mAudio)
  263. return;
  264. float interval = mFarDistance - mNearDistance;
  265. if (interval > 0.0f)
  266. {
  267. Vector3 relativePos(mAudio->getListenerRotation().getInverse() * (getWorldPosition() - mAudio->getListenerPosition()));
  268. float distance = clamp(relativePos.getLength() - mNearDistance, 0.0f, interval);
  269. mAttenuation = powf(1.0f - distance / interval, mRolloffFactor);
  270. mPanning = relativePos.getNormalized().mX;
  271. }
  272. }