// TODO: This deleteModel() method uses the PacketType_MODEL_ADD_OR_EDIT message to send // a changed model with a shouldDie() property set to true. This works and is currently the only // way to tell the model server to delete a model. But we should change this to use the PacketType_MODEL_ERASE // message which takes a list of model id's to delete. void ModelsScriptingInterface::deleteModel(ModelItemID modelID) { // setup properties to kill the model ModelItemProperties properties; properties.setShouldDie(true); uint32_t actualID = modelID.id; // if the model is unknown, attempt to look it up if (!modelID.isKnownID) { actualID = ModelItem::getIDfromCreatorTokenID(modelID.creatorTokenID); } // if at this point, we know the id, send the update to the model server if (actualID != UNKNOWN_MODEL_ID) { modelID.id = actualID; modelID.isKnownID = true; queueModelMessage(PacketTypeModelAddOrEdit, modelID, properties); } // If we have a local model tree set, then also update it. if (_modelTree) { _modelTree->lockForWrite(); _modelTree->deleteModel(modelID); _modelTree->unlock(); } }
ModelItemProperties ModelsScriptingInterface::getModelProperties(ModelItemID modelID) { ModelItemProperties results; ModelItemID identity = identifyModel(modelID); if (!identity.isKnownID) { results.setIsUnknownID(); return results; } if (_modelTree) { _modelTree->lockForRead(); const ModelItem* model = _modelTree->findModelByID(identity.id, true); if (model) { results.copyFromModelItem(*model); } else { results.setIsUnknownID(); } _modelTree->unlock(); } return results; }
bool ModelItem::encodeModelEditMessageDetails(PacketType command, ModelItemID id, const ModelItemProperties& properties, unsigned char* bufferOut, int sizeIn, int& sizeOut) { bool success = true; // assume the best unsigned char* copyAt = bufferOut; sizeOut = 0; // get the octal code for the modelItem // this could be a problem if the caller doesn't include position.... glm::vec3 rootPosition(0); float rootScale = 0.5f; unsigned char* octcode = pointToOctalCode(rootPosition.x, rootPosition.y, rootPosition.z, rootScale); // TODO: Consider this old code... including the correct octree for where the modelItem will go matters for // modelItem servers with different jurisdictions, but for now, we'll send everything to the root, since the // tree does the right thing... // //unsigned char* octcode = pointToOctalCode(details[i].position.x, details[i].position.y, // details[i].position.z, details[i].radius); int octets = numberOfThreeBitSectionsInCode(octcode); int lengthOfOctcode = bytesRequiredForCodeLength(octets); // add it to our message memcpy(copyAt, octcode, lengthOfOctcode); copyAt += lengthOfOctcode; sizeOut += lengthOfOctcode; // Now add our edit content details... bool isNewModelItem = (id.id == NEW_MODEL); // id memcpy(copyAt, &id.id, sizeof(id.id)); copyAt += sizeof(id.id); sizeOut += sizeof(id.id); // special case for handling "new" modelItems if (isNewModelItem) { // If this is a NEW_MODEL, then we assume that there's an additional uint32_t creatorToken, that // we want to send back to the creator as an map to the actual id memcpy(copyAt, &id.creatorTokenID, sizeof(id.creatorTokenID)); copyAt += sizeof(id.creatorTokenID); sizeOut += sizeof(id.creatorTokenID); } // lastEdited quint64 lastEdited = properties.getLastEdited(); memcpy(copyAt, &lastEdited, sizeof(lastEdited)); copyAt += sizeof(lastEdited); sizeOut += sizeof(lastEdited); // For new modelItems, all remaining items are mandatory, for an edited modelItem, All of the remaining items are // optional, and may or may not be included based on their included values in the properties included bits uint16_t packetContainsBits = properties.getChangedBits(); if (!isNewModelItem) { memcpy(copyAt, &packetContainsBits, sizeof(packetContainsBits)); copyAt += sizeof(packetContainsBits); sizeOut += sizeof(packetContainsBits); } // radius if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_RADIUS) == MODEL_PACKET_CONTAINS_RADIUS)) { float radius = properties.getRadius() / (float) TREE_SCALE; memcpy(copyAt, &radius, sizeof(radius)); copyAt += sizeof(radius); sizeOut += sizeof(radius); } // position if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_POSITION) == MODEL_PACKET_CONTAINS_POSITION)) { glm::vec3 position = properties.getPosition() / (float)TREE_SCALE; memcpy(copyAt, &position, sizeof(position)); copyAt += sizeof(position); sizeOut += sizeof(position); } // color if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_COLOR) == MODEL_PACKET_CONTAINS_COLOR)) { rgbColor color = { properties.getColor().red, properties.getColor().green, properties.getColor().blue }; memcpy(copyAt, color, sizeof(color)); copyAt += sizeof(color); sizeOut += sizeof(color); } // shoulDie if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_SHOULDDIE) == MODEL_PACKET_CONTAINS_SHOULDDIE)) { bool shouldDie = properties.getShouldDie(); memcpy(copyAt, &shouldDie, sizeof(shouldDie)); copyAt += sizeof(shouldDie); sizeOut += sizeof(shouldDie); } // modelURL if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_MODEL_URL) == MODEL_PACKET_CONTAINS_MODEL_URL)) { uint16_t urlLength = properties.getModelURL().size() + 1; memcpy(copyAt, &urlLength, sizeof(urlLength)); copyAt += sizeof(urlLength); sizeOut += sizeof(urlLength); memcpy(copyAt, qPrintable(properties.getModelURL()), urlLength); copyAt += urlLength; sizeOut += urlLength; } // modelRotation if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_MODEL_ROTATION) == MODEL_PACKET_CONTAINS_MODEL_ROTATION)) { int bytes = packOrientationQuatToBytes(copyAt, properties.getModelRotation()); copyAt += bytes; sizeOut += bytes; } // animationURL if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_ANIMATION_URL) == MODEL_PACKET_CONTAINS_ANIMATION_URL)) { uint16_t urlLength = properties.getAnimationURL().size() + 1; memcpy(copyAt, &urlLength, sizeof(urlLength)); copyAt += sizeof(urlLength); sizeOut += sizeof(urlLength); memcpy(copyAt, qPrintable(properties.getAnimationURL()), urlLength); copyAt += urlLength; sizeOut += urlLength; } // animationIsPlaying if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_ANIMATION_PLAYING) == MODEL_PACKET_CONTAINS_ANIMATION_PLAYING)) { bool animationIsPlaying = properties.getAnimationIsPlaying(); memcpy(copyAt, &animationIsPlaying, sizeof(animationIsPlaying)); copyAt += sizeof(animationIsPlaying); sizeOut += sizeof(animationIsPlaying); } // animationFrameIndex if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_ANIMATION_FRAME) == MODEL_PACKET_CONTAINS_ANIMATION_FRAME)) { float animationFrameIndex = properties.getAnimationFrameIndex(); memcpy(copyAt, &animationFrameIndex, sizeof(animationFrameIndex)); copyAt += sizeof(animationFrameIndex); sizeOut += sizeof(animationFrameIndex); } // animationFPS if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_ANIMATION_FPS) == MODEL_PACKET_CONTAINS_ANIMATION_FPS)) { float animationFPS = properties.getAnimationFPS(); memcpy(copyAt, &animationFPS, sizeof(animationFPS)); copyAt += sizeof(animationFPS); sizeOut += sizeof(animationFPS); } bool wantDebugging = false; if (wantDebugging) { qDebug("encodeModelItemEditMessageDetails()...."); qDebug("ModelItem id :%u", id.id); qDebug(" nextID:%u", _nextID); } // cleanup delete[] octcode; return success; }
void ModelTests::modelTreeTests(bool verbose) { int testsTaken = 0; int testsPassed = 0; int testsFailed = 0; if (verbose) { qDebug() << "******************************************************************************************"; } qDebug() << "ModelTests::modelTreeTests()"; // Tree, id, and model properties used in many tests below... ModelTree tree; uint32_t id = 1; ModelItemID modelID(id); modelID.isKnownID = false; // this is a temporary workaround to allow local tree models to be added with known IDs ModelItemProperties properties; float oneMeter = 1.0f; float halfMeter = oneMeter / 2.0f; float halfOfDomain = TREE_SCALE * 0.5f; glm::vec3 positionNearOriginInMeters(oneMeter, oneMeter, oneMeter); // when using properties, these are in meter not tree units glm::vec3 positionAtCenterInMeters(halfOfDomain, halfOfDomain, halfOfDomain); glm::vec3 positionNearOriginInTreeUnits = positionNearOriginInMeters / (float)TREE_SCALE; glm::vec3 positionAtCenterInTreeUnits = positionAtCenterInMeters / (float)TREE_SCALE; { testsTaken++; QString testName = "add model to tree and search"; if (verbose) { qDebug() << "Test" << testsTaken <<":" << qPrintable(testName); } properties.setPosition(positionAtCenterInMeters); properties.setRadius(halfMeter); properties.setModelURL("https://s3-us-west-1.amazonaws.com/highfidelity-public/ozan/theater.fbx"); tree.addModel(modelID, properties); float targetRadius = oneMeter * 2.0 / (float)TREE_SCALE; // in tree units const ModelItem* foundModelByRadius = tree.findClosestModel(positionAtCenterInTreeUnits, targetRadius); const ModelItem* foundModelByID = tree.findModelByID(id); if (verbose) { qDebug() << "foundModelByRadius=" << foundModelByRadius; qDebug() << "foundModelByID=" << foundModelByID; } bool passed = foundModelByRadius && foundModelByID && (foundModelByRadius == foundModelByID); if (passed) { testsPassed++; } else { testsFailed++; qDebug() << "FAILED - Test" << testsTaken <<":" << qPrintable(testName); } } modelID.isKnownID = true; // this is a temporary workaround to allow local tree models to be added with known IDs { testsTaken++; QString testName = "change position of model in tree"; if (verbose) { qDebug() << "Test" << testsTaken <<":" << qPrintable(testName); } glm::vec3 newPosition = positionNearOriginInMeters; properties.setPosition(newPosition); tree.updateModel(modelID, properties); float targetRadius = oneMeter * 2.0 / (float)TREE_SCALE; // in tree units const ModelItem* foundModelByRadius = tree.findClosestModel(positionNearOriginInTreeUnits, targetRadius); const ModelItem* foundModelByID = tree.findModelByID(id); if (verbose) { qDebug() << "foundModelByRadius=" << foundModelByRadius; qDebug() << "foundModelByID=" << foundModelByID; } // NOTE: This test is currently expected to fail in the production code. There's a bug in ModelTree::updateModel() // that does not update the actual location of the model into the correct element when modified locally. So this // test will fail. There's a new optimized and correctly working version of updateModel() that fixes this problem. bool passed = foundModelByRadius && foundModelByID && (foundModelByRadius == foundModelByID); if (passed) { testsPassed++; qDebug() << "NOTE: Expected to FAIL - Test" << testsTaken <<":" << qPrintable(testName); } else { testsFailed++; qDebug() << "FAILED - Test" << testsTaken <<":" << qPrintable(testName); qDebug() << "NOTE: Expected to FAIL - Test" << testsTaken <<":" << qPrintable(testName); } } { testsTaken++; QString testName = "change position of model in tree back to center"; if (verbose) { qDebug() << "Test" << testsTaken <<":" << qPrintable(testName); } glm::vec3 newPosition = positionAtCenterInMeters; properties.setPosition(newPosition); tree.updateModel(modelID, properties); float targetRadius = oneMeter * 2.0 / (float)TREE_SCALE; // in tree units const ModelItem* foundModelByRadius = tree.findClosestModel(positionAtCenterInTreeUnits, targetRadius); const ModelItem* foundModelByID = tree.findModelByID(id); if (verbose) { qDebug() << "foundModelByRadius=" << foundModelByRadius; qDebug() << "foundModelByID=" << foundModelByID; } bool passed = foundModelByRadius && foundModelByID && (foundModelByRadius == foundModelByID); if (passed) { testsPassed++; } else { testsFailed++; qDebug() << "FAILED - Test" << testsTaken <<":" << qPrintable(testName); } } { testsTaken++; QString testName = "Performance - findClosestModel() 1,000,000 times"; if (verbose) { qDebug() << "Test" << testsTaken <<":" << qPrintable(testName); } float targetRadius = oneMeter * 2.0 / (float)TREE_SCALE; // in tree units const int TEST_ITERATIONS = 1000000; quint64 start = usecTimestampNow(); const ModelItem* foundModelByRadius = NULL; for (int i = 0; i < TEST_ITERATIONS; i++) { foundModelByRadius = tree.findClosestModel(positionAtCenterInTreeUnits, targetRadius); } quint64 end = usecTimestampNow(); if (verbose) { qDebug() << "foundModelByRadius=" << foundModelByRadius; } bool passed = foundModelByRadius; if (passed) { testsPassed++; } else { testsFailed++; qDebug() << "FAILED - Test" << testsTaken <<":" << qPrintable(testName); } float USECS_PER_MSECS = 1000.0f; float elapsedInMSecs = (float)(end - start) / USECS_PER_MSECS; qDebug() << "TIME - Test" << testsTaken <<":" << qPrintable(testName) << "elapsed=" << elapsedInMSecs << "msecs"; } { testsTaken++; QString testName = "Performance - findModelByID() 1,000,000 times"; if (verbose) { qDebug() << "Test" << testsTaken <<":" << qPrintable(testName); } const int TEST_ITERATIONS = 1000000; quint64 start = usecTimestampNow(); const ModelItem* foundModelByID = NULL; for (int i = 0; i < TEST_ITERATIONS; i++) { foundModelByID = tree.findModelByID(id); } quint64 end = usecTimestampNow(); if (verbose) { qDebug() << "foundModelByID=" << foundModelByID; } bool passed = foundModelByID; if (passed) { testsPassed++; } else { testsFailed++; qDebug() << "FAILED - Test" << testsTaken <<":" << qPrintable(testName); } float USECS_PER_MSECS = 1000.0f; float elapsedInMSecs = (float)(end - start) / USECS_PER_MSECS; qDebug() << "TIME - Test" << testsTaken <<":" << qPrintable(testName) << "elapsed=" << elapsedInMSecs << "msecs"; } { testsTaken++; QString testName = "Performance - add model to tree 10,000 times"; if (verbose) { qDebug() << "Test" << testsTaken <<":" << qPrintable(testName); } const int TEST_ITERATIONS = 10000; quint64 start = usecTimestampNow(); for (int i = 0; i < TEST_ITERATIONS; i++) { uint32_t id = i + 2; // make sure it doesn't collide with previous model ids ModelItemID modelID(id); modelID.isKnownID = false; // this is a temporary workaround to allow local tree models to be added with known IDs float randomX = randFloatInRange(0.0f ,(float)TREE_SCALE); float randomY = randFloatInRange(0.0f ,(float)TREE_SCALE); float randomZ = randFloatInRange(0.0f ,(float)TREE_SCALE); glm::vec3 randomPositionInMeters(randomX,randomY,randomZ); properties.setPosition(randomPositionInMeters); properties.setRadius(halfMeter); properties.setModelURL("https://s3-us-west-1.amazonaws.com/highfidelity-public/ozan/theater.fbx"); tree.addModel(modelID, properties); } quint64 end = usecTimestampNow(); bool passed = true; if (passed) { testsPassed++; } else { testsFailed++; qDebug() << "FAILED - Test" << testsTaken <<":" << qPrintable(testName); } float USECS_PER_MSECS = 1000.0f; float elapsedInMSecs = (float)(end - start) / USECS_PER_MSECS; qDebug() << "TIME - Test" << testsTaken <<":" << qPrintable(testName) << "elapsed=" << elapsedInMSecs << "msecs"; } qDebug() << " tests passed:" << testsPassed << "out of" << testsTaken; if (verbose) { qDebug() << "******************************************************************************************"; } }
void ModelItemPropertiesFromScriptValue(const QScriptValue &object, ModelItemProperties& properties) { properties.copyFromScriptValue(object); }
QScriptValue ModelItemPropertiesToScriptValue(QScriptEngine* engine, const ModelItemProperties& properties) { return properties.copyToScriptValue(engine); }
void ModelItem::setProperties(const ModelItemProperties& properties) { properties.copyToModelItem(*this); }
ModelItemProperties ModelItem::getProperties() const { ModelItemProperties properties; properties.copyFromModelItem(*this); return properties; }