EntityItemProperties EntityScriptingInterface::getEntityProperties(EntityItemID entityID) { EntityItemProperties results; EntityItemID identity = identifyEntity(entityID); if (_entityTree) { _entityTree->lockForRead(); EntityItem* entity = const_cast<EntityItem*>(_entityTree->findEntityByEntityItemID(identity)); if (entity) { results = entity->getProperties(); // TODO: improve sitting points and naturalDimensions in the future, // for now we've included the old sitting points model behavior for entity types that are models // we've also added this hack for setting natural dimensions of models if (entity->getType() == EntityTypes::Model) { const FBXGeometry* geometry = _entityTree->getGeometryForEntity(entity); if (geometry) { results.setSittingPoints(geometry->sittingPoints); Extents meshExtents = geometry->getUnscaledMeshExtents(); results.setNaturalDimensions(meshExtents.maximum - meshExtents.minimum); } } } else { results.setIsUnknownID(); } _entityTree->unlock(); } return results; }
void EntityTreeElement::initializeExtraEncodeData(EncodeBitstreamParams& params) const { OctreeElementExtraEncodeData* extraEncodeData = params.extraEncodeData; assert(extraEncodeData); // EntityTrees always require extra encode data on their encoding passes // Check to see if this element yet has encode data... if it doesn't create it if (!extraEncodeData->contains(this)) { EntityTreeElementExtraEncodeData* entityTreeElementExtraEncodeData = new EntityTreeElementExtraEncodeData(); entityTreeElementExtraEncodeData->elementCompleted = (_entityItems->size() == 0); for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { EntityTreeElement* child = getChildAtIndex(i); if (!child) { entityTreeElementExtraEncodeData->childCompleted[i] = true; // if no child exists, it is completed } else { if (child->hasEntities()) { entityTreeElementExtraEncodeData->childCompleted[i] = false; // HAS ENTITIES NEEDS ENCODING } else { entityTreeElementExtraEncodeData->childCompleted[i] = true; // child doesn't have enities, it is completed } } } for (uint16_t i = 0; i < _entityItems->size(); i++) { EntityItem* entity = (*_entityItems)[i]; entityTreeElementExtraEncodeData->entities.insert(entity->getEntityItemID(), entity->getEntityProperties(params)); } // TODO: some of these inserts might be redundant!!! extraEncodeData->insert(this, entityTreeElementExtraEncodeData); } }
// private void EntitySimulation::sortEntitiesThatMoved() { // NOTE: this is only for entities that have been moved by THIS EntitySimulation. // External changes to entity position/shape are expected to be sorted outside of the EntitySimulation. PerformanceTimer perfTimer("sortingEntities"); MovingEntitiesOperator moveOperator(_entityTree); AACube domainBounds(glm::vec3(0.0f,0.0f,0.0f), (float)TREE_SCALE); QSet<EntityItem*>::iterator itemItr = _entitiesToBeSorted.begin(); while (itemItr != _entitiesToBeSorted.end()) { EntityItem* entity = *itemItr; // check to see if this movement has sent the entity outside of the domain. AACube newCube = entity->getMaximumAACube(); if (!domainBounds.touches(newCube)) { qCDebug(entities) << "Entity " << entity->getEntityItemID() << " moved out of domain bounds."; _entitiesToDelete.insert(entity); _mortalEntities.remove(entity); _updateableEntities.remove(entity); removeEntityInternal(entity); itemItr = _entitiesToBeSorted.erase(itemItr); } else { moveOperator.addEntityToMoveList(entity, newCube); ++itemItr; } } if (moveOperator.hasMovingEntities()) { PerformanceTimer perfTimer("recurseTreeWithOperator"); _entityTree->recurseTreeWithOperator(&moveOperator); } sortEntitiesThatMovedInternal(); _entitiesToBeSorted.clear(); }
void Map::DestroyBlock(int i, int j) { if (GetBlock(i, j) == 0) return; int id = blocks[i][j].Id; SetBlock(i, j, 0); EntityItem* item = new EntityItem(Block::GetBlockById(id)->GetDrop()); item->SetPosition(glm::vec2(i + rand() % 500 / 1000.0, j + rand() % 500 / 1000.0)); drops.push_back(item); entities.push_back(item); }
// private void EntitySimulation::callUpdateOnEntitiesThatNeedIt(const quint64& now) { PerformanceTimer perfTimer("updatingEntities"); QSet<EntityItem*>::iterator itemItr = _updateableEntities.begin(); while (itemItr != _updateableEntities.end()) { EntityItem* entity = *itemItr; // TODO: catch transition from needing update to not as a "change" // so we don't have to scan for it here. if (!entity->needsToCallUpdate()) { itemItr = _updateableEntities.erase(itemItr); } else { entity->update(now); ++itemItr; } } }
EntityItemID EntityScriptingInterface::editEntity(EntityItemID entityID, const EntityItemProperties& properties) { EntityItemID actualID = entityID; // if the entity is unknown, attempt to look it up if (!entityID.isKnownID) { actualID = EntityItemID::getIDfromCreatorTokenID(entityID.creatorTokenID); if (actualID.id != UNKNOWN_ENTITY_ID) { entityID.id = actualID.id; entityID.isKnownID = true; } } // If we have a local entity tree set, then also update it. We can do this even if we don't know // the actual id, because we can edit out local entities just with creatorTokenID if (_entityTree) { _entityTree->lockForWrite(); _entityTree->updateEntity(entityID, properties); _entityTree->unlock(); } // if at this point, we know the id, send the update to the entity server if (entityID.isKnownID) { // make sure the properties has a type, so that the encode can know which properties to include if (properties.getType() == EntityTypes::Unknown) { EntityItem* entity = _entityTree->findEntityByEntityItemID(entityID); if (entity) { EntityItemProperties tempProperties = properties; tempProperties.setType(entity->getType()); queueEntityMessage(PacketTypeEntityAddOrEdit, entityID, tempProperties); return entityID; } } // if the properties already includes the type, then use it as is queueEntityMessage(PacketTypeEntityAddOrEdit, entityID, properties); } return entityID; }
void EntityScriptingInterface::deleteEntity(EntityItemID entityID) { EntityItemID actualID = entityID; // if the entity is unknown, attempt to look it up if (!entityID.isKnownID) { actualID = EntityItemID::getIDfromCreatorTokenID(entityID.creatorTokenID); if (actualID.id != UNKNOWN_ENTITY_ID) { entityID.id = actualID.id; entityID.isKnownID = true; } } bool shouldDelete = true; // If we have a local entity tree set, then also update it. if (_entityTree) { _entityTree->lockForWrite(); EntityItem* entity = const_cast<EntityItem*>(_entityTree->findEntityByEntityItemID(actualID)); if (entity) { if (entity->getLocked()) { shouldDelete = false; } else { _entityTree->deleteEntity(entityID); } } _entityTree->unlock(); } // if at this point, we know the id, and we should still delete the entity, send the update to the entity server if (shouldDelete && entityID.isKnownID) { getEntityPacketSender()->queueEraseEntityMessage(entityID); } }
// private void EntitySimulation::expireMortalEntities(const quint64& now) { if (now > _nextExpiry) { // only search for expired entities if we expect to find one _nextExpiry = quint64(-1); QSet<EntityItem*>::iterator itemItr = _mortalEntities.begin(); while (itemItr != _mortalEntities.end()) { EntityItem* entity = *itemItr; quint64 expiry = entity->getExpiry(); if (expiry < now) { _entitiesToDelete.insert(entity); itemItr = _mortalEntities.erase(itemItr); _updateableEntities.remove(entity); _entitiesToBeSorted.remove(entity); removeEntityInternal(entity); } else { if (expiry < _nextExpiry) { // remeber the smallest _nextExpiry so we know when to start the next search _nextExpiry = expiry; } ++itemItr; } } } }
void OctreeTests::modelItemTests() { #if 0 // TODO - repair/replace these bool verbose = true; //verbose = true; EntityTreeElementExtraEncodeData modelTreeElementExtraEncodeData; int testsTaken = 0; int testsPassed = 0; int testsFailed = 0; if (verbose) { qDebug() << "******************************************************************************************"; } qDebug() << "OctreeTests::modelItemTests()"; EncodeBitstreamParams params; OctreePacketData packetData; EntityItem entityItem; entityItem.setID(1042); entityItem.setModelURL("http://foo.com/foo.fbx"); bool appendResult = entityItem.appendEntityData(&packetData, params, &modelTreeElementExtraEncodeData); int bytesWritten = packetData.getUncompressedSize(); if (verbose) { qDebug() << "Test 1: bytesRead == bytesWritten ..."; qDebug() << "appendResult=" << appendResult; qDebug() << "bytesWritten=" << bytesWritten; } { ReadBitstreamToTreeParams args; EntityItem modelItemFromBuffer; const unsigned char* data = packetData.getUncompressedData(); int bytesLeftToRead = packetData.getUncompressedSize(); int bytesRead = modelItemFromBuffer.readEntityDataFromBuffer(data, bytesLeftToRead, args); if (verbose) { qDebug() << "bytesRead=" << bytesRead; qDebug() << "modelItemFromBuffer.getID()=" << modelItemFromBuffer.getID(); qDebug() << "modelItemFromBuffer.getModelURL()=" << modelItemFromBuffer.getModelURL(); } QCOMPARE(bytesRead, bytesWritten); if (verbose) { qDebug() << "Test 2: modelItemFromBuffer.getModelURL() == 'http://foo.com/foo.fbx'"; } QCOMPARE(modelItemFromBuffer.getModelURL(), "http://foo.com/foo.fbx"); } // TEST 3: // Reset the packet, fill it with data so that EntityItem header won't fit, and verify that we don't let it fit { packetData.reset(); int remainingSpace = 10; int almostFullOfData = MAX_OCTREE_UNCOMRESSED_PACKET_SIZE - remainingSpace; QByteArray garbageData(almostFullOfData, 0); packetData.appendValue(garbageData); appendResult = entityItem.appendEntityData(&packetData, params, &modelTreeElementExtraEncodeData); bytesWritten = packetData.getUncompressedSize() - almostFullOfData; if (verbose) { qDebug() << "Test 3: attempt to appendEntityData in nearly full packetData ..."; qDebug() << "appendResult=" << appendResult; qDebug() << "bytesWritten=" << bytesWritten; } QCOMPARE(appendResult, false); QCOMPARE(bytesWritten, 0); } // TEST 4: // Reset the packet, fill it with data so that some of EntityItem won't fit, and verify that we write what can fit { packetData.reset(); int remainingSpace = 50; int almostFullOfData = MAX_OCTREE_UNCOMRESSED_PACKET_SIZE - remainingSpace; QByteArray garbageData(almostFullOfData, 0); packetData.appendValue(garbageData); appendResult = entityItem.appendEntityData(&packetData, params, &modelTreeElementExtraEncodeData); bytesWritten = packetData.getUncompressedSize() - almostFullOfData; if (verbose) { qDebug() << "Test 4: attempt to appendEntityData in nearly full packetData which some should fit ..."; qDebug() << "appendResult=" << appendResult; qDebug() << "bytesWritten=" << bytesWritten; } QCOMPARE(appendResult, true); ReadBitstreamToTreeParams args; EntityItem modelItemFromBuffer; const unsigned char* data = packetData.getUncompressedData() + almostFullOfData; int bytesLeftToRead = packetData.getUncompressedSize() - almostFullOfData; int bytesRead = modelItemFromBuffer.readEntityDataFromBuffer(data, bytesLeftToRead, args); if (verbose) { qDebug() << "Test 5: partial EntityItem written ... bytesRead == bytesWritten..."; qDebug() << "bytesRead=" << bytesRead; qDebug() << "modelItemFromBuffer.getID()=" << modelItemFromBuffer.getID(); qDebug() << "modelItemFromBuffer.getModelURL()=" << modelItemFromBuffer.getModelURL(); } QCOMPARE(bytesRead, bytesWritten); if (verbose) { qDebug() << "Test 6: partial EntityItem written ... getModelURL() NOT SET ..."; } QCOMPARE(modelItemFromBuffer.getModelURL(), ""); } if (verbose) { qDebug() << "******************************************************************************************"; } QCOMPARE(testsPassed, testsTaken); if (verbose) { qDebug() << "******************************************************************************************"; } #endif }
void TreeWidgetController::reinitialize() { QProgressDialog dlg(tr("Reinitializing view..."), QString(), 0, 3, this); // Perform a BFS and remember the expanded entity items. map<DatabaseEntity::EntityType, set<int> > expandedEntities; queue<QTreeWidgetItem *> q; q.push(invisibleRootItem()); while (!q.empty()) { QTreeWidgetItem *currentItem = q.front(); q.pop(); // Don't bother with non-expanded items. if (currentItem != invisibleRootItem() && !currentItem->isExpanded()) continue; // Enqueue the current item's children. for (int i = 0; i < currentItem->childCount(); i++) q.push(currentItem->child(i)); // Don't bother with non-entity types. if (currentItem->type() != EntityItem::EntityType) continue; EntityItem *entityItem = static_cast<EntityItem *>(currentItem); if (entityItem->type() != DatabaseEntity::Feature) { const int id = getEntityID(entityItem->entityPtr()); set<int> &s = expandedEntities[entityItem->entityPtr()->entityType()]; s.insert(id); } } // Backup the expansion-states of the root items. const bool expandResponses = _rootResponses->isExpanded(); const bool expandLabels = _rootLabels->isExpanded(); const bool expandProcesses = _rootProcesses->isExpanded(); const bool expandClassificationObjects = _rootClassificationObjects->isExpanded(); // Clear the model. if (_rootResponses) invisibleRootItem()->removeChild(_rootResponses); if (_rootLabels) invisibleRootItem()->removeChild(_rootLabels); if (_rootProcesses) invisibleRootItem()->removeChild(_rootProcesses); if (_rootClassificationObjects) invisibleRootItem()->removeChild(_rootClassificationObjects); dlg.setValue(1); // Reload. initializeView(); dlg.setValue(2); // Perform a BFS and reopen the formerly expanded items. q.push(invisibleRootItem()); while (!q.empty()) { QTreeWidgetItem *currentItem = q.front(); q.pop(); // Enqueue the current item's children. for (int i = 0; i < currentItem->childCount(); i++) q.push(currentItem->child(i)); // Don't bother with non-entity types. if (currentItem->type() != EntityItem::EntityType) continue; // Determine the entity's id and reexpand this item if this id is stored // in the entity type's corresponding set of expanded ids. EntityItem *entityItem = static_cast<EntityItem *>(currentItem); const int id = getEntityID(entityItem->entityPtr()); if (id) { set<int> &s = expandedEntities[entityItem->entityPtr()->entityType()]; if (s.find(id) != s.end()) { QTreeWidgetItem *item = entityItem; while (item && item != invisibleRootItem()) { item->setExpanded(true); item = item->parent(); } } } } dlg.setValue(3); // Restore the expansion-states of the root items. if (expandResponses && !_rootResponses->isExpanded()) _rootResponses->setExpanded(true); if (expandLabels && !_rootLabels->isExpanded()) _rootLabels->setExpanded(true); if (expandProcesses && !_rootProcesses->isExpanded()) _rootProcesses->setExpanded(true); if (expandClassificationObjects && !_rootClassificationObjects->isExpanded()) _rootClassificationObjects->setExpanded(true); }
OctreeElement::AppendState EntityTreeElement::appendElementData(OctreePacketData* packetData, EncodeBitstreamParams& params) const { OctreeElement::AppendState appendElementState = OctreeElement::COMPLETED; // assume the best... // first, check the params.extraEncodeData to see if there's any partial re-encode data for this element OctreeElementExtraEncodeData* extraEncodeData = params.extraEncodeData; EntityTreeElementExtraEncodeData* entityTreeElementExtraEncodeData = NULL; bool hadElementExtraData = false; if (extraEncodeData && extraEncodeData->contains(this)) { entityTreeElementExtraEncodeData = static_cast<EntityTreeElementExtraEncodeData*>(extraEncodeData->value(this)); hadElementExtraData = true; } else { // if there wasn't one already, then create one entityTreeElementExtraEncodeData = new EntityTreeElementExtraEncodeData(); entityTreeElementExtraEncodeData->elementCompleted = (_entityItems->size() == 0); for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { EntityTreeElement* child = getChildAtIndex(i); if (!child) { entityTreeElementExtraEncodeData->childCompleted[i] = true; // if no child exists, it is completed } else { if (child->hasEntities()) { entityTreeElementExtraEncodeData->childCompleted[i] = false; } else { entityTreeElementExtraEncodeData->childCompleted[i] = true; // if the child doesn't have enities, it is completed } } } for (uint16_t i = 0; i < _entityItems->size(); i++) { EntityItem* entity = (*_entityItems)[i]; entityTreeElementExtraEncodeData->entities.insert(entity->getEntityItemID(), entity->getEntityProperties(params)); } } //assert(extraEncodeData); //assert(extraEncodeData->contains(this)); //entityTreeElementExtraEncodeData = static_cast<EntityTreeElementExtraEncodeData*>(extraEncodeData->value(this)); LevelDetails elementLevel = packetData->startLevel(); // write our entities out... first determine which of the entities are in view based on our params uint16_t numberOfEntities = 0; uint16_t actualNumberOfEntities = 0; QVector<uint16_t> indexesOfEntitiesToInclude; // It's possible that our element has been previous completed. In this case we'll simply not include any of our // entities for encoding. This is needed because we encode the element data at the "parent" level, and so we // need to handle the case where our sibling elements need encoding but we don't. if (!entityTreeElementExtraEncodeData->elementCompleted) { for (uint16_t i = 0; i < _entityItems->size(); i++) { EntityItem* entity = (*_entityItems)[i]; bool includeThisEntity = true; if (!params.forceSendScene && entity->getLastChangedOnServer() < params.lastViewFrustumSent) { includeThisEntity = false; } if (hadElementExtraData) { includeThisEntity = includeThisEntity && entityTreeElementExtraEncodeData->entities.contains(entity->getEntityItemID()); } if (includeThisEntity && params.viewFrustum) { // we want to use the maximum possible box for this, so that we don't have to worry about the nuance of // simulation changing what's visible. consider the case where the entity contains an angular velocity // the entity may not be in view and then in view a frame later, let the client side handle it's view // frustum culling on rendering. AACube entityCube = entity->getMaximumAACube(); entityCube.scale(TREE_SCALE); if (params.viewFrustum->cubeInFrustum(entityCube) == ViewFrustum::OUTSIDE) { includeThisEntity = false; // out of view, don't include it } } if (includeThisEntity) { indexesOfEntitiesToInclude << i; numberOfEntities++; } } } int numberOfEntitiesOffset = packetData->getUncompressedByteOffset(); bool successAppendEntityCount = packetData->appendValue(numberOfEntities); if (successAppendEntityCount) { foreach (uint16_t i, indexesOfEntitiesToInclude) { EntityItem* entity = (*_entityItems)[i]; LevelDetails entityLevel = packetData->startLevel(); OctreeElement::AppendState appendEntityState = entity->appendEntityData(packetData, params, entityTreeElementExtraEncodeData); // If none of this entity data was able to be appended, then discard it // and don't include it in our entity count if (appendEntityState == OctreeElement::NONE) { packetData->discardLevel(entityLevel); } else { // If either ALL or some of it got appended, then end the level (commit it) // and include the entity in our final count of entities packetData->endLevel(entityLevel); actualNumberOfEntities++; } // If the entity item got completely appended, then we can remove it from the extra encode data if (appendEntityState == OctreeElement::COMPLETED) { entityTreeElementExtraEncodeData->entities.remove(entity->getEntityItemID()); } // If any part of the entity items didn't fit, then the element is considered partial // NOTE: if the entity item didn't fit or only partially fit, then the entity item should have // added itself to the extra encode data. if (appendEntityState != OctreeElement::COMPLETED) { appendElementState = OctreeElement::PARTIAL; } } } else {