From 98f165f2ae68d3f9a9c0c4e530325790ada62678 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Fri, 26 Jun 2015 16:23:16 -0700 Subject: [PATCH] Avatar collision sounds. collisionsSoundURL can be set in preferences: Currently defaults to https://s3.amazonaws.com/hifi-public/sounds/Collisions-hitsandslaps/airhockey_hit1.wav. Can be empty, which means no sound, rather than restoring default. MyAvatar.collisionSoundURL can read/written in scripts. Preloads when set, so that it won't have to fetch on first collision. Plays at start of collision only, with volume proportional to "velocity change"^2. --- interface/src/Application.cpp | 1 + interface/src/avatar/AvatarManager.cpp | 25 +++++++++++ interface/src/avatar/MyAvatar.cpp | 11 +++++ interface/src/avatar/MyAvatar.h | 6 +++ interface/src/ui/PreferencesDialog.cpp | 4 ++ interface/ui/preferencesDialog.ui | 60 ++++++++++++++++++++++++++ 6 files changed, 107 insertions(+) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index d9c5589631..0224639cbf 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2202,6 +2202,7 @@ void Application::init() { // Make sure any new sounds are loaded as soon as know about them. connect(tree, &EntityTree::newCollisionSoundURL, DependencyManager::get().data(), &SoundCache::getSound); + connect(_myAvatar, &MyAvatar::newCollisionSoundURL, DependencyManager::get().data(), &SoundCache::getSound); } void Application::closeMirrorView() { diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 88f550d68c..d39a8522af 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -257,6 +257,31 @@ void AvatarManager::handleOutgoingChanges(VectorOfMotionStates& motionStates) { void AvatarManager::handleCollisionEvents(CollisionEvents& collisionEvents) { // TODO: expose avatar collision events to JS + for (Collision collision : collisionEvents) { + if (collision.idA.isNull() || collision.idB.isNull()) { + MyAvatar* myAvatar = getMyAvatar(); + const QString& collisionSoundURL = myAvatar->getCollisionSoundURL(); + if (!collisionSoundURL.isEmpty()) { + const float velocityChange = glm::length(collision.velocityChange); + const float MIN_AVATAR_COLLISION_ACCELERATION = 0.01; + const bool isSound = (collision.type == CONTACT_EVENT_TYPE_START) && (velocityChange > MIN_AVATAR_COLLISION_ACCELERATION); + + if (!isSound) { + break; + } + // Your avatar sound is personal to you, so let's say the "mass" part of the kinetic energy is already accounted for. + const float energy = velocityChange * velocityChange; + const float COLLISION_ENERGY_AT_FULL_VOLUME = 0.5f; + const float energyFactorOfFull = fmin(1.0f, energy / COLLISION_ENERGY_AT_FULL_VOLUME); + + // For general entity collisionSoundURL, playSound supports changing the pitch for the sound based on the size of the object, + // but most avatars are roughly the same size, so let's not be so fancy yet. + const float AVATAR_STRETCH_FACTOR = 1.0f; + + AudioInjector::playSound(collisionSoundURL, energyFactorOfFull, AVATAR_STRETCH_FACTOR, myAvatar->getPosition()); + } + } + } } void AvatarManager::updateAvatarPhysicsShape(const QUuid& id) { diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 4b140e0569..b0e31361c2 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -70,6 +70,7 @@ float DEFAULT_SCRIPTED_MOTOR_TIMESCALE = 1.0e6f; const int SCRIPTED_MOTOR_CAMERA_FRAME = 0; const int SCRIPTED_MOTOR_AVATAR_FRAME = 1; const int SCRIPTED_MOTOR_WORLD_FRAME = 2; +const QString& DEFAULT_AVATAR_COLLISION_SOUND_URL = "https://s3.amazonaws.com/hifi-public/sounds/Collisions-hitsandslaps/airhockey_hit1.wav"; const float MyAvatar::ZOOM_MIN = 0.5f; const float MyAvatar::ZOOM_MAX = 10.0f; @@ -90,6 +91,7 @@ MyAvatar::MyAvatar() : _scriptedMotorTimescale(DEFAULT_SCRIPTED_MOTOR_TIMESCALE), _scriptedMotorFrame(SCRIPTED_MOTOR_CAMERA_FRAME), _motionBehaviors(AVATAR_MOTION_DEFAULTS), + _collisionSoundURL(""), _characterController(this), _lookAtTargetAvatar(), _shouldRender(true), @@ -664,6 +666,7 @@ void MyAvatar::saveData() { settings.endArray(); settings.setValue("displayName", _displayName); + settings.setValue("collisionSoundURL", _collisionSoundURL); settings.endGroup(); } @@ -789,6 +792,7 @@ void MyAvatar::loadData() { settings.endArray(); setDisplayName(settings.value("displayName").toString()); + setCollisionSoundURL(settings.value("collisionSoundURL", DEFAULT_AVATAR_COLLISION_SOUND_URL).toString()); settings.endGroup(); } @@ -1183,6 +1187,13 @@ void MyAvatar::clearScriptableSettings() { _scriptedMotorTimescale = DEFAULT_SCRIPTED_MOTOR_TIMESCALE; } +void MyAvatar::setCollisionSoundURL(const QString& url) { + if (!url.isEmpty() && (url != _collisionSoundURL)) { + emit newCollisionSoundURL(QUrl(url)); + } + _collisionSoundURL = url; +} + void MyAvatar::attach(const QString& modelURL, const QString& jointName, const glm::vec3& translation, const glm::quat& rotation, float scale, bool allowDuplicates, bool useSaved) { if (QThread::currentThread() != thread()) { diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 2fea09ee27..14633e529b 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -25,6 +25,7 @@ class MyAvatar : public Avatar { Q_PROPERTY(glm::vec3 motorVelocity READ getScriptedMotorVelocity WRITE setScriptedMotorVelocity) Q_PROPERTY(float motorTimescale READ getScriptedMotorTimescale WRITE setScriptedMotorTimescale) Q_PROPERTY(QString motorReferenceFrame READ getScriptedMotorFrame WRITE setScriptedMotorFrame) + Q_PROPERTY(QString collisionSoundURL READ getCollisionSoundURL WRITE setCollisionSoundURL) //TODO: make gravity feature work Q_PROPERTY(glm::vec3 gravity READ getGravity WRITE setGravity) public: @@ -150,6 +151,9 @@ public: void setScriptedMotorTimescale(float timescale); void setScriptedMotorFrame(QString frame); + const QString& getCollisionSoundURL() {return _collisionSoundURL; } + void setCollisionSoundURL(const QString& url); + void clearScriptableSettings(); virtual void attach(const QString& modelURL, const QString& jointName = QString(), @@ -204,6 +208,7 @@ public slots: signals: void transformChanged(); + void newCollisionSoundURL(const QUrl& url); private: @@ -233,6 +238,7 @@ private: float _scriptedMotorTimescale; // timescale for avatar to achieve its target velocity int _scriptedMotorFrame; quint32 _motionBehaviors; + QString _collisionSoundURL; DynamicCharacterController _characterController; diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index eca250a428..b230fdfcd7 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -127,6 +127,8 @@ void PreferencesDialog::loadPreferences() { _displayNameString = myAvatar->getDisplayName(); ui.displayNameEdit->setText(_displayNameString); + ui.collisionSoundURLEdit->setText(myAvatar->getCollisionSoundURL()); + ui.sendDataCheckBox->setChecked(!menuInstance->isOptionChecked(MenuOption::DisableActivityLogger)); ui.snapshotLocationEdit->setText(Snapshot::snapshotsLocation.get()); @@ -204,6 +206,8 @@ void PreferencesDialog::savePreferences() { myAvatar->sendIdentityPacket(); } + myAvatar->setCollisionSoundURL(ui.collisionSoundURLEdit->text()); + if (!Menu::getInstance()->isOptionChecked(MenuOption::DisableActivityLogger) != ui.sendDataCheckBox->isChecked()) { Menu::getInstance()->triggerOption(MenuOption::DisableActivityLogger); diff --git a/interface/ui/preferencesDialog.ui b/interface/ui/preferencesDialog.ui index e74b89075e..78f9f5bf09 100644 --- a/interface/ui/preferencesDialog.ui +++ b/interface/ui/preferencesDialog.ui @@ -189,6 +189,66 @@ + + + + + 0 + + + 7 + + + 7 + + + + + + Arial + + + + <html><head/><body><p>Avatar collision sound URL <span style=" color:#909090;">(optional)</span></p></body></html> + + + Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft + + + collisionSoundURLEdit + + + + + + + 4 + + + 5 + + + 4 + + + + + + Arial + + + + Qt::LeftToRight + + + Enter the URL of a sound to play when you bump into something + + + + + + +