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/Particle.cpp b/libraries/particles/src/Particle.cpp index dcb344f164..e395996c45 100644 --- a/libraries/particles/src/Particle.cpp +++ b/libraries/particles/src/Particle.cpp @@ -474,21 +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() { - 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); - bool isStillMoving = (velocityScalar > STILL_MOVING); - const float REALLY_OLD = 30.0f; // 30 seconds - bool isReallyOld = (getLifetime() > REALLY_OLD); + 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 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/ParticleCollisionSystem.cpp b/libraries/particles/src/ParticleCollisionSystem.cpp index 8544932181..dee0259eb7 100644 --- a/libraries/particles/src/ParticleCollisionSystem.cpp +++ b/libraries/particles/src/ParticleCollisionSystem.cpp @@ -11,7 +11,6 @@ #include #include #include -#include #include #include @@ -43,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]; @@ -72,18 +71,18 @@ 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; - 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 } @@ -136,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 @@ -146,18 +149,18 @@ 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; // 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) @@ -165,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(collision._addedVelocity) / SLOW_PADDLE_SPEED; + float attenuationFactor = glm::length(collisionInfo._addedVelocity) / HALTING_SPEED; + float damping = DAMPING; if (attenuationFactor < 1.f) { - collision._addedVelocity *= attenuationFactor; + 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 - 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,36 +191,37 @@ 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) / HALTING_SPEED; + float damping = DAMPING; if (attenuationFactor < 1.f) { - collision._addedVelocity *= attenuationFactor; + 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 - 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); } } } } } - // 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,20 +233,19 @@ void ParticleCollisionSystem::applyHardCollision(Particle* particle, const glm:: glm::vec3 velocity = particle->getVelocity(); const float EPSILON = 0.0f; - float velocityDotPenetration = glm::dot(velocity, penetration); - if (velocityDotPenetration > EPSILON) { - position -= penetration; - static float HALTING_VELOCITY = 0.2f / (float)(TREE_SCALE); - // cancel out the velocity component in the direction of penetration + 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; - float penetrationLength = glm::length(penetration); - glm::vec3 direction = penetration / penetrationLength; - velocity -= (glm::dot(velocity, direction) * (1.0f + elasticity)) * direction; - velocity += addedVelocity; - 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 - 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; 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: diff --git a/libraries/particles/src/ParticleTreeElement.cpp b/libraries/particles/src/ParticleTreeElement.cpp index 44cb8db008..101c6f6c47 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(_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 - 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__) */