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.
This commit is contained in:
Howard Stearns 2015-06-26 16:23:16 -07:00
parent 8dcd91fdab
commit 98f165f2ae
6 changed files with 107 additions and 0 deletions

View file

@ -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<SoundCache>().data(), &SoundCache::getSound);
connect(_myAvatar, &MyAvatar::newCollisionSoundURL, DependencyManager::get<SoundCache>().data(), &SoundCache::getSound);
}
void Application::closeMirrorView() {

View file

@ -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) {

View file

@ -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()) {

View file

@ -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;

View file

@ -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);

View file

@ -189,6 +189,66 @@
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_5csu">
<property name="spacing">
<number>0</number>
</property>
<property name="topMargin">
<number>7</number>
</property>
<property name="bottomMargin">
<number>7</number>
</property>
<item>
<widget class="QLabel" name="avatarCollisionSoundURLLabel">
<property name="font">
<font>
<family>Arial</family>
</font>
</property>
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Avatar collision sound URL &lt;span style=&quot; color:#909090;&quot;&gt;(optional)&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="alignment">
<set>Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft</set>
</property>
<property name="buddy">
<cstring>collisionSoundURLEdit</cstring>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_17csu">
<property name="topMargin">
<number>4</number>
</property>
<property name="rightMargin">
<number>5</number>
</property>
<property name="bottomMargin">
<number>4</number>
</property>
<item>
<widget class="QLineEdit" name="collisionSoundURLEdit">
<property name="font">
<font>
<family>Arial</family>
</font>
</property>
<property name="layoutDirection">
<enum>Qt::LeftToRight</enum>
</property>
<property name="placeholderText">
<string>Enter the URL of a sound to play when you bump into something</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_appearance">