overte/libraries/particles/src/ParticleTree.cpp

525 lines
19 KiB
C++

//
// ParticleTree.cpp
// hifi
//
// Created by Brad Hefta-Gaub on 12/4/13.
// Copyright (c) 2013 High Fidelity, Inc. All rights reserved.
//
#include "ParticleTree.h"
ParticleTree::ParticleTree(bool shouldReaverage) : Octree(shouldReaverage) {
ParticleTreeElement* rootNode = createNewElement();
_rootNode = rootNode;
}
ParticleTreeElement* ParticleTree::createNewElement(unsigned char * octalCode) {
ParticleTreeElement* newElement = new ParticleTreeElement(octalCode);
newElement->setTree(this);
return newElement;
}
bool ParticleTree::handlesEditPacketType(PACKET_TYPE packetType) const {
// we handle these types of "edit" packets
switch (packetType) {
case PACKET_TYPE_PARTICLE_ADD_OR_EDIT:
case PACKET_TYPE_PARTICLE_ERASE:
return true;
}
return false;
}
class FindAndDeleteParticlesArgs {
public:
QList<uint32_t> _idsToDelete;
};
bool ParticleTree::findAndDeleteOperation(OctreeElement* element, void* extraData) {
//qDebug() << "findAndDeleteOperation()";
FindAndDeleteParticlesArgs* args = static_cast< FindAndDeleteParticlesArgs*>(extraData);
// if we've found and deleted all our target particles, then we can stop looking
if (args->_idsToDelete.size() <= 0) {
return false;
}
ParticleTreeElement* particleTreeElement = static_cast<ParticleTreeElement*>(element);
//qDebug() << "findAndDeleteOperation() args->_idsToDelete.size():" << args->_idsToDelete.size();
for (QList<uint32_t>::iterator it = args->_idsToDelete.begin(); it != args->_idsToDelete.end(); it++) {
uint32_t particleID = *it;
//qDebug() << "findAndDeleteOperation() particleID:" << particleID;
if (particleTreeElement->removeParticleWithID(particleID)) {
// if the particle was in this element, then remove it from our search list.
//qDebug() << "findAndDeleteOperation() it = args->_idsToDelete.erase(it)";
it = args->_idsToDelete.erase(it);
}
if (it == args->_idsToDelete.end()) {
//qDebug() << "findAndDeleteOperation() breaking";
break;
}
}
// if we've found and deleted all our target particles, then we can stop looking
if (args->_idsToDelete.size() <= 0) {
return false;
}
return true;
}
class FindAndUpdateParticleArgs {
public:
const Particle& searchParticle;
bool found;
};
bool ParticleTree::findAndUpdateOperation(OctreeElement* element, void* extraData) {
FindAndUpdateParticleArgs* args = static_cast<FindAndUpdateParticleArgs*>(extraData);
ParticleTreeElement* particleTreeElement = static_cast<ParticleTreeElement*>(element);
if (particleTreeElement->containsParticle(args->searchParticle)) {
particleTreeElement->updateParticle(args->searchParticle);
args->found = true;
return false; // stop searching
}
return true;
}
void ParticleTree::storeParticle(const Particle& particle, Node* senderNode) {
// First, look for the existing particle in the tree..
FindAndUpdateParticleArgs args = { particle, false };
recurseTreeWithOperation(findAndUpdateOperation, &args);
// if we didn't find it in the tree, then store it...
if (!args.found) {
glm::vec3 position = particle.getPosition();
float size = std::max(MINIMUM_PARTICLE_ELEMENT_SIZE, particle.getRadius());
ParticleTreeElement* element = (ParticleTreeElement*)getOrCreateChildElementAt(position.x, position.y, position.z, size);
element->storeParticle(particle, senderNode);
}
// what else do we need to do here to get reaveraging to work
_isDirty = true;
}
class FindNearPointArgs {
public:
glm::vec3 position;
float targetRadius;
bool found;
const Particle* closestParticle;
float closestParticleDistance;
};
bool ParticleTree::findNearPointOperation(OctreeElement* element, void* extraData) {
FindNearPointArgs* args = static_cast<FindNearPointArgs*>(extraData);
ParticleTreeElement* particleTreeElement = static_cast<ParticleTreeElement*>(element);
glm::vec3 penetration;
bool sphereIntersection = particleTreeElement->getAABox().findSpherePenetration(args->position,
args->targetRadius, penetration);
// If this particleTreeElement contains the point, then search it...
if (sphereIntersection) {
const Particle* thisClosestParticle = particleTreeElement->getClosestParticle(args->position);
// we may have gotten NULL back, meaning no particle was available
if (thisClosestParticle) {
glm::vec3 particlePosition = thisClosestParticle->getPosition();
float distanceFromPointToParticle = glm::distance(particlePosition, args->position);
// If we're within our target radius
if (distanceFromPointToParticle <= args->targetRadius) {
// we are closer than anything else we've found
if (distanceFromPointToParticle < args->closestParticleDistance) {
args->closestParticle = thisClosestParticle;
args->closestParticleDistance = distanceFromPointToParticle;
args->found = true;
}
}
}
// we should be able to optimize this...
return true; // keep searching in case children have closer particles
}
// if this element doesn't contain the point, then none of it's children can contain the point, so stop searching
return false;
}
const Particle* ParticleTree::findClosestParticle(glm::vec3 position, float targetRadius) {
FindNearPointArgs args = { position, targetRadius, false, NULL, FLT_MAX };
lockForRead();
recurseTreeWithOperation(findNearPointOperation, &args);
unlock();
return args.closestParticle;
}
class FindAllNearPointArgs {
public:
glm::vec3 position;
float targetRadius;
QVector<const Particle*> particles;
};
bool ParticleTree::findInSphereOperation(OctreeElement* element, void* extraData) {
FindAllNearPointArgs* args = static_cast<FindAllNearPointArgs*>(extraData);
glm::vec3 penetration;
bool sphereIntersection = element->getAABox().findSpherePenetration(args->position,
args->targetRadius, penetration);
// If this element contains the point, then search it...
if (sphereIntersection) {
ParticleTreeElement* particleTreeElement = static_cast<ParticleTreeElement*>(element);
particleTreeElement->getParticles(args->position, args->targetRadius, args->particles);
return true; // keep searching in case children have closer particles
}
// if this element doesn't contain the point, then none of it's children can contain the point, so stop searching
return false;
}
void ParticleTree::findParticles(const glm::vec3& center, float radius, QVector<const Particle*>& foundParticles) {
FindAllNearPointArgs args = { center, radius };
lockForRead();
recurseTreeWithOperation(findInSphereOperation, &args);
unlock();
// swap the two lists of particle pointers instead of copy
foundParticles.swap(args.particles);
}
class FindParticlesInBoxArgs {
public:
FindParticlesInBoxArgs(const AABox& box)
: _box(box), _foundParticles() {
}
AABox _box;
QVector<Particle*> _foundParticles;
};
bool findInBoxForUpdateOperation(OctreeElement* element, void* extraData) {
FindParticlesInBoxArgs* args = static_cast< FindParticlesInBoxArgs*>(extraData);
const AABox& elementBox = element->getAABox();
if (elementBox.touches(args->_box)) {
ParticleTreeElement* particleTreeElement = static_cast<ParticleTreeElement*>(element);
particleTreeElement->getParticlesForUpdate(args->_box, args->_foundParticles);
return true;
}
return false;
}
void ParticleTree::findParticlesForUpdate(const AABox& box, QVector<Particle*> foundParticles) {
FindParticlesInBoxArgs args(box);
lockForRead();
recurseTreeWithOperation(findInBoxForUpdateOperation, &args);
unlock();
// swap the two lists of particle pointers instead of copy
foundParticles.swap(args._foundParticles);
}
class FindByIDArgs {
public:
uint32_t id;
bool found;
const Particle* foundParticle;
};
bool ParticleTree::findByIDOperation(OctreeElement* element, void* extraData) {
//qDebug() << "ParticleTree::findByIDOperation()....";
FindByIDArgs* args = static_cast<FindByIDArgs*>(extraData);
ParticleTreeElement* particleTreeElement = static_cast<ParticleTreeElement*>(element);
// if already found, stop looking
if (args->found) {
return false;
}
// as the tree element if it has this particle
const Particle* foundParticle = particleTreeElement->getParticleWithID(args->id);
if (foundParticle) {
args->foundParticle = foundParticle;
args->found = true;
return false;
}
// keep looking
return true;
}
const Particle* ParticleTree::findParticleByID(uint32_t id, bool alreadyLocked) {
FindByIDArgs args = { id, false, NULL };
if (!alreadyLocked) {
//qDebug() << "ParticleTree::findParticleByID().... about to call lockForRead()....";
lockForRead();
//qDebug() << "ParticleTree::findParticleByID().... after call lockForRead()....";
}
recurseTreeWithOperation(findByIDOperation, &args);
if (!alreadyLocked) {
unlock();
}
return args.foundParticle;
}
int ParticleTree::processEditPacketData(PACKET_TYPE packetType, unsigned char* packetData, int packetLength,
unsigned char* editData, int maxLength, Node* senderNode) {
int processedBytes = 0;
// we handle these types of "edit" packets
switch (packetType) {
case PACKET_TYPE_PARTICLE_ADD_OR_EDIT: {
bool isValid;
Particle newParticle = Particle::fromEditPacket(editData, maxLength, processedBytes, this, isValid);
if (isValid) {
storeParticle(newParticle, senderNode);
if (newParticle.isNewlyCreated()) {
notifyNewlyCreatedParticle(newParticle, senderNode);
}
}
} break;
// TODO: wire in support here for server to get PACKET_TYPE_PARTICLE_ERASE messages
// instead of using PACKET_TYPE_PARTICLE_ADD_OR_EDIT messages to delete particles
case PACKET_TYPE_PARTICLE_ERASE: {
processedBytes = 0;
} break;
}
return processedBytes;
}
void ParticleTree::notifyNewlyCreatedParticle(const Particle& newParticle, Node* senderNode) {
_newlyCreatedHooksLock.lockForRead();
for (size_t i = 0; i < _newlyCreatedHooks.size(); i++) {
_newlyCreatedHooks[i]->particleCreated(newParticle, senderNode);
}
_newlyCreatedHooksLock.unlock();
}
void ParticleTree::addNewlyCreatedHook(NewlyCreatedParticleHook* hook) {
_newlyCreatedHooksLock.lockForWrite();
_newlyCreatedHooks.push_back(hook);
_newlyCreatedHooksLock.unlock();
}
void ParticleTree::removeNewlyCreatedHook(NewlyCreatedParticleHook* hook) {
_newlyCreatedHooksLock.lockForWrite();
for (size_t i = 0; i < _newlyCreatedHooks.size(); i++) {
if (_newlyCreatedHooks[i] == hook) {
_newlyCreatedHooks.erase(_newlyCreatedHooks.begin() + i);
break;
}
}
_newlyCreatedHooksLock.unlock();
}
bool ParticleTree::updateOperation(OctreeElement* element, void* extraData) {
ParticleTreeUpdateArgs* args = static_cast<ParticleTreeUpdateArgs*>(extraData);
ParticleTreeElement* particleTreeElement = static_cast<ParticleTreeElement*>(element);
particleTreeElement->update(*args);
return true;
}
bool ParticleTree::pruneOperation(OctreeElement* element, void* extraData) {
ParticleTreeElement* particleTreeElement = static_cast<ParticleTreeElement*>(element);
for (int i = 0; i < NUMBER_OF_CHILDREN; i++) {
ParticleTreeElement* childAt = particleTreeElement->getChildAtIndex(i);
if (childAt && childAt->isLeaf() && !childAt->hasParticles()) {
particleTreeElement->deleteChildAtIndex(i);
}
}
return true;
}
void ParticleTree::update() {
_isDirty = true;
ParticleTreeUpdateArgs args = { };
recurseTreeWithOperation(updateOperation, &args);
// now add back any of the particles that moved elements....
int movingParticles = args._movingParticles.size();
for (int i = 0; i < movingParticles; i++) {
bool shouldDie = args._movingParticles[i].getShouldDie();
// if the particle is still inside our total bounds, then re-add it
AABox treeBounds = getRoot()->getAABox();
if (!shouldDie && treeBounds.contains(args._movingParticles[i].getPosition())) {
storeParticle(args._movingParticles[i]);
} else {
uint32_t particleID = args._movingParticles[i].getID();
uint64_t deletedAt = usecTimestampNow();
_recentlyDeletedParticlesLock.lockForWrite();
_recentlyDeletedParticleIDs.insert(deletedAt, particleID);
_recentlyDeletedParticlesLock.unlock();
}
}
// prune the tree...
recurseTreeWithOperation(pruneOperation, NULL);
}
bool ParticleTree::hasParticlesDeletedSince(uint64_t sinceTime) {
// we can probably leverage the ordered nature of QMultiMap to do this quickly...
bool hasSomethingNewer = false;
_recentlyDeletedParticlesLock.lockForRead();
QMultiMap<uint64_t, uint32_t>::const_iterator iterator = _recentlyDeletedParticleIDs.constBegin();
while (iterator != _recentlyDeletedParticleIDs.constEnd()) {
//qDebug() << "considering... time/key:" << iterator.key();
if (iterator.key() > sinceTime) {
//qDebug() << "YES newer... time/key:" << iterator.key();
hasSomethingNewer = true;
}
++iterator;
}
_recentlyDeletedParticlesLock.unlock();
return hasSomethingNewer;
}
// sinceTime is an in/out parameter - it will be side effected with the last time sent out
bool ParticleTree::encodeParticlesDeletedSince(uint64_t& sinceTime, unsigned char* outputBuffer, size_t maxLength,
size_t& outputLength) {
bool hasMoreToSend = true;
unsigned char* copyAt = outputBuffer;
size_t numBytesPacketHeader = populateTypeAndVersion(outputBuffer, PACKET_TYPE_PARTICLE_ERASE);
copyAt += numBytesPacketHeader;
outputLength = numBytesPacketHeader;
uint16_t numberOfIds = 0; // placeholder for now
unsigned char* numberOfIDsAt = copyAt;
memcpy(copyAt, &numberOfIds, sizeof(numberOfIds));
copyAt += sizeof(numberOfIds);
outputLength += sizeof(numberOfIds);
// we keep a multi map of particle IDs to timestamps, we only want to include the particle IDs that have been
// deleted since we last sent to this node
_recentlyDeletedParticlesLock.lockForRead();
QMultiMap<uint64_t, uint32_t>::const_iterator iterator = _recentlyDeletedParticleIDs.constBegin();
while (iterator != _recentlyDeletedParticleIDs.constEnd()) {
QList<uint32_t> values = _recentlyDeletedParticleIDs.values(iterator.key());
for (int valueItem = 0; valueItem < values.size(); ++valueItem) {
//qDebug() << "considering... " << iterator.key() << ": " << values.at(valueItem);
// if the timestamp is more recent then out last sent time, include it
if (iterator.key() > sinceTime) {
//qDebug() << "including... " << iterator.key() << ": " << values.at(valueItem);
uint32_t particleID = values.at(valueItem);
memcpy(copyAt, &particleID, sizeof(particleID));
copyAt += sizeof(particleID);
outputLength += sizeof(particleID);
numberOfIds++;
// check to make sure we have room for one more id...
if (outputLength + sizeof(uint32_t) > maxLength) {
break;
}
}
}
// check to make sure we have room for one more id...
if (outputLength + sizeof(uint32_t) > maxLength) {
// let our caller know how far we got
sinceTime = iterator.key();
break;
}
++iterator;
}
// if we got to the end, then we're done sending
if (iterator == _recentlyDeletedParticleIDs.constEnd()) {
hasMoreToSend = false;
}
_recentlyDeletedParticlesLock.unlock();
// replace the correct count for ids included
memcpy(numberOfIDsAt, &numberOfIds, sizeof(numberOfIds));
return hasMoreToSend;
}
// called by the server when it knows all nodes have been sent deleted packets
void ParticleTree::forgetParticlesDeletedBefore(uint64_t sinceTime) {
//qDebug() << "forgetParticlesDeletedBefore()";
QSet<uint64_t> keysToRemove;
_recentlyDeletedParticlesLock.lockForWrite();
QMultiMap<uint64_t, uint32_t>::iterator iterator = _recentlyDeletedParticleIDs.begin();
// First find all the keys in the map that are older and need to be deleted
while (iterator != _recentlyDeletedParticleIDs.end()) {
//qDebug() << "considering... time/key:" << iterator.key();
if (iterator.key() <= sinceTime) {
//qDebug() << "YES older... time/key:" << iterator.key();
keysToRemove << iterator.key();
}
++iterator;
}
// Now run through the keysToRemove and remove them
foreach (uint64_t value, keysToRemove) {
//qDebug() << "removing the key, _recentlyDeletedParticleIDs.remove(value); time/key:" << value;
_recentlyDeletedParticleIDs.remove(value);
}
_recentlyDeletedParticlesLock.unlock();
//qDebug() << "DONE forgetParticlesDeletedBefore()";
}
void ParticleTree::processEraseMessage(const QByteArray& dataByteArray, const HifiSockAddr& senderSockAddr,
Node* sourceNode) {
//qDebug() << "ParticleTree::processEraseMessage()...";
const unsigned char* packetData = (const unsigned char*)dataByteArray.constData();
const unsigned char* dataAt = packetData;
size_t packetLength = dataByteArray.size();
size_t numBytesPacketHeader = numBytesForPacketHeader(packetData);
size_t processedBytes = numBytesPacketHeader;
dataAt += numBytesPacketHeader;
uint16_t numberOfIds = 0; // placeholder for now
memcpy(&numberOfIds, dataAt, sizeof(numberOfIds));
dataAt += sizeof(numberOfIds);
processedBytes += sizeof(numberOfIds);
//qDebug() << "got erase message for numberOfIds:" << numberOfIds;
if (numberOfIds > 0) {
FindAndDeleteParticlesArgs args;
for (size_t i = 0; i < numberOfIds; i++) {
if (processedBytes + sizeof(uint32_t) > packetLength) {
//qDebug() << "bailing?? processedBytes:" << processedBytes << " packetLength:" << packetLength;
break; // bail to prevent buffer overflow
}
uint32_t particleID = 0; // placeholder for now
memcpy(&particleID, dataAt, sizeof(particleID));
dataAt += sizeof(particleID);
processedBytes += sizeof(particleID);
//qDebug() << "got erase message for particleID:" << particleID;
args._idsToDelete.push_back(particleID);
}
// calling recurse to actually delete the particles
//qDebug() << "calling recurse to actually delete the particles";
recurseTreeWithOperation(findAndDeleteOperation, &args);
}
}