diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 4a829b3191..5b0d5c65ce 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -3013,8 +3013,19 @@ void Application::init() { _entityClipboardRenderer.setTree(_entityClipboard); // Make sure any new sounds are loaded as soon as know about them. - connect(tree.get(), &EntityTree::newCollisionSoundURL, DependencyManager::get().data(), &SoundCache::getSound); - connect(getMyAvatar(), &MyAvatar::newCollisionSoundURL, DependencyManager::get().data(), &SoundCache::getSound); + connect(tree.get(), &EntityTree::newCollisionSoundURL, this, [this](QUrl newURL, EntityItemID id) { + EntityTreePointer tree = getEntities()->getTree(); + if (auto entity = tree->findEntityByEntityItemID(id)) { + auto sound = DependencyManager::get()->getSound(newURL); + entity->setCollisionSound(sound); + } + }, Qt::QueuedConnection); + connect(getMyAvatar(), &MyAvatar::newCollisionSoundURL, this, [this](QUrl newURL) { + if (auto avatar = getMyAvatar()) { + auto sound = DependencyManager::get()->getSound(newURL); + avatar->setCollisionSound(sound); + } + }, Qt::QueuedConnection); } void Application::updateLOD() const { diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 402245d0c3..e098aac677 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -309,9 +309,11 @@ void AvatarManager::handleCollisionEvents(const CollisionEvents& collisionEvents // my avatar. (Other user machines will make a similar analysis and inject sound for their collisions.) 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); + auto collisionSound = myAvatar->getCollisionSound(); + if (collisionSound) { + const auto characterController = myAvatar->getCharacterController(); + const float avatarVelocityChange = (characterController ? glm::length(characterController->getVelocityChange()) : 0.0f); + const float velocityChange = glm::length(collision.velocityChange) + avatarVelocityChange; const float MIN_AVATAR_COLLISION_ACCELERATION = 0.01f; const bool isSound = (collision.type == CONTACT_EVENT_TYPE_START) && (velocityChange > MIN_AVATAR_COLLISION_ACCELERATION); @@ -327,7 +329,7 @@ void AvatarManager::handleCollisionEvents(const CollisionEvents& collisionEvents // 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()); + AudioInjector::playSound(collisionSound, energyFactorOfFull, AVATAR_STRETCH_FACTOR, myAvatar->getPosition()); myAvatar->collisionWithEntity(collision); return; } diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index bad60643ec..f6caedb014 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -23,7 +23,6 @@ #include #include #include -#include #include #include #include @@ -32,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -97,7 +97,6 @@ MyAvatar::MyAvatar(RigPointer rig) : _scriptedMotorTimescale(DEFAULT_SCRIPTED_MOTOR_TIMESCALE), _scriptedMotorFrame(SCRIPTED_MOTOR_CAMERA_FRAME), _motionBehaviors(AVATAR_MOTION_DEFAULTS), - _collisionSoundURL(""), _characterController(this), _lookAtTargetAvatar(), _shouldRender(true), @@ -1232,12 +1231,20 @@ void MyAvatar::clearScriptableSettings() { } void MyAvatar::setCollisionSoundURL(const QString& url) { - _collisionSoundURL = url; - if (!url.isEmpty() && (url != _collisionSoundURL)) { - emit newCollisionSoundURL(QUrl(url)); + if (url != _collisionSoundURL) { + _collisionSoundURL = url; + + emit newCollisionSoundURL(QUrl(_collisionSoundURL)); } } +SharedSoundPointer MyAvatar::getCollisionSound() { + if (!_collisionSound) { + _collisionSound = DependencyManager::get()->getSound(_collisionSoundURL); + } + return _collisionSound; +} + void MyAvatar::attach(const QString& modelURL, const QString& jointName, const glm::vec3& translation, const glm::quat& rotation, float scale, bool isSoft, diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index fee1a9add3..d25318c9fb 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -16,6 +16,7 @@ #include #include +#include #include @@ -222,6 +223,9 @@ public: const QString& getCollisionSoundURL() { return _collisionSoundURL; } void setCollisionSoundURL(const QString& url); + SharedSoundPointer getCollisionSound(); + void setCollisionSound(SharedSoundPointer sound) { _collisionSound = sound; } + void clearScriptableSettings(); float getBoomLength() const { return _boomLength; } @@ -362,6 +366,8 @@ private: quint32 _motionBehaviors; QString _collisionSoundURL; + SharedSoundPointer _collisionSound; + MyCharacterController _characterController; AvatarWeakPointer _lookAtTargetAvatar; diff --git a/libraries/audio/src/AudioInjector.cpp b/libraries/audio/src/AudioInjector.cpp index a6515f5f65..878a4c627c 100644 --- a/libraries/audio/src/AudioInjector.cpp +++ b/libraries/audio/src/AudioInjector.cpp @@ -365,17 +365,9 @@ void AudioInjector::stopAndDeleteLater() { QMetaObject::invokeMethod(this, "deleteLater", Qt::QueuedConnection); } -AudioInjector* AudioInjector::playSound(const QString& soundUrl, const float volume, const float stretchFactor, const glm::vec3 position) { - if (soundUrl.isEmpty()) { - return NULL; - } - auto soundCache = DependencyManager::get(); - if (soundCache.isNull()) { - return NULL; - } - SharedSoundPointer sound = soundCache->getSound(QUrl(soundUrl)); - if (sound.isNull() || !sound->isReady()) { - return NULL; +AudioInjector* AudioInjector::playSound(SharedSoundPointer sound, const float volume, const float stretchFactor, const glm::vec3 position) { + if (!sound || !sound->isReady()) { + return nullptr; } AudioInjectorOptions options; @@ -385,7 +377,7 @@ AudioInjector* AudioInjector::playSound(const QString& soundUrl, const float vol QByteArray samples = sound->getByteArray(); if (stretchFactor == 1.0f) { - return playSoundAndDelete(samples, options, NULL); + return playSoundAndDelete(samples, options, nullptr); } const int standardRate = AudioConstants::SAMPLE_RATE; @@ -403,7 +395,7 @@ AudioInjector* AudioInjector::playSound(const QString& soundUrl, const float vol nInputFrames); Q_UNUSED(nOutputFrames); - return playSoundAndDelete(resampled, options, NULL); + return playSoundAndDelete(resampled, options, nullptr); } AudioInjector* AudioInjector::playSoundAndDelete(const QByteArray& buffer, const AudioInjectorOptions options, AbstractAudioInterface* localInterface) { diff --git a/libraries/audio/src/AudioInjector.h b/libraries/audio/src/AudioInjector.h index 2dad2856b9..c90256429d 100644 --- a/libraries/audio/src/AudioInjector.h +++ b/libraries/audio/src/AudioInjector.h @@ -60,7 +60,7 @@ public: static AudioInjector* playSoundAndDelete(const QByteArray& buffer, const AudioInjectorOptions options, AbstractAudioInterface* localInterface); static AudioInjector* playSound(const QByteArray& buffer, const AudioInjectorOptions options, AbstractAudioInterface* localInterface); - static AudioInjector* playSound(const QString& soundUrl, const float volume, const float stretchFactor, const glm::vec3 position); + static AudioInjector* playSound(SharedSoundPointer sound, const float volume, const float stretchFactor, const glm::vec3 position); public slots: void restart(); diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 013385a169..814faa8874 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -820,14 +820,14 @@ void EntityTreeRenderer::playEntityCollisionSound(const QUuid& myNodeID, EntityT return; } - QString collisionSoundURL; + SharedSoundPointer collisionSound; float mass = 1.0; // value doesn't get used, but set it so compiler is quiet AACube minAACube; bool success = false; _tree->withReadLock([&] { EntityItemPointer entity = entityTree->findEntityByEntityItemID(id); if (entity) { - collisionSoundURL = entity->getCollisionSoundURL(); + collisionSound = entity->getCollisionSound(); mass = entity->computeMass(); minAACube = entity->getMinimumAACube(success); } @@ -835,9 +835,10 @@ void EntityTreeRenderer::playEntityCollisionSound(const QUuid& myNodeID, EntityT if (!success) { return; } - if (collisionSoundURL.isEmpty()) { + if (!collisionSound) { return; } + const float COLLISION_PENETRATION_TO_VELOCITY = 50; // as a subsitute for RELATIVE entity->getVelocity() // The collision.penetration is a pretty good indicator of changed velocity AFTER the initial contact, // but that first contact depends on exactly where we hit in the physics step. @@ -859,11 +860,10 @@ void EntityTreeRenderer::playEntityCollisionSound(const QUuid& myNodeID, EntityT const float COLLISION_SOUND_COMPRESSION_RANGE = 1.0f; // This section could be removed when the value is 1, but let's see how it goes. const float volume = (energyFactorOfFull * COLLISION_SOUND_COMPRESSION_RANGE) + (1.0f - COLLISION_SOUND_COMPRESSION_RANGE); - // Shift the pitch down by ln(1 + (size / COLLISION_SIZE_FOR_STANDARD_PITCH)) / ln(2) const float COLLISION_SIZE_FOR_STANDARD_PITCH = 0.2f; const float stretchFactor = log(1.0f + (minAACube.getLargestDimension() / COLLISION_SIZE_FOR_STANDARD_PITCH)) / log(2); - AudioInjector::playSound(collisionSoundURL, volume, stretchFactor, position); + AudioInjector::playSound(collisionSound, volume, stretchFactor, position); } void EntityTreeRenderer::entityCollisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index bdedbbce77..a36c69b880 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -852,6 +852,23 @@ void EntityItem::setHref(QString value) { _href = value; } +void EntityItem::setCollisionSoundURL(const QString& value) { + if (_collisionSoundURL != value) { + _collisionSoundURL = value; + + if (auto myTree = getTree()) { + myTree->notifyNewCollisionSoundURL(_collisionSoundURL, getEntityItemID()); + } + } +} + +SharedSoundPointer EntityItem::getCollisionSound() { + if (!_collisionSound) { + _collisionSound = DependencyManager::get()->getSound(_collisionSoundURL); + } + return _collisionSound; +} + void EntityItem::simulate(const quint64& now) { if (_lastSimulated == 0) { _lastSimulated = now; diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index ecb9800e70..210dbba284 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -24,6 +24,7 @@ #include #include #include +#include #include #include "EntityItemID.h" @@ -250,7 +251,10 @@ public: void setScriptTimestamp(const quint64 value) { _scriptTimestamp = value; } const QString& getCollisionSoundURL() const { return _collisionSoundURL; } - void setCollisionSoundURL(const QString& value) { _collisionSoundURL = value; } + void setCollisionSoundURL(const QString& value); + + SharedSoundPointer getCollisionSound(); + void setCollisionSound(SharedSoundPointer sound) { _collisionSound = sound; } const glm::vec3& getRegistrationPoint() const { return _registrationPoint; } /// registration point as ratio of entity @@ -478,6 +482,7 @@ protected: quint64 _loadedScriptTimestamp{ ENTITY_ITEM_DEFAULT_SCRIPT_TIMESTAMP + 1 }; QString _collisionSoundURL; + SharedSoundPointer _collisionSound; glm::vec3 _registrationPoint; float _angularDamping; bool _visible; diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index b4f0c484d5..7b6756f3c1 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -95,7 +95,6 @@ void EntityTree::postAddEntity(EntityItemPointer entity) { } _isDirty = true; - maybeNotifyNewCollisionSoundURL("", entity->getCollisionSoundURL()); emit addingEntity(entity->getEntityItemID()); // find and hook up any entities with this entity as a (previously) missing parent @@ -223,7 +222,6 @@ bool EntityTree::updateEntityWithElement(EntityItemPointer entity, const EntityI QString entityScriptBefore = entity->getScript(); quint64 entityScriptTimestampBefore = entity->getScriptTimestamp(); - QString collisionSoundURLBefore = entity->getCollisionSoundURL(); uint32_t preFlags = entity->getDirtyFlags(); AACube newQueryAACube; @@ -295,7 +293,6 @@ bool EntityTree::updateEntityWithElement(EntityItemPointer entity, const EntityI if (entityScriptBefore != entityScriptAfter || reload) { emitEntityScriptChanging(entity->getEntityItemID(), reload); // the entity script has changed } - maybeNotifyNewCollisionSoundURL(collisionSoundURLBefore, entity->getCollisionSoundURL()); } // TODO: this final containingElement check should eventually be removed (or wrapped in an #ifdef DEBUG). @@ -362,10 +359,8 @@ void EntityTree::emitEntityScriptChanging(const EntityItemID& entityItemID, cons emit entityScriptChanging(entityItemID, reload); } -void EntityTree::maybeNotifyNewCollisionSoundURL(const QString& previousCollisionSoundURL, const QString& nextCollisionSoundURL) { - if (!nextCollisionSoundURL.isEmpty() && (nextCollisionSoundURL != previousCollisionSoundURL)) { - emit newCollisionSoundURL(QUrl(nextCollisionSoundURL)); - } +void EntityTree::notifyNewCollisionSoundURL(const QString& newURL, const EntityItemID& entityID) { + emit newCollisionSoundURL(QUrl(newURL), entityID); } void EntityTree::setSimulation(EntitySimulation* simulation) { diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index 54e516d01d..83bc31aa92 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -249,6 +249,8 @@ public: void forgetAvatarID(QUuid avatarID) { _avatarIDs -= avatarID; } void deleteDescendantsOfAvatar(QUuid avatarID); + void notifyNewCollisionSoundURL(const QString& newCollisionSoundURL, const EntityItemID& entityID); + public slots: void callLoader(EntityItemID entityID); @@ -256,7 +258,7 @@ signals: void deletingEntity(const EntityItemID& entityID); void addingEntity(const EntityItemID& entityID); void entityScriptChanging(const EntityItemID& entityItemID, const bool reload); - void newCollisionSoundURL(const QUrl& url); + void newCollisionSoundURL(const QUrl& url, const EntityItemID& entityID); void clearingEntities(); protected: @@ -301,7 +303,6 @@ protected: bool _wantEditLogging = false; bool _wantTerseEditLogging = false; - void maybeNotifyNewCollisionSoundURL(const QString& oldCollisionSoundURL, const QString& newCollisionSoundURL); // some performance tracking properties - only used in server trees diff --git a/libraries/entities/src/EntityTreeElement.cpp b/libraries/entities/src/EntityTreeElement.cpp index 8270dc7e69..a919dc4251 100644 --- a/libraries/entities/src/EntityTreeElement.cpp +++ b/libraries/entities/src/EntityTreeElement.cpp @@ -34,8 +34,6 @@ OctreeElementPointer EntityTreeElement::createNewElement(unsigned char* octalCod return newChild; } - - void EntityTreeElement::init(unsigned char* octalCode) { OctreeElement::init(octalCode); _octreeMemoryUsage += sizeof(EntityTreeElement); diff --git a/libraries/physics/src/CharacterController.cpp b/libraries/physics/src/CharacterController.cpp index f685aee748..6ebe05ba86 100644 --- a/libraries/physics/src/CharacterController.cpp +++ b/libraries/physics/src/CharacterController.cpp @@ -430,6 +430,14 @@ glm::vec3 CharacterController::getLinearVelocity() const { return velocity; } +glm::vec3 CharacterController::getVelocityChange() const { + glm::vec3 velocity(0.0f); + if (_rigidBody) { + velocity = bulletToGLM(_rigidBody->getLinearVelocity()); + } + return velocity; +} + void CharacterController::preSimulation() { if (_enabled && _dynamicsWorld) { quint64 now = usecTimestampNow(); @@ -437,6 +445,7 @@ void CharacterController::preSimulation() { // slam body to where it is supposed to be _rigidBody->setWorldTransform(_characterBodyTransform); btVector3 velocity = _rigidBody->getLinearVelocity(); + _preSimulationVelocity = velocity; btVector3 actualVertVelocity = velocity.dot(_currentUp) * _currentUp; btVector3 actualHorizVelocity = velocity - actualVertVelocity; @@ -531,6 +540,9 @@ void CharacterController::preSimulation() { void CharacterController::postSimulation() { // postSimulation() exists for symmetry and just in case we need to do something here later + + btVector3 velocity = _rigidBody->getLinearVelocity(); + _velocityChange = velocity - _preSimulationVelocity; } diff --git a/libraries/physics/src/CharacterController.h b/libraries/physics/src/CharacterController.h index d810e904a7..a54ab97a14 100644 --- a/libraries/physics/src/CharacterController.h +++ b/libraries/physics/src/CharacterController.h @@ -77,6 +77,7 @@ public: glm::vec3 getFollowVelocity() const; glm::vec3 getLinearVelocity() const; + glm::vec3 getVelocityChange() const; float getCapsuleRadius() const { return _radius; } float getCapsuleHalfHeight() const { return _halfHeight; } @@ -112,6 +113,8 @@ protected: btVector3 _currentUp; btVector3 _targetVelocity; btVector3 _parentVelocity; + btVector3 _preSimulationVelocity; + btVector3 _velocityChange; btTransform _followDesiredBodyTransform; btScalar _followTimeRemaining; btTransform _characterBodyTransform; diff --git a/tests/entities/CMakeLists.txt b/tests/entities/CMakeLists.txt index b12771b368..a6ce4cf956 100644 --- a/tests/entities/CMakeLists.txt +++ b/tests/entities/CMakeLists.txt @@ -7,6 +7,6 @@ setup_hifi_project(Network Script) set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/") # link in the shared libraries -link_hifi_libraries(entities avatars shared octree gpu model fbx networking animation) +link_hifi_libraries(entities avatars shared octree gpu model fbx networking animation audio) package_libraries_for_deployment()