diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index f67e233118..8bc845ff24 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1754,6 +1754,7 @@ void Application::init() { _myCamera.setMode(CAMERA_MODE_FIRST_PERSON); _myCamera.setModeShiftRate(1.0f); _myAvatar.setDisplayingLookatVectors(false); + _avatarManager.setMyAvatar(&_myAvatar); _mirrorCamera.setMode(CAMERA_MODE_MIRROR); _mirrorCamera.setAspectRatio((float)MIRROR_VIEW_WIDTH / (float)MIRROR_VIEW_HEIGHT); @@ -1800,7 +1801,7 @@ void Application::init() { _metavoxels.init(); - _particleCollisionSystem.init(&_particleEditSender, _particles.getTree(), _voxels.getTree(), &_audio, &_myAvatar); + _particleCollisionSystem.init(&_particleEditSender, _particles.getTree(), _voxels.getTree(), &_audio, &_avatarManager); _palette.init(_glWidget->width(), _glWidget->height()); _palette.addAction(Menu::getInstance()->getActionForOption(MenuOption::VoxelAddMode), 0, 0); @@ -2314,7 +2315,7 @@ void Application::update(float deltaTime) { updateCursor(deltaTime); // Handle cursor updates _particles.update(); // update the particles... - _particleCollisionSystem.update(); // handle collisions for the particles... + _particleCollisionSystem.update(); // collide the particles... } void Application::updateAvatar(float deltaTime) { diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 91a356c952..fc3ac73044 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -16,17 +16,30 @@ #include "AvatarManager.h" +// We add _myAvatar into the hash with all the other AvatarData, and we use the default NULL QUid as the key. +const QUuid MY_AVATAR_KEY; // NULL key + AvatarManager::AvatarManager(QObject* parent) : _lookAtTargetAvatar(), _lookAtOtherPosition(), _lookAtIndicatorScale(1.0f), - _avatarHash(), - _avatarFades() + _avatarFades(), + _myAvatar(NULL) { // register a meta type for the weak pointer we'll use for the owning avatar mixer for each avatar qRegisterMetaType >("NodeWeakPointer"); } +void AvatarManager::setMyAvatar(MyAvatar* myAvatar) { + if (!_myAvatar) { + // can only ever set this once + _myAvatar = myAvatar; + // add _myAvatar to the list + AvatarSharedPointer myPointer = AvatarSharedPointer(_myAvatar); + _avatarHash.insert(MY_AVATAR_KEY, myPointer); + } +} + void AvatarManager::updateLookAtTargetAvatar(glm::vec3 &eyePosition) { bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); PerformanceWarning warn(showWarnings, "Application::updateLookatTargetAvatar()"); @@ -34,22 +47,26 @@ void AvatarManager::updateLookAtTargetAvatar(glm::vec3 &eyePosition) { Application* applicationInstance = Application::getInstance(); if (!applicationInstance->isMousePressed()) { - foreach (const AvatarSharedPointer& avatar, _avatarHash) { - float distance; - - if (avatar->findRayIntersection(applicationInstance->getMouseRayOrigin(), - applicationInstance->getMouseRayDirection(), distance)) { - // rescale to compensate for head embiggening - eyePosition = (avatar->getHead().calculateAverageEyePosition() - avatar->getHead().getScalePivot()) * - (avatar->getScale() / avatar->getHead().getScale()) + avatar->getHead().getScalePivot(); - - _lookAtIndicatorScale = avatar->getHead().getScale(); - _lookAtOtherPosition = avatar->getHead().getPosition(); - - _lookAtTargetAvatar = avatar; - - // found the look at target avatar, return - return; + glm::vec3 mouseOrigin = applicationInstance->getMouseRayOrigin(); + glm::vec3 mouseDirection = applicationInstance->getMouseRayDirection(); + + foreach (const AvatarSharedPointer& avatarPointer, _avatarHash) { + Avatar* avatar = static_cast(avatarPointer.data()); + if (avatar != static_cast(_myAvatar)) { + float distance; + if (avatar->findRayIntersection(mouseOrigin, mouseDirection, distance)) { + // rescale to compensate for head embiggening + eyePosition = (avatar->getHead().calculateAverageEyePosition() - avatar->getHead().getScalePivot()) * + (avatar->getScale() / avatar->getHead().getScale()) + avatar->getHead().getScalePivot(); + + _lookAtIndicatorScale = avatar->getHead().getScale(); + _lookAtOtherPosition = avatar->getHead().getPosition(); + + _lookAtTargetAvatar = avatarPointer; + + // found the look at target avatar, return + return; + } } } @@ -61,21 +78,28 @@ void AvatarManager::updateAvatars(float deltaTime) { bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); PerformanceWarning warn(showWarnings, "Application::updateAvatars()"); + Application* applicationInstance = Application::getInstance(); + glm::vec3 mouseOrigin = applicationInstance->getMouseRayOrigin(); + glm::vec3 mouseDirection = applicationInstance->getMouseRayDirection(); + // simulate avatars - AvatarHash::iterator avatar = _avatarHash.begin(); - while (avatar != _avatarHash.end()) { - if (avatar->data()->getOwningAvatarMixer()) { + AvatarHash::iterator avatarIterator = _avatarHash.begin(); + while (avatarIterator != _avatarHash.end()) { + Avatar* avatar = static_cast(avatarIterator.value().data()); + if (avatar == static_cast(_myAvatar)) { + // for now skip updates to _myAvatar because it is done explicitly in Application + // TODO: update _myAvatar in this context + ++avatarIterator; + continue; + } + if (avatar->getOwningAvatarMixer()) { // this avatar's mixer is still around, go ahead and simulate it - avatar->data()->simulate(deltaTime, NULL); - - Application* applicationInstance = Application::getInstance(); - - avatar->data()->setMouseRay(applicationInstance->getMouseRayOrigin(), - applicationInstance->getMouseRayDirection()); - avatar++; + avatar->simulate(deltaTime, NULL); + avatar->setMouseRay(mouseOrigin, mouseDirection); + ++avatarIterator; } else { // the mixer that owned this avatar is gone, give it to the vector of fades and kill it - avatar = removeAvatarAtHashIterator(avatar); + avatarIterator = erase(avatarIterator); } } @@ -89,41 +113,44 @@ void AvatarManager::renderAvatars(bool forceRenderHead, bool selfAvatarOnly) { } PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "Application::renderAvatars()"); + bool renderLookAtVectors = Menu::getInstance()->isOptionChecked(MenuOption::LookAtVectors); if (!selfAvatarOnly) { - // Render avatars of other nodes - foreach (const AvatarSharedPointer& avatar, _avatarHash) { + foreach (const AvatarSharedPointer& avatarPointer, _avatarHash) { + Avatar* avatar = static_cast(avatarPointer.data()); if (!avatar->isInitialized()) { avatar->init(); } - avatar->render(false); - avatar->setDisplayingLookatVectors(Menu::getInstance()->isOptionChecked(MenuOption::LookAtVectors)); + if (avatar == static_cast(_myAvatar)) { + avatar->render(forceRenderHead); + } else { + avatar->render(false); + } + avatar->setDisplayingLookatVectors(renderLookAtVectors); } - renderAvatarFades(); + } else if (_myAvatar) { + // Render my own Avatar + _myAvatar->render(forceRenderHead); + _myAvatar->setDisplayingLookatVectors(renderLookAtVectors); } - - // Render my own Avatar - Avatar* myAvatar = Application::getInstance()->getAvatar(); - myAvatar->render(forceRenderHead); - myAvatar->setDisplayingLookatVectors(Menu::getInstance()->isOptionChecked(MenuOption::LookAtVectors)); } void AvatarManager::simulateAvatarFades(float deltaTime) { - QVector::iterator fadingAvatar = _avatarFades.begin(); + QVector::iterator fadingIterator = _avatarFades.begin(); - while (fadingAvatar != _avatarFades.end()) { - const float SHRINK_RATE = 0.9f; - - fadingAvatar->data()->setTargetScale(fadingAvatar->data()->getScale() * SHRINK_RATE); - - const float MIN_FADE_SCALE = 0.001f; - - if (fadingAvatar->data()->getTargetScale() < MIN_FADE_SCALE) { - fadingAvatar = _avatarFades.erase(fadingAvatar); + const float SHRINK_RATE = 0.9f; + const float MIN_FADE_SCALE = 0.001f; + + while (fadingIterator != _avatarFades.end()) { + Avatar* avatar = static_cast(fadingIterator->data()); + avatar->setTargetScale(avatar->getScale() * SHRINK_RATE); + if (avatar->getTargetScale() < MIN_FADE_SCALE) { + fadingIterator = _avatarFades.erase(fadingIterator); } else { - fadingAvatar->data()->simulate(deltaTime, NULL); + avatar->simulate(deltaTime, NULL); + ++fadingIterator; } } } @@ -133,29 +160,36 @@ void AvatarManager::renderAvatarFades() { Glower glower; foreach(const AvatarSharedPointer& fadingAvatar, _avatarFades) { - fadingAvatar->render(false); + Avatar* avatar = static_cast(fadingAvatar.data()); + avatar->render(false); } } void AvatarManager::processDataServerResponse(const QString& userString, const QStringList& keyList, const QStringList &valueList) { + QUuid avatarKey = QUuid(userString); + if (avatarKey == MY_AVATAR_KEY) { + // ignore updates to our own mesh + return; + } for (int i = 0; i < keyList.size(); i++) { if (valueList[i] != " ") { if (keyList[i] == DataServerKey::FaceMeshURL || keyList[i] == DataServerKey::SkeletonURL) { // mesh URL for a UUID, find avatar in our list - AvatarSharedPointer matchingAvatar = _avatarHash.value(QUuid(userString)); + AvatarSharedPointer matchingAvatar = _avatarHash.value(avatarKey); if (matchingAvatar) { + Avatar* avatar = static_cast(matchingAvatar.data()); if (keyList[i] == DataServerKey::FaceMeshURL) { qDebug() << "Changing mesh to" << valueList[i] << "for avatar with UUID" - << uuidStringWithoutCurlyBraces(QUuid(userString)); + << uuidStringWithoutCurlyBraces(avatarKey); - QMetaObject::invokeMethod(&matchingAvatar->getHead().getFaceModel(), + QMetaObject::invokeMethod(&(avatar->getHead().getFaceModel()), "setURL", Q_ARG(QUrl, QUrl(valueList[i]))); } else if (keyList[i] == DataServerKey::SkeletonURL) { qDebug() << "Changing skeleton to" << valueList[i] << "for avatar with UUID" - << uuidStringWithoutCurlyBraces(QString(userString)); + << uuidStringWithoutCurlyBraces(avatarKey.toString()); - QMetaObject::invokeMethod(&matchingAvatar->getSkeletonModel(), + QMetaObject::invokeMethod(&(avatar->getSkeletonModel()), "setURL", Q_ARG(QUrl, QUrl(valueList[i]))); } } @@ -179,15 +213,17 @@ void AvatarManager::processAvatarMixerDatagram(const QByteArray& datagram, const // only add them if mixerWeakPointer points to something (meaning that mixer is still around) while (bytesRead < datagram.size() && mixerWeakPointer.data()) { QUuid nodeUUID = QUuid::fromRfc4122(datagram.mid(bytesRead, NUM_BYTES_RFC4122_UUID)); + // TODO: skip the data if nodeUUID is same as MY_AVATAR_KEY AvatarSharedPointer matchingAvatar = _avatarHash.value(nodeUUID); if (!matchingAvatar) { // construct a new Avatar for this node - matchingAvatar = AvatarSharedPointer(new Avatar()); - matchingAvatar->setOwningAvatarMixer(mixerWeakPointer); + Avatar* avatar = new Avatar(); + avatar->setOwningAvatarMixer(mixerWeakPointer); // insert the new avatar into our hash + matchingAvatar = AvatarSharedPointer(avatar); _avatarHash.insert(nodeUUID, matchingAvatar); // new UUID requires mesh and skeleton request to data-server @@ -214,21 +250,26 @@ void AvatarManager::processKillAvatar(const QByteArray& datagram) { // remove the avatar with that UUID from our hash, if it exists AvatarHash::iterator matchedAvatar = _avatarHash.find(nodeUUID); if (matchedAvatar != _avatarHash.end()) { - removeAvatarAtHashIterator(matchedAvatar); + erase(matchedAvatar); } } -AvatarHash::iterator AvatarManager::removeAvatarAtHashIterator(const AvatarHash::iterator& iterator) { - qDebug() << "Removing Avatar with UUID" << iterator.key() << "from AvatarManager hash."; - _avatarFades.push_back(iterator.value()); - return _avatarHash.erase(iterator); +AvatarHash::iterator AvatarManager::erase(const AvatarHash::iterator& iterator) { + if (iterator.key() != MY_AVATAR_KEY) { + qDebug() << "Removing Avatar with UUID" << iterator.key() << "from AvatarManager hash."; + _avatarFades.push_back(iterator.value()); + return AvatarHashMap::erase(iterator); + } else { + // never remove _myAvatar from the list + AvatarHash::iterator returnIterator = iterator; + return ++returnIterator; + } } void AvatarManager::clearHash() { // clear the AvatarManager hash - typically happens on the removal of the avatar-mixer AvatarHash::iterator removeAvatar = _avatarHash.begin(); - while (removeAvatar != _avatarHash.end()) { - removeAvatar = removeAvatarAtHashIterator(removeAvatar); + removeAvatar = erase(removeAvatar); } } diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h index 7d0f11646d..369444bcc7 100644 --- a/interface/src/avatar/AvatarManager.h +++ b/interface/src/avatar/AvatarManager.h @@ -13,30 +13,30 @@ #include #include +#include #include #include "Avatar.h" -typedef QSharedPointer AvatarSharedPointer; -typedef QHash AvatarHash; +class MyAvatar; -class AvatarManager : public QObject, public DataServerCallbackObject { +class AvatarManager : public QObject, public DataServerCallbackObject, public AvatarHashMap { Q_OBJECT public: AvatarManager(QObject* parent = 0); + + void setMyAvatar(MyAvatar* myAvatar); - const AvatarHash& getAvatarHash() { return _avatarHash; } - int size() const { return _avatarHash.size(); } - - Avatar* getLookAtTargetAvatar() const { return _lookAtTargetAvatar.data(); } + AvatarData* getLookAtTargetAvatar() const { return _lookAtTargetAvatar.data(); } void updateLookAtTargetAvatar(glm::vec3& eyePosition); void updateAvatars(float deltaTime); void renderAvatars(bool forceRenderHead, bool selfAvatarOnly = false); + // virtual override void clearHash(); - + public slots: void processDataServerResponse(const QString& userString, const QStringList& keyList, const QStringList& valueList); @@ -44,17 +44,20 @@ public slots: void processKillAvatar(const QByteArray& datagram); private: + AvatarManager(const AvatarManager& other); + void simulateAvatarFades(float deltaTime); void renderAvatarFades(); - AvatarHash::iterator removeAvatarAtHashIterator(const AvatarHash::iterator& iterator); + // virtual override + AvatarHash::iterator erase(const AvatarHash::iterator& iterator); - QWeakPointer _lookAtTargetAvatar; + QWeakPointer _lookAtTargetAvatar; glm::vec3 _lookAtOtherPosition; float _lookAtIndicatorScale; - AvatarHash _avatarHash; QVector _avatarFades; + MyAvatar* _myAvatar; }; #endif /* defined(__hifi__AvatarManager__) */ diff --git a/interface/src/avatar/Hand.cpp b/interface/src/avatar/Hand.cpp index 18d6031f98..b441000cc1 100644 --- a/interface/src/avatar/Hand.cpp +++ b/interface/src/avatar/Hand.cpp @@ -183,7 +183,12 @@ void Hand::updateCollisions() { glm::vec3 totalPenetration; // check other avatars - foreach (const AvatarSharedPointer& avatar, Application::getInstance()->getAvatarManager().getAvatarHash()) { + foreach (const AvatarSharedPointer& avatarPointer, Application::getInstance()->getAvatarManager().getAvatarHash()) { + Avatar* avatar = static_cast(avatarPointer.data()); + if (avatar == _owningAvatar) { + // don't collid with our own hands + continue; + } if (Menu::getInstance()->isOptionChecked(MenuOption::PlaySlaps)) { // Check for palm collisions glm::vec3 myPalmPosition = palm.getPosition(); @@ -205,9 +210,9 @@ void Hand::updateCollisions() { const float PALM_COLLIDE_DURATION_MAX = 0.75f; const float PALM_COLLIDE_DECAY_PER_SAMPLE = 0.01f; Application::getInstance()->getAudio()->startDrumSound(PALM_COLLIDE_VOLUME, - PALM_COLLIDE_FREQUENCY, - PALM_COLLIDE_DURATION_MAX, - PALM_COLLIDE_DECAY_PER_SAMPLE); + PALM_COLLIDE_FREQUENCY, + PALM_COLLIDE_DURATION_MAX, + PALM_COLLIDE_DECAY_PER_SAMPLE); // If the other person's palm is in motion, move mine downward to show I was hit const float MIN_VELOCITY_FOR_SLAP = 0.05f; if (glm::length(otherPalm.getVelocity()) > MIN_VELOCITY_FOR_SLAP) { diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index ca7a3b863b..6910826524 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -791,14 +791,15 @@ void MyAvatar::updateChatCircle(float deltaTime) { // find all circle-enabled members and sort by distance QVector sortedAvatars; - foreach (const AvatarSharedPointer& avatar, Application::getInstance()->getAvatarManager().getAvatarHash()) { - SortedAvatar sortedAvatar; - sortedAvatar.avatar = avatar.data(); - - if (!sortedAvatar.avatar->isChatCirclingEnabled()) { + foreach (const AvatarSharedPointer& avatarPointer, Application::getInstance()->getAvatarManager().getAvatarHash()) { + Avatar* avatar = static_cast(avatarPointer.data()); + if ( ! avatar->isChatCirclingEnabled() || + avatar == static_cast(this)) { continue; } - + + SortedAvatar sortedAvatar; + sortedAvatar.avatar = avatar; sortedAvatar.distance = glm::distance(_position, sortedAvatar.avatar->getPosition()); sortedAvatars.append(sortedAvatar); } diff --git a/interface/src/ui/Snapshot.h b/interface/src/ui/Snapshot.h index 39548cdff8..26315678f9 100644 --- a/interface/src/ui/Snapshot.h +++ b/interface/src/ui/Snapshot.h @@ -9,9 +9,9 @@ #ifndef __hifi__Snapshot__ #define __hifi__Snapshot__ -#import -#import -#import +#include +#include +#include #include diff --git a/libraries/avatars/src/AvatarHashMap.cpp b/libraries/avatars/src/AvatarHashMap.cpp new file mode 100644 index 0000000000..b1bb42edbf --- /dev/null +++ b/libraries/avatars/src/AvatarHashMap.cpp @@ -0,0 +1,30 @@ +// +// AvatarHashMap.cpp +// hifi +// +// Created by Stephen AndrewMeadows on 1/28/2014. +// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// + +#include "AvatarHashMap.h" + +AvatarHashMap::AvatarHashMap() : + _avatarHash() +{ +} + +void AvatarHashMap::insert(const QUuid& id, AvatarSharedPointer avatar) { + _avatarHash.insert(id, avatar); +} + +void AvatarHashMap::clearHash() { + AvatarHash::iterator removeAvatar = _avatarHash.begin(); + while (removeAvatar != _avatarHash.end()) { + removeAvatar = erase(removeAvatar); + } +} + +AvatarHash::iterator AvatarHashMap::erase(const AvatarHash::iterator& iterator) { + return _avatarHash.erase(iterator); +} + diff --git a/libraries/avatars/src/AvatarHashMap.h b/libraries/avatars/src/AvatarHashMap.h new file mode 100644 index 0000000000..37e9004d6f --- /dev/null +++ b/libraries/avatars/src/AvatarHashMap.h @@ -0,0 +1,37 @@ +// +// AvatarHashMap.h +// hifi +// +// Created by Stephen AndrewMeadows on 1/28/2014. +// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// + +#ifndef __hifi__AvatarHashMap__ +#define __hifi__AvatarHashMap__ + +#include +#include +#include + +#include "AvatarData.h" + +typedef QSharedPointer AvatarSharedPointer; +typedef QHash AvatarHash; + +class AvatarHashMap { +public: + AvatarHashMap(); + + const AvatarHash& getAvatarHash() { return _avatarHash; } + int size() const { return _avatarHash.size(); } + + virtual void insert(const QUuid& id, AvatarSharedPointer avatar); + virtual void clearHash(); + +protected: + virtual AvatarHash::iterator erase(const AvatarHash::iterator& iterator); + + AvatarHash _avatarHash; +}; + +#endif /* defined(__hifi__AvatarHashMap__) */ diff --git a/libraries/octree/src/AABox.cpp b/libraries/octree/src/AABox.cpp index 670e2b8a6b..1313111765 100644 --- a/libraries/octree/src/AABox.cpp +++ b/libraries/octree/src/AABox.cpp @@ -117,6 +117,13 @@ bool AABox::contains(const AABox& otherBox) const { return true; } +bool AABox::touches(const AABox& otherBox) const { + glm::vec3 relativeCenter = _corner - otherBox._corner + (glm::vec3(_scale - otherBox._scale) * 0.5f); + float totalHalfScale = 0.5f * (_scale + otherBox._scale); + return fabs(relativeCenter.x) <= totalHalfScale && + fabs(relativeCenter.y) <= totalHalfScale && + fabs(relativeCenter.z) <= totalHalfScale; +} // determines whether a value is within the expanded extents static bool isWithinExpanded(float value, float corner, float size, float expansion) { diff --git a/libraries/octree/src/AABox.h b/libraries/octree/src/AABox.h index aec0fff450..731b82be4c 100644 --- a/libraries/octree/src/AABox.h +++ b/libraries/octree/src/AABox.h @@ -61,6 +61,7 @@ public: bool contains(const glm::vec3& point) const; bool contains(const AABox& otherBox) const; + bool touches(const AABox& otherBox) const; bool expandedContains(const glm::vec3& point, float expansion) const; bool expandedIntersectsSegment(const glm::vec3& start, const glm::vec3& end, float expansion) const; bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face) const; diff --git a/libraries/particles/src/Particle.cpp b/libraries/particles/src/Particle.cpp index ecda364e85..c48068199a 100644 --- a/libraries/particles/src/Particle.cpp +++ b/libraries/particles/src/Particle.cpp @@ -661,7 +661,50 @@ void Particle::adjustEditPacketForClockSkew(unsigned char* codeColorBuffer, ssiz } } +// HALTING_* params are determined using expected acceleration of gravity over some timescale. +// This is a HACK for particles that bounce in a 1.0 gravitational field and should eventually be made more universal. +const float HALTING_PARTICLE_PERIOD = 0.0167f; // ~1/60th of a second +const float HALTING_PARTICLE_SPEED = 9.8 * HALTING_PARTICLE_PERIOD / (float)(TREE_SCALE); + +void Particle::applyHardCollision(const CollisionInfo& collisionInfo) { + // + // Update the particle in response to a hard collision. Position will be reset exactly + // to outside the colliding surface. Velocity will be modified according to elasticity. + // + // if elasticity = 0.0, collision is inelastic (vel normal to collision is lost) + // if elasticity = 1.0, collision is 100% elastic. + // + glm::vec3 position = getPosition(); + glm::vec3 velocity = getVelocity(); + + const float EPSILON = 0.0f; + glm::vec3 relativeVelocity = collisionInfo._addedVelocity - velocity; + float velocityDotPenetration = glm::dot(relativeVelocity, collisionInfo._penetration); + if (velocityDotPenetration < EPSILON) { + // particle is moving into collision surface + // + // TODO: do something smarter here by comparing the mass of the particle vs that of the other thing + // (other's mass could be stored in the Collision Info). The smaller mass should surrender more + // position offset and should slave more to the other's velocity in the static-friction case. + position -= collisionInfo._penetration; + + if (glm::length(relativeVelocity) < HALTING_PARTICLE_SPEED) { + // static friction kicks in and particle moves with colliding object + velocity = collisionInfo._addedVelocity; + } else { + glm::vec3 direction = glm::normalize(collisionInfo._penetration); + velocity += glm::dot(relativeVelocity, direction) * (1.0f + collisionInfo._elasticity) * direction; // dynamic reflection + velocity += glm::clamp(collisionInfo._damping, 0.0f, 1.0f) * (relativeVelocity - glm::dot(relativeVelocity, direction) * direction); // dynamic friction + } + } + + // change the local particle too... + setPosition(position); + setVelocity(velocity); +} + // MIN_VALID_SPEED is obtained by computing speed gained at one gravity during the shortest expected frame period +// This is a HACK for particles that bounce in a 1.0 gravitational field and should eventually be made more universal. const float MIN_EXPECTED_FRAME_PERIOD = 0.005f; // 1/200th of a second const float MIN_VALID_SPEED = 9.8 * MIN_EXPECTED_FRAME_PERIOD / (float)(TREE_SCALE); diff --git a/libraries/particles/src/Particle.h b/libraries/particles/src/Particle.h index ebd21a6b52..3e45cfb2b2 100644 --- a/libraries/particles/src/Particle.h +++ b/libraries/particles/src/Particle.h @@ -16,6 +16,7 @@ #include #include +#include #include #include @@ -246,6 +247,8 @@ public: static void adjustEditPacketForClockSkew(unsigned char* codeColorBuffer, ssize_t length, int clockSkew); + void applyHardCollision(const CollisionInfo& collisionInfo); + void update(const uint64_t& now); void collisionWithParticle(Particle* other); void collisionWithVoxel(VoxelDetail* voxel); diff --git a/libraries/particles/src/ParticleCollisionSystem.cpp b/libraries/particles/src/ParticleCollisionSystem.cpp index 7e8866e7d4..a2394935b1 100644 --- a/libraries/particles/src/ParticleCollisionSystem.cpp +++ b/libraries/particles/src/ParticleCollisionSystem.cpp @@ -20,17 +20,19 @@ #include "ParticleTree.h" ParticleCollisionSystem::ParticleCollisionSystem(ParticleEditPacketSender* packetSender, - ParticleTree* particles, VoxelTree* voxels, AbstractAudioInterface* audio, AvatarData* selfAvatar) { - init(packetSender, particles, voxels, audio, selfAvatar); + ParticleTree* particles, VoxelTree* voxels, AbstractAudioInterface* audio, + AvatarHashMap* avatars) { + init(packetSender, particles, voxels, audio, avatars); } void ParticleCollisionSystem::init(ParticleEditPacketSender* packetSender, - ParticleTree* particles, VoxelTree* voxels, AbstractAudioInterface* audio, AvatarData* selfAvatar) { + ParticleTree* particles, VoxelTree* voxels, AbstractAudioInterface* audio, + AvatarHashMap* avatars) { _packetSender = packetSender; _particles = particles; _voxels = voxels; _audio = audio; - _selfAvatar = selfAvatar; + _avatars = avatars; } ParticleCollisionSystem::~ParticleCollisionSystem() { @@ -73,6 +75,8 @@ void ParticleCollisionSystem::updateCollisionWithVoxels(Particle* particle) { const float DAMPING = 0.05f; const float COLLISION_FREQUENCY = 0.5f; CollisionInfo collisionInfo; + collisionInfo._damping = DAMPING; + collisionInfo._elasticity = ELASTICITY; VoxelDetail* voxelDetails = NULL; if (_voxels->findSpherePenetration(center, radius, collisionInfo._penetration, (void**)&voxelDetails)) { @@ -81,7 +85,8 @@ void ParticleCollisionSystem::updateCollisionWithVoxels(Particle* particle) { updateCollisionSound(particle, collisionInfo._penetration, COLLISION_FREQUENCY); collisionInfo._penetration /= (float)(TREE_SCALE); - applyHardCollision(particle, ELASTICITY, DAMPING, collisionInfo); + particle->applyHardCollision(collisionInfo); + queueParticlePropertiesUpdate(particle); delete voxelDetails; // cleanup returned details } @@ -144,9 +149,8 @@ const float MIN_EXPECTED_FRAME_PERIOD = 0.0167f; // 1/60th of a second const float HALTING_SPEED = 9.8 * MIN_EXPECTED_FRAME_PERIOD / (float)(TREE_SCALE); void ParticleCollisionSystem::updateCollisionWithAvatars(Particle* particle) { - // particles that are in hand, don't collide with avatars - if (particle->getInHand()) { + if (!_avatars || particle->getInHand()) { return; } @@ -157,10 +161,11 @@ void ParticleCollisionSystem::updateCollisionWithAvatars(Particle* particle) { const float COLLISION_FREQUENCY = 0.5f; glm::vec3 penetration; - // first check the selfAvatar if set... - if (_selfAvatar) { - AvatarData* avatar = (AvatarData*)_selfAvatar; + foreach (const AvatarSharedPointer& avatarPointer, _avatars->getAvatarHash()) { + AvatarData* avatar = avatarPointer.data(); CollisionInfo collisionInfo; + collisionInfo._damping = DAMPING; + collisionInfo._elasticity = ELASTICITY; if (avatar->findSphereCollision(center, radius, collisionInfo)) { collisionInfo._addedVelocity /= (float)(TREE_SCALE); glm::vec3 relativeVelocity = collisionInfo._addedVelocity - particle->getVelocity(); @@ -185,90 +190,21 @@ void ParticleCollisionSystem::updateCollisionWithAvatars(Particle* particle) { updateCollisionSound(particle, collisionInfo._penetration, COLLISION_FREQUENCY); collisionInfo._penetration /= (float)(TREE_SCALE); - applyHardCollision(particle, elasticity, damping, collisionInfo); + particle->applyHardCollision(collisionInfo); + queueParticlePropertiesUpdate(particle); } } } - - // loop through all the other avatars for potential interactions... -// foreach (const SharedNodePointer& node, NodeList::getInstance()->getNodeHash()) { -// //qDebug() << "updateCollisionWithAvatars()... node:" << *node << "\n"; -// if (node->getLinkedData() && node->getType() == NODE_TYPE_AGENT) { -// AvatarData* avatar = static_cast(node->getLinkedData()); -// CollisionInfo collisionInfo; -// if (avatar->findSphereCollision(center, radius, collisionInfo)) { -// collisionInfo._addedVelocity /= (float)(TREE_SCALE); -// glm::vec3 relativeVelocity = collisionInfo._addedVelocity - particle->getVelocity(); -// if (glm::dot(relativeVelocity, collisionInfo._penetration) < 0.f) { -// // HACK BEGIN: to allow paddle hands to "hold" particles we attenuate soft collisions against the avatar. -// // NOTE: the physics are wrong (particles cannot roll) but it IS possible to catch a slow moving particle. -// // TODO: make this less hacky when we have more per-collision details -// float elasticity = ELASTICITY; -// float attenuationFactor = glm::length(collisionInfo._addedVelocity) / HALTING_SPEED; -// float damping = DAMPING; -// if (attenuationFactor < 1.f) { -// collisionInfo._addedVelocity *= attenuationFactor; -// elasticity *= attenuationFactor; -// // NOTE: the math below keeps the damping piecewise continuous, -// // while ramping it up to 1.0 when attenuationFactor = 0 -// damping = DAMPING + (1.f - attenuationFactor) * (1.f - DAMPING); -// } -// // HACK END -// -// updateCollisionSound(particle, collisionInfo._penetration, COLLISION_FREQUENCY); -// collisionInfo._penetration /= (float)(TREE_SCALE); -// applyHardCollision(particle, ELASTICITY, damping, collisionInfo); -// } -// } -// } -// } } -// TODO: convert applyHardCollision() to take a CollisionInfo& instead of penetration + addedVelocity -void ParticleCollisionSystem::applyHardCollision(Particle* particle, float elasticity, float damping, const CollisionInfo& collisionInfo) { - // - // Update the particle in response to a hard collision. Position will be reset exactly - // to outside the colliding surface. Velocity will be modified according to elasticity. - // - // if elasticity = 0.0, collision is inelastic (vel normal to collision is lost) - // if elasticity = 1.0, collision is 100% elastic. - // - glm::vec3 position = particle->getPosition(); - glm::vec3 velocity = particle->getVelocity(); - - const float EPSILON = 0.0f; - glm::vec3 relativeVelocity = collisionInfo._addedVelocity - velocity; - float velocityDotPenetration = glm::dot(relativeVelocity, collisionInfo._penetration); - if (velocityDotPenetration < EPSILON) { - // particle is moving into collision surface - position -= collisionInfo._penetration; - - if (glm::length(relativeVelocity) < HALTING_SPEED) { - // static friction kicks in and particle moves with colliding object - velocity = collisionInfo._addedVelocity; - } else { - glm::vec3 direction = glm::normalize(collisionInfo._penetration); - velocity += glm::dot(relativeVelocity, direction) * (1.0f + elasticity) * direction; // dynamic reflection - velocity += glm::clamp(damping, 0.0f, 1.0f) * (relativeVelocity - glm::dot(relativeVelocity, direction) * direction); // dynamic friction - } - } - const bool wantDebug = false; - if (wantDebug) { - printf("ParticleCollisionSystem::applyHardCollision() particle id:%d new velocity:%f,%f,%f inHand:%s\n", - particle->getID(), velocity.x, velocity.y, velocity.z, debug::valueOf(particle->getInHand())); - } - - // send off the result to the particle server +void ParticleCollisionSystem::queueParticlePropertiesUpdate(Particle* particle) { + // queue the result for sending to the particle server ParticleProperties properties; ParticleID particleID(particle->getID()); properties.copyFromParticle(*particle); - properties.setPosition(position * (float)TREE_SCALE); - properties.setVelocity(velocity * (float)TREE_SCALE); + properties.setPosition(particle->getPosition() * (float)TREE_SCALE); + properties.setVelocity(particle->getVelocity() * (float)TREE_SCALE); _packetSender->queueParticleEditMessage(PACKET_TYPE_PARTICLE_ADD_OR_EDIT, particleID, properties); - - // change the local particle too... - particle->setPosition(position); - particle->setVelocity(velocity); } diff --git a/libraries/particles/src/ParticleCollisionSystem.h b/libraries/particles/src/ParticleCollisionSystem.h index cf52d01a7a..4a61693fa6 100644 --- a/libraries/particles/src/ParticleCollisionSystem.h +++ b/libraries/particles/src/ParticleCollisionSystem.h @@ -16,6 +16,7 @@ #include #include +#include #include #include #include @@ -33,21 +34,21 @@ const glm::vec3 NO_ADDED_VELOCITY = glm::vec3(0); class ParticleCollisionSystem { public: ParticleCollisionSystem(ParticleEditPacketSender* packetSender = NULL, ParticleTree* particles = NULL, - VoxelTree* voxels = NULL, - AbstractAudioInterface* audio = NULL, - AvatarData* selfAvatar = NULL); + VoxelTree* voxels = NULL, AbstractAudioInterface* audio = NULL, + AvatarHashMap* avatars = NULL); void init(ParticleEditPacketSender* packetSender, ParticleTree* particles, VoxelTree* voxels, - AbstractAudioInterface* audio = NULL, AvatarData* selfAvatar = NULL); + AbstractAudioInterface* audio = NULL, AvatarHashMap* _avatars = NULL); ~ParticleCollisionSystem(); void update(); + void checkParticle(Particle* particle); void updateCollisionWithVoxels(Particle* particle); void updateCollisionWithParticles(Particle* particle); void updateCollisionWithAvatars(Particle* particle); - void applyHardCollision(Particle* particle, float elasticity, float damping, const CollisionInfo& collisionInfo); + void queueParticlePropertiesUpdate(Particle* particle); void updateCollisionSound(Particle* particle, const glm::vec3 &penetration, float frequency); private: @@ -58,7 +59,7 @@ private: ParticleTree* _particles; VoxelTree* _voxels; AbstractAudioInterface* _audio; - AvatarData* _selfAvatar; + AvatarHashMap* _avatars; }; #endif /* defined(__hifi__ParticleCollisionSystem__) */ diff --git a/libraries/particles/src/ParticleTree.cpp b/libraries/particles/src/ParticleTree.cpp index 7e83c56a42..3e224be48b 100644 --- a/libraries/particles/src/ParticleTree.cpp +++ b/libraries/particles/src/ParticleTree.cpp @@ -170,16 +170,14 @@ public: bool ParticleTree::findInSphereOperation(OctreeElement* element, void* extraData) { FindAllNearPointArgs* args = static_cast(extraData); - ParticleTreeElement* particleTreeElement = static_cast(element); - glm::vec3 penetration; - bool sphereIntersection = particleTreeElement->getAABox().findSpherePenetration(args->position, + bool sphereIntersection = element->getAABox().findSpherePenetration(args->position, args->targetRadius, penetration); - // If this particleTreeElement contains the point, then search it... + // If this element contains the point, then search it... if (sphereIntersection) { - QVector moreParticles = particleTreeElement->getParticles(args->position, args->targetRadius); - args->particles << moreParticles; + ParticleTreeElement* particleTreeElement = static_cast(element); + particleTreeElement->getParticles(args->position, args->targetRadius, args->particles); return true; // keep searching in case children have closer particles } @@ -187,13 +185,43 @@ bool ParticleTree::findInSphereOperation(OctreeElement* element, void* extraData return false; } -QVector ParticleTree::findParticles(const glm::vec3& center, float radius) { - QVector result; +void ParticleTree::findParticles(const glm::vec3& center, float radius, QVector& foundParticles) { FindAllNearPointArgs args = { center, radius }; lockForRead(); recurseTreeWithOperation(findInSphereOperation, &args); unlock(); - return args.particles; + // swap the two lists of particle pointers instead of copy + foundParticles.swap(args.particles); +} + +class FindParticlesInBoxArgs { +public: + FindParticlesInBoxArgs(const AABox& box) + : _box(box), _foundParticles() { + } + + AABox _box; + QVector _foundParticles; +}; + +bool findInBoxForUpdateOperation(OctreeElement* element, void* extraData) { + FindParticlesInBoxArgs* args = static_cast< FindParticlesInBoxArgs*>(extraData); + const AABox& elementBox = element->getAABox(); + if (elementBox.touches(args->_box)) { + ParticleTreeElement* particleTreeElement = static_cast(element); + particleTreeElement->getParticlesForUpdate(args->_box, args->_foundParticles); + return true; + } + return false; +} + +void ParticleTree::findParticlesForUpdate(const AABox& box, QVector foundParticles) { + FindParticlesInBoxArgs args(box); + lockForRead(); + recurseTreeWithOperation(findInBoxForUpdateOperation, &args); + unlock(); + // swap the two lists of particle pointers instead of copy + foundParticles.swap(args._foundParticles); } class FindByIDArgs { diff --git a/libraries/particles/src/ParticleTree.h b/libraries/particles/src/ParticleTree.h index 5fa25b2c2d..a19bad9892 100644 --- a/libraries/particles/src/ParticleTree.h +++ b/libraries/particles/src/ParticleTree.h @@ -42,7 +42,19 @@ public: void storeParticle(const Particle& particle, Node* senderNode = NULL); const Particle* findClosestParticle(glm::vec3 position, float targetRadius); const Particle* findParticleByID(uint32_t id, bool alreadyLocked = false); - QVector findParticles(const glm::vec3& center, float radius); + + /// finds all particles that touch a sphere + /// \param center the center of the sphere + /// \param radius the radius of the sphere + /// \param foundParticles[out] vector of const Particle* + /// \remark Side effect: any initial contents in foundParticles will be lost + void findParticles(const glm::vec3& center, float radius, QVector& foundParticles); + + /// finds all particles that touch a box + /// \param box the query box + /// \param foundParticles[out] vector of non-const Particle* + /// \remark Side effect: any initial contents in particles will be lost + void findParticlesForUpdate(const AABox& box, QVector foundParticles); void addNewlyCreatedHook(NewlyCreatedParticleHook* hook); void removeNewlyCreatedHook(NewlyCreatedParticleHook* hook); diff --git a/libraries/particles/src/ParticleTreeElement.cpp b/libraries/particles/src/ParticleTreeElement.cpp index 1454eadcc9..72d3e1b7b0 100644 --- a/libraries/particles/src/ParticleTreeElement.cpp +++ b/libraries/particles/src/ParticleTreeElement.cpp @@ -185,21 +185,34 @@ const Particle* ParticleTreeElement::getClosestParticle(glm::vec3 position) cons return closestParticle; } -QVector ParticleTreeElement::getParticles(glm::vec3 searchPosition, float searchRadius) const { - QVector results; +void ParticleTreeElement::getParticles(const glm::vec3& searchPosition, float searchRadius, QVector& foundParticles) const { uint16_t numberOfParticles = _particles->size(); for (uint16_t i = 0; i < numberOfParticles; i++) { const Particle* particle = &(*_particles)[i]; glm::vec3 particlePosition = particle->getPosition(); - float particleRadius = particle->getRadius(); - glm::vec3 penetration; - - // check to see that the particle (penetrator) penetrates the search area - if (findSphereSpherePenetration(particlePosition, particleRadius, searchPosition, searchRadius, penetration)) { - results << particle; + float distance = glm::length(particle->getPosition() - searchPosition); + if (distance < searchRadius + particle->getRadius()) { + foundParticles.push_back(particle); } } - return results; +} + +void ParticleTreeElement::getParticlesForUpdate(const AABox& box, QVector& foundParticles) { + QList::iterator particleItr = _particles->begin(); + QList::iterator particleEnd = _particles->end(); + AABox particleBox; + while(particleItr != particleEnd) { + Particle* particle = &(*particleItr); + float radius = particle->getRadius(); + // NOTE: we actually do box-box collision queries here, which is sloppy but good enough for now + // TODO: decide whether to replace particleBox-box query with sphere-box (requires a square root + // but will be slightly more accurate). + particleBox.setBox(particle->getPosition() - glm::vec3(radius), 2.f * radius); + if (particleBox.touches(_box)) { + foundParticles.push_back(particle); + } + ++particleItr; + } } const Particle* ParticleTreeElement::getParticleWithID(uint32_t id) const { @@ -228,8 +241,6 @@ bool ParticleTreeElement::removeParticleWithID(uint32_t id) { return foundParticle; } - - int ParticleTreeElement::readElementDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args) { diff --git a/libraries/particles/src/ParticleTreeElement.h b/libraries/particles/src/ParticleTreeElement.h index 2705ad4292..1d30bfbdfd 100644 --- a/libraries/particles/src/ParticleTreeElement.h +++ b/libraries/particles/src/ParticleTreeElement.h @@ -89,12 +89,22 @@ public: bool containsParticle(const Particle& particle) const; bool updateParticle(const Particle& particle); const Particle* getClosestParticle(glm::vec3 position) const; - QVector getParticles(glm::vec3 position, float radius) const; + + /// finds all particles that touch a sphere + /// \param position the center of the query sphere + /// \param radius the radius of the query sphere + /// \param particles[out] vector of const Particle* + void getParticles(const glm::vec3& position, float radius, QVector& foundParticles) const; + + /// finds all particles that touch a box + /// \param box the query box + /// \param particles[out] vector of non-const Particle* + void getParticlesForUpdate(const AABox& box, QVector& foundParticles); + const Particle* getParticleWithID(uint32_t id) const; bool removeParticleWithID(uint32_t id); - protected: virtual void init(unsigned char * octalCode); diff --git a/libraries/particles/src/ParticlesScriptingInterface.cpp b/libraries/particles/src/ParticlesScriptingInterface.cpp index 0641cdba7c..2bbade964d 100644 --- a/libraries/particles/src/ParticlesScriptingInterface.cpp +++ b/libraries/particles/src/ParticlesScriptingInterface.cpp @@ -150,7 +150,8 @@ ParticleID ParticlesScriptingInterface::findClosestParticle(const glm::vec3& cen QVector ParticlesScriptingInterface::findParticles(const glm::vec3& center, float radius) const { QVector result; if (_particleTree) { - QVector particles = _particleTree->findParticles(center/(float)TREE_SCALE, radius/(float)TREE_SCALE); + QVector particles; + _particleTree->findParticles(center/(float)TREE_SCALE, radius/(float)TREE_SCALE, particles); foreach (const Particle* particle, particles) { ParticleID thisParticleID(particle->getID(), UNKNOWN_TOKEN, true); diff --git a/libraries/shared/src/CollisionInfo.h b/libraries/shared/src/CollisionInfo.h index 1e4801788e..38ae64e30c 100644 --- a/libraries/shared/src/CollisionInfo.h +++ b/libraries/shared/src/CollisionInfo.h @@ -13,11 +13,19 @@ class CollisionInfo { public: - CollisionInfo() : _penetration(0.f), _addedVelocity(0.f) { } + CollisionInfo() + : _damping(0.f), + _elasticity(1.f), + _penetration(0.f), + _addedVelocity(0.f) { + } + ~CollisionInfo() {} //glm::vec3 _point; //glm::vec3 _normal; + float _damping; + float _elasticity; glm::vec3 _penetration; // depth that bodyA is penetrates bodyB glm::vec3 _addedVelocity; };