From d0a53f817a42eb01774612c75be68e5e56d27454 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Fri, 17 Jan 2014 17:14:57 -0800 Subject: [PATCH 1/9] 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/9] 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/9] 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/9] 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/9] 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/9] 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/9] 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; From 0eed041deb70a71f0ba509b22c18d90db56d450f Mon Sep 17 00:00:00 2001 From: aw Date: Mon, 20 Jan 2014 22:58:01 -0800 Subject: [PATCH 8/9] scripts save on exit and load on entry --- interface/src/Application.cpp | 58 ++++++++++++++++---- interface/src/Application.h | 9 ++- interface/src/ControllerScriptingInterface.h | 1 + interface/src/Menu.cpp | 2 +- libraries/script-engine/src/ScriptEngine.cpp | 13 +++-- libraries/script-engine/src/ScriptEngine.h | 5 +- 6 files changed, 69 insertions(+), 19 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 229cedf55c..0bbaa38362 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -232,6 +232,7 @@ Application::Application(int& argc, char** argv, timeval &startup_time) : _window->setCentralWidget(_glWidget); restoreSizeAndPosition(); + loadScripts(); _window->setVisible(true); _glWidget->setFocusPolicy(Qt::StrongFocus); _glWidget->setFocus(); @@ -270,7 +271,7 @@ Application::~Application() { _audio.thread()->wait(); storeSizeAndPosition(); - + saveScripts(); _sharedVoxelSystem.changeTree(new VoxelTree); VoxelTreeElement::removeDeleteHook(&_voxels); // we don't need to do this processing on shutdown @@ -4237,13 +4238,38 @@ void Application::packetSentNotification(ssize_t length) { _bandwidthMeter.outputStream(BandwidthMeter::VOXELS).updateValue(length); } -void Application::loadScript() { - // shut down and stop any existing script - QString desktopLocation = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation); - QString suggestedName = desktopLocation.append("/script.js"); +void Application::loadScripts(){ + // loads all saved scripts + QSettings* settings = new QSettings(this); + int size = settings->beginReadArray("Settings"); + for(int i=0; isetArrayIndex(i); + QString string = settings->value("script").toString(); + loadScript(string); + } + settings->endArray(); - QString fileNameString = QFileDialog::getOpenFileName(_glWidget, tr("Open Script"), suggestedName, - tr("JavaScript Files (*.js)")); +} + +void Application::saveScripts(){ + // saves all current running scripts + QSettings* settings = new QSettings(this); + settings->beginWriteArray("Settings"); + for(int i=0; i<_activeScripts.size(); ++i){ + settings->setArrayIndex(i); + settings->setValue("script", _activeScripts.at(i)); + } + settings->endArray(); + +} + +void Application::removeScriptName(const QString& fileNameString) +{ + _activeScripts.removeOne(fileNameString); +} + +void Application::loadScript(const QString& fileNameString){ + _activeScripts.append(fileNameString); QByteArray fileNameAscii = fileNameString.toLocal8Bit(); const char* fileName = fileNameAscii.data(); @@ -4270,9 +4296,7 @@ void Application::loadScript() { // start the script on a new thread... bool wantMenuItems = true; // tells the ScriptEngine object to add menu items for itself - - ScriptEngine* scriptEngine = new ScriptEngine(script, wantMenuItems, fileName, Menu::getInstance(), - &_controllerScriptingInterface); + ScriptEngine* scriptEngine = new ScriptEngine(script, wantMenuItems, fileName, Menu::getInstance(), &_controllerScriptingInterface); scriptEngine->setupMenuItems(); // setup the packet senders and jurisdiction listeners of the script engine's scripting interfaces so @@ -4286,8 +4310,9 @@ void Application::loadScript() { connect(workerThread, SIGNAL(started()), scriptEngine, SLOT(run())); // when the thread is terminated, add both scriptEngine and thread to the deleteLater queue - connect(scriptEngine, SIGNAL(finished()), scriptEngine, SLOT(deleteLater())); + connect(scriptEngine, SIGNAL(finished(const QString&)), scriptEngine, SLOT(deleteLater())); connect(workerThread, SIGNAL(finished()), workerThread, SLOT(deleteLater())); + connect(scriptEngine, SIGNAL(finished(const QString&)), this, SLOT(removeScriptName(const QString&))); // when the application is about to quit, stop our script engine so it unwinds properly connect(this, SIGNAL(aboutToQuit()), scriptEngine, SLOT(stop())); @@ -4301,6 +4326,17 @@ void Application::loadScript() { _window->activateWindow(); } +void Application::loadDialog() { + // shut down and stop any existing script + QString desktopLocation = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation); + QString suggestedName = desktopLocation.append("/script.js"); + + QString fileNameString = QFileDialog::getOpenFileName(_glWidget, tr("Open Script"), suggestedName, + tr("JavaScript Files (*.js)")); + + loadScript(fileNameString); +} + void Application::toggleLogDialog() { if (! _logDialog) { _logDialog = new LogDialog(_glWidget, getLogger()); diff --git a/interface/src/Application.h b/interface/src/Application.h index 7e78cced4c..be4c687043 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -103,7 +104,10 @@ public: ~Application(); void restoreSizeAndPosition(); + void loadScript(const QString& fileNameString); + void loadScripts(); void storeSizeAndPosition(); + void saveScripts(); void initializeGL(); void paintGL(); void resizeGL(int width, int height); @@ -221,7 +225,7 @@ public slots: void doKillLocalVoxels(); void decreaseVoxelSize(); void increaseVoxelSize(); - void loadScript(); + void loadDialog(); void toggleLogDialog(); void initAvatarAndViewFrustum(); @@ -250,6 +254,8 @@ private slots: void shrinkMirrorView(); void resetSensors(); + void removeScriptName(const QString& fileNameString); + private: void resetCamerasOnResizeGL(Camera& camera, int width, int height); void updateProjectionMatrix(); @@ -373,6 +379,7 @@ private: Faceshift _faceshift; SixenseManager _sixenseManager; + QStringList _activeScripts; Camera _myCamera; // My view onto the world Camera _viewFrustumOffsetCamera; // The camera we use to sometimes show the view frustum from an offset mode diff --git a/interface/src/ControllerScriptingInterface.h b/interface/src/ControllerScriptingInterface.h index d0e032d52f..69daefa3fb 100644 --- a/interface/src/ControllerScriptingInterface.h +++ b/interface/src/ControllerScriptingInterface.h @@ -12,6 +12,7 @@ #include #include +class PalmData; /// handles scripting of input controller commands from JS class ControllerScriptingInterface : public AbstractControllerScriptingInterface { diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 7299615ecc..87edfa82a2 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -91,7 +91,7 @@ Menu::Menu() : SLOT(login()))); addDisabledActionAndSeparator(fileMenu, "Scripts"); - addActionToQMenuAndActionHash(fileMenu, MenuOption::LoadScript, Qt::CTRL | Qt::Key_O, appInstance, SLOT(loadScript())); + addActionToQMenuAndActionHash(fileMenu, MenuOption::LoadScript, Qt::CTRL | Qt::Key_O, appInstance, SLOT(loadDialog())); _activeScriptsMenu = fileMenu->addMenu("Running Scripts"); addDisabledActionAndSeparator(fileMenu, "Voxels"); diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 6b85cf33ad..e3f694f7cc 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -38,17 +38,20 @@ static QScriptValue soundConstructor(QScriptContext* context, QScriptEngine* eng return soundScriptValue; } -ScriptEngine::ScriptEngine(const QString& scriptContents, bool wantMenuItems, - const char* scriptMenuName, AbstractMenuInterface* menu, +ScriptEngine::ScriptEngine(const QString& scriptContents, bool wantMenuItems, const QString& fileNameString, AbstractMenuInterface* menu, AbstractControllerScriptingInterface* controllerScriptingInterface) { _scriptContents = scriptContents; _isFinished = false; _isRunning = false; _isInitialized = false; + _fileNameString = fileNameString; + + QByteArray fileNameAscii = fileNameString.toLocal8Bit(); + const char* scriptMenuName = fileNameAscii.data(); // some clients will use these menu features _wantMenuItems = wantMenuItems; - if (scriptMenuName) { + if (!fileNameString.isEmpty()) { _scriptMenuName = "Stop "; _scriptMenuName.append(scriptMenuName); _scriptMenuName.append(QString(" [%1]").arg(_scriptNumber)); @@ -233,9 +236,11 @@ void ScriptEngine::run() { thread()->quit(); } - emit finished(); + emit finished(_fileNameString); + _isRunning = false; } + void ScriptEngine::stop() { _isFinished = true; } diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index 06597f82a8..d1a4d6cf51 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -29,7 +29,7 @@ class ScriptEngine : public QObject { Q_OBJECT public: ScriptEngine(const QString& scriptContents = NO_SCRIPT, bool wantMenuItems = false, - const char* scriptMenuName = NULL, AbstractMenuInterface* menu = NULL, + const QString& scriptMenuName = QString(""), AbstractMenuInterface* menu = NULL, AbstractControllerScriptingInterface* controllerScriptingInterface = NULL); ~ScriptEngine(); @@ -58,7 +58,7 @@ signals: void willSendAudioDataCallback(); void willSendVisualDataCallback(); void scriptEnding(); - void finished(); + void finished(const QString& fileNameString); protected: QString _scriptContents; @@ -74,6 +74,7 @@ private: AudioScriptingInterface _audioScriptingInterface; bool _wantMenuItems; QString _scriptMenuName; + QString _fileNameString; AbstractMenuInterface* _menu; static int _scriptNumber; }; From 141c20f4ef64a98599f83f54b1b8d3154d56db6f Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 21 Jan 2014 09:26:23 -0800 Subject: [PATCH 9/9] Fix formatting to abide by compressed K&R style. --- libraries/particles/src/ParticleTreeElement.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/libraries/particles/src/ParticleTreeElement.cpp b/libraries/particles/src/ParticleTreeElement.cpp index 101c6f6c47..2be0c330d2 100644 --- a/libraries/particles/src/ParticleTreeElement.cpp +++ b/libraries/particles/src/ParticleTreeElement.cpp @@ -80,9 +80,7 @@ void ParticleTreeElement::update(ParticleTreeUpdateArgs& args) { // erase this particle particleItr = _particles->erase(particleItr); - } - else - { + } else { ++particleItr; } }