From d0a53f817a42eb01774612c75be68e5e56d27454 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Fri, 17 Jan 2014 17:14:57 -0800 Subject: [PATCH 1/7] applyHardCollision() now takes a CollisionInfo rather than two vectors ("penetration" and "addedVelocity") --- .../particles/src/ParticleCollisionSystem.cpp | 64 +++++++++---------- .../particles/src/ParticleCollisionSystem.h | 4 +- 2 files changed, 32 insertions(+), 36 deletions(-) diff --git a/libraries/particles/src/ParticleCollisionSystem.cpp b/libraries/particles/src/ParticleCollisionSystem.cpp index 8544932181..5e0a6d4e39 100644 --- a/libraries/particles/src/ParticleCollisionSystem.cpp +++ b/libraries/particles/src/ParticleCollisionSystem.cpp @@ -11,7 +11,6 @@ #include #include #include -#include #include #include @@ -74,16 +73,16 @@ void ParticleCollisionSystem::updateCollisionWithVoxels(Particle* particle) { const float ELASTICITY = 0.4f; const float DAMPING = 0.0f; const float COLLISION_FREQUENCY = 0.5f; - glm::vec3 penetration; + CollisionInfo collisionInfo; VoxelDetail* voxelDetails = NULL; - if (_voxels->findSpherePenetration(center, radius, penetration, (void**)&voxelDetails)) { + if (_voxels->findSpherePenetration(center, radius, collisionInfo._penetration, (void**)&voxelDetails)) { // let the particles run their collision scripts if they have them particle->collisionWithVoxel(voxelDetails); - penetration /= (float)(TREE_SCALE); - updateCollisionSound(particle, penetration, COLLISION_FREQUENCY); - applyHardCollision(particle, penetration, ELASTICITY, DAMPING); + collisionInfo._penetration /= (float)(TREE_SCALE); + updateCollisionSound(particle, collisionInfo._penetration, COLLISION_FREQUENCY); + applyHardCollision(particle, ELASTICITY, DAMPING, collisionInfo); delete voxelDetails; // cleanup returned details } @@ -153,11 +152,11 @@ void ParticleCollisionSystem::updateCollisionWithAvatars(Particle* particle) { // first check the selfAvatar if set... if (_selfAvatar) { AvatarData* avatar = (AvatarData*)_selfAvatar; - CollisionInfo collision; - if (avatar->findSphereCollision(center, radius, collision)) { - collision._addedVelocity /= (float)(TREE_SCALE); - glm::vec3 relativeVelocity = collision._addedVelocity - particle->getVelocity(); - if (glm::dot(relativeVelocity, collision._penetration) < 0.f) { + 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) { // only collide when particle and collision point are moving toward each other // (doing this prevents some "collision snagging" when particle penetrates the object) @@ -166,16 +165,16 @@ void ParticleCollisionSystem::updateCollisionWithAvatars(Particle* particle) { // TODO: make this less hacky when we have more per-collision details float elasticity = ELASTICITY; float SLOW_PADDLE_SPEED = 5.0e-5f; - float attenuationFactor = glm::length(collision._addedVelocity) / SLOW_PADDLE_SPEED; + float attenuationFactor = glm::length(collisionInfo._addedVelocity) / SLOW_PADDLE_SPEED; if (attenuationFactor < 1.f) { - collision._addedVelocity *= attenuationFactor; + collisionInfo._addedVelocity *= attenuationFactor; elasticity *= attenuationFactor; } // HACK END - collision._penetration /= (float)(TREE_SCALE); - updateCollisionSound(particle, collision._penetration, COLLISION_FREQUENCY); - applyHardCollision(particle, collision._penetration, elasticity, DAMPING, collision._addedVelocity); + collisionInfo._penetration /= (float)(TREE_SCALE); + updateCollisionSound(particle, collisionInfo._penetration, COLLISION_FREQUENCY); + applyHardCollision(particle, elasticity, DAMPING, collisionInfo); } } } @@ -185,26 +184,26 @@ void ParticleCollisionSystem::updateCollisionWithAvatars(Particle* particle) { //qDebug() << "updateCollisionWithAvatars()... node:" << *node << "\n"; if (node->getLinkedData() && node->getType() == NODE_TYPE_AGENT) { AvatarData* avatar = static_cast(node->getLinkedData()); - CollisionInfo collision; - if (avatar->findSphereCollision(center, radius, collision)) { - collision._addedVelocity /= (float)(TREE_SCALE); - glm::vec3 relativeVelocity = collision._addedVelocity - particle->getVelocity(); - if (glm::dot(relativeVelocity, collision._penetration) < 0.f) { + 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 SLOW_PADDLE_SPEED = 5.0e-5f; - float attenuationFactor = glm::length(collision._addedVelocity) / SLOW_PADDLE_SPEED; + float attenuationFactor = glm::length(collisionInfo._addedVelocity) / SLOW_PADDLE_SPEED; if (attenuationFactor < 1.f) { - collision._addedVelocity *= attenuationFactor; + collisionInfo._addedVelocity *= attenuationFactor; elasticity *= attenuationFactor; } // HACK END - collision._penetration /= (float)(TREE_SCALE); - updateCollisionSound(particle, collision._penetration, COLLISION_FREQUENCY); - applyHardCollision(particle, collision._penetration, ELASTICITY, DAMPING, collision._addedVelocity); + collisionInfo._penetration /= (float)(TREE_SCALE); + updateCollisionSound(particle, collisionInfo._penetration, COLLISION_FREQUENCY); + applyHardCollision(particle, ELASTICITY, DAMPING, collisionInfo); } } } @@ -213,8 +212,7 @@ void ParticleCollisionSystem::updateCollisionWithAvatars(Particle* particle) { // TODO: convert applyHardCollision() to take a CollisionInfo& instead of penetration + addedVelocity -void ParticleCollisionSystem::applyHardCollision(Particle* particle, const glm::vec3& penetration, - float elasticity, float damping, const glm::vec3& 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. @@ -226,16 +224,14 @@ void ParticleCollisionSystem::applyHardCollision(Particle* particle, const glm:: glm::vec3 velocity = particle->getVelocity(); const float EPSILON = 0.0f; - float velocityDotPenetration = glm::dot(velocity, penetration); + float velocityDotPenetration = glm::dot(velocity, collisionInfo._penetration); if (velocityDotPenetration > EPSILON) { - position -= penetration; + position -= collisionInfo._penetration; static float HALTING_VELOCITY = 0.2f / (float)(TREE_SCALE); // cancel out the velocity component in the direction of penetration - float penetrationLength = glm::length(penetration); - glm::vec3 direction = penetration / penetrationLength; - velocity -= (glm::dot(velocity, direction) * (1.0f + elasticity)) * direction; - velocity += addedVelocity; + glm::vec3 direction = glm::normalize(collisionInfo._penetration); + velocity += collisionInfo._addedVelocity - (glm::dot(velocity, direction) * (1.0f + elasticity)) * direction; velocity *= glm::clamp(1.f - damping, 0.0f, 1.0f); if (glm::length(velocity) < HALTING_VELOCITY) { // If moving really slowly after a collision, and not applying forces, stop altogether diff --git a/libraries/particles/src/ParticleCollisionSystem.h b/libraries/particles/src/ParticleCollisionSystem.h index 13766a0264..cf52d01a7a 100644 --- a/libraries/particles/src/ParticleCollisionSystem.h +++ b/libraries/particles/src/ParticleCollisionSystem.h @@ -16,6 +16,7 @@ #include #include +#include #include #include @@ -46,8 +47,7 @@ public: void updateCollisionWithVoxels(Particle* particle); void updateCollisionWithParticles(Particle* particle); void updateCollisionWithAvatars(Particle* particle); - void applyHardCollision(Particle* particle, const glm::vec3& penetration, float elasticity, float damping, - const glm::vec3& addedVelocity = NO_ADDED_VELOCITY); + void applyHardCollision(Particle* particle, float elasticity, float damping, const CollisionInfo& collisionInfo); void updateCollisionSound(Particle* particle, const glm::vec3 &penetration, float frequency); private: From 61e1b25e70c7dd7015036d76d9c6da3ed01221f6 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Mon, 20 Jan 2014 12:43:27 -0800 Subject: [PATCH 2/7] ParticleTreeElement::_particles is now a QList<> instead of std::vector<> for faster erase() --- interface/src/ParticleTreeRenderer.cpp | 2 +- .../particles/src/ParticleCollisionSystem.cpp | 9 +- .../particles/src/ParticleTreeElement.cpp | 92 +++++++++++-------- libraries/particles/src/ParticleTreeElement.h | 16 ++-- 4 files changed, 71 insertions(+), 48 deletions(-) diff --git a/interface/src/ParticleTreeRenderer.cpp b/interface/src/ParticleTreeRenderer.cpp index c6ed1488e4..81ef7f1221 100644 --- a/interface/src/ParticleTreeRenderer.cpp +++ b/interface/src/ParticleTreeRenderer.cpp @@ -32,7 +32,7 @@ void ParticleTreeRenderer::renderElement(OctreeElement* element, RenderArgs* arg // we need to iterate the actual particles of the element ParticleTreeElement* particleTreeElement = (ParticleTreeElement*)element; - const std::vector& particles = particleTreeElement->getParticles(); + const QList& particles = particleTreeElement->getParticles(); uint16_t numberOfParticles = particles.size(); diff --git a/libraries/particles/src/ParticleCollisionSystem.cpp b/libraries/particles/src/ParticleCollisionSystem.cpp index 5e0a6d4e39..dc8f2f5269 100644 --- a/libraries/particles/src/ParticleCollisionSystem.cpp +++ b/libraries/particles/src/ParticleCollisionSystem.cpp @@ -42,7 +42,7 @@ bool ParticleCollisionSystem::updateOperation(OctreeElement* element, void* extr ParticleTreeElement* particleTreeElement = static_cast(element); // iterate the particles... - std::vector& particles = particleTreeElement->getParticles(); + QList& particles = particleTreeElement->getParticles(); uint16_t numberOfParticles = particles.size(); for (uint16_t i = 0; i < numberOfParticles; i++) { Particle* particle = &particles[i]; @@ -227,13 +227,14 @@ void ParticleCollisionSystem::applyHardCollision(Particle* particle, float elast float velocityDotPenetration = glm::dot(velocity, collisionInfo._penetration); if (velocityDotPenetration > EPSILON) { position -= collisionInfo._penetration; - static float HALTING_VELOCITY = 0.2f / (float)(TREE_SCALE); // cancel out the velocity component in the direction of penetration - glm::vec3 direction = glm::normalize(collisionInfo._penetration); velocity += collisionInfo._addedVelocity - (glm::dot(velocity, direction) * (1.0f + elasticity)) * direction; velocity *= glm::clamp(1.f - damping, 0.0f, 1.0f); - if (glm::length(velocity) < HALTING_VELOCITY) { + + // TODO: move this halt logic into Particle::update() method + static float HALTING_SPEED = 0.2f / (float)(TREE_SCALE); + if (glm::length(velocity) < HALTING_SPEED) { // If moving really slowly after a collision, and not applying forces, stop altogether velocity *= 0.f; } diff --git a/libraries/particles/src/ParticleTreeElement.cpp b/libraries/particles/src/ParticleTreeElement.cpp index 44cb8db008..525f567ed4 100644 --- a/libraries/particles/src/ParticleTreeElement.cpp +++ b/libraries/particles/src/ParticleTreeElement.cpp @@ -11,12 +11,14 @@ #include "ParticleTree.h" #include "ParticleTreeElement.h" -ParticleTreeElement::ParticleTreeElement(unsigned char* octalCode) : OctreeElement() { +ParticleTreeElement::ParticleTreeElement(unsigned char* octalCode) : OctreeElement(), _particles(NULL) { init(octalCode); }; ParticleTreeElement::~ParticleTreeElement() { _voxelMemoryUsage -= sizeof(ParticleTreeElement); + delete _particles; + _particles = NULL; } // This will be called primarily on addChildAt(), which means we're adding a child of our @@ -31,6 +33,7 @@ OctreeElement* ParticleTreeElement::createNewElement(unsigned char* octalCode) c void ParticleTreeElement::init(unsigned char* octalCode) { OctreeElement::init(octalCode); + _particles = new QList; _voxelMemoryUsage += sizeof(ParticleTreeElement); } @@ -45,12 +48,12 @@ bool ParticleTreeElement::appendElementData(OctreePacketData* packetData) const bool success = true; // assume the best... // write our particles out... - uint16_t numberOfParticles = _particles.size(); + uint16_t numberOfParticles = _particles->size(); success = packetData->appendValue(numberOfParticles); if (success) { for (uint16_t i = 0; i < numberOfParticles; i++) { - const Particle& particle = _particles[i]; + const Particle& particle = (*_particles)[i]; success = particle.appendParticleData(packetData); if (!success) { break; @@ -62,35 +65,42 @@ bool ParticleTreeElement::appendElementData(OctreePacketData* packetData) const void ParticleTreeElement::update(ParticleTreeUpdateArgs& args) { markWithChangedTime(); + // TODO: early exit when _particles is empty // update our contained particles - uint16_t numberOfParticles = _particles.size(); - - for (uint16_t i = 0; i < numberOfParticles; i++) { - _particles[i].update(); + QList::iterator particleItr = _particles->begin(); + while(particleItr != _particles->end()) { + Particle& particle = (*particleItr); + particle.update(); // If the particle wants to die, or if it's left our bounding box, then move it // into the arguments moving particles. These will be added back or deleted completely - if (_particles[i].getShouldDie() || !_box.contains(_particles[i].getPosition())) { - args._movingParticles.push_back(_particles[i]); + if (particle.getShouldDie() || !_box.contains(particle.getPosition())) { + args._movingParticles.push_back(particle); // erase this particle - _particles.erase(_particles.begin()+i); - - // reduce our index since we just removed this item - i--; - numberOfParticles--; + particleItr = _particles->erase(particleItr); + } + else + { + ++particleItr; } } + // TODO: if _particles is empty after while loop consider freeing memory in _particles if + // internal array is too big (QList internal array does not decrease size except in dtor and + // assignment operator). Otherwise _particles could become a "resource leak" for large + // roaming piles of particles. } bool ParticleTreeElement::findSpherePenetration(const glm::vec3& center, float radius, glm::vec3& penetration, void** penetratedObject) const { - uint16_t numberOfParticles = _particles.size(); - for (uint16_t i = 0; i < numberOfParticles; i++) { - glm::vec3 particleCenter = _particles[i].getPosition(); - float particleRadius = _particles[i].getRadius(); + QList::iterator particleItr = _particles->begin(); + QList::const_iterator particleEnd = _particles->end(); + while(particleItr != particleEnd) { + Particle& particle = (*particleItr); + glm::vec3 particleCenter = particle.getPosition(); + float particleRadius = particle.getRadius(); // don't penetrate yourself if (particleCenter == center && particleRadius == radius) { @@ -102,23 +112,28 @@ bool ParticleTreeElement::findSpherePenetration(const glm::vec3& center, float r const bool IN_HAND_PARTICLES_DONT_COLLIDE = false; if (IN_HAND_PARTICLES_DONT_COLLIDE) { // don't penetrate if the particle is "inHand" -- they don't collide - if (_particles[i].getInHand()) { - return false; + if (particle.getInHand()) { + ++particleItr; + continue; } } if (findSphereSpherePenetration(center, radius, particleCenter, particleRadius, penetration)) { - *penetratedObject = (void*)&_particles[i]; + // return true on first valid particle penetration + *penetratedObject = (void*)(&particle); return true; } + ++particleItr; } return false; } bool ParticleTreeElement::containsParticle(const Particle& particle) const { - uint16_t numberOfParticles = _particles.size(); + // TODO: remove this method and force callers to use getParticleWithID() instead + uint16_t numberOfParticles = _particles->size(); + uint32_t particleID = particle.getID(); for (uint16_t i = 0; i < numberOfParticles; i++) { - if (_particles[i].getID() == particle.getID()) { + if ((*_particles)[i].getID() == particleID) { return true; } } @@ -126,13 +141,17 @@ bool ParticleTreeElement::containsParticle(const Particle& particle) const { } bool ParticleTreeElement::updateParticle(const Particle& particle) { + // NOTE: this method must first lookup the particle by ID, hence it is O(N) + // and "particle is not found" is worst-case (full N) but maybe we don't care? + // (guaranteed that num particles per elemen is small?) const bool wantDebug = false; - uint16_t numberOfParticles = _particles.size(); + uint16_t numberOfParticles = _particles->size(); for (uint16_t i = 0; i < numberOfParticles; i++) { - if (_particles[i].getID() == particle.getID()) { - int difference = _particles[i].getLastUpdated() - particle.getLastUpdated(); - bool changedOnServer = _particles[i].getLastEdited() < particle.getLastEdited(); - bool localOlder = _particles[i].getLastUpdated() < particle.getLastUpdated(); + Particle& thisParticle = (*_particles)[i]; + if (thisParticle.getID() == particle.getID()) { + int difference = thisParticle.getLastUpdated() - particle.getLastUpdated(); + bool changedOnServer = thisParticle.getLastEdited() < particle.getLastEdited(); + bool localOlder = thisParticle.getLastUpdated() < particle.getLastUpdated(); if (changedOnServer || localOlder) { if (wantDebug) { printf("local particle [id:%d] %s and %s than server particle by %d, particle.isNewlyCreated()=%s\n", @@ -140,7 +159,7 @@ bool ParticleTreeElement::updateParticle(const Particle& particle) { (localOlder ? "OLDER" : "NEWER"), difference, debug::valueOf(particle.isNewlyCreated()) ); } - _particles[i].copyChangedProperties(particle); + thisParticle.copyChangedProperties(particle); } else { if (wantDebug) { printf(">>> IGNORING SERVER!!! Would've caused jutter! <<< " @@ -159,22 +178,23 @@ bool ParticleTreeElement::updateParticle(const Particle& particle) { const Particle* ParticleTreeElement::getClosestParticle(glm::vec3 position) const { const Particle* closestParticle = NULL; float closestParticleDistance = FLT_MAX; - uint16_t numberOfParticles = _particles.size(); + uint16_t numberOfParticles = _particles->size(); for (uint16_t i = 0; i < numberOfParticles; i++) { - float distanceToParticle = glm::distance(position, _particles[i].getPosition()); + float distanceToParticle = glm::distance(position, (*_particles)[i].getPosition()); if (distanceToParticle < closestParticleDistance) { - closestParticle = &_particles[i]; + closestParticle = &(*_particles)[i]; } } return closestParticle; } const Particle* ParticleTreeElement::getParticleWithID(uint32_t id) const { + // NOTE: this lookup is O(N) but maybe we don't care? (guaranteed that num particles per elemen is small?) const Particle* foundParticle = NULL; - uint16_t numberOfParticles = _particles.size(); + uint16_t numberOfParticles = _particles->size(); for (uint16_t i = 0; i < numberOfParticles; i++) { - if (_particles[i].getID() == id) { - foundParticle = &_particles[i]; + if ((*_particles)[i].getID() == id) { + foundParticle = &(*_particles)[i]; break; } } @@ -229,7 +249,7 @@ bool ParticleTreeElement::collapseChildren() { void ParticleTreeElement::storeParticle(const Particle& particle, Node* senderNode) { - _particles.push_back(particle); + _particles->push_back(particle); markWithChangedTime(); } diff --git a/libraries/particles/src/ParticleTreeElement.h b/libraries/particles/src/ParticleTreeElement.h index 971f5f5481..ab697aed1b 100644 --- a/libraries/particles/src/ParticleTreeElement.h +++ b/libraries/particles/src/ParticleTreeElement.h @@ -10,9 +10,10 @@ #ifndef __hifi__ParticleTreeElement__ #define __hifi__ParticleTreeElement__ -#include +//#include #include +#include #include "Particle.h" #include "ParticleTree.h" @@ -22,7 +23,7 @@ class ParticleTreeElement; class ParticleTreeUpdateArgs { public: - std::vector _movingParticles; + QList _movingParticles; }; class ParticleTreeElement : public OctreeElement { @@ -34,7 +35,6 @@ class ParticleTreeElement : public OctreeElement { public: virtual ~ParticleTreeElement(); - virtual void init(unsigned char * octalCode); // type safe versions of OctreeElement methods ParticleTreeElement* getChildAtIndex(int index) { return (ParticleTreeElement*)OctreeElement::getChildAtIndex(index); } @@ -79,9 +79,9 @@ public: virtual bool findSpherePenetration(const glm::vec3& center, float radius, glm::vec3& penetration, void** penetratedObject) const; - const std::vector& getParticles() const { return _particles; } - std::vector& getParticles() { return _particles; } - bool hasParticles() const { return _particles.size() > 0; } + const QList& getParticles() const { return *_particles; } + QList& getParticles() { return *_particles; } + bool hasParticles() const { return _particles->size() > 0; } void update(ParticleTreeUpdateArgs& args); void setTree(ParticleTree* tree) { _myTree = tree; } @@ -93,10 +93,12 @@ public: protected: + virtual void init(unsigned char * octalCode); + void storeParticle(const Particle& particle, Node* senderNode = NULL); ParticleTree* _myTree; - std::vector _particles; + QList* _particles; }; #endif /* defined(__hifi__ParticleTreeElement__) */ From 79e4b6d69166ec939ee5529396258b0a614ef601 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Mon, 20 Jan 2014 13:27:19 -0800 Subject: [PATCH 3/7] Removing per-particle calls to usecTimestampNow() which can add up when simulating a lot of particles. --- libraries/particles/src/Particle.cpp | 8 +++----- libraries/particles/src/Particle.h | 2 +- libraries/particles/src/ParticleTreeElement.cpp | 2 +- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/libraries/particles/src/Particle.cpp b/libraries/particles/src/Particle.cpp index dcb344f164..a6d0a0ded7 100644 --- a/libraries/particles/src/Particle.cpp +++ b/libraries/particles/src/Particle.cpp @@ -475,15 +475,13 @@ void Particle::adjustEditPacketForClockSkew(unsigned char* codeColorBuffer, ssiz } -void Particle::update() { - uint64_t now = usecTimestampNow(); - float elapsed = static_cast(now - _lastUpdated); +void Particle::update(const uint64_t& now) { + float timeElapsed = (float)(now - _lastUpdated) / (float)(USECS_PER_SECOND); _lastUpdated = now; - float timeElapsed = elapsed / static_cast(USECS_PER_SECOND); // calculate our default shouldDie state... then allow script to change it if it wants... float velocityScalar = glm::length(getVelocity()); - const float STILL_MOVING = 0.05f / static_cast(TREE_SCALE); + const float STILL_MOVING = 0.05f / (float)(TREE_SCALE); bool isStillMoving = (velocityScalar > STILL_MOVING); const float REALLY_OLD = 30.0f; // 30 seconds bool isReallyOld = (getLifetime() > REALLY_OLD); diff --git a/libraries/particles/src/Particle.h b/libraries/particles/src/Particle.h index 2c112daeeb..d53c7b9c40 100644 --- a/libraries/particles/src/Particle.h +++ b/libraries/particles/src/Particle.h @@ -117,7 +117,7 @@ public: static void adjustEditPacketForClockSkew(unsigned char* codeColorBuffer, ssize_t length, int clockSkew); - void update(); + void update(const uint64_t& now); void collisionWithParticle(Particle* other); void collisionWithVoxel(VoxelDetail* voxel); diff --git a/libraries/particles/src/ParticleTreeElement.cpp b/libraries/particles/src/ParticleTreeElement.cpp index 525f567ed4..101c6f6c47 100644 --- a/libraries/particles/src/ParticleTreeElement.cpp +++ b/libraries/particles/src/ParticleTreeElement.cpp @@ -71,7 +71,7 @@ void ParticleTreeElement::update(ParticleTreeUpdateArgs& args) { QList::iterator particleItr = _particles->begin(); while(particleItr != _particles->end()) { Particle& particle = (*particleItr); - particle.update(); + particle.update(_lastChanged); // If the particle wants to die, or if it's left our bounding box, then move it // into the arguments moving particles. These will be added back or deleted completely From 1eb2f19320406cf30af08965fe04e93eba6beaa8 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Mon, 20 Jan 2014 13:34:08 -0800 Subject: [PATCH 4/7] Removing one more usecTimestampNow() call in Particle::update() --- libraries/particles/src/Particle.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/particles/src/Particle.cpp b/libraries/particles/src/Particle.cpp index a6d0a0ded7..5ab9f1c470 100644 --- a/libraries/particles/src/Particle.cpp +++ b/libraries/particles/src/Particle.cpp @@ -484,7 +484,7 @@ void Particle::update(const uint64_t& now) { const float STILL_MOVING = 0.05f / (float)(TREE_SCALE); bool isStillMoving = (velocityScalar > STILL_MOVING); const float REALLY_OLD = 30.0f; // 30 seconds - bool isReallyOld = (getLifetime() > REALLY_OLD); + bool isReallyOld = ((float)(now - _created) > REALLY_OLD); bool isInHand = getInHand(); bool shouldDie = getShouldDie() || (!isInHand && !isStillMoving && isReallyOld); setShouldDie(shouldDie); From 1ed57e044a16e21401c62e6e63d0cf1353ac6687 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Mon, 20 Jan 2014 13:40:15 -0800 Subject: [PATCH 5/7] Fixing measure of "old" particles while simplifying the math (faster!). --- libraries/particles/src/Particle.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/particles/src/Particle.cpp b/libraries/particles/src/Particle.cpp index 5ab9f1c470..0c677c4058 100644 --- a/libraries/particles/src/Particle.cpp +++ b/libraries/particles/src/Particle.cpp @@ -483,8 +483,8 @@ void Particle::update(const uint64_t& now) { float velocityScalar = glm::length(getVelocity()); const float STILL_MOVING = 0.05f / (float)(TREE_SCALE); bool isStillMoving = (velocityScalar > STILL_MOVING); - const float REALLY_OLD = 30.0f; // 30 seconds - bool isReallyOld = ((float)(now - _created) > REALLY_OLD); + const uint64_t REALLY_OLD = 30 * USECS_PER_SECOND; // 30 seconds + bool isReallyOld = ((now - _created) > REALLY_OLD); bool isInHand = getInHand(); bool shouldDie = getShouldDie() || (!isInHand && !isStillMoving && isReallyOld); setShouldDie(shouldDie); From 516a590e4fcb28426696d2df8da8ae8d4620917f Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Mon, 20 Jan 2014 17:33:09 -0800 Subject: [PATCH 6/7] Modifying how the "particle is stopped" logic reads with minimal modification to how it works. --- libraries/particles/src/Particle.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/libraries/particles/src/Particle.cpp b/libraries/particles/src/Particle.cpp index 0c677c4058..e395996c45 100644 --- a/libraries/particles/src/Particle.cpp +++ b/libraries/particles/src/Particle.cpp @@ -474,19 +474,21 @@ void Particle::adjustEditPacketForClockSkew(unsigned char* codeColorBuffer, ssiz } } +// MIN_VALID_SPEED is obtained by computing speed gained at one gravity during the shortest expected frame period +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); void Particle::update(const uint64_t& now) { float timeElapsed = (float)(now - _lastUpdated) / (float)(USECS_PER_SECOND); _lastUpdated = now; // calculate our default shouldDie state... then allow script to change it if it wants... - float velocityScalar = glm::length(getVelocity()); - const float STILL_MOVING = 0.05f / (float)(TREE_SCALE); - bool isStillMoving = (velocityScalar > STILL_MOVING); + float speed = glm::length(_velocity); + bool isStopped = (speed < MIN_VALID_SPEED); const uint64_t REALLY_OLD = 30 * USECS_PER_SECOND; // 30 seconds bool isReallyOld = ((now - _created) > REALLY_OLD); bool isInHand = getInHand(); - bool shouldDie = getShouldDie() || (!isInHand && !isStillMoving && isReallyOld); + bool shouldDie = getShouldDie() || (!isInHand && isStopped && isReallyOld); setShouldDie(shouldDie); runUpdateScript(); // allow the javascript to alter our state From 966cd76e2c5d05816a38cc527ce62073c9ab842c Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Mon, 20 Jan 2014 17:34:24 -0800 Subject: [PATCH 7/7] Adding static friction for easier "catching" of particles on paddle hands. --- .../particles/src/ParticleCollisionSystem.cpp | 49 +++++++++++-------- 1 file changed, 29 insertions(+), 20 deletions(-) diff --git a/libraries/particles/src/ParticleCollisionSystem.cpp b/libraries/particles/src/ParticleCollisionSystem.cpp index dc8f2f5269..dee0259eb7 100644 --- a/libraries/particles/src/ParticleCollisionSystem.cpp +++ b/libraries/particles/src/ParticleCollisionSystem.cpp @@ -71,7 +71,7 @@ void ParticleCollisionSystem::updateCollisionWithVoxels(Particle* particle) { glm::vec3 center = particle->getPosition() * (float)(TREE_SCALE); float radius = particle->getRadius() * (float)(TREE_SCALE); const float ELASTICITY = 0.4f; - const float DAMPING = 0.0f; + const float DAMPING = 0.05f; const float COLLISION_FREQUENCY = 0.5f; CollisionInfo collisionInfo; VoxelDetail* voxelDetails = NULL; @@ -135,6 +135,10 @@ void ParticleCollisionSystem::updateCollisionWithParticles(Particle* particleA) } } +// MIN_VALID_SPEED is obtained by computing speed gained at one gravity after the shortest expected frame +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 @@ -145,7 +149,7 @@ void ParticleCollisionSystem::updateCollisionWithAvatars(Particle* particle) { glm::vec3 center = particle->getPosition() * (float)(TREE_SCALE); float radius = particle->getRadius() * (float)(TREE_SCALE); const float ELASTICITY = 0.9f; - const float DAMPING = 0.0f; + const float DAMPING = 0.1f; const float COLLISION_FREQUENCY = 0.5f; glm::vec3 penetration; @@ -164,17 +168,20 @@ void ParticleCollisionSystem::updateCollisionWithAvatars(Particle* particle) { // 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 SLOW_PADDLE_SPEED = 5.0e-5f; - float attenuationFactor = glm::length(collisionInfo._addedVelocity) / SLOW_PADDLE_SPEED; + 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 collisionInfo._penetration /= (float)(TREE_SCALE); updateCollisionSound(particle, collisionInfo._penetration, COLLISION_FREQUENCY); - applyHardCollision(particle, elasticity, DAMPING, collisionInfo); + applyHardCollision(particle, elasticity, damping, collisionInfo); } } } @@ -193,24 +200,26 @@ void ParticleCollisionSystem::updateCollisionWithAvatars(Particle* particle) { // 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 SLOW_PADDLE_SPEED = 5.0e-5f; - float attenuationFactor = glm::length(collisionInfo._addedVelocity) / SLOW_PADDLE_SPEED; + 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 collisionInfo._penetration /= (float)(TREE_SCALE); updateCollisionSound(particle, collisionInfo._penetration, COLLISION_FREQUENCY); - applyHardCollision(particle, ELASTICITY, DAMPING, collisionInfo); + 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) { // @@ -224,19 +233,19 @@ void ParticleCollisionSystem::applyHardCollision(Particle* particle, float elast glm::vec3 velocity = particle->getVelocity(); const float EPSILON = 0.0f; - float velocityDotPenetration = glm::dot(velocity, collisionInfo._penetration); - if (velocityDotPenetration > EPSILON) { + 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; - // cancel out the velocity component in the direction of penetration - glm::vec3 direction = glm::normalize(collisionInfo._penetration); - velocity += collisionInfo._addedVelocity - (glm::dot(velocity, direction) * (1.0f + elasticity)) * direction; - velocity *= glm::clamp(1.f - damping, 0.0f, 1.0f); - // TODO: move this halt logic into Particle::update() method - static float HALTING_SPEED = 0.2f / (float)(TREE_SCALE); - if (glm::length(velocity) < HALTING_SPEED) { - // If moving really slowly after a collision, and not applying forces, stop altogether - velocity *= 0.f; + 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;