From aac42058ac5e152e2077f0d7ea9aace380faf123 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Wed, 30 Apr 2014 09:25:52 -0700 Subject: [PATCH 01/13] first cut at modelserver --- assignment-client/CMakeLists.txt | 1 + assignment-client/src/Agent.cpp | 18 +++++++- assignment-client/src/Agent.h | 4 ++ assignment-client/src/octree/OctreeServer.cpp | 2 +- domain-server/src/DomainServer.cpp | 2 +- interface/CMakeLists.txt | 1 + interface/src/Application.cpp | 46 +++++++++++++++++-- interface/src/Application.h | 2 + interface/src/ui/OctreeStatsDialog.cpp | 3 ++ interface/src/ui/Stats.cpp | 1 + libraries/networking/src/Assignment.cpp | 2 + libraries/networking/src/Assignment.h | 1 + libraries/networking/src/Node.cpp | 1 + libraries/networking/src/Node.h | 1 + libraries/networking/src/PacketHeaders.h | 5 ++ libraries/particles/src/ParticleTree.cpp | 2 +- libraries/particles/src/ParticleTree.h | 1 + libraries/script-engine/CMakeLists.txt | 1 + libraries/script-engine/src/ScriptEngine.cpp | 2 + libraries/script-engine/src/ScriptEngine.h | 6 +++ 20 files changed, 92 insertions(+), 10 deletions(-) diff --git a/assignment-client/CMakeLists.txt b/assignment-client/CMakeLists.txt index b78d4f81f9..e783001228 100644 --- a/assignment-client/CMakeLists.txt +++ b/assignment-client/CMakeLists.txt @@ -30,6 +30,7 @@ link_hifi_library(octree ${TARGET_NAME} "${ROOT_DIR}") link_hifi_library(voxels ${TARGET_NAME} "${ROOT_DIR}") link_hifi_library(fbx ${TARGET_NAME} "${ROOT_DIR}") link_hifi_library(particles ${TARGET_NAME} "${ROOT_DIR}") +link_hifi_library(models ${TARGET_NAME} "${ROOT_DIR}") link_hifi_library(metavoxels ${TARGET_NAME} "${ROOT_DIR}") link_hifi_library(networking ${TARGET_NAME} "${ROOT_DIR}") link_hifi_library(script-engine ${TARGET_NAME} "${ROOT_DIR}") diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index e39cb39307..9b4f22679a 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -25,7 +25,9 @@ #include #include #include -#include + +#include // TODO: consider moving to scriptengine.h +#include // TODO: consider moving to scriptengine.h #include "Agent.h" @@ -68,6 +70,10 @@ void Agent::readPendingDatagrams() { _scriptEngine.getParticlesScriptingInterface()->getJurisdictionListener()-> queueReceivedPacket(matchedNode, receivedPacket); break; + case NodeType::ModelServer: + _scriptEngine.getModelsScriptingInterface()->getJurisdictionListener()-> + queueReceivedPacket(matchedNode, receivedPacket); + break; } } @@ -86,6 +92,8 @@ void Agent::readPendingDatagrams() { || datagramPacketType == PacketTypeParticleErase || datagramPacketType == PacketTypeOctreeStats || datagramPacketType == PacketTypeVoxelData + || datagramPacketType == PacketTypeModelData + || datagramPacketType == PacketTypeModelErase ) { // Make sure our Node and NodeList knows we've heard from this node. SharedNodePointer sourceNode = nodeList->sendingNodeForPacket(receivedPacket); @@ -117,6 +125,10 @@ void Agent::readPendingDatagrams() { _particleViewer.processDatagram(mutablePacket, sourceNode); } + if (datagramPacketType == PacketTypeModelData || datagramPacketType == PacketTypeModelErase) { + _modelViewer.processDatagram(mutablePacket, sourceNode); + } + if (datagramPacketType == PacketTypeVoxelData) { _voxelViewer.processDatagram(mutablePacket, sourceNode); } @@ -159,7 +171,9 @@ void Agent::run() { << NodeType::AudioMixer << NodeType::AvatarMixer << NodeType::VoxelServer - << NodeType::ParticleServer); + << NodeType::ParticleServer + << NodeType::ModelServer + ); // figure out the URL for the script for this agent assignment QUrl scriptURL; diff --git a/assignment-client/src/Agent.h b/assignment-client/src/Agent.h index 9f6a8089cf..6b874a03a8 100644 --- a/assignment-client/src/Agent.h +++ b/assignment-client/src/Agent.h @@ -20,6 +20,9 @@ #include #include +#include +#include +#include #include #include #include @@ -64,6 +67,7 @@ private: ParticleTreeHeadlessViewer _particleViewer; VoxelTreeHeadlessViewer _voxelViewer; + ModelTreeHeadlessViewer _modelViewer; MixedAudioRingBuffer _receivedAudioBuffer; AvatarHashMap _avatarHashMap; diff --git a/assignment-client/src/octree/OctreeServer.cpp b/assignment-client/src/octree/OctreeServer.cpp index bd04dd85d7..5769c15ef1 100644 --- a/assignment-client/src/octree/OctreeServer.cpp +++ b/assignment-client/src/octree/OctreeServer.cpp @@ -833,7 +833,7 @@ void OctreeServer::readPendingDatagrams() { SharedNodePointer matchingNode = nodeList->sendingNodeForPacket(receivedPacket); if (packetType == getMyQueryMessageType()) { - // If we got a PacketType_VOXEL_QUERY, then we're talking to an NodeType_t_AVATAR, and we + // If we got a query packet, then we're talking to an agent, and we // need to make sure we have it in our nodeList. if (matchingNode) { nodeList->updateNodeWithDataFromPacket(matchingNode, receivedPacket); diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index e65f3968e0..77d5afffd7 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -341,7 +341,7 @@ void DomainServer::populateDefaultStaticAssignmentsExcludingTypes(const QSetaddSetOfNodeTypesToNodeInterestSet(NodeSet() << NodeType::AudioMixer << NodeType::AvatarMixer - << NodeType::VoxelServer << NodeType::ParticleServer + << NodeType::VoxelServer << NodeType::ParticleServer << NodeType::ModelServer << NodeType::MetavoxelServer); // connect to the packet sent signal of the _voxelEditSender and the _particleEditSender @@ -758,6 +758,8 @@ void Application::controlledBroadcastToNodes(const QByteArray& packet, const Nod channel = BandwidthMeter::AVATARS; break; case NodeType::VoxelServer: + case NodeType::ParticleServer: + case NodeType::ModelServer: channel = BandwidthMeter::VOXELS; break; default: @@ -1264,8 +1266,8 @@ void Application::dropEvent(QDropEvent *event) { void Application::sendPingPackets() { QByteArray pingPacket = NodeList::getInstance()->constructPingPacket(); - controlledBroadcastToNodes(pingPacket, NodeSet() << NodeType::VoxelServer - << NodeType::ParticleServer + controlledBroadcastToNodes(pingPacket, NodeSet() + << NodeType::VoxelServer << NodeType::ParticleServer << NodeType::ModelServer << NodeType::AudioMixer << NodeType::AvatarMixer << NodeType::MetavoxelServer); } @@ -2025,6 +2027,7 @@ void Application::updateMyAvatar(float deltaTime) { _lastQueriedTime = now; queryOctree(NodeType::VoxelServer, PacketTypeVoxelQuery, _voxelServerJurisdictions); queryOctree(NodeType::ParticleServer, PacketTypeParticleQuery, _particleServerJurisdictions); + queryOctree(NodeType::ModelServer, PacketTypeModelQuery, _modelServerJurisdictions); _lastQueriedViewFrustum = _viewFrustum; } } @@ -3163,7 +3166,7 @@ void Application::nodeKilled(SharedNodePointer node) { _voxelFades.push_back(fade); } - // If the voxel server is going away, remove it from our jurisdiction map so we don't send voxels to a dead server + // If the particle server is going away, remove it from our jurisdiction map so we don't send voxels to a dead server _particleServerJurisdictions.erase(_particleServerJurisdictions.find(nodeUUID)); } @@ -3174,6 +3177,37 @@ void Application::nodeKilled(SharedNodePointer node) { } _octreeSceneStatsLock.unlock(); + } else if (node->getType() == NodeType::ModelServer) { + QUuid nodeUUID = node->getUUID(); + // see if this is the first we've heard of this node... + if (_modelServerJurisdictions.find(nodeUUID) != _modelServerJurisdictions.end()) { + unsigned char* rootCode = _modelServerJurisdictions[nodeUUID].getRootOctalCode(); + VoxelPositionSize rootDetails; + voxelDetailsForCode(rootCode, rootDetails); + + qDebug("model server going away...... v[%f, %f, %f, %f]", + rootDetails.x, rootDetails.y, rootDetails.z, rootDetails.s); + + // Add the jurisditionDetails object to the list of "fade outs" + if (!Menu::getInstance()->isOptionChecked(MenuOption::DontFadeOnVoxelServerChanges)) { + VoxelFade fade(VoxelFade::FADE_OUT, NODE_KILLED_RED, NODE_KILLED_GREEN, NODE_KILLED_BLUE); + fade.voxelDetails = rootDetails; + const float slightly_smaller = 0.99f; + fade.voxelDetails.s = fade.voxelDetails.s * slightly_smaller; + _voxelFades.push_back(fade); + } + + // If the model server is going away, remove it from our jurisdiction map so we don't send voxels to a dead server + _modelServerJurisdictions.erase(_modelServerJurisdictions.find(nodeUUID)); + } + + // also clean up scene stats for that server + _octreeSceneStatsLock.lockForWrite(); + if (_octreeServerSceneStats.find(nodeUUID) != _octreeServerSceneStats.end()) { + _octreeServerSceneStats.erase(nodeUUID); + } + _octreeSceneStatsLock.unlock(); + } else if (node->getType() == NodeType::AvatarMixer) { // our avatar mixer has gone away - clear the hash of avatars _avatarManager.clearOtherAvatars(); @@ -3226,8 +3260,10 @@ int Application::parseOctreeStats(const QByteArray& packet, const SharedNodePoin NodeToJurisdictionMap* jurisdiction = NULL; if (sendingNode->getType() == NodeType::VoxelServer) { jurisdiction = &_voxelServerJurisdictions; - } else { + } else if (sendingNode->getType() == NodeType::ParticleServer) { jurisdiction = &_particleServerJurisdictions; + } else { + jurisdiction = &_modelServerJurisdictions; } diff --git a/interface/src/Application.h b/interface/src/Application.h index 325770a8df..042b6dc7f8 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -243,6 +243,7 @@ public: glm::vec2 getViewportDimensions() const{ return glm::vec2(_glWidget->width(),_glWidget->height()); } NodeToJurisdictionMap& getVoxelServerJurisdictions() { return _voxelServerJurisdictions; } NodeToJurisdictionMap& getParticleServerJurisdictions() { return _particleServerJurisdictions; } + NodeToJurisdictionMap& getModelServerJurisdictions() { return _modelServerJurisdictions; } void pasteVoxelsToOctalCode(const unsigned char* octalCodeDestination); void skipVersion(QString latestVersion); @@ -500,6 +501,7 @@ private: NodeToJurisdictionMap _voxelServerJurisdictions; NodeToJurisdictionMap _particleServerJurisdictions; + NodeToJurisdictionMap _modelServerJurisdictions; NodeToOctreeSceneStats _octreeServerSceneStats; QReadWriteLock _octreeSceneStatsLock; diff --git a/interface/src/ui/OctreeStatsDialog.cpp b/interface/src/ui/OctreeStatsDialog.cpp index c56aa0b6ab..ceeb2cf2d7 100644 --- a/interface/src/ui/OctreeStatsDialog.cpp +++ b/interface/src/ui/OctreeStatsDialog.cpp @@ -232,6 +232,9 @@ void OctreeStatsDialog::showAllOctreeServers() { showOctreeServersOfType(serverCount, NodeType::ParticleServer, "Particle", Application::getInstance()->getParticleServerJurisdictions()); + showOctreeServersOfType(serverCount, NodeType::ModelServer, "Model", + Application::getInstance()->getModelServerJurisdictions()); + if (_voxelServerLabelsCount > serverCount) { for (int i = serverCount; i < _voxelServerLabelsCount; i++) { int serverLabel = _voxelServerLables[i]; diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp index 2f1c055540..a391ed239c 100644 --- a/interface/src/ui/Stats.cpp +++ b/interface/src/ui/Stats.cpp @@ -237,6 +237,7 @@ void Stats::display( int voxelServerCount = 0; foreach (const SharedNodePointer& node, nodeList->getNodeHash()) { + // TODO: this should also support particles and models if (node->getType() == NodeType::VoxelServer) { totalPingVoxel += node->getPingMs(); voxelServerCount++; diff --git a/libraries/networking/src/Assignment.cpp b/libraries/networking/src/Assignment.cpp index dd318aad8e..620c826846 100644 --- a/libraries/networking/src/Assignment.cpp +++ b/libraries/networking/src/Assignment.cpp @@ -29,6 +29,8 @@ Assignment::Type Assignment::typeForNodeType(NodeType_t nodeType) { return Assignment::VoxelServerType; case NodeType::ParticleServer: return Assignment::ParticleServerType; + case NodeType::ModelServer: + return Assignment::ModelServerType; case NodeType::MetavoxelServer: return Assignment::MetavoxelServerType; default: diff --git a/libraries/networking/src/Assignment.h b/libraries/networking/src/Assignment.h index f0f7e8db1a..54aebf9257 100644 --- a/libraries/networking/src/Assignment.h +++ b/libraries/networking/src/Assignment.h @@ -32,6 +32,7 @@ public: VoxelServerType, ParticleServerType, MetavoxelServerType, + ModelServerType, AllTypes }; diff --git a/libraries/networking/src/Node.cpp b/libraries/networking/src/Node.cpp index 15ee443e1f..05f425374b 100644 --- a/libraries/networking/src/Node.cpp +++ b/libraries/networking/src/Node.cpp @@ -29,6 +29,7 @@ void NodeType::init() { TypeNameHash.insert(NodeType::DomainServer, "Domain Server"); TypeNameHash.insert(NodeType::VoxelServer, "Voxel Server"); TypeNameHash.insert(NodeType::ParticleServer, "Particle Server"); + TypeNameHash.insert(NodeType::ModelServer, "Model Server"); TypeNameHash.insert(NodeType::MetavoxelServer, "Metavoxel Server"); TypeNameHash.insert(NodeType::Agent, "Agent"); TypeNameHash.insert(NodeType::AudioMixer, "Audio Mixer"); diff --git a/libraries/networking/src/Node.h b/libraries/networking/src/Node.h index 0f63d01525..f52cda0d0d 100644 --- a/libraries/networking/src/Node.h +++ b/libraries/networking/src/Node.h @@ -30,6 +30,7 @@ namespace NodeType { const NodeType_t DomainServer = 'D'; const NodeType_t VoxelServer = 'V'; const NodeType_t ParticleServer = 'P'; + const NodeType_t ModelServer = 'o'; const NodeType_t MetavoxelServer = 'm'; const NodeType_t EnvironmentServer = 'E'; const NodeType_t Agent = 'I'; diff --git a/libraries/networking/src/PacketHeaders.h b/libraries/networking/src/PacketHeaders.h index b7535e5064..e69bc341be 100644 --- a/libraries/networking/src/PacketHeaders.h +++ b/libraries/networking/src/PacketHeaders.h @@ -61,6 +61,11 @@ enum PacketType { PacketTypeDomainConnectRequest, PacketTypeDomainServerRequireDTLS, PacketTypeNodeJsonStats, + PacketTypeModelQuery, + PacketTypeModelData, + PacketTypeModelAddOrEdit, + PacketTypeModelErase, + PacketTypeModelAddResponse, }; typedef char PacketVersion; diff --git a/libraries/particles/src/ParticleTree.cpp b/libraries/particles/src/ParticleTree.cpp index aeaf25e23c..09e034ccd1 100644 --- a/libraries/particles/src/ParticleTree.cpp +++ b/libraries/particles/src/ParticleTree.cpp @@ -320,7 +320,7 @@ public: QVector _foundParticles; }; -bool findInBoxForUpdateOperation(OctreeElement* element, void* extraData) { +bool ParticleTree::findInBoxForUpdateOperation(OctreeElement* element, void* extraData) { FindParticlesInBoxArgs* args = static_cast< FindParticlesInBoxArgs*>(extraData); const AABox& elementBox = element->getAABox(); if (elementBox.touches(args->_box)) { diff --git a/libraries/particles/src/ParticleTree.h b/libraries/particles/src/ParticleTree.h index a31c2d38aa..76b9926bdf 100644 --- a/libraries/particles/src/ParticleTree.h +++ b/libraries/particles/src/ParticleTree.h @@ -84,6 +84,7 @@ private: static bool findByIDOperation(OctreeElement* element, void* extraData); static bool findAndDeleteOperation(OctreeElement* element, void* extraData); static bool findAndUpdateParticleIDOperation(OctreeElement* element, void* extraData); + static bool findInBoxForUpdateOperation(OctreeElement* element, void* extraData); void notifyNewlyCreatedParticle(const Particle& newParticle, const SharedNodePointer& senderNode); diff --git a/libraries/script-engine/CMakeLists.txt b/libraries/script-engine/CMakeLists.txt index 48d13e7742..ee918ff864 100644 --- a/libraries/script-engine/CMakeLists.txt +++ b/libraries/script-engine/CMakeLists.txt @@ -26,6 +26,7 @@ link_hifi_library(octree ${TARGET_NAME} "${ROOT_DIR}") link_hifi_library(voxels ${TARGET_NAME} "${ROOT_DIR}") link_hifi_library(fbx ${TARGET_NAME} "${ROOT_DIR}") link_hifi_library(particles ${TARGET_NAME} "${ROOT_DIR}") +link_hifi_library(models ${TARGET_NAME} "${ROOT_DIR}") # link ZLIB find_package(ZLIB) diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index b8b755e099..f4d77b13e1 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include @@ -36,6 +37,7 @@ VoxelsScriptingInterface ScriptEngine::_voxelsScriptingInterface; ParticlesScriptingInterface ScriptEngine::_particlesScriptingInterface; +ModelsScriptingInterface ScriptEngine::_modelsScriptingInterface; static QScriptValue soundConstructor(QScriptContext* context, QScriptEngine* engine) { QUrl soundURL = QUrl(context->argument(0).toString()); diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index 9ea99276d3..4a86ffafe4 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -30,7 +30,9 @@ #include "ScriptUUID.h" #include "Vec3.h" +class ModelsScriptingInterface; class ParticlesScriptingInterface; +class VoxelsScriptingInterface; const QString NO_SCRIPT(""); @@ -52,6 +54,9 @@ public: /// Access the ParticlesScriptingInterface in order to initialize it with a custom packet sender and jurisdiction listener static ParticlesScriptingInterface* getParticlesScriptingInterface() { return &_particlesScriptingInterface; } + /// Access the ModelsScriptingInterface in order to initialize it with a custom packet sender and jurisdiction listener + static ModelsScriptingInterface* getModelsScriptingInterface() { return &_modelsScriptingInterface; } + /// sets the script contents, will return false if failed, will fail if script is already running bool setScriptContents(const QString& scriptContents, const QString& fileNameString = QString("")); @@ -126,6 +131,7 @@ private: static VoxelsScriptingInterface _voxelsScriptingInterface; static ParticlesScriptingInterface _particlesScriptingInterface; + static ModelsScriptingInterface _modelsScriptingInterface; AbstractControllerScriptingInterface* _controllerScriptingInterface; AudioScriptingInterface _audioScriptingInterface; From f993f984c9984f907cf5e9c2406bb25d651b4895 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Wed, 30 Apr 2014 09:26:14 -0700 Subject: [PATCH 02/13] first cut at modelserver --- assignment-client/src/models/ModelNodeData.h | 34 + assignment-client/src/models/ModelServer.cpp | 137 ++ assignment-client/src/models/ModelServer.h | 50 + .../src/models/ModelServerConsts.h | 19 + libraries/models/CMakeLists.txt | 39 + .../models/src/ModelEditPacketSender.cpp | 60 + libraries/models/src/ModelEditPacketSender.h | 37 + libraries/models/src/ModelItem.cpp | 1350 +++++++++++++++++ libraries/models/src/ModelItem.h | 362 +++++ libraries/models/src/ModelTree.cpp | 636 ++++++++ libraries/models/src/ModelTree.h | 99 ++ libraries/models/src/ModelTreeElement.cpp | 340 +++++ libraries/models/src/ModelTreeElement.h | 130 ++ .../models/src/ModelTreeHeadlessViewer.cpp | 38 + .../models/src/ModelTreeHeadlessViewer.h | 45 + .../models/src/ModelsScriptingInterface.cpp | 176 +++ .../models/src/ModelsScriptingInterface.h | 73 + 17 files changed, 3625 insertions(+) create mode 100644 assignment-client/src/models/ModelNodeData.h create mode 100644 assignment-client/src/models/ModelServer.cpp create mode 100644 assignment-client/src/models/ModelServer.h create mode 100644 assignment-client/src/models/ModelServerConsts.h create mode 100644 libraries/models/CMakeLists.txt create mode 100644 libraries/models/src/ModelEditPacketSender.cpp create mode 100644 libraries/models/src/ModelEditPacketSender.h create mode 100644 libraries/models/src/ModelItem.cpp create mode 100644 libraries/models/src/ModelItem.h create mode 100644 libraries/models/src/ModelTree.cpp create mode 100644 libraries/models/src/ModelTree.h create mode 100644 libraries/models/src/ModelTreeElement.cpp create mode 100644 libraries/models/src/ModelTreeElement.h create mode 100644 libraries/models/src/ModelTreeHeadlessViewer.cpp create mode 100644 libraries/models/src/ModelTreeHeadlessViewer.h create mode 100644 libraries/models/src/ModelsScriptingInterface.cpp create mode 100644 libraries/models/src/ModelsScriptingInterface.h diff --git a/assignment-client/src/models/ModelNodeData.h b/assignment-client/src/models/ModelNodeData.h new file mode 100644 index 0000000000..dda83e0225 --- /dev/null +++ b/assignment-client/src/models/ModelNodeData.h @@ -0,0 +1,34 @@ +// +// ModelNodeData.h +// assignment-client/src/models +// +// Created by Brad Hefta-Gaub on 4/29/14 +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_ModelNodeData_h +#define hifi_ModelNodeData_h + +#include + +#include "../octree/OctreeQueryNode.h" + +class ModelNodeData : public OctreeQueryNode { +public: + ModelNodeData() : + OctreeQueryNode(), + _lastDeletedModelsSentAt(0) { }; + + virtual PacketType getMyPacketType() const { return PacketTypeModelData; } + + quint64 getLastDeletedModelsSentAt() const { return _lastDeletedModelsSentAt; } + void setLastDeletedModelsSentAt(quint64 sentAt) { _lastDeletedModelsSentAt = sentAt; } + +private: + quint64 _lastDeletedModelsSentAt; +}; + +#endif // hifi_ModelNodeData_h diff --git a/assignment-client/src/models/ModelServer.cpp b/assignment-client/src/models/ModelServer.cpp new file mode 100644 index 0000000000..a71c98ed83 --- /dev/null +++ b/assignment-client/src/models/ModelServer.cpp @@ -0,0 +1,137 @@ +// +// ModelServer.cpp +// assignment-client/src/models +// +// Created by Brad Hefta-Gaub on 4/29/14 +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include +#include + +#include "ModelServer.h" +#include "ModelServerConsts.h" +#include "ModelNodeData.h" + +const char* MODEL_SERVER_NAME = "Model"; +const char* MODEL_SERVER_LOGGING_TARGET_NAME = "model-server"; +const char* LOCAL_MODELS_PERSIST_FILE = "resources/models.svo"; + +ModelServer::ModelServer(const QByteArray& packet) : OctreeServer(packet) { + // nothing special to do here... +} + +ModelServer::~ModelServer() { + ModelTree* tree = (ModelTree*)_tree; + tree->removeNewlyCreatedHook(this); +} + +OctreeQueryNode* ModelServer::createOctreeQueryNode() { + return new ModelNodeData(); +} + +Octree* ModelServer::createTree() { + ModelTree* tree = new ModelTree(true); + tree->addNewlyCreatedHook(this); + return tree; +} + +void ModelServer::beforeRun() { + QTimer* pruneDeletedModelsTimer = new QTimer(this); + connect(pruneDeletedModelsTimer, SIGNAL(timeout()), this, SLOT(pruneDeletedModels())); + const int PRUNE_DELETED_MODELS_INTERVAL_MSECS = 1 * 1000; // once every second + pruneDeletedModelsTimer->start(PRUNE_DELETED_MODELS_INTERVAL_MSECS); +} + +void ModelServer::modelCreated(const ModelItem& newModel, const SharedNodePointer& senderNode) { + unsigned char outputBuffer[MAX_PACKET_SIZE]; + unsigned char* copyAt = outputBuffer; + + int numBytesPacketHeader = populatePacketHeader(reinterpret_cast(outputBuffer), PacketTypeModelAddResponse); + int packetLength = numBytesPacketHeader; + copyAt += numBytesPacketHeader; + + // encode the creatorTokenID + uint32_t creatorTokenID = newModel.getCreatorTokenID(); + memcpy(copyAt, &creatorTokenID, sizeof(creatorTokenID)); + copyAt += sizeof(creatorTokenID); + packetLength += sizeof(creatorTokenID); + + // encode the model ID + uint32_t modelID = newModel.getID(); + memcpy(copyAt, &modelID, sizeof(modelID)); + copyAt += sizeof(modelID); + packetLength += sizeof(modelID); + + NodeList::getInstance()->writeDatagram((char*) outputBuffer, packetLength, senderNode); +} + + +// ModelServer will use the "special packets" to send list of recently deleted models +bool ModelServer::hasSpecialPacketToSend(const SharedNodePointer& node) { + bool shouldSendDeletedModels = false; + + // check to see if any new models have been added since we last sent to this node... + ModelNodeData* nodeData = static_cast(node->getLinkedData()); + if (nodeData) { + quint64 deletedModelsSentAt = nodeData->getLastDeletedModelsSentAt(); + + ModelTree* tree = static_cast(_tree); + shouldSendDeletedModels = tree->hasModelsDeletedSince(deletedModelsSentAt); + } + + return shouldSendDeletedModels; +} + +int ModelServer::sendSpecialPacket(const SharedNodePointer& node) { + unsigned char outputBuffer[MAX_PACKET_SIZE]; + size_t packetLength = 0; + + ModelNodeData* nodeData = static_cast(node->getLinkedData()); + if (nodeData) { + quint64 deletedModelsSentAt = nodeData->getLastDeletedModelsSentAt(); + quint64 deletePacketSentAt = usecTimestampNow(); + + ModelTree* tree = static_cast(_tree); + bool hasMoreToSend = true; + + // TODO: is it possible to send too many of these packets? what if you deleted 1,000,000 models? + while (hasMoreToSend) { + hasMoreToSend = tree->encodeModelsDeletedSince(deletedModelsSentAt, + outputBuffer, MAX_PACKET_SIZE, packetLength); + + //qDebug() << "sending PacketType_MODEL_ERASE packetLength:" << packetLength; + + NodeList::getInstance()->writeDatagram((char*) outputBuffer, packetLength, SharedNodePointer(node)); + } + + nodeData->setLastDeletedModelsSentAt(deletePacketSentAt); + } + + // TODO: caller is expecting a packetLength, what if we send more than one packet?? + return packetLength; +} + +void ModelServer::pruneDeletedModels() { + ModelTree* tree = static_cast(_tree); + if (tree->hasAnyDeletedModels()) { + + //qDebug() << "there are some deleted models to consider..."; + quint64 earliestLastDeletedModelsSent = usecTimestampNow() + 1; // in the future + foreach (const SharedNodePointer& otherNode, NodeList::getInstance()->getNodeHash()) { + if (otherNode->getLinkedData()) { + ModelNodeData* nodeData = static_cast(otherNode->getLinkedData()); + quint64 nodeLastDeletedModelsSentAt = nodeData->getLastDeletedModelsSentAt(); + if (nodeLastDeletedModelsSentAt < earliestLastDeletedModelsSent) { + earliestLastDeletedModelsSent = nodeLastDeletedModelsSentAt; + } + } + } + //qDebug() << "earliestLastDeletedModelsSent=" << earliestLastDeletedModelsSent; + tree->forgetModelsDeletedBefore(earliestLastDeletedModelsSent); + } +} + diff --git a/assignment-client/src/models/ModelServer.h b/assignment-client/src/models/ModelServer.h new file mode 100644 index 0000000000..da51137385 --- /dev/null +++ b/assignment-client/src/models/ModelServer.h @@ -0,0 +1,50 @@ +// +// ModelServer.h +// assignment-client/src/models +// +// Created by Brad Hefta-Gaub on 4/29/14 +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_ModelServer_h +#define hifi_ModelServer_h + +#include "../octree/OctreeServer.h" + +#include "ModelItem.h" +#include "ModelServerConsts.h" +#include "ModelTree.h" + +/// Handles assignments of type ModelServer - sending models to various clients. +class ModelServer : public OctreeServer, public NewlyCreatedModelHook { + Q_OBJECT +public: + ModelServer(const QByteArray& packet); + ~ModelServer(); + + // Subclasses must implement these methods + virtual OctreeQueryNode* createOctreeQueryNode(); + virtual Octree* createTree(); + virtual unsigned char getMyNodeType() const { return NodeType::ModelServer; } + virtual PacketType getMyQueryMessageType() const { return PacketTypeModelQuery; } + virtual const char* getMyServerName() const { return MODEL_SERVER_NAME; } + virtual const char* getMyLoggingServerTargetName() const { return MODEL_SERVER_LOGGING_TARGET_NAME; } + virtual const char* getMyDefaultPersistFilename() const { return LOCAL_MODELS_PERSIST_FILE; } + + // subclass may implement these method + virtual void beforeRun(); + virtual bool hasSpecialPacketToSend(const SharedNodePointer& node); + virtual int sendSpecialPacket(const SharedNodePointer& node); + + virtual void modelCreated(const ModelItem& newModel, const SharedNodePointer& senderNode); + +public slots: + void pruneDeletedModels(); + +private: +}; + +#endif // hifi_ModelServer_h diff --git a/assignment-client/src/models/ModelServerConsts.h b/assignment-client/src/models/ModelServerConsts.h new file mode 100644 index 0000000000..7d480caaac --- /dev/null +++ b/assignment-client/src/models/ModelServerConsts.h @@ -0,0 +1,19 @@ +// +// ModelServerConsts.h +// assignment-client/src/models +// +// Created by Brad Hefta-Gaub on 4/29/14 +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_ModelServerConsts_h +#define hifi_ModelServerConsts_h + +extern const char* MODEL_SERVER_NAME; +extern const char* MODEL_SERVER_LOGGING_TARGET_NAME; +extern const char* LOCAL_MODELS_PERSIST_FILE; + +#endif // hifi_ModelServerConsts_h diff --git a/libraries/models/CMakeLists.txt b/libraries/models/CMakeLists.txt new file mode 100644 index 0000000000..5bd02714d2 --- /dev/null +++ b/libraries/models/CMakeLists.txt @@ -0,0 +1,39 @@ +cmake_minimum_required(VERSION 2.8) + +if (WIN32) + cmake_policy (SET CMP0020 NEW) +endif (WIN32) + +set(ROOT_DIR ../..) +set(MACRO_DIR "${ROOT_DIR}/cmake/macros") + +# setup for find modules +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/../../cmake/modules/") + +set(TARGET_NAME models) + +find_package(Qt5Widgets REQUIRED) + +include(${MACRO_DIR}/SetupHifiLibrary.cmake) +setup_hifi_library(${TARGET_NAME}) + +include(${MACRO_DIR}/IncludeGLM.cmake) +include_glm(${TARGET_NAME} "${ROOT_DIR}") + +include(${MACRO_DIR}/LinkHifiLibrary.cmake) +link_hifi_library(shared ${TARGET_NAME} "${ROOT_DIR}") +link_hifi_library(octree ${TARGET_NAME} "${ROOT_DIR}") +link_hifi_library(fbx ${TARGET_NAME} "${ROOT_DIR}") +link_hifi_library(networking ${TARGET_NAME} "${ROOT_DIR}") + +# link ZLIB and GnuTLS +find_package(ZLIB) +find_package(GnuTLS REQUIRED) + +# add a definition for ssize_t so that windows doesn't bail on gnutls.h +if (WIN32) + add_definitions(-Dssize_t=long) +endif () + +include_directories(SYSTEM "${ZLIB_INCLUDE_DIRS}" "${GNUTLS_INCLUDE_DIR}") +target_link_libraries(${TARGET_NAME} "${ZLIB_LIBRARIES}" Qt5::Widgets "${GNUTLS_LIBRARY}") diff --git a/libraries/models/src/ModelEditPacketSender.cpp b/libraries/models/src/ModelEditPacketSender.cpp new file mode 100644 index 0000000000..ad14e1f11b --- /dev/null +++ b/libraries/models/src/ModelEditPacketSender.cpp @@ -0,0 +1,60 @@ +// +// ModelEditPacketSender.cpp +// libraries/particles/src +// +// Created by Brad Hefta-Gaub on 8/12/13. +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include +#include +#include +#include +#include "ModelEditPacketSender.h" +#include "ModelItem.h" + + +void ModelEditPacketSender::sendEditModelMessage(PacketType type, ModelItemID modelID, const ModelItemProperties& properties) { + // allows app to disable sending if for example voxels have been disabled + if (!_shouldSend) { + return; // bail early + } + + static unsigned char bufferOut[MAX_PACKET_SIZE]; + int sizeOut = 0; + + // This encodes the voxel edit message into a buffer... + if (ModelItem::encodeModelEditMessageDetails(type, modelID, properties, &bufferOut[0], _maxPacketSize, sizeOut)){ + // If we don't have voxel jurisdictions, then we will simply queue up these packets and wait till we have + // jurisdictions for processing + if (!serversExist()) { + queuePendingPacketToNodes(type, bufferOut, sizeOut); + } else { + queuePacketToNodes(bufferOut, sizeOut); + } + } +} + +void ModelEditPacketSender::adjustEditPacketForClockSkew(unsigned char* codeColorBuffer, ssize_t length, int clockSkew) { + ModelItem::adjustEditPacketForClockSkew(codeColorBuffer, length, clockSkew); +} + + +void ModelEditPacketSender::queueModelEditMessage(PacketType type, ModelItemID modelID, + const ModelItemProperties& properties) { + if (!_shouldSend) { + return; // bail early + } + + // use MAX_PACKET_SIZE since it's static and guaranteed to be larger than _maxPacketSize + static unsigned char bufferOut[MAX_PACKET_SIZE]; + int sizeOut = 0; + + if (ModelItem::encodeModelEditMessageDetails(type, modelID, properties, &bufferOut[0], _maxPacketSize, sizeOut)) { + queueOctreeEditMessage(type, bufferOut, sizeOut); + } +} + diff --git a/libraries/models/src/ModelEditPacketSender.h b/libraries/models/src/ModelEditPacketSender.h new file mode 100644 index 0000000000..b4b19adae1 --- /dev/null +++ b/libraries/models/src/ModelEditPacketSender.h @@ -0,0 +1,37 @@ +// +// ModelEditPacketSender.h +// libraries/particles/src +// +// Created by Brad Hefta-Gaub on 8/12/13. +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_ModelEditPacketSender_h +#define hifi_ModelEditPacketSender_h + +#include + +#include "ModelItem.h" + +/// Utility for processing, packing, queueing and sending of outbound edit voxel messages. +class ModelEditPacketSender : public OctreeEditPacketSender { + Q_OBJECT +public: + /// Send particle add message immediately + /// NOTE: ModelItemProperties assumes that all distances are in meter units + void sendEditModelMessage(PacketType type, ModelItemID modelID, const ModelItemProperties& properties); + + /// Queues an array of several voxel edit messages. Will potentially send a pending multi-command packet. Determines + /// which voxel-server node or nodes the packet should be sent to. Can be called even before voxel servers are known, in + /// which case up to MaxPendingMessages will be buffered and processed when voxel servers are known. + /// NOTE: ModelItemProperties assumes that all distances are in meter units + void queueModelEditMessage(PacketType type, ModelItemID modelID, const ModelItemProperties& properties); + + // My server type is the particle server + virtual unsigned char getMyNodeType() const { return NodeType::ModelServer; } + virtual void adjustEditPacketForClockSkew(unsigned char* codeColorBuffer, ssize_t length, int clockSkew); +}; +#endif // hifi_ModelEditPacketSender_h diff --git a/libraries/models/src/ModelItem.cpp b/libraries/models/src/ModelItem.cpp new file mode 100644 index 0000000000..2ebff1137a --- /dev/null +++ b/libraries/models/src/ModelItem.cpp @@ -0,0 +1,1350 @@ +// +// ModelItem.cpp +// libraries/particles/src +// +// Created by Brad Hefta-Gaub on 12/4/13. +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include + +#include +#include +#include // usecTimestampNow() +#include +#include + + +// This is not ideal, but adding script-engine as a linked library, will cause a circular reference +// I'm open to other potential solutions. Could we change cmake to allow libraries to reference each others +// headers, but not link to each other, this is essentially what this construct is doing, but would be +// better to add includes to the include path, but not link +#include "../../script-engine/src/ScriptEngine.h" + +#include "ModelsScriptingInterface.h" +#include "ModelItem.h" +#include "ModelTree.h" + +uint32_t ModelItem::_nextID = 0; +//VoxelEditPacketSender* ModelItem::_voxelEditSender = NULL; +//ModelItemEditPacketSender* ModelItem::_particleEditSender = NULL; + +// for locally created particles +std::map ModelItem::_tokenIDsToIDs; +uint32_t ModelItem::_nextCreatorTokenID = 0; + +uint32_t ModelItem::getIDfromCreatorTokenID(uint32_t creatorTokenID) { + if (_tokenIDsToIDs.find(creatorTokenID) != _tokenIDsToIDs.end()) { + return _tokenIDsToIDs[creatorTokenID]; + } + return UNKNOWN_MODEL_ID; +} + +uint32_t ModelItem::getNextCreatorTokenID() { + uint32_t creatorTokenID = _nextCreatorTokenID; + _nextCreatorTokenID++; + return creatorTokenID; +} + +void ModelItem::handleAddModelResponse(const QByteArray& packet) { + const unsigned char* dataAt = reinterpret_cast(packet.data()); + int numBytesPacketHeader = numBytesForPacketHeader(packet); + dataAt += numBytesPacketHeader; + + uint32_t creatorTokenID; + memcpy(&creatorTokenID, dataAt, sizeof(creatorTokenID)); + dataAt += sizeof(creatorTokenID); + + uint32_t particleID; + memcpy(&particleID, dataAt, sizeof(particleID)); + dataAt += sizeof(particleID); + + // add our token to id mapping + _tokenIDsToIDs[creatorTokenID] = particleID; +} + +ModelItem::ModelItem() { + rgbColor noColor = { 0, 0, 0 }; + init(glm::vec3(0,0,0), 0, noColor, glm::vec3(0,0,0), + MODEL_DEFAULT_GRAVITY, MODEL_DEFAULT_DAMPING, MODEL_DEFAULT_LIFETIME, MODEL_NOT_IN_HAND, MODEL_DEFAULT_SCRIPT, NEW_MODEL); +} + +ModelItem::ModelItem(const ModelItemID& particleID, const ModelItemProperties& properties) { + _id = particleID.id; + _creatorTokenID = particleID.creatorTokenID; + + // init values with defaults before calling setProperties + uint64_t now = usecTimestampNow(); + _lastEdited = now; + _lastUpdated = now; + _created = now; // will get updated as appropriate in setAge() + + _position = glm::vec3(0,0,0); + _radius = 0; + _mass = 1.0f; + rgbColor noColor = { 0, 0, 0 }; + memcpy(_color, noColor, sizeof(_color)); + _velocity = glm::vec3(0,0,0); + _damping = MODEL_DEFAULT_DAMPING; + _lifetime = MODEL_DEFAULT_LIFETIME; + _gravity = MODEL_DEFAULT_GRAVITY; + _script = MODEL_DEFAULT_SCRIPT; + _inHand = MODEL_NOT_IN_HAND; + _shouldDie = false; + _modelURL = MODEL_DEFAULT_MODEL_URL; + _modelTranslation = MODEL_DEFAULT_MODEL_TRANSLATION; + _modelRotation = MODEL_DEFAULT_MODEL_ROTATION; + _modelScale = MODEL_DEFAULT_MODEL_SCALE; + + setProperties(properties); +} + + +ModelItem::~ModelItem() { +} + +void ModelItem::init(glm::vec3 position, float radius, rgbColor color, glm::vec3 velocity, glm::vec3 gravity, + float damping, float lifetime, bool inHand, QString updateScript, uint32_t id) { + if (id == NEW_MODEL) { + _id = _nextID; + _nextID++; + } else { + _id = id; + } + quint64 now = usecTimestampNow(); + _lastEdited = now; + _lastUpdated = now; + _created = now; // will get updated as appropriate in setAge() + + _position = position; + _radius = radius; + _mass = 1.0f; + memcpy(_color, color, sizeof(_color)); + _velocity = velocity; + _damping = damping; + _lifetime = lifetime; + _gravity = gravity; + _script = updateScript; + _inHand = inHand; + _shouldDie = false; + _modelURL = MODEL_DEFAULT_MODEL_URL; + _modelTranslation = MODEL_DEFAULT_MODEL_TRANSLATION; + _modelRotation = MODEL_DEFAULT_MODEL_ROTATION; + _modelScale = MODEL_DEFAULT_MODEL_SCALE; +} + +void ModelItem::setMass(float value) { + if (value > 0.0f) { + _mass = value; + } +} + +bool ModelItem::appendModelData(OctreePacketData* packetData) const { + + bool success = packetData->appendValue(getID()); + + //qDebug("ModelItem::appendModelData()... getID()=%d", getID()); + + if (success) { + success = packetData->appendValue(getAge()); + } + if (success) { + success = packetData->appendValue(getLastUpdated()); + } + if (success) { + success = packetData->appendValue(getLastEdited()); + } + if (success) { + success = packetData->appendValue(getRadius()); + } + if (success) { + success = packetData->appendPosition(getPosition()); + } + if (success) { + success = packetData->appendColor(getColor()); + } + if (success) { + success = packetData->appendValue(getVelocity()); + } + if (success) { + success = packetData->appendValue(getGravity()); + } + if (success) { + success = packetData->appendValue(getDamping()); + } + if (success) { + success = packetData->appendValue(getLifetime()); + } + if (success) { + success = packetData->appendValue(getInHand()); + } + if (success) { + success = packetData->appendValue(getShouldDie()); + } + if (success) { + uint16_t scriptLength = _script.size() + 1; // include NULL + success = packetData->appendValue(scriptLength); + if (success) { + success = packetData->appendRawData((const unsigned char*)qPrintable(_script), scriptLength); + } + } + + // modelURL + if (success) { + uint16_t modelURLLength = _modelURL.size() + 1; // include NULL + success = packetData->appendValue(modelURLLength); + if (success) { + success = packetData->appendRawData((const unsigned char*)qPrintable(_modelURL), modelURLLength); + } + } + + // modelScale + if (success) { + success = packetData->appendValue(getModelScale()); + } + + // modelTranslation + if (success) { + success = packetData->appendValue(getModelTranslation()); + } + // modelRotation + if (success) { + success = packetData->appendValue(getModelRotation()); + } + return success; +} + +int ModelItem::expectedBytes() { + int expectedBytes = sizeof(uint32_t) // id + + sizeof(float) // age + + sizeof(quint64) // last updated + + sizeof(quint64) // lasted edited + + sizeof(float) // radius + + sizeof(glm::vec3) // position + + sizeof(rgbColor) // color + + sizeof(glm::vec3) // velocity + + sizeof(glm::vec3) // gravity + + sizeof(float) // damping + + sizeof(float) // lifetime + + sizeof(bool); // inhand + // potentially more... + return expectedBytes; +} + +int ModelItem::readModelDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args) { + int bytesRead = 0; + if (bytesLeftToRead >= expectedBytes()) { + int clockSkew = args.sourceNode ? args.sourceNode->getClockSkewUsec() : 0; + + const unsigned char* dataAt = data; + + // id + memcpy(&_id, dataAt, sizeof(_id)); + dataAt += sizeof(_id); + bytesRead += sizeof(_id); + + // age + float age; + memcpy(&age, dataAt, sizeof(age)); + dataAt += sizeof(age); + bytesRead += sizeof(age); + setAge(age); + + // _lastUpdated + memcpy(&_lastUpdated, dataAt, sizeof(_lastUpdated)); + dataAt += sizeof(_lastUpdated); + bytesRead += sizeof(_lastUpdated); + _lastUpdated -= clockSkew; + + // _lastEdited + memcpy(&_lastEdited, dataAt, sizeof(_lastEdited)); + dataAt += sizeof(_lastEdited); + bytesRead += sizeof(_lastEdited); + _lastEdited -= clockSkew; + + // radius + memcpy(&_radius, dataAt, sizeof(_radius)); + dataAt += sizeof(_radius); + bytesRead += sizeof(_radius); + + // position + memcpy(&_position, dataAt, sizeof(_position)); + dataAt += sizeof(_position); + bytesRead += sizeof(_position); + + // color + memcpy(_color, dataAt, sizeof(_color)); + dataAt += sizeof(_color); + bytesRead += sizeof(_color); + + // velocity + memcpy(&_velocity, dataAt, sizeof(_velocity)); + dataAt += sizeof(_velocity); + bytesRead += sizeof(_velocity); + + // gravity + memcpy(&_gravity, dataAt, sizeof(_gravity)); + dataAt += sizeof(_gravity); + bytesRead += sizeof(_gravity); + + // damping + memcpy(&_damping, dataAt, sizeof(_damping)); + dataAt += sizeof(_damping); + bytesRead += sizeof(_damping); + + // lifetime + memcpy(&_lifetime, dataAt, sizeof(_lifetime)); + dataAt += sizeof(_lifetime); + bytesRead += sizeof(_lifetime); + + // inHand + memcpy(&_inHand, dataAt, sizeof(_inHand)); + dataAt += sizeof(_inHand); + bytesRead += sizeof(_inHand); + + // shouldDie + memcpy(&_shouldDie, dataAt, sizeof(_shouldDie)); + dataAt += sizeof(_shouldDie); + bytesRead += sizeof(_shouldDie); + + // script + uint16_t scriptLength; + memcpy(&scriptLength, dataAt, sizeof(scriptLength)); + dataAt += sizeof(scriptLength); + bytesRead += sizeof(scriptLength); + QString tempString((const char*)dataAt); + _script = tempString; + dataAt += scriptLength; + bytesRead += scriptLength; + + // modelURL + uint16_t modelURLLength; + memcpy(&modelURLLength, dataAt, sizeof(modelURLLength)); + dataAt += sizeof(modelURLLength); + bytesRead += sizeof(modelURLLength); + QString modelURLString((const char*)dataAt); + _modelURL = modelURLString; + dataAt += modelURLLength; + bytesRead += modelURLLength; + + // modelScale + memcpy(&_modelScale, dataAt, sizeof(_modelScale)); + dataAt += sizeof(_modelScale); + bytesRead += sizeof(_modelScale); + + // modelTranslation + memcpy(&_modelTranslation, dataAt, sizeof(_modelTranslation)); + dataAt += sizeof(_modelTranslation); + bytesRead += sizeof(_modelTranslation); + + // modelRotation + int bytes = unpackOrientationQuatFromBytes(dataAt, _modelRotation); + dataAt += bytes; + bytesRead += bytes; + + //printf("ModelItem::readModelDataFromBuffer()... "); debugDump(); + } + return bytesRead; +} + +ModelItem ModelItem::fromEditPacket(const unsigned char* data, int length, int& processedBytes, ModelTree* tree, bool& valid) { + + ModelItem newModelItem; // id and _lastUpdated will get set here... + const unsigned char* dataAt = data; + processedBytes = 0; + + // the first part of the data is our octcode... + int octets = numberOfThreeBitSectionsInCode(data); + int lengthOfOctcode = bytesRequiredForCodeLength(octets); + + // we don't actually do anything with this octcode... + dataAt += lengthOfOctcode; + processedBytes += lengthOfOctcode; + + // id + uint32_t editID; + memcpy(&editID, dataAt, sizeof(editID)); + dataAt += sizeof(editID); + processedBytes += sizeof(editID); + + bool isNewModelItem = (editID == NEW_MODEL); + + // special case for handling "new" particles + 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 + uint32_t creatorTokenID; + memcpy(&creatorTokenID, dataAt, sizeof(creatorTokenID)); + dataAt += sizeof(creatorTokenID); + processedBytes += sizeof(creatorTokenID); + + newModelItem.setCreatorTokenID(creatorTokenID); + newModelItem._newlyCreated = true; + newModelItem.setAge(0); // this guy is new! + + } else { + // look up the existing particle + const ModelItem* existingModelItem = tree->findModelByID(editID, true); + + // copy existing properties before over-writing with new properties + if (existingModelItem) { + newModelItem = *existingModelItem; + } else { + // the user attempted to edit a particle that doesn't exist + qDebug() << "user attempted to edit a particle that doesn't exist..."; + valid = false; + return newModelItem; + } + newModelItem._id = editID; + newModelItem._newlyCreated = false; + } + + // if we got this far, then our result will be valid + valid = true; + + + // lastEdited + memcpy(&newModelItem._lastEdited, dataAt, sizeof(newModelItem._lastEdited)); + dataAt += sizeof(newModelItem._lastEdited); + processedBytes += sizeof(newModelItem._lastEdited); + + // 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 = 0; + if (!isNewModelItem) { + memcpy(&packetContainsBits, dataAt, sizeof(packetContainsBits)); + dataAt += sizeof(packetContainsBits); + processedBytes += sizeof(packetContainsBits); + } + + + // radius + if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_RADIUS) == MODEL_PACKET_CONTAINS_RADIUS)) { + memcpy(&newModelItem._radius, dataAt, sizeof(newModelItem._radius)); + dataAt += sizeof(newModelItem._radius); + processedBytes += sizeof(newModelItem._radius); + } + + // position + if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_POSITION) == MODEL_PACKET_CONTAINS_POSITION)) { + memcpy(&newModelItem._position, dataAt, sizeof(newModelItem._position)); + dataAt += sizeof(newModelItem._position); + processedBytes += sizeof(newModelItem._position); + } + + // color + if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_COLOR) == MODEL_PACKET_CONTAINS_COLOR)) { + memcpy(newModelItem._color, dataAt, sizeof(newModelItem._color)); + dataAt += sizeof(newModelItem._color); + processedBytes += sizeof(newModelItem._color); + } + + // velocity + if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_VELOCITY) == MODEL_PACKET_CONTAINS_VELOCITY)) { + memcpy(&newModelItem._velocity, dataAt, sizeof(newModelItem._velocity)); + dataAt += sizeof(newModelItem._velocity); + processedBytes += sizeof(newModelItem._velocity); + } + + // gravity + if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_GRAVITY) == MODEL_PACKET_CONTAINS_GRAVITY)) { + memcpy(&newModelItem._gravity, dataAt, sizeof(newModelItem._gravity)); + dataAt += sizeof(newModelItem._gravity); + processedBytes += sizeof(newModelItem._gravity); + } + + // damping + if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_DAMPING) == MODEL_PACKET_CONTAINS_DAMPING)) { + memcpy(&newModelItem._damping, dataAt, sizeof(newModelItem._damping)); + dataAt += sizeof(newModelItem._damping); + processedBytes += sizeof(newModelItem._damping); + } + + // lifetime + if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_LIFETIME) == MODEL_PACKET_CONTAINS_LIFETIME)) { + memcpy(&newModelItem._lifetime, dataAt, sizeof(newModelItem._lifetime)); + dataAt += sizeof(newModelItem._lifetime); + processedBytes += sizeof(newModelItem._lifetime); + } + + // TODO: make inHand and shouldDie into single bits + // inHand + if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_INHAND) == MODEL_PACKET_CONTAINS_INHAND)) { + memcpy(&newModelItem._inHand, dataAt, sizeof(newModelItem._inHand)); + dataAt += sizeof(newModelItem._inHand); + processedBytes += sizeof(newModelItem._inHand); + } + + // shouldDie + if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_SHOULDDIE) == MODEL_PACKET_CONTAINS_SHOULDDIE)) { + memcpy(&newModelItem._shouldDie, dataAt, sizeof(newModelItem._shouldDie)); + dataAt += sizeof(newModelItem._shouldDie); + processedBytes += sizeof(newModelItem._shouldDie); + } + + // script + if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_SCRIPT) == MODEL_PACKET_CONTAINS_SCRIPT)) { + uint16_t scriptLength; + memcpy(&scriptLength, dataAt, sizeof(scriptLength)); + dataAt += sizeof(scriptLength); + processedBytes += sizeof(scriptLength); + QString tempString((const char*)dataAt); + newModelItem._script = tempString; + dataAt += scriptLength; + processedBytes += scriptLength; + } + + // modelURL + if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_MODEL_URL) == MODEL_PACKET_CONTAINS_MODEL_URL)) { + uint16_t modelURLLength; + memcpy(&modelURLLength, dataAt, sizeof(modelURLLength)); + dataAt += sizeof(modelURLLength); + processedBytes += sizeof(modelURLLength); + QString tempString((const char*)dataAt); + newModelItem._modelURL = tempString; + dataAt += modelURLLength; + processedBytes += modelURLLength; + } + + // modelScale + if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_MODEL_SCALE) == MODEL_PACKET_CONTAINS_MODEL_SCALE)) { + memcpy(&newModelItem._modelScale, dataAt, sizeof(newModelItem._modelScale)); + dataAt += sizeof(newModelItem._modelScale); + processedBytes += sizeof(newModelItem._modelScale); + } + + // modelTranslation + if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_MODEL_TRANSLATION) == MODEL_PACKET_CONTAINS_MODEL_TRANSLATION)) { + memcpy(&newModelItem._modelTranslation, dataAt, sizeof(newModelItem._modelTranslation)); + dataAt += sizeof(newModelItem._modelTranslation); + processedBytes += sizeof(newModelItem._modelTranslation); + } + + // modelRotation + if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_MODEL_ROTATION) == MODEL_PACKET_CONTAINS_MODEL_ROTATION)) { + int bytes = unpackOrientationQuatFromBytes(dataAt, newModelItem._modelRotation); + dataAt += bytes; + processedBytes += bytes; + } + + const bool wantDebugging = false; + if (wantDebugging) { + qDebug("ModelItem::fromEditPacket()..."); + qDebug() << " ModelItem id in packet:" << editID; + newModelItem.debugDump(); + } + + return newModelItem; +} + +void ModelItem::debugDump() const { + qDebug("ModelItem id :%u", _id); + qDebug(" age:%f", getAge()); + qDebug(" edited ago:%f", getEditedAgo()); + qDebug(" should die:%s", debug::valueOf(getShouldDie())); + qDebug(" position:%f,%f,%f", _position.x, _position.y, _position.z); + qDebug(" radius:%f", getRadius()); + qDebug(" velocity:%f,%f,%f", _velocity.x, _velocity.y, _velocity.z); + qDebug(" gravity:%f,%f,%f", _gravity.x, _gravity.y, _gravity.z); + qDebug(" color:%d,%d,%d", _color[0], _color[1], _color[2]); +} + +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 particle + + // 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 particle will go matters for + // particle 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" particles + 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 particles, all remaining items are mandatory, for an edited particle, 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); + } + + // velocity + if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_VELOCITY) == MODEL_PACKET_CONTAINS_VELOCITY)) { + glm::vec3 velocity = properties.getVelocity() / (float)TREE_SCALE; + memcpy(copyAt, &velocity, sizeof(velocity)); + copyAt += sizeof(velocity); + sizeOut += sizeof(velocity); + } + + // gravity + if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_GRAVITY) == MODEL_PACKET_CONTAINS_GRAVITY)) { + glm::vec3 gravity = properties.getGravity() / (float)TREE_SCALE; + memcpy(copyAt, &gravity, sizeof(gravity)); + copyAt += sizeof(gravity); + sizeOut += sizeof(gravity); + } + + // damping + if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_DAMPING) == MODEL_PACKET_CONTAINS_DAMPING)) { + float damping = properties.getDamping(); + memcpy(copyAt, &damping, sizeof(damping)); + copyAt += sizeof(damping); + sizeOut += sizeof(damping); + } + + // lifetime + if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_LIFETIME) == MODEL_PACKET_CONTAINS_LIFETIME)) { + float lifetime = properties.getLifetime(); + memcpy(copyAt, &lifetime, sizeof(lifetime)); + copyAt += sizeof(lifetime); + sizeOut += sizeof(lifetime); + } + + // inHand + if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_INHAND) == MODEL_PACKET_CONTAINS_INHAND)) { + bool inHand = properties.getInHand(); + memcpy(copyAt, &inHand, sizeof(inHand)); + copyAt += sizeof(inHand); + sizeOut += sizeof(inHand); + } + + // 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); + } + + // script + if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_SCRIPT) == MODEL_PACKET_CONTAINS_SCRIPT)) { + uint16_t scriptLength = properties.getScript().size() + 1; + memcpy(copyAt, &scriptLength, sizeof(scriptLength)); + copyAt += sizeof(scriptLength); + sizeOut += sizeof(scriptLength); + memcpy(copyAt, qPrintable(properties.getScript()), scriptLength); + copyAt += scriptLength; + sizeOut += scriptLength; + } + + // 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; + } + + // modelScale + if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_MODEL_SCALE) == MODEL_PACKET_CONTAINS_MODEL_SCALE)) { + float modelScale = properties.getModelScale(); + memcpy(copyAt, &modelScale, sizeof(modelScale)); + copyAt += sizeof(modelScale); + sizeOut += sizeof(modelScale); + } + + // modelTranslation + if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_MODEL_TRANSLATION) == MODEL_PACKET_CONTAINS_MODEL_TRANSLATION)) { + glm::vec3 modelTranslation = properties.getModelTranslation(); // should this be relative to TREE_SCALE?? + memcpy(copyAt, &modelTranslation, sizeof(modelTranslation)); + copyAt += sizeof(modelTranslation); + sizeOut += sizeof(modelTranslation); + } + + // modelRotation + if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_MODEL_ROTATION) == MODEL_PACKET_CONTAINS_MODEL_ROTATION)) { + int bytes = packOrientationQuatToBytes(copyAt, properties.getModelRotation()); + copyAt += bytes; + sizeOut += bytes; + } + + bool wantDebugging = false; + if (wantDebugging) { + qDebug("encodeModelItemEditMessageDetails()...."); + qDebug("ModelItem id :%u", id.id); + qDebug(" nextID:%u", _nextID); + } + + // cleanup + delete[] octcode; + + return success; +} + +// adjust any internal timestamps to fix clock skew for this server +void ModelItem::adjustEditPacketForClockSkew(unsigned char* codeColorBuffer, ssize_t length, int clockSkew) { + unsigned char* dataAt = codeColorBuffer; + int octets = numberOfThreeBitSectionsInCode(dataAt); + int lengthOfOctcode = bytesRequiredForCodeLength(octets); + dataAt += lengthOfOctcode; + + // id + uint32_t id; + memcpy(&id, dataAt, sizeof(id)); + dataAt += sizeof(id); + // special case for handling "new" particles + if (id == NEW_MODEL) { + // 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 + dataAt += sizeof(uint32_t); + } + + // lastEdited + quint64 lastEditedInLocalTime; + memcpy(&lastEditedInLocalTime, dataAt, sizeof(lastEditedInLocalTime)); + quint64 lastEditedInServerTime = lastEditedInLocalTime + clockSkew; + memcpy(dataAt, &lastEditedInServerTime, sizeof(lastEditedInServerTime)); + const bool wantDebug = false; + if (wantDebug) { + qDebug("ModelItem::adjustEditPacketForClockSkew()..."); + qDebug() << " lastEditedInLocalTime: " << lastEditedInLocalTime; + qDebug() << " clockSkew: " << clockSkew; + qDebug() << " lastEditedInServerTime: " << lastEditedInServerTime; + } +} + +// HALTING_* params are determined using expected acceleration of gravity over some timescale. +// This is a HACK for particles that bounce in a 1.0 gravitational field and should eventually be made more universal. +const float HALTING_MODEL_PERIOD = 0.0167f; // ~1/60th of a second +const float HALTING_MODEL_SPEED = 9.8 * HALTING_MODEL_PERIOD / (float)(TREE_SCALE); + +void ModelItem::applyHardCollision(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. + // + // if elasticity = 0.0, collision is inelastic (vel normal to collision is lost) + // if elasticity = 1.0, collision is 100% elastic. + // + glm::vec3 position = getPosition(); + glm::vec3 velocity = getVelocity(); + + const float EPSILON = 0.0f; + glm::vec3 relativeVelocity = collisionInfo._addedVelocity - velocity; + float velocityDotPenetration = glm::dot(relativeVelocity, collisionInfo._penetration); + if (velocityDotPenetration < EPSILON) { + // particle is moving into collision surface + // + // TODO: do something smarter here by comparing the mass of the particle vs that of the other thing + // (other's mass could be stored in the Collision Info). The smaller mass should surrender more + // position offset and should slave more to the other's velocity in the static-friction case. + position -= collisionInfo._penetration; + + if (glm::length(relativeVelocity) < HALTING_MODEL_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 + collisionInfo._elasticity) * direction; // dynamic reflection + velocity += glm::clamp(collisionInfo._damping, 0.0f, 1.0f) * (relativeVelocity - glm::dot(relativeVelocity, direction) * direction); // dynamic friction + } + } + + // change the local particle too... + setPosition(position); + setVelocity(velocity); +} + +void ModelItem::update(const quint64& 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... + bool isInHand = getInHand(); + bool shouldDie = (getAge() > getLifetime()) || getShouldDie(); + setShouldDie(shouldDie); + + //executeUpdateScripts(); // allow the javascript to alter our state + + // If the ball is in hand, it doesn't move or have gravity effect it + if (!isInHand) { + _position += _velocity * timeElapsed; + + // handle bounces off the ground... + if (_position.y <= 0) { + _velocity = _velocity * glm::vec3(1,-1,1); + _position.y = 0; + } + + // handle gravity.... + _velocity += _gravity * timeElapsed; + + // handle damping + glm::vec3 dampingResistance = _velocity * _damping; + _velocity -= dampingResistance * timeElapsed; + //qDebug("applying damping to ModelItem timeElapsed=%f",timeElapsed); + } +} + +void ModelItem::setAge(float age) { + quint64 ageInUsecs = age * USECS_PER_SECOND; + _created = usecTimestampNow() - ageInUsecs; +} + +void ModelItem::copyChangedProperties(const ModelItem& other) { + float age = getAge(); + *this = other; + setAge(age); +} + +ModelItemProperties ModelItem::getProperties() const { + ModelItemProperties properties; + properties.copyFromModelItem(*this); + return properties; +} + +void ModelItem::setProperties(const ModelItemProperties& properties) { + properties.copyToModelItem(*this); +} + +ModelItemProperties::ModelItemProperties() : + _position(0), + _color(), + _radius(MODEL_DEFAULT_RADIUS), + _velocity(0), + _gravity(MODEL_DEFAULT_GRAVITY), + _damping(MODEL_DEFAULT_DAMPING), + _lifetime(MODEL_DEFAULT_LIFETIME), + _script(""), + _inHand(false), + _shouldDie(false), + _modelURL(""), + _modelScale(MODEL_DEFAULT_MODEL_SCALE), + _modelTranslation(MODEL_DEFAULT_MODEL_TRANSLATION), + _modelRotation(MODEL_DEFAULT_MODEL_ROTATION), + + _id(UNKNOWN_MODEL_ID), + _idSet(false), + _lastEdited(usecTimestampNow()), + + _positionChanged(false), + _colorChanged(false), + _radiusChanged(false), + _velocityChanged(false), + _gravityChanged(false), + _dampingChanged(false), + _lifetimeChanged(false), + _scriptChanged(false), + _inHandChanged(false), + _shouldDieChanged(false), + _modelURLChanged(false), + _modelScaleChanged(false), + _modelTranslationChanged(false), + _modelRotationChanged(false), + _defaultSettings(true) +{ +} + + +uint16_t ModelItemProperties::getChangedBits() const { + uint16_t changedBits = 0; + if (_radiusChanged) { + changedBits += MODEL_PACKET_CONTAINS_RADIUS; + } + + if (_positionChanged) { + changedBits += MODEL_PACKET_CONTAINS_POSITION; + } + + if (_colorChanged) { + changedBits += MODEL_PACKET_CONTAINS_COLOR; + } + + if (_velocityChanged) { + changedBits += MODEL_PACKET_CONTAINS_VELOCITY; + } + + if (_gravityChanged) { + changedBits += MODEL_PACKET_CONTAINS_GRAVITY; + } + + if (_dampingChanged) { + changedBits += MODEL_PACKET_CONTAINS_DAMPING; + } + + if (_lifetimeChanged) { + changedBits += MODEL_PACKET_CONTAINS_LIFETIME; + } + + if (_inHandChanged) { + changedBits += MODEL_PACKET_CONTAINS_INHAND; + } + + if (_scriptChanged) { + changedBits += MODEL_PACKET_CONTAINS_SCRIPT; + } + + if (_shouldDieChanged) { + changedBits += MODEL_PACKET_CONTAINS_SHOULDDIE; + } + + if (_modelURLChanged) { + changedBits += MODEL_PACKET_CONTAINS_MODEL_URL; + } + + if (_modelScaleChanged) { + changedBits += MODEL_PACKET_CONTAINS_MODEL_SCALE; + } + + if (_modelTranslationChanged) { + changedBits += MODEL_PACKET_CONTAINS_MODEL_TRANSLATION; + } + + if (_modelRotationChanged) { + changedBits += MODEL_PACKET_CONTAINS_MODEL_ROTATION; + } + + return changedBits; +} + + +QScriptValue ModelItemProperties::copyToScriptValue(QScriptEngine* engine) const { + QScriptValue properties = engine->newObject(); + + QScriptValue position = vec3toScriptValue(engine, _position); + properties.setProperty("position", position); + + QScriptValue color = xColorToScriptValue(engine, _color); + properties.setProperty("color", color); + + properties.setProperty("radius", _radius); + + QScriptValue velocity = vec3toScriptValue(engine, _velocity); + properties.setProperty("velocity", velocity); + + QScriptValue gravity = vec3toScriptValue(engine, _gravity); + properties.setProperty("gravity", gravity); + + properties.setProperty("damping", _damping); + properties.setProperty("lifetime", _lifetime); + properties.setProperty("script", _script); + properties.setProperty("inHand", _inHand); + properties.setProperty("shouldDie", _shouldDie); + + properties.setProperty("modelURL", _modelURL); + + properties.setProperty("modelScale", _modelScale); + + QScriptValue modelTranslation = vec3toScriptValue(engine, _modelTranslation); + properties.setProperty("modelTranslation", modelTranslation); + + QScriptValue modelRotation = quatToScriptValue(engine, _modelRotation); + properties.setProperty("modelRotation", modelRotation); + + + if (_idSet) { + properties.setProperty("id", _id); + properties.setProperty("isKnownID", (_id != UNKNOWN_MODEL_ID)); + } + + return properties; +} + +void ModelItemProperties::copyFromScriptValue(const QScriptValue &object) { + + QScriptValue position = object.property("position"); + if (position.isValid()) { + QScriptValue x = position.property("x"); + QScriptValue y = position.property("y"); + QScriptValue z = position.property("z"); + if (x.isValid() && y.isValid() && z.isValid()) { + glm::vec3 newPosition; + newPosition.x = x.toVariant().toFloat(); + newPosition.y = y.toVariant().toFloat(); + newPosition.z = z.toVariant().toFloat(); + if (_defaultSettings || newPosition != _position) { + _position = newPosition; + _positionChanged = true; + } + } + } + + QScriptValue color = object.property("color"); + if (color.isValid()) { + QScriptValue red = color.property("red"); + QScriptValue green = color.property("green"); + QScriptValue blue = color.property("blue"); + if (red.isValid() && green.isValid() && blue.isValid()) { + xColor newColor; + newColor.red = red.toVariant().toInt(); + newColor.green = green.toVariant().toInt(); + newColor.blue = blue.toVariant().toInt(); + if (_defaultSettings || (newColor.red != _color.red || + newColor.green != _color.green || + newColor.blue != _color.blue)) { + _color = newColor; + _colorChanged = true; + } + } + } + + QScriptValue radius = object.property("radius"); + if (radius.isValid()) { + float newRadius; + newRadius = radius.toVariant().toFloat(); + if (_defaultSettings || newRadius != _radius) { + _radius = newRadius; + _radiusChanged = true; + } + } + + QScriptValue velocity = object.property("velocity"); + if (velocity.isValid()) { + QScriptValue x = velocity.property("x"); + QScriptValue y = velocity.property("y"); + QScriptValue z = velocity.property("z"); + if (x.isValid() && y.isValid() && z.isValid()) { + glm::vec3 newVelocity; + newVelocity.x = x.toVariant().toFloat(); + newVelocity.y = y.toVariant().toFloat(); + newVelocity.z = z.toVariant().toFloat(); + if (_defaultSettings || newVelocity != _velocity) { + _velocity = newVelocity; + _velocityChanged = true; + } + } + } + + QScriptValue gravity = object.property("gravity"); + if (gravity.isValid()) { + QScriptValue x = gravity.property("x"); + QScriptValue y = gravity.property("y"); + QScriptValue z = gravity.property("z"); + if (x.isValid() && y.isValid() && z.isValid()) { + glm::vec3 newGravity; + newGravity.x = x.toVariant().toFloat(); + newGravity.y = y.toVariant().toFloat(); + newGravity.z = z.toVariant().toFloat(); + if (_defaultSettings || newGravity != _gravity) { + _gravity = newGravity; + _gravityChanged = true; + } + } + } + + QScriptValue damping = object.property("damping"); + if (damping.isValid()) { + float newDamping; + newDamping = damping.toVariant().toFloat(); + if (_defaultSettings || newDamping != _damping) { + _damping = newDamping; + _dampingChanged = true; + } + } + + QScriptValue lifetime = object.property("lifetime"); + if (lifetime.isValid()) { + float newLifetime; + newLifetime = lifetime.toVariant().toFloat(); + if (_defaultSettings || newLifetime != _lifetime) { + _lifetime = newLifetime; + _lifetimeChanged = true; + } + } + + QScriptValue script = object.property("script"); + if (script.isValid()) { + QString newScript; + newScript = script.toVariant().toString(); + if (_defaultSettings || newScript != _script) { + _script = newScript; + _scriptChanged = true; + } + } + + QScriptValue inHand = object.property("inHand"); + if (inHand.isValid()) { + bool newInHand; + newInHand = inHand.toVariant().toBool(); + if (_defaultSettings || newInHand != _inHand) { + _inHand = newInHand; + _inHandChanged = true; + } + } + + QScriptValue shouldDie = object.property("shouldDie"); + if (shouldDie.isValid()) { + bool newShouldDie; + newShouldDie = shouldDie.toVariant().toBool(); + if (_defaultSettings || newShouldDie != _shouldDie) { + _shouldDie = newShouldDie; + _shouldDieChanged = true; + } + } + + QScriptValue modelURL = object.property("modelURL"); + if (modelURL.isValid()) { + QString newModelURL; + newModelURL = modelURL.toVariant().toString(); + if (_defaultSettings || newModelURL != _modelURL) { + _modelURL = newModelURL; + _modelURLChanged = true; + } + } + + QScriptValue modelScale = object.property("modelScale"); + if (modelScale.isValid()) { + float newModelScale; + newModelScale = modelScale.toVariant().toFloat(); + if (_defaultSettings || newModelScale != _modelScale) { + _modelScale = newModelScale; + _modelScaleChanged = true; + } + } + + QScriptValue modelTranslation = object.property("modelTranslation"); + if (modelTranslation.isValid()) { + QScriptValue x = modelTranslation.property("x"); + QScriptValue y = modelTranslation.property("y"); + QScriptValue z = modelTranslation.property("z"); + if (x.isValid() && y.isValid() && z.isValid()) { + glm::vec3 newModelTranslation; + newModelTranslation.x = x.toVariant().toFloat(); + newModelTranslation.y = y.toVariant().toFloat(); + newModelTranslation.z = z.toVariant().toFloat(); + if (_defaultSettings || newModelTranslation != _modelTranslation) { + _modelTranslation = newModelTranslation; + _modelTranslationChanged = true; + } + } + } + + + QScriptValue modelRotation = object.property("modelRotation"); + if (modelRotation.isValid()) { + QScriptValue x = modelRotation.property("x"); + QScriptValue y = modelRotation.property("y"); + QScriptValue z = modelRotation.property("z"); + QScriptValue w = modelRotation.property("w"); + if (x.isValid() && y.isValid() && z.isValid() && w.isValid()) { + glm::quat newModelRotation; + newModelRotation.x = x.toVariant().toFloat(); + newModelRotation.y = y.toVariant().toFloat(); + newModelRotation.z = z.toVariant().toFloat(); + newModelRotation.w = w.toVariant().toFloat(); + if (_defaultSettings || newModelRotation != _modelRotation) { + _modelRotation = newModelRotation; + _modelRotationChanged = true; + } + } + } + + _lastEdited = usecTimestampNow(); +} + +void ModelItemProperties::copyToModelItem(ModelItem& particle) const { + bool somethingChanged = false; + if (_positionChanged) { + particle.setPosition(_position / (float) TREE_SCALE); + somethingChanged = true; + } + + if (_colorChanged) { + particle.setColor(_color); + somethingChanged = true; + } + + if (_radiusChanged) { + particle.setRadius(_radius / (float) TREE_SCALE); + somethingChanged = true; + } + + if (_velocityChanged) { + particle.setVelocity(_velocity / (float) TREE_SCALE); + somethingChanged = true; + } + + if (_gravityChanged) { + particle.setGravity(_gravity / (float) TREE_SCALE); + somethingChanged = true; + } + + if (_dampingChanged) { + particle.setDamping(_damping); + somethingChanged = true; + } + + if (_lifetimeChanged) { + particle.setLifetime(_lifetime); + somethingChanged = true; + } + + if (_scriptChanged) { + particle.setScript(_script); + somethingChanged = true; + } + + if (_inHandChanged) { + particle.setInHand(_inHand); + somethingChanged = true; + } + + if (_shouldDieChanged) { + particle.setShouldDie(_shouldDie); + somethingChanged = true; + } + + if (_modelURLChanged) { + particle.setModelURL(_modelURL); + somethingChanged = true; + } + + if (_modelScaleChanged) { + particle.setModelScale(_modelScale); + somethingChanged = true; + } + + if (_modelTranslationChanged) { + particle.setModelTranslation(_modelTranslation); + somethingChanged = true; + } + + if (_modelRotationChanged) { + particle.setModelRotation(_modelRotation); + somethingChanged = true; + } + + if (somethingChanged) { + bool wantDebug = false; + if (wantDebug) { + uint64_t now = usecTimestampNow(); + int elapsed = now - _lastEdited; + qDebug() << "ModelItemProperties::copyToModelItem() AFTER update... edited AGO=" << elapsed << + "now=" << now << " _lastEdited=" << _lastEdited; + } + particle.setLastEdited(_lastEdited); + } +} + +void ModelItemProperties::copyFromModelItem(const ModelItem& particle) { + _position = particle.getPosition() * (float) TREE_SCALE; + _color = particle.getXColor(); + _radius = particle.getRadius() * (float) TREE_SCALE; + _velocity = particle.getVelocity() * (float) TREE_SCALE; + _gravity = particle.getGravity() * (float) TREE_SCALE; + _damping = particle.getDamping(); + _lifetime = particle.getLifetime(); + _script = particle.getScript(); + _inHand = particle.getInHand(); + _shouldDie = particle.getShouldDie(); + _modelURL = particle.getModelURL(); + _modelScale = particle.getModelScale(); + _modelTranslation = particle.getModelTranslation(); + _modelRotation = particle.getModelRotation(); + + _id = particle.getID(); + _idSet = true; + + _positionChanged = false; + _colorChanged = false; + _radiusChanged = false; + _velocityChanged = false; + _gravityChanged = false; + _dampingChanged = false; + _lifetimeChanged = false; + _scriptChanged = false; + _inHandChanged = false; + _shouldDieChanged = false; + _modelURLChanged = false; + _modelScaleChanged = false; + _modelTranslationChanged = false; + _modelRotationChanged = false; + _defaultSettings = false; +} + +QScriptValue ModelItemPropertiesToScriptValue(QScriptEngine* engine, const ModelItemProperties& properties) { + return properties.copyToScriptValue(engine); +} + +void ModelItemPropertiesFromScriptValue(const QScriptValue &object, ModelItemProperties& properties) { + properties.copyFromScriptValue(object); +} + + +QScriptValue ModelItemIDtoScriptValue(QScriptEngine* engine, const ModelItemID& id) { + QScriptValue obj = engine->newObject(); + obj.setProperty("id", id.id); + obj.setProperty("creatorTokenID", id.creatorTokenID); + obj.setProperty("isKnownID", id.isKnownID); + return obj; +} + +void ModelItemIDfromScriptValue(const QScriptValue &object, ModelItemID& id) { + id.id = object.property("id").toVariant().toUInt(); + id.creatorTokenID = object.property("creatorTokenID").toVariant().toUInt(); + id.isKnownID = object.property("isKnownID").toVariant().toBool(); +} + + diff --git a/libraries/models/src/ModelItem.h b/libraries/models/src/ModelItem.h new file mode 100644 index 0000000000..d691a4e98b --- /dev/null +++ b/libraries/models/src/ModelItem.h @@ -0,0 +1,362 @@ +// +// ModelItem.h +// libraries/particles/src +// +// Created by Brad Hefta-Gaub on 12/4/13. +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_ModelItem_h +#define hifi_ModelItem_h + +#include +#include + +#include +#include + +#include +#include +#include + +class ModelItem; +class ModelEditPacketSender; +class ModelItemProperties; +class ModelsScriptingInterface; +class ModelTree; +class ScriptEngine; +class VoxelEditPacketSender; +class VoxelsScriptingInterface; +struct VoxelDetail; + +const uint32_t NEW_MODEL = 0xFFFFFFFF; +const uint32_t UNKNOWN_MODEL_TOKEN = 0xFFFFFFFF; +const uint32_t UNKNOWN_MODEL_ID = 0xFFFFFFFF; + +const uint16_t MODEL_PACKET_CONTAINS_RADIUS = 1; +const uint16_t MODEL_PACKET_CONTAINS_POSITION = 2; +const uint16_t MODEL_PACKET_CONTAINS_COLOR = 4; +const uint16_t MODEL_PACKET_CONTAINS_VELOCITY = 8; +const uint16_t MODEL_PACKET_CONTAINS_GRAVITY = 16; +const uint16_t MODEL_PACKET_CONTAINS_DAMPING = 32; +const uint16_t MODEL_PACKET_CONTAINS_LIFETIME = 64; +const uint16_t MODEL_PACKET_CONTAINS_INHAND = 128; +const uint16_t MODEL_PACKET_CONTAINS_SCRIPT = 256; +const uint16_t MODEL_PACKET_CONTAINS_SHOULDDIE = 512; +const uint16_t MODEL_PACKET_CONTAINS_MODEL_URL = 1024; +const uint16_t MODEL_PACKET_CONTAINS_MODEL_TRANSLATION = 1024; +const uint16_t MODEL_PACKET_CONTAINS_MODEL_ROTATION = 2048; +const uint16_t MODEL_PACKET_CONTAINS_MODEL_SCALE = 4096; + +const float MODEL_DEFAULT_LIFETIME = 10.0f; // particles live for 10 seconds by default +const float MODEL_DEFAULT_DAMPING = 0.99f; +const float MODEL_DEFAULT_RADIUS = 0.1f / TREE_SCALE; +const float MODEL_MINIMUM_PARTICLE_ELEMENT_SIZE = (1.0f / 100000.0f) / TREE_SCALE; // smallest size container +const glm::vec3 MODEL_DEFAULT_GRAVITY(0, (-9.8f / TREE_SCALE), 0); +const QString MODEL_DEFAULT_SCRIPT(""); +const QString MODEL_DEFAULT_MODEL_URL(""); +const glm::vec3 MODEL_DEFAULT_MODEL_TRANSLATION(0, 0, 0); +const glm::quat MODEL_DEFAULT_MODEL_ROTATION(0, 0, 0, 0); +const float MODEL_DEFAULT_MODEL_SCALE = 1.0f; +const bool MODEL_IN_HAND = true; // it's in a hand +const bool MODEL_NOT_IN_HAND = !MODEL_IN_HAND; // it's not in a hand + +/// A collection of properties of a particle used in the scripting API. Translates between the actual properties of a particle +/// and a JavaScript style hash/QScriptValue storing a set of properties. Used in scripting to set/get the complete set of +/// particle properties via JavaScript hashes/QScriptValues +/// all units for position, velocity, gravity, radius, etc are in meter units +class ModelItemProperties { +public: + ModelItemProperties(); + + QScriptValue copyToScriptValue(QScriptEngine* engine) const; + void copyFromScriptValue(const QScriptValue& object); + + void copyToModelItem(ModelItem& particle) const; + void copyFromModelItem(const ModelItem& particle); + + const glm::vec3& getPosition() const { return _position; } + xColor getColor() const { return _color; } + float getRadius() const { return _radius; } + const glm::vec3& getVelocity() const { return _velocity; } + const glm::vec3& getGravity() const { return _gravity; } + float getDamping() const { return _damping; } + float getLifetime() const { return _lifetime; } + const QString& getScript() const { return _script; } + bool getInHand() const { return _inHand; } + bool getShouldDie() const { return _shouldDie; } + const QString& getModelURL() const { return _modelURL; } + float getModelScale() const { return _modelScale; } + const glm::vec3& getModelTranslation() const { return _modelTranslation; } + const glm::quat& getModelRotation() const { return _modelRotation; } + + quint64 getLastEdited() const { return _lastEdited; } + uint16_t getChangedBits() const; + + /// set position in meter units + void setPosition(const glm::vec3& value) { _position = value; _positionChanged = true; } + + /// set velocity in meter units + void setVelocity(const glm::vec3& value) { _velocity = value; _velocityChanged = true; } + void setColor(const xColor& value) { _color = value; _colorChanged = true; } + void setRadius(float value) { _radius = value; _radiusChanged = true; } + + /// set gravity in meter units + void setGravity(const glm::vec3& value) { _gravity = value; _gravityChanged = true; } + void setInHand(bool inHand) { _inHand = inHand; _inHandChanged = true; } + void setDamping(float value) { _damping = value; _dampingChanged = true; } + void setShouldDie(bool shouldDie) { _shouldDie = shouldDie; _shouldDieChanged = true; } + void setLifetime(float value) { _lifetime = value; _lifetimeChanged = true; } + void setScript(const QString& updateScript) { _script = updateScript; _scriptChanged = true; } + + // model related properties + void setModelURL(const QString& url) { _modelURL = url; _modelURLChanged = true; } + void setModelScale(float scale) { _modelScale = scale; _modelScaleChanged = true; } + void setModelTranslation(const glm::vec3& translation) { _modelTranslation = translation; + _modelTranslationChanged = true; } + void setModelRotation(const glm::quat& rotation) { _modelRotation = rotation; _modelRotationChanged = true; } + + /// used by ModelScriptingInterface to return ModelItemProperties for unknown particles + void setIsUnknownID() { _id = UNKNOWN_MODEL_ID; _idSet = true; } + +private: + glm::vec3 _position; + xColor _color; + float _radius; + glm::vec3 _velocity; + glm::vec3 _gravity; + float _damping; + float _lifetime; + QString _script; + bool _inHand; + bool _shouldDie; + QString _modelURL; + float _modelScale; + glm::vec3 _modelTranslation; + glm::quat _modelRotation; + + uint32_t _id; + bool _idSet; + quint64 _lastEdited; + + bool _positionChanged; + bool _colorChanged; + bool _radiusChanged; + bool _velocityChanged; + bool _gravityChanged; + bool _dampingChanged; + bool _lifetimeChanged; + bool _scriptChanged; + bool _inHandChanged; + bool _shouldDieChanged; + bool _modelURLChanged; + bool _modelScaleChanged; + bool _modelTranslationChanged; + bool _modelRotationChanged; + bool _defaultSettings; +}; +Q_DECLARE_METATYPE(ModelItemProperties); +QScriptValue ModelItemPropertiesToScriptValue(QScriptEngine* engine, const ModelItemProperties& properties); +void ModelItemPropertiesFromScriptValue(const QScriptValue &object, ModelItemProperties& properties); + + +/// Abstract ID for editing particles. Used in ModelItem JS API - When particles are created in the JS api, they are given a +/// local creatorTokenID, the actual id for the particle is not known until the server responds to the creator with the +/// correct mapping. This class works with the scripting API an allows the developer to edit particles they created. +class ModelItemID { +public: + ModelItemID() : + id(NEW_MODEL), creatorTokenID(UNKNOWN_MODEL_TOKEN), isKnownID(false) { }; + + ModelItemID(uint32_t id, uint32_t creatorTokenID, bool isKnownID) : + id(id), creatorTokenID(creatorTokenID), isKnownID(isKnownID) { }; + + ModelItemID(uint32_t id) : + id(id), creatorTokenID(UNKNOWN_MODEL_TOKEN), isKnownID(true) { }; + + uint32_t id; + uint32_t creatorTokenID; + bool isKnownID; +}; + +Q_DECLARE_METATYPE(ModelItemID); +Q_DECLARE_METATYPE(QVector); +QScriptValue ModelItemIDtoScriptValue(QScriptEngine* engine, const ModelItemID& properties); +void ModelItemIDfromScriptValue(const QScriptValue &object, ModelItemID& properties); + + + +/// ModelItem class - this is the actual particle class. +class ModelItem { + +public: + ModelItem(); + + ModelItem(const ModelItemID& particleID, const ModelItemProperties& properties); + + /// creates an NEW particle from an PACKET_TYPE_PARTICLE_ADD_OR_EDIT edit data buffer + static ModelItem fromEditPacket(const unsigned char* data, int length, int& processedBytes, ModelTree* tree, bool& valid); + + virtual ~ModelItem(); + virtual void init(glm::vec3 position, float radius, rgbColor color, glm::vec3 velocity, + glm::vec3 gravity = MODEL_DEFAULT_GRAVITY, float damping = MODEL_DEFAULT_DAMPING, float lifetime = MODEL_DEFAULT_LIFETIME, + bool inHand = MODEL_NOT_IN_HAND, QString updateScript = MODEL_DEFAULT_SCRIPT, uint32_t id = NEW_MODEL); + + /// get position in domain scale units (0.0 - 1.0) + const glm::vec3& getPosition() const { return _position; } + + const rgbColor& getColor() const { return _color; } + xColor getXColor() const { xColor color = { _color[RED_INDEX], _color[GREEN_INDEX], _color[BLUE_INDEX] }; return color; } + + /// get radius in domain scale units (0.0 - 1.0) + float getRadius() const { return _radius; } + float getMass() const { return _mass; } + + /// get velocity in domain scale units (0.0 - 1.0) + const glm::vec3& getVelocity() const { return _velocity; } + + /// get gravity in domain scale units (0.0 - 1.0) + const glm::vec3& getGravity() const { return _gravity; } + + bool getInHand() const { return _inHand; } + float getDamping() const { return _damping; } + float getLifetime() const { return _lifetime; } + + // model related properties + bool hasModel() const { return !_modelURL.isEmpty(); } + const QString& getModelURL() const { return _modelURL; } + float getModelScale() const { return _modelScale; } + const glm::vec3& getModelTranslation() const { return _modelTranslation; } + const glm::quat& getModelRotation() const { return _modelRotation; } + + ModelItemID getModelItemID() const { return ModelItemID(getID(), getCreatorTokenID(), getID() != UNKNOWN_MODEL_ID); } + ModelItemProperties getProperties() const; + + /// The last updated/simulated time of this particle from the time perspective of the authoritative server/source + quint64 getLastUpdated() const { return _lastUpdated; } + + /// The last edited time of this particle from the time perspective of the authoritative server/source + quint64 getLastEdited() const { return _lastEdited; } + void setLastEdited(quint64 lastEdited) { _lastEdited = lastEdited; } + + /// lifetime of the particle in seconds + float getAge() const { return static_cast(usecTimestampNow() - _created) / static_cast(USECS_PER_SECOND); } + float getEditedAgo() const { return static_cast(usecTimestampNow() - _lastEdited) / static_cast(USECS_PER_SECOND); } + uint32_t getID() const { return _id; } + void setID(uint32_t id) { _id = id; } + bool getShouldDie() const { return _shouldDie; } + QString getScript() const { return _script; } + uint32_t getCreatorTokenID() const { return _creatorTokenID; } + bool isNewlyCreated() const { return _newlyCreated; } + + /// set position in domain scale units (0.0 - 1.0) + void setPosition(const glm::vec3& value) { _position = value; } + + /// set velocity in domain scale units (0.0 - 1.0) + void setVelocity(const glm::vec3& value) { _velocity = value; } + void setColor(const rgbColor& value) { memcpy(_color, value, sizeof(_color)); } + void setColor(const xColor& value) { + _color[RED_INDEX] = value.red; + _color[GREEN_INDEX] = value.green; + _color[BLUE_INDEX] = value.blue; + } + /// set radius in domain scale units (0.0 - 1.0) + void setRadius(float value) { _radius = value; } + void setMass(float value); + + /// set gravity in domain scale units (0.0 - 1.0) + void setGravity(const glm::vec3& value) { _gravity = value; } + void setInHand(bool inHand) { _inHand = inHand; } + void setDamping(float value) { _damping = value; } + void setShouldDie(bool shouldDie) { _shouldDie = shouldDie; } + void setLifetime(float value) { _lifetime = value; } + void setScript(QString updateScript) { _script = updateScript; } + void setCreatorTokenID(uint32_t creatorTokenID) { _creatorTokenID = creatorTokenID; } + + // model related properties + void setModelURL(const QString& url) { _modelURL = url; } + void setModelScale(float scale) { _modelScale = scale; } + void setModelTranslation(const glm::vec3& translation) { _modelTranslation = translation; } + void setModelRotation(const glm::quat& rotation) { _modelRotation = rotation; } + + void setProperties(const ModelItemProperties& properties); + + bool appendModelData(OctreePacketData* packetData) const; + int readModelDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args); + static int expectedBytes(); + + static bool encodeModelEditMessageDetails(PacketType command, ModelItemID id, const ModelItemProperties& details, + unsigned char* bufferOut, int sizeIn, int& sizeOut); + + static void adjustEditPacketForClockSkew(unsigned char* codeColorBuffer, ssize_t length, int clockSkew); + + void applyHardCollision(const CollisionInfo& collisionInfo); + + void update(const quint64& now); + + void debugDump() const; + + // similar to assignment/copy, but it handles keeping lifetime accurate + void copyChangedProperties(const ModelItem& other); + + static VoxelEditPacketSender* getVoxelEditPacketSender() { return _voxelEditSender; } + static ModelEditPacketSender* getModelEditPacketSender() { return _particleEditSender; } + + static void setVoxelEditPacketSender(VoxelEditPacketSender* senderInterface) + { _voxelEditSender = senderInterface; } + + static void setModelEditPacketSender(ModelEditPacketSender* senderInterface) + { _particleEditSender = senderInterface; } + + + // these methods allow you to create particles, and later edit them. + static uint32_t getIDfromCreatorTokenID(uint32_t creatorTokenID); + static uint32_t getNextCreatorTokenID(); + static void handleAddModelResponse(const QByteArray& packet); + +protected: + static VoxelEditPacketSender* _voxelEditSender; + static ModelEditPacketSender* _particleEditSender; + + void setAge(float age); + + glm::vec3 _position; + rgbColor _color; + float _radius; + float _mass; + glm::vec3 _velocity; + uint32_t _id; + static uint32_t _nextID; + bool _shouldDie; + glm::vec3 _gravity; + float _damping; + float _lifetime; + QString _script; + bool _inHand; + + // model related items + QString _modelURL; + float _modelScale; + glm::vec3 _modelTranslation; + glm::quat _modelRotation; + + uint32_t _creatorTokenID; + bool _newlyCreated; + + quint64 _lastUpdated; + quint64 _lastEdited; + + // this doesn't go on the wire, we send it as lifetime + quint64 _created; + + // used by the static interfaces for creator token ids + static uint32_t _nextCreatorTokenID; + static std::map _tokenIDsToIDs; +}; + + + +#endif // hifi_ModelItem_h diff --git a/libraries/models/src/ModelTree.cpp b/libraries/models/src/ModelTree.cpp new file mode 100644 index 0000000000..250e8855e2 --- /dev/null +++ b/libraries/models/src/ModelTree.cpp @@ -0,0 +1,636 @@ +// +// ModelTree.cpp +// libraries/models/src +// +// Created by Brad Hefta-Gaub on 12/4/13. +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "ModelTree.h" + +ModelTree::ModelTree(bool shouldReaverage) : Octree(shouldReaverage) { + _rootNode = createNewElement(); +} + +ModelTreeElement* ModelTree::createNewElement(unsigned char * octalCode) { + ModelTreeElement* newElement = new ModelTreeElement(octalCode); + newElement->setTree(this); + return newElement; +} + +bool ModelTree::handlesEditPacketType(PacketType packetType) const { + // we handle these types of "edit" packets + switch (packetType) { + case PacketTypeModelAddOrEdit: + case PacketTypeModelErase: + return true; + default: + return false; + } +} + +class FindAndDeleteModelsArgs { +public: + QList _idsToDelete; +}; + +bool ModelTree::findAndDeleteOperation(OctreeElement* element, void* extraData) { + //qDebug() << "findAndDeleteOperation()"; + + FindAndDeleteModelsArgs* args = static_cast< FindAndDeleteModelsArgs*>(extraData); + + // if we've found and deleted all our target models, then we can stop looking + if (args->_idsToDelete.size() <= 0) { + return false; + } + + ModelTreeElement* modelTreeElement = static_cast(element); + + //qDebug() << "findAndDeleteOperation() args->_idsToDelete.size():" << args->_idsToDelete.size(); + + for (QList::iterator it = args->_idsToDelete.begin(); it != args->_idsToDelete.end(); it++) { + uint32_t modelID = *it; + //qDebug() << "findAndDeleteOperation() modelID:" << modelID; + + if (modelTreeElement->removeModelWithID(modelID)) { + // if the model 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 models, then we can stop looking + if (args->_idsToDelete.size() <= 0) { + return false; + } + return true; +} + + +class FindAndUpdateModelArgs { +public: + const ModelItem& searchModel; + bool found; +}; + +bool ModelTree::findAndUpdateOperation(OctreeElement* element, void* extraData) { + FindAndUpdateModelArgs* args = static_cast(extraData); + ModelTreeElement* modelTreeElement = static_cast(element); + // Note: updateModel() will only operate on correctly found models + if (modelTreeElement->updateModel(args->searchModel)) { + args->found = true; + return false; // stop searching + } + return true; +} + +void ModelTree::storeModel(const ModelItem& model, const SharedNodePointer& senderNode) { + // First, look for the existing model in the tree.. + FindAndUpdateModelArgs args = { model, false }; + recurseTreeWithOperation(findAndUpdateOperation, &args); + + // if we didn't find it in the tree, then store it... + if (!args.found) { + glm::vec3 position = model.getPosition(); + float size = std::max(MODEL_MINIMUM_PARTICLE_ELEMENT_SIZE, model.getRadius()); + + ModelTreeElement* element = (ModelTreeElement*)getOrCreateChildElementAt(position.x, position.y, position.z, size); + element->storeModel(model); + } + // what else do we need to do here to get reaveraging to work + _isDirty = true; +} + +class FindAndUpdateModelWithIDandPropertiesArgs { +public: + const ModelItemID& modelID; + const ModelItemProperties& properties; + bool found; +}; + +bool ModelTree::findAndUpdateWithIDandPropertiesOperation(OctreeElement* element, void* extraData) { + FindAndUpdateModelWithIDandPropertiesArgs* args = static_cast(extraData); + ModelTreeElement* modelTreeElement = static_cast(element); + // Note: updateModel() will only operate on correctly found models + if (modelTreeElement->updateModel(args->modelID, args->properties)) { + args->found = true; + return false; // stop searching + } + + // if we've found our model stop searching + if (args->found) { + return false; + } + + return true; +} + +void ModelTree::updateModel(const ModelItemID& modelID, const ModelItemProperties& properties) { + // First, look for the existing model in the tree.. + FindAndUpdateModelWithIDandPropertiesArgs args = { modelID, properties, false }; + recurseTreeWithOperation(findAndUpdateWithIDandPropertiesOperation, &args); + // if we found it in the tree, then mark the tree as dirty + if (args.found) { + _isDirty = true; + } +} + +void ModelTree::addModel(const ModelItemID& modelID, const ModelItemProperties& properties) { + // This only operates on locally created models + if (modelID.isKnownID) { + return; // not allowed + } + ModelItem model(modelID, properties); + glm::vec3 position = model.getPosition(); + float size = std::max(MODEL_MINIMUM_PARTICLE_ELEMENT_SIZE, model.getRadius()); + + ModelTreeElement* element = (ModelTreeElement*)getOrCreateChildElementAt(position.x, position.y, position.z, size); + element->storeModel(model); + + _isDirty = true; +} + +void ModelTree::deleteModel(const ModelItemID& modelID) { + if (modelID.isKnownID) { + FindAndDeleteModelsArgs args; + args._idsToDelete.push_back(modelID.id); + recurseTreeWithOperation(findAndDeleteOperation, &args); + } +} + +// scans the tree and handles mapping locally created models to know IDs. +// in the event that this tree is also viewing the scene, then we need to also +// search the tree to make sure we don't have a duplicate model from the viewing +// operation. +bool ModelTree::findAndUpdateModelItemIDOperation(OctreeElement* element, void* extraData) { + bool keepSearching = true; + + FindAndUpdateModelItemIDArgs* args = static_cast(extraData); + ModelTreeElement* modelTreeElement = static_cast(element); + + // Note: updateModelItemID() will only operate on correctly found models + modelTreeElement->updateModelItemID(args); + + // if we've found and replaced both the creatorTokenID and the viewedModel, then we + // can stop looking, otherwise we will keep looking + if (args->creatorTokenFound && args->viewedModelFound) { + keepSearching = false; + } + + return keepSearching; +} + +void ModelTree::handleAddModelResponse(const QByteArray& packet) { + int numBytesPacketHeader = numBytesForPacketHeader(packet); + + const unsigned char* dataAt = reinterpret_cast(packet.data()) + numBytesPacketHeader; + + uint32_t creatorTokenID; + memcpy(&creatorTokenID, dataAt, sizeof(creatorTokenID)); + dataAt += sizeof(creatorTokenID); + + uint32_t modelID; + memcpy(&modelID, dataAt, sizeof(modelID)); + dataAt += sizeof(modelID); + + // update models in our tree + bool assumeModelFound = !getIsViewing(); // if we're not a viewing tree, then we don't have to find the actual model + FindAndUpdateModelItemIDArgs args = { + modelID, + creatorTokenID, + false, + assumeModelFound, + getIsViewing() + }; + + const bool wantDebug = false; + if (wantDebug) { + qDebug() << "looking for creatorTokenID=" << creatorTokenID << " modelID=" << modelID + << " getIsViewing()=" << getIsViewing(); + } + lockForWrite(); + recurseTreeWithOperation(findAndUpdateModelItemIDOperation, &args); + unlock(); +} + + +class FindNearPointArgs { +public: + glm::vec3 position; + float targetRadius; + bool found; + const ModelItem* closestModel; + float closestModelDistance; +}; + + +bool ModelTree::findNearPointOperation(OctreeElement* element, void* extraData) { + FindNearPointArgs* args = static_cast(extraData); + ModelTreeElement* modelTreeElement = static_cast(element); + + glm::vec3 penetration; + bool sphereIntersection = modelTreeElement->getAABox().findSpherePenetration(args->position, + args->targetRadius, penetration); + + // If this modelTreeElement contains the point, then search it... + if (sphereIntersection) { + const ModelItem* thisClosestModel = modelTreeElement->getClosestModel(args->position); + + // we may have gotten NULL back, meaning no model was available + if (thisClosestModel) { + glm::vec3 modelPosition = thisClosestModel->getPosition(); + float distanceFromPointToModel = glm::distance(modelPosition, args->position); + + // If we're within our target radius + if (distanceFromPointToModel <= args->targetRadius) { + // we are closer than anything else we've found + if (distanceFromPointToModel < args->closestModelDistance) { + args->closestModel = thisClosestModel; + args->closestModelDistance = distanceFromPointToModel; + args->found = true; + } + } + } + + // we should be able to optimize this... + return true; // keep searching in case children have closer models + } + + // if this element doesn't contain the point, then none of it's children can contain the point, so stop searching + return false; +} + +const ModelItem* ModelTree::findClosestModel(glm::vec3 position, float targetRadius) { + FindNearPointArgs args = { position, targetRadius, false, NULL, FLT_MAX }; + lockForRead(); + recurseTreeWithOperation(findNearPointOperation, &args); + unlock(); + return args.closestModel; +} + +class FindAllNearPointArgs { +public: + glm::vec3 position; + float targetRadius; + QVector models; +}; + + +bool ModelTree::findInSphereOperation(OctreeElement* element, void* extraData) { + FindAllNearPointArgs* args = static_cast(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) { + ModelTreeElement* modelTreeElement = static_cast(element); + modelTreeElement->getModels(args->position, args->targetRadius, args->models); + return true; // keep searching in case children have closer models + } + + // if this element doesn't contain the point, then none of it's children can contain the point, so stop searching + return false; +} + +void ModelTree::findModels(const glm::vec3& center, float radius, QVector& foundModels) { + FindAllNearPointArgs args = { center, radius }; + lockForRead(); + recurseTreeWithOperation(findInSphereOperation, &args); + unlock(); + // swap the two lists of model pointers instead of copy + foundModels.swap(args.models); +} + +class FindModelsInBoxArgs { +public: + FindModelsInBoxArgs(const AABox& box) + : _box(box), _foundModels() { + } + + AABox _box; + QVector _foundModels; +}; + +bool ModelTree::findInBoxForUpdateOperation(OctreeElement* element, void* extraData) { + FindModelsInBoxArgs* args = static_cast< FindModelsInBoxArgs*>(extraData); + const AABox& elementBox = element->getAABox(); + if (elementBox.touches(args->_box)) { + ModelTreeElement* modelTreeElement = static_cast(element); + modelTreeElement->getModelsForUpdate(args->_box, args->_foundModels); + return true; + } + return false; +} + +void ModelTree::findModelsForUpdate(const AABox& box, QVector foundModels) { + FindModelsInBoxArgs args(box); + lockForRead(); + recurseTreeWithOperation(findInBoxForUpdateOperation, &args); + unlock(); + // swap the two lists of model pointers instead of copy + foundModels.swap(args._foundModels); +} + +class FindByIDArgs { +public: + uint32_t id; + bool found; + const ModelItem* foundModel; +}; + + +bool ModelTree::findByIDOperation(OctreeElement* element, void* extraData) { +//qDebug() << "ModelTree::findByIDOperation()...."; + + FindByIDArgs* args = static_cast(extraData); + ModelTreeElement* modelTreeElement = static_cast(element); + + // if already found, stop looking + if (args->found) { + return false; + } + + // as the tree element if it has this model + const ModelItem* foundModel = modelTreeElement->getModelWithID(args->id); + if (foundModel) { + args->foundModel = foundModel; + args->found = true; + return false; + } + + // keep looking + return true; +} + + +const ModelItem* ModelTree::findModelByID(uint32_t id, bool alreadyLocked) { + FindByIDArgs args = { id, false, NULL }; + + if (!alreadyLocked) { + lockForRead(); + } + recurseTreeWithOperation(findByIDOperation, &args); + if (!alreadyLocked) { + unlock(); + } + return args.foundModel; +} + + +int ModelTree::processEditPacketData(PacketType packetType, const unsigned char* packetData, int packetLength, + const unsigned char* editData, int maxLength, const SharedNodePointer& senderNode) { + + int processedBytes = 0; + // we handle these types of "edit" packets + switch (packetType) { + case PacketTypeModelAddOrEdit: { + bool isValid; + ModelItem newModel = ModelItem::fromEditPacket(editData, maxLength, processedBytes, this, isValid); + if (isValid) { + storeModel(newModel, senderNode); + if (newModel.isNewlyCreated()) { + notifyNewlyCreatedModel(newModel, 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 models + case PacketTypeModelErase: + processedBytes = 0; + break; + default: + processedBytes = 0; + break; + } + + return processedBytes; +} + +void ModelTree::notifyNewlyCreatedModel(const ModelItem& newModel, const SharedNodePointer& senderNode) { + _newlyCreatedHooksLock.lockForRead(); + for (size_t i = 0; i < _newlyCreatedHooks.size(); i++) { + _newlyCreatedHooks[i]->modelCreated(newModel, senderNode); + } + _newlyCreatedHooksLock.unlock(); +} + +void ModelTree::addNewlyCreatedHook(NewlyCreatedModelHook* hook) { + _newlyCreatedHooksLock.lockForWrite(); + _newlyCreatedHooks.push_back(hook); + _newlyCreatedHooksLock.unlock(); +} + +void ModelTree::removeNewlyCreatedHook(NewlyCreatedModelHook* 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 ModelTree::updateOperation(OctreeElement* element, void* extraData) { + ModelTreeUpdateArgs* args = static_cast(extraData); + ModelTreeElement* modelTreeElement = static_cast(element); + modelTreeElement->update(*args); + return true; +} + +bool ModelTree::pruneOperation(OctreeElement* element, void* extraData) { + ModelTreeElement* modelTreeElement = static_cast(element); + for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { + ModelTreeElement* childAt = modelTreeElement->getChildAtIndex(i); + if (childAt && childAt->isLeaf() && !childAt->hasModels()) { + modelTreeElement->deleteChildAtIndex(i); + } + } + return true; +} + +void ModelTree::update() { + lockForWrite(); + _isDirty = true; + + ModelTreeUpdateArgs args = { }; + recurseTreeWithOperation(updateOperation, &args); + + // now add back any of the models that moved elements.... + int movingModels = args._movingModels.size(); + for (int i = 0; i < movingModels; i++) { + bool shouldDie = args._movingModels[i].getShouldDie(); + + // if the model is still inside our total bounds, then re-add it + AABox treeBounds = getRoot()->getAABox(); + + if (!shouldDie && treeBounds.contains(args._movingModels[i].getPosition())) { + storeModel(args._movingModels[i]); + } else { + uint32_t modelID = args._movingModels[i].getID(); + quint64 deletedAt = usecTimestampNow(); + _recentlyDeletedModelsLock.lockForWrite(); + _recentlyDeletedModelItemIDs.insert(deletedAt, modelID); + _recentlyDeletedModelsLock.unlock(); + } + } + + // prune the tree... + recurseTreeWithOperation(pruneOperation, NULL); + unlock(); +} + + +bool ModelTree::hasModelsDeletedSince(quint64 sinceTime) { + // we can probably leverage the ordered nature of QMultiMap to do this quickly... + bool hasSomethingNewer = false; + + _recentlyDeletedModelsLock.lockForRead(); + QMultiMap::const_iterator iterator = _recentlyDeletedModelItemIDs.constBegin(); + while (iterator != _recentlyDeletedModelItemIDs.constEnd()) { + //qDebug() << "considering... time/key:" << iterator.key(); + if (iterator.key() > sinceTime) { + //qDebug() << "YES newer... time/key:" << iterator.key(); + hasSomethingNewer = true; + } + ++iterator; + } + _recentlyDeletedModelsLock.unlock(); + return hasSomethingNewer; +} + +// sinceTime is an in/out parameter - it will be side effected with the last time sent out +bool ModelTree::encodeModelsDeletedSince(quint64& sinceTime, unsigned char* outputBuffer, size_t maxLength, + size_t& outputLength) { + + bool hasMoreToSend = true; + + unsigned char* copyAt = outputBuffer; + size_t numBytesPacketHeader = populatePacketHeader(reinterpret_cast(outputBuffer), PacketTypeModelErase); + 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 model IDs to timestamps, we only want to include the model IDs that have been + // deleted since we last sent to this node + _recentlyDeletedModelsLock.lockForRead(); + QMultiMap::const_iterator iterator = _recentlyDeletedModelItemIDs.constBegin(); + while (iterator != _recentlyDeletedModelItemIDs.constEnd()) { + QList values = _recentlyDeletedModelItemIDs.values(iterator.key()); + for (int valueItem = 0; valueItem < values.size(); ++valueItem) { + + // if the timestamp is more recent then out last sent time, include it + if (iterator.key() > sinceTime) { + uint32_t modelID = values.at(valueItem); + memcpy(copyAt, &modelID, sizeof(modelID)); + copyAt += sizeof(modelID); + outputLength += sizeof(modelID); + 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 == _recentlyDeletedModelItemIDs.constEnd()) { + hasMoreToSend = false; + } + _recentlyDeletedModelsLock.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 ModelTree::forgetModelsDeletedBefore(quint64 sinceTime) { + //qDebug() << "forgetModelsDeletedBefore()"; + QSet keysToRemove; + + _recentlyDeletedModelsLock.lockForWrite(); + QMultiMap::iterator iterator = _recentlyDeletedModelItemIDs.begin(); + + // First find all the keys in the map that are older and need to be deleted + while (iterator != _recentlyDeletedModelItemIDs.end()) { + if (iterator.key() <= sinceTime) { + keysToRemove << iterator.key(); + } + ++iterator; + } + + // Now run through the keysToRemove and remove them + foreach (quint64 value, keysToRemove) { + //qDebug() << "removing the key, _recentlyDeletedModelItemIDs.remove(value); time/key:" << value; + _recentlyDeletedModelItemIDs.remove(value); + } + + _recentlyDeletedModelsLock.unlock(); +} + + +void ModelTree::processEraseMessage(const QByteArray& dataByteArray, const SharedNodePointer& sourceNode) { + + const unsigned char* packetData = (const unsigned char*)dataByteArray.constData(); + const unsigned char* dataAt = packetData; + size_t packetLength = dataByteArray.size(); + + size_t numBytesPacketHeader = numBytesForPacketHeader(dataByteArray); + size_t processedBytes = numBytesPacketHeader; + dataAt += numBytesPacketHeader; + + uint16_t numberOfIds = 0; // placeholder for now + memcpy(&numberOfIds, dataAt, sizeof(numberOfIds)); + dataAt += sizeof(numberOfIds); + processedBytes += sizeof(numberOfIds); + + if (numberOfIds > 0) { + FindAndDeleteModelsArgs args; + + for (size_t i = 0; i < numberOfIds; i++) { + if (processedBytes + sizeof(uint32_t) > packetLength) { + break; // bail to prevent buffer overflow + } + + uint32_t modelID = 0; // placeholder for now + memcpy(&modelID, dataAt, sizeof(modelID)); + dataAt += sizeof(modelID); + processedBytes += sizeof(modelID); + + args._idsToDelete.push_back(modelID); + } + + // calling recurse to actually delete the models + recurseTreeWithOperation(findAndDeleteOperation, &args); + } +} diff --git a/libraries/models/src/ModelTree.h b/libraries/models/src/ModelTree.h new file mode 100644 index 0000000000..02086ecd89 --- /dev/null +++ b/libraries/models/src/ModelTree.h @@ -0,0 +1,99 @@ +// +// ModelTree.h +// libraries/models/src +// +// Created by Brad Hefta-Gaub on 12/4/13. +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_ModelTree_h +#define hifi_ModelTree_h + +#include +#include "ModelTreeElement.h" + +class NewlyCreatedModelHook { +public: + virtual void modelCreated(const ModelItem& newModel, const SharedNodePointer& senderNode) = 0; +}; + +class ModelTree : public Octree { + Q_OBJECT +public: + ModelTree(bool shouldReaverage = false); + + /// Implements our type specific root element factory + virtual ModelTreeElement* createNewElement(unsigned char * octalCode = NULL); + + /// Type safe version of getRoot() + ModelTreeElement* getRoot() { return (ModelTreeElement*)_rootNode; } + + + // These methods will allow the OctreeServer to send your tree inbound edit packets of your + // own definition. Implement these to allow your octree based server to support editing + virtual bool getWantSVOfileVersions() const { return true; } + virtual PacketType expectedDataPacketType() const { return PacketTypeModelData; } + virtual bool handlesEditPacketType(PacketType packetType) const; + virtual int processEditPacketData(PacketType packetType, const unsigned char* packetData, int packetLength, + const unsigned char* editData, int maxLength, const SharedNodePointer& senderNode); + + virtual void update(); + + void storeModel(const ModelItem& model, const SharedNodePointer& senderNode = SharedNodePointer()); + void updateModel(const ModelItemID& modelID, const ModelItemProperties& properties); + void addModel(const ModelItemID& modelID, const ModelItemProperties& properties); + void deleteModel(const ModelItemID& modelID); + const ModelItem* findClosestModel(glm::vec3 position, float targetRadius); + const ModelItem* findModelByID(uint32_t id, bool alreadyLocked = false); + + /// finds all models that touch a sphere + /// \param center the center of the sphere + /// \param radius the radius of the sphere + /// \param foundModels[out] vector of const ModelItem* + /// \remark Side effect: any initial contents in foundModels will be lost + void findModels(const glm::vec3& center, float radius, QVector& foundModels); + + /// finds all models that touch a box + /// \param box the query box + /// \param foundModels[out] vector of non-const ModelItem* + /// \remark Side effect: any initial contents in models will be lost + void findModelsForUpdate(const AABox& box, QVector foundModels); + + void addNewlyCreatedHook(NewlyCreatedModelHook* hook); + void removeNewlyCreatedHook(NewlyCreatedModelHook* hook); + + bool hasAnyDeletedModels() const { return _recentlyDeletedModelItemIDs.size() > 0; } + bool hasModelsDeletedSince(quint64 sinceTime); + bool encodeModelsDeletedSince(quint64& sinceTime, unsigned char* packetData, size_t maxLength, size_t& outputLength); + void forgetModelsDeletedBefore(quint64 sinceTime); + + void processEraseMessage(const QByteArray& dataByteArray, const SharedNodePointer& sourceNode); + void handleAddModelResponse(const QByteArray& packet); + +private: + + static bool updateOperation(OctreeElement* element, void* extraData); + static bool findAndUpdateOperation(OctreeElement* element, void* extraData); + static bool findAndUpdateWithIDandPropertiesOperation(OctreeElement* element, void* extraData); + static bool findNearPointOperation(OctreeElement* element, void* extraData); + static bool findInSphereOperation(OctreeElement* element, void* extraData); + static bool pruneOperation(OctreeElement* element, void* extraData); + static bool findByIDOperation(OctreeElement* element, void* extraData); + static bool findAndDeleteOperation(OctreeElement* element, void* extraData); + static bool findAndUpdateModelItemIDOperation(OctreeElement* element, void* extraData); + static bool findInBoxForUpdateOperation(OctreeElement* element, void* extraData); + + void notifyNewlyCreatedModel(const ModelItem& newModel, const SharedNodePointer& senderNode); + + QReadWriteLock _newlyCreatedHooksLock; + std::vector _newlyCreatedHooks; + + + QReadWriteLock _recentlyDeletedModelsLock; + QMultiMap _recentlyDeletedModelItemIDs; +}; + +#endif // hifi_ModelTree_h diff --git a/libraries/models/src/ModelTreeElement.cpp b/libraries/models/src/ModelTreeElement.cpp new file mode 100644 index 0000000000..b799d377df --- /dev/null +++ b/libraries/models/src/ModelTreeElement.cpp @@ -0,0 +1,340 @@ +// +// ModelTreeElement.cpp +// libraries/models/src +// +// Created by Brad Hefta-Gaub on 12/4/13. +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include + +#include "ModelTree.h" +#include "ModelTreeElement.h" + +ModelTreeElement::ModelTreeElement(unsigned char* octalCode) : OctreeElement(), _modelItems(NULL) { + init(octalCode); +}; + +ModelTreeElement::~ModelTreeElement() { + _voxelMemoryUsage -= sizeof(ModelTreeElement); + delete _modelItems; + _modelItems = NULL; +} + +// This will be called primarily on addChildAt(), which means we're adding a child of our +// own type to our own tree. This means we should initialize that child with any tree and type +// specific settings that our children must have. One example is out VoxelSystem, which +// we know must match ours. +OctreeElement* ModelTreeElement::createNewElement(unsigned char* octalCode) { + ModelTreeElement* newChild = new ModelTreeElement(octalCode); + newChild->setTree(_myTree); + return newChild; +} + +void ModelTreeElement::init(unsigned char* octalCode) { + OctreeElement::init(octalCode); + _modelItems = new QList; + _voxelMemoryUsage += sizeof(ModelTreeElement); +} + +ModelTreeElement* ModelTreeElement::addChildAtIndex(int index) { + ModelTreeElement* newElement = (ModelTreeElement*)OctreeElement::addChildAtIndex(index); + newElement->setTree(_myTree); + return newElement; +} + + +bool ModelTreeElement::appendElementData(OctreePacketData* packetData) const { + bool success = true; // assume the best... + + // write our models out... + uint16_t numberOfModels = _modelItems->size(); + success = packetData->appendValue(numberOfModels); + + if (success) { + for (uint16_t i = 0; i < numberOfModels; i++) { + const ModelItem& model = (*_modelItems)[i]; + success = model.appendModelData(packetData); + if (!success) { + break; + } + } + } + return success; +} + +void ModelTreeElement::update(ModelTreeUpdateArgs& args) { + markWithChangedTime(); + // TODO: early exit when _modelItems is empty + + // update our contained models + QList::iterator modelItr = _modelItems->begin(); + while(modelItr != _modelItems->end()) { + ModelItem& model = (*modelItr); + model.update(_lastChanged); + + // If the model wants to die, or if it's left our bounding box, then move it + // into the arguments moving models. These will be added back or deleted completely + if (model.getShouldDie() || !_box.contains(model.getPosition())) { + args._movingModels.push_back(model); + + // erase this model + modelItr = _modelItems->erase(modelItr); + } else { + ++modelItr; + } + } + // TODO: if _modelItems is empty after while loop consider freeing memory in _modelItems if + // internal array is too big (QList internal array does not decrease size except in dtor and + // assignment operator). Otherwise _modelItems could become a "resource leak" for large + // roaming piles of models. +} + +bool ModelTreeElement::findSpherePenetration(const glm::vec3& center, float radius, + glm::vec3& penetration, void** penetratedObject) const { + QList::iterator modelItr = _modelItems->begin(); + QList::const_iterator modelEnd = _modelItems->end(); + while(modelItr != modelEnd) { + ModelItem& model = (*modelItr); + glm::vec3 modelCenter = model.getPosition(); + float modelRadius = model.getRadius(); + + // don't penetrate yourself + if (modelCenter == center && modelRadius == radius) { + return false; + } + + // We've considered making "inHand" models not collide, if we want to do that, + // we should change this setting... but now, we do allow inHand models to collide + const bool IN_HAND_PARTICLES_DONT_COLLIDE = false; + if (IN_HAND_PARTICLES_DONT_COLLIDE) { + // don't penetrate if the model is "inHand" -- they don't collide + if (model.getInHand()) { + ++modelItr; + continue; + } + } + + if (findSphereSpherePenetration(center, radius, modelCenter, modelRadius, penetration)) { + // return true on first valid model penetration + *penetratedObject = (void*)(&model); + return true; + } + ++modelItr; + } + return false; +} + +bool ModelTreeElement::updateModel(const ModelItem& model) { + // NOTE: this method must first lookup the model by ID, hence it is O(N) + // and "model is not found" is worst-case (full N) but maybe we don't care? + // (guaranteed that num models per elemen is small?) + const bool wantDebug = false; + uint16_t numberOfModels = _modelItems->size(); + for (uint16_t i = 0; i < numberOfModels; i++) { + ModelItem& thisModel = (*_modelItems)[i]; + if (thisModel.getID() == model.getID()) { + int difference = thisModel.getLastUpdated() - model.getLastUpdated(); + bool changedOnServer = thisModel.getLastEdited() < model.getLastEdited(); + bool localOlder = thisModel.getLastUpdated() < model.getLastUpdated(); + if (changedOnServer || localOlder) { + if (wantDebug) { + qDebug("local model [id:%d] %s and %s than server model by %d, model.isNewlyCreated()=%s", + model.getID(), (changedOnServer ? "CHANGED" : "same"), + (localOlder ? "OLDER" : "NEWER"), + difference, debug::valueOf(model.isNewlyCreated()) ); + } + thisModel.copyChangedProperties(model); + } else { + if (wantDebug) { + qDebug(">>> IGNORING SERVER!!! Would've caused jutter! <<< " + "local model [id:%d] %s and %s than server model by %d, model.isNewlyCreated()=%s", + model.getID(), (changedOnServer ? "CHANGED" : "same"), + (localOlder ? "OLDER" : "NEWER"), + difference, debug::valueOf(model.isNewlyCreated()) ); + } + } + return true; + } + } + return false; +} + +bool ModelTreeElement::updateModel(const ModelItemID& modelID, const ModelItemProperties& properties) { + uint16_t numberOfModels = _modelItems->size(); + for (uint16_t i = 0; i < numberOfModels; i++) { + // note: unlike storeModel() which is called from inbound packets, this is only called by local editors + // and therefore we can be confident that this change is higher priority and should be honored + ModelItem& thisModel = (*_modelItems)[i]; + + bool found = false; + if (modelID.isKnownID) { + found = thisModel.getID() == modelID.id; + } else { + found = thisModel.getCreatorTokenID() == modelID.creatorTokenID; + } + if (found) { + thisModel.setProperties(properties); + + const bool wantDebug = false; + if (wantDebug) { + uint64_t now = usecTimestampNow(); + int elapsed = now - thisModel.getLastEdited(); + + qDebug() << "ModelTreeElement::updateModel() AFTER update... edited AGO=" << elapsed << + "now=" << now << " thisModel.getLastEdited()=" << thisModel.getLastEdited(); + } + return true; + } + } + return false; +} + +void ModelTreeElement::updateModelItemID(FindAndUpdateModelItemIDArgs* args) { + uint16_t numberOfModels = _modelItems->size(); + for (uint16_t i = 0; i < numberOfModels; i++) { + ModelItem& thisModel = (*_modelItems)[i]; + + if (!args->creatorTokenFound) { + // first, we're looking for matching creatorTokenIDs, if we find that, then we fix it to know the actual ID + if (thisModel.getCreatorTokenID() == args->creatorTokenID) { + thisModel.setID(args->modelID); + args->creatorTokenFound = true; + } + } + + // if we're in an isViewing tree, we also need to look for an kill any viewed models + if (!args->viewedModelFound && args->isViewing) { + if (thisModel.getCreatorTokenID() == UNKNOWN_MODEL_TOKEN && thisModel.getID() == args->modelID) { + _modelItems->removeAt(i); // remove the model at this index + numberOfModels--; // this means we have 1 fewer model in this list + i--; // and we actually want to back up i as well. + args->viewedModelFound = true; + } + } + } +} + + + +const ModelItem* ModelTreeElement::getClosestModel(glm::vec3 position) const { + const ModelItem* closestModel = NULL; + float closestModelDistance = FLT_MAX; + uint16_t numberOfModels = _modelItems->size(); + for (uint16_t i = 0; i < numberOfModels; i++) { + float distanceToModel = glm::distance(position, (*_modelItems)[i].getPosition()); + if (distanceToModel < closestModelDistance) { + closestModel = &(*_modelItems)[i]; + } + } + return closestModel; +} + +void ModelTreeElement::getModels(const glm::vec3& searchPosition, float searchRadius, QVector& foundModels) const { + uint16_t numberOfModels = _modelItems->size(); + for (uint16_t i = 0; i < numberOfModels; i++) { + const ModelItem* model = &(*_modelItems)[i]; + float distance = glm::length(model->getPosition() - searchPosition); + if (distance < searchRadius + model->getRadius()) { + foundModels.push_back(model); + } + } +} + +void ModelTreeElement::getModelsForUpdate(const AABox& box, QVector& foundModels) { + QList::iterator modelItr = _modelItems->begin(); + QList::iterator modelEnd = _modelItems->end(); + AABox modelBox; + while(modelItr != modelEnd) { + ModelItem* model = &(*modelItr); + float radius = model->getRadius(); + // NOTE: we actually do box-box collision queries here, which is sloppy but good enough for now + // TODO: decide whether to replace modelBox-box query with sphere-box (requires a square root + // but will be slightly more accurate). + modelBox.setBox(model->getPosition() - glm::vec3(radius), 2.f * radius); + if (modelBox.touches(_box)) { + foundModels.push_back(model); + } + ++modelItr; + } +} + +const ModelItem* ModelTreeElement::getModelWithID(uint32_t id) const { + // NOTE: this lookup is O(N) but maybe we don't care? (guaranteed that num models per elemen is small?) + const ModelItem* foundModel = NULL; + uint16_t numberOfModels = _modelItems->size(); + for (uint16_t i = 0; i < numberOfModels; i++) { + if ((*_modelItems)[i].getID() == id) { + foundModel = &(*_modelItems)[i]; + break; + } + } + return foundModel; +} + +bool ModelTreeElement::removeModelWithID(uint32_t id) { + bool foundModel = false; + uint16_t numberOfModels = _modelItems->size(); + for (uint16_t i = 0; i < numberOfModels; i++) { + if ((*_modelItems)[i].getID() == id) { + foundModel = true; + _modelItems->removeAt(i); + break; + } + } + return foundModel; +} + +int ModelTreeElement::readElementDataFromBuffer(const unsigned char* data, int bytesLeftToRead, + ReadBitstreamToTreeParams& args) { + + const unsigned char* dataAt = data; + int bytesRead = 0; + uint16_t numberOfModels = 0; + int expectedBytesPerModel = ModelItem::expectedBytes(); + + if (bytesLeftToRead >= (int)sizeof(numberOfModels)) { + // read our models in.... + numberOfModels = *(uint16_t*)dataAt; + dataAt += sizeof(numberOfModels); + bytesLeftToRead -= (int)sizeof(numberOfModels); + bytesRead += sizeof(numberOfModels); + + if (bytesLeftToRead >= (int)(numberOfModels * expectedBytesPerModel)) { + for (uint16_t i = 0; i < numberOfModels; i++) { + ModelItem tempModel; + int bytesForThisModel = tempModel.readModelDataFromBuffer(dataAt, bytesLeftToRead, args); + _myTree->storeModel(tempModel); + dataAt += bytesForThisModel; + bytesLeftToRead -= bytesForThisModel; + bytesRead += bytesForThisModel; + } + } + } + + return bytesRead; +} + +// will average a "common reduced LOD view" from the the child elements... +void ModelTreeElement::calculateAverageFromChildren() { + // nothing to do here yet... +} + +// will detect if children are leaves AND collapsable into the parent node +// and in that case will collapse children and make this node +// a leaf, returns TRUE if all the leaves are collapsed into a +// single node +bool ModelTreeElement::collapseChildren() { + // nothing to do here yet... + return false; +} + + +void ModelTreeElement::storeModel(const ModelItem& model) { + _modelItems->push_back(model); + markWithChangedTime(); +} + diff --git a/libraries/models/src/ModelTreeElement.h b/libraries/models/src/ModelTreeElement.h new file mode 100644 index 0000000000..ce03d50065 --- /dev/null +++ b/libraries/models/src/ModelTreeElement.h @@ -0,0 +1,130 @@ +// +// ModelTreeElement.h +// libraries/models/src +// +// Created by Brad Hefta-Gaub on 12/4/13. +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_ModelTreeElement_h +#define hifi_ModelTreeElement_h + +#include +#include + +#include "ModelItem.h" +#include "ModelTree.h" + +class ModelTree; +class ModelTreeElement; + +class ModelTreeUpdateArgs { +public: + QList _movingModels; +}; + +class FindAndUpdateModelItemIDArgs { +public: + uint32_t modelID; + uint32_t creatorTokenID; + bool creatorTokenFound; + bool viewedModelFound; + bool isViewing; +}; + + + +class ModelTreeElement : public OctreeElement { + friend class ModelTree; // to allow createElement to new us... + + ModelTreeElement(unsigned char* octalCode = NULL); + + virtual OctreeElement* createNewElement(unsigned char* octalCode = NULL); + +public: + virtual ~ModelTreeElement(); + + // type safe versions of OctreeElement methods + ModelTreeElement* getChildAtIndex(int index) { return (ModelTreeElement*)OctreeElement::getChildAtIndex(index); } + + // methods you can and should override to implement your tree functionality + + /// Adds a child to the current element. Override this if there is additional child initialization your class needs. + virtual ModelTreeElement* addChildAtIndex(int index); + + /// Override this to implement LOD averaging on changes to the tree. + virtual void calculateAverageFromChildren(); + + /// Override this to implement LOD collapsing and identical child pruning on changes to the tree. + virtual bool collapseChildren(); + + /// Should this element be considered to have content in it. This will be used in collision and ray casting methods. + /// By default we assume that only leaves are actual content, but some octrees may have different semantics. + virtual bool hasContent() const { return isLeaf(); } + + /// Override this to break up large octree elements when an edit operation is performed on a smaller octree element. + /// For example, if the octrees represent solid cubes and a delete of a smaller octree element is done then the + /// meaningful split would be to break the larger cube into smaller cubes of the same color/texture. + virtual void splitChildren() { } + + /// Override to indicate that this element requires a split before editing lower elements in the octree + virtual bool requiresSplit() const { return false; } + + /// Override to serialize the state of this element. This is used for persistance and for transmission across the network. + virtual bool appendElementData(OctreePacketData* packetData) const; + + /// Override to deserialize the state of this element. This is used for loading from a persisted file or from reading + /// from the network. + virtual int readElementDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args); + + /// Override to indicate that the item is currently rendered in the rendering engine. By default we assume that if + /// the element should be rendered, then your rendering engine is rendering. But some rendering engines my have cases + /// where an element is not actually rendering all should render elements. If the isRendered() state doesn't match the + /// shouldRender() state, the tree will remark elements as changed even in cases there the elements have not changed. + virtual bool isRendered() const { return getShouldRender(); } + virtual bool deleteApproved() const { return !hasModels(); } + + virtual bool findSpherePenetration(const glm::vec3& center, float radius, + glm::vec3& penetration, void** penetratedObject) const; + + const QList& getModels() const { return *_modelItems; } + QList& getModels() { return *_modelItems; } + bool hasModels() const { return _modelItems->size() > 0; } + + void update(ModelTreeUpdateArgs& args); + void setTree(ModelTree* tree) { _myTree = tree; } + + bool updateModel(const ModelItem& model); + bool updateModel(const ModelItemID& modelID, const ModelItemProperties& properties); + void updateModelItemID(FindAndUpdateModelItemIDArgs* args); + + const ModelItem* getClosestModel(glm::vec3 position) const; + + /// finds all models that touch a sphere + /// \param position the center of the query sphere + /// \param radius the radius of the query sphere + /// \param models[out] vector of const ModelItem* + void getModels(const glm::vec3& position, float radius, QVector& foundModels) const; + + /// finds all models that touch a box + /// \param box the query box + /// \param models[out] vector of non-const ModelItem* + void getModelsForUpdate(const AABox& box, QVector& foundModels); + + const ModelItem* getModelWithID(uint32_t id) const; + + bool removeModelWithID(uint32_t id); + +protected: + virtual void init(unsigned char * octalCode); + + void storeModel(const ModelItem& model); + + ModelTree* _myTree; + QList* _modelItems; +}; + +#endif // hifi_ModelTreeElement_h diff --git a/libraries/models/src/ModelTreeHeadlessViewer.cpp b/libraries/models/src/ModelTreeHeadlessViewer.cpp new file mode 100644 index 0000000000..5a0907b201 --- /dev/null +++ b/libraries/models/src/ModelTreeHeadlessViewer.cpp @@ -0,0 +1,38 @@ +// +// ModelTreeHeadlessViewer.cpp +// libraries/models/src +// +// Created by Brad Hefta-Gaub on 2/26/14. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "ModelTreeHeadlessViewer.h" + +ModelTreeHeadlessViewer::ModelTreeHeadlessViewer() : + OctreeHeadlessViewer() { +} + +ModelTreeHeadlessViewer::~ModelTreeHeadlessViewer() { +} + +void ModelTreeHeadlessViewer::init() { + OctreeHeadlessViewer::init(); +} + + +void ModelTreeHeadlessViewer::update() { + if (_tree) { + ModelTree* tree = static_cast(_tree); + if (tree->tryLockForWrite()) { + tree->update(); + tree->unlock(); + } + } +} + +void ModelTreeHeadlessViewer::processEraseMessage(const QByteArray& dataByteArray, const SharedNodePointer& sourceNode) { + static_cast(_tree)->processEraseMessage(dataByteArray, sourceNode); +} diff --git a/libraries/models/src/ModelTreeHeadlessViewer.h b/libraries/models/src/ModelTreeHeadlessViewer.h new file mode 100644 index 0000000000..0b5cde473d --- /dev/null +++ b/libraries/models/src/ModelTreeHeadlessViewer.h @@ -0,0 +1,45 @@ +// +// ModelTreeHeadlessViewer.h +// libraries/models/src +// +// Created by Brad Hefta-Gaub on 2/26/14. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_ModelTreeHeadlessViewer_h +#define hifi_ModelTreeHeadlessViewer_h + +#include +#include +#include +#include +#include +#include + +#include "ModelTree.h" + +// Generic client side Octree renderer class. +class ModelTreeHeadlessViewer : public OctreeHeadlessViewer { + Q_OBJECT +public: + ModelTreeHeadlessViewer(); + virtual ~ModelTreeHeadlessViewer(); + + virtual Octree* createTree() { return new ModelTree(true); } + virtual NodeType_t getMyNodeType() const { return NodeType::ModelServer; } + virtual PacketType getMyQueryMessageType() const { return PacketTypeModelQuery; } + virtual PacketType getExpectedPacketType() const { return PacketTypeModelData; } + + void update(); + + ModelTree* getTree() { return (ModelTree*)_tree; } + + void processEraseMessage(const QByteArray& dataByteArray, const SharedNodePointer& sourceNode); + + virtual void init(); +}; + +#endif // hifi_ModelTreeHeadlessViewer_h diff --git a/libraries/models/src/ModelsScriptingInterface.cpp b/libraries/models/src/ModelsScriptingInterface.cpp new file mode 100644 index 0000000000..446b0280a4 --- /dev/null +++ b/libraries/models/src/ModelsScriptingInterface.cpp @@ -0,0 +1,176 @@ +// +// ModelsScriptingInterface.cpp +// libraries/models/src +// +// Created by Brad Hefta-Gaub on 12/6/13. +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "ModelsScriptingInterface.h" +#include "ModelTree.h" + +ModelsScriptingInterface::ModelsScriptingInterface() : + _nextCreatorTokenID(0), + _modelTree(NULL) +{ +} + + +void ModelsScriptingInterface::queueModelMessage(PacketType packetType, + ModelItemID modelID, const ModelItemProperties& properties) { + getModelPacketSender()->queueModelEditMessage(packetType, modelID, properties); +} + +ModelItemID ModelsScriptingInterface::addModel(const ModelItemProperties& properties) { + + // The application will keep track of creatorTokenID + uint32_t creatorTokenID = ModelItem::getNextCreatorTokenID(); + + ModelItemID id(NEW_MODEL, creatorTokenID, false ); + + // queue the packet + queueModelMessage(PacketTypeModelAddOrEdit, id, properties); + + // If we have a local model tree set, then also update it. + if (_modelTree) { + _modelTree->lockForWrite(); + _modelTree->addModel(id, properties); + _modelTree->unlock(); + } + + return id; +} + +ModelItemID ModelsScriptingInterface::identifyModel(ModelItemID modelID) { + uint32_t actualID = modelID.id; + + if (!modelID.isKnownID) { + actualID = ModelItem::getIDfromCreatorTokenID(modelID.creatorTokenID); + if (actualID == UNKNOWN_MODEL_ID) { + return modelID; // bailing early + } + + // found it! + modelID.id = actualID; + modelID.isKnownID = true; + } + return modelID; +} + +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; +} + + + +ModelItemID ModelsScriptingInterface::editModel(ModelItemID modelID, const ModelItemProperties& properties) { + 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. We can do this even if we don't know + // the actual id, because we can edit out local models just with creatorTokenID + if (_modelTree) { + _modelTree->lockForWrite(); + _modelTree->updateModel(modelID, properties); + _modelTree->unlock(); + } + + return modelID; +} + + +// 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(); + } +} + +ModelItemID ModelsScriptingInterface::findClosestModel(const glm::vec3& center, float radius) const { + ModelItemID result(UNKNOWN_MODEL_ID, UNKNOWN_MODEL_TOKEN, false); + if (_modelTree) { + _modelTree->lockForRead(); + const ModelItem* closestModel = _modelTree->findClosestModel(center/(float)TREE_SCALE, + radius/(float)TREE_SCALE); + _modelTree->unlock(); + if (closestModel) { + result.id = closestModel->getID(); + result.isKnownID = true; + } + } + return result; +} + + +QVector ModelsScriptingInterface::findModels(const glm::vec3& center, float radius) const { + QVector result; + if (_modelTree) { + _modelTree->lockForRead(); + QVector models; + _modelTree->findModels(center/(float)TREE_SCALE, radius/(float)TREE_SCALE, models); + _modelTree->unlock(); + + foreach (const ModelItem* model, models) { + ModelItemID thisModelItemID(model->getID(), UNKNOWN_MODEL_TOKEN, true); + result << thisModelItemID; + } + } + return result; +} + diff --git a/libraries/models/src/ModelsScriptingInterface.h b/libraries/models/src/ModelsScriptingInterface.h new file mode 100644 index 0000000000..bf8e193f25 --- /dev/null +++ b/libraries/models/src/ModelsScriptingInterface.h @@ -0,0 +1,73 @@ +// +// ModelsScriptingInterface.h +// libraries/models/src +// +// Created by Brad Hefta-Gaub on 12/6/13. +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_ModelsScriptingInterface_h +#define hifi_ModelsScriptingInterface_h + +#include + +#include + +#include +#include "ModelEditPacketSender.h" + +/// handles scripting of Model commands from JS passed to assigned clients +class ModelsScriptingInterface : public OctreeScriptingInterface { + Q_OBJECT +public: + ModelsScriptingInterface(); + + ModelEditPacketSender* getModelPacketSender() const { return (ModelEditPacketSender*)getPacketSender(); } + virtual NodeType_t getServerNodeType() const { return NodeType::ModelServer; } + virtual OctreeEditPacketSender* createPacketSender() { return new ModelEditPacketSender(); } + + void setModelTree(ModelTree* modelTree) { _modelTree = modelTree; } + ModelTree* getModelTree(ModelTree*) { return _modelTree; } + +public slots: + /// adds a model with the specific properties + ModelItemID addModel(const ModelItemProperties& properties); + + /// identify a recently created model to determine its true ID + ModelItemID identifyModel(ModelItemID modelID); + + /// gets the current model properties for a specific model + /// this function will not find return results in script engine contexts which don't have access to models + ModelItemProperties getModelProperties(ModelItemID modelID); + + /// edits a model updating only the included properties, will return the identified ModelItemID in case of + /// successful edit, if the input modelID is for an unknown model this function will have no effect + ModelItemID editModel(ModelItemID modelID, const ModelItemProperties& properties); + + /// deletes a model + void deleteModel(ModelItemID modelID); + + /// finds the closest model to the center point, within the radius + /// will return a ModelItemID.isKnownID = false if no models are in the radius + /// this function will not find any models in script engine contexts which don't have access to models + ModelItemID findClosestModel(const glm::vec3& center, float radius) const; + + /// finds models within the search sphere specified by the center point and radius + /// this function will not find any models in script engine contexts which don't have access to models + QVector findModels(const glm::vec3& center, float radius) const; + +signals: + void modelCollisionWithVoxel(const ModelItemID& modelID, const VoxelDetail& voxel, const CollisionInfo& collision); + void modelCollisionWithModel(const ModelItemID& idA, const ModelItemID& idB, const CollisionInfo& collision); + +private: + void queueModelMessage(PacketType packetType, ModelItemID modelID, const ModelItemProperties& properties); + + uint32_t _nextCreatorTokenID; + ModelTree* _modelTree; +}; + +#endif // hifi_ModelsScriptingInterface_h From 0182a6d8954d390773f1cbba6a53a41b95ec7d94 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Wed, 30 Apr 2014 11:12:49 -0700 Subject: [PATCH 03/13] models working end to end --- assignment-client/src/AssignmentFactory.cpp | 3 +++ interface/CMakeLists.txt | 2 +- interface/src/Application.cpp | 20 +++++++++++++++++++ interface/src/Application.h | 7 ++++++- interface/src/Menu.cpp | 3 ++- interface/src/Menu.h | 4 +--- .../{ => particles}/ParticleTreeRenderer.cpp | 0 .../{ => particles}/ParticleTreeRenderer.h | 0 libraries/networking/src/PacketHeaders.h | 2 +- libraries/script-engine/src/ScriptEngine.cpp | 13 ++++++++---- 10 files changed, 43 insertions(+), 11 deletions(-) rename interface/src/{ => particles}/ParticleTreeRenderer.cpp (100%) rename interface/src/{ => particles}/ParticleTreeRenderer.h (100%) diff --git a/assignment-client/src/AssignmentFactory.cpp b/assignment-client/src/AssignmentFactory.cpp index cdf5c591e1..d2c2016328 100644 --- a/assignment-client/src/AssignmentFactory.cpp +++ b/assignment-client/src/AssignmentFactory.cpp @@ -16,6 +16,7 @@ #include "audio/AudioMixer.h" #include "avatars/AvatarMixer.h" #include "metavoxels/MetavoxelServer.h" +#include "models/ModelServer.h" #include "particles/ParticleServer.h" #include "voxels/VoxelServer.h" @@ -41,6 +42,8 @@ ThreadedAssignment* AssignmentFactory::unpackAssignment(const QByteArray& packet return new ParticleServer(packet); case Assignment::MetavoxelServerType: return new MetavoxelServer(packet); + case Assignment::ModelServerType: + return new ModelServer(packet); default: return NULL; } diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index f7c39a7479..0a56109260 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -49,7 +49,7 @@ configure_file(InterfaceVersion.h.in "${PROJECT_BINARY_DIR}/includes/InterfaceVe # grab the implementation and header files from src dirs file(GLOB INTERFACE_SRCS src/*.cpp src/*.h) -foreach(SUBDIR avatar devices renderer ui starfield location scripting voxels) +foreach(SUBDIR avatar devices renderer ui starfield location scripting voxels particles models) file(GLOB_RECURSE SUBDIR_SRCS src/${SUBDIR}/*.cpp src/${SUBDIR}/*.h) set(INTERFACE_SRCS ${INTERFACE_SRCS} "${SUBDIR_SRCS}") endforeach(SUBDIR) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 71bf02cf97..8014ba6161 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -54,6 +54,7 @@ #include #include #include +#include #include #include #include @@ -1665,6 +1666,9 @@ void Application::init() { _particles.init(); _particles.setViewFrustum(getViewFrustum()); + _models.init(); + _models.setViewFrustum(getViewFrustum()); + _metavoxels.init(); _particleCollisionSystem.init(&_particleEditSender, _particles.getTree(), _voxels.getTree(), &_audio, &_avatarManager); @@ -1995,6 +1999,8 @@ void Application::update(float deltaTime) { _particles.update(); // update the particles... _particleCollisionSystem.update(); // collide the particles... + _models.update(); // update the models... + _overlays.update(deltaTime); // let external parties know we're updating @@ -2335,6 +2341,7 @@ void Application::updateShadowMap() { _avatarManager.renderAvatars(Avatar::SHADOW_RENDER_MODE); _particles.render(); + _models.render(); glPopMatrix(); @@ -2501,6 +2508,13 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) { _particles.render(); } + // render models... + if (Menu::getInstance()->isOptionChecked(MenuOption::Models)) { + PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), + "Application::displaySide() ... models..."); + _models.render(); + } + // render the ambient occlusion effect if enabled if (Menu::getInstance()->isOptionChecked(MenuOption::AmbientOcclusion)) { PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), @@ -3095,6 +3109,9 @@ void Application::domainChanged(const QString& domainHostname) { // reset the particle renderer _particles.clear(); + // reset the model renderer + _models.clear(); + // reset the voxels renderer _voxels.killLocalVoxels(); } @@ -3428,6 +3445,9 @@ ScriptEngine* Application::loadScript(const QString& scriptName, bool loadScript scriptEngine->getParticlesScriptingInterface()->setPacketSender(&_particleEditSender); scriptEngine->getParticlesScriptingInterface()->setParticleTree(_particles.getTree()); + scriptEngine->getModelsScriptingInterface()->setPacketSender(&_modelEditSender); + scriptEngine->getModelsScriptingInterface()->setModelTree(_models.getTree()); + // hook our avatar object into this script engine scriptEngine->setAvatarData(_myAvatar, "MyAvatar"); // leave it as a MyAvatar class to expose thrust features diff --git a/interface/src/Application.h b/interface/src/Application.h index 3fe30a1b08..a220525bc3 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -29,6 +29,7 @@ #include #include +#include #include #include #include @@ -51,7 +52,6 @@ #include "Menu.h" #include "MetavoxelSystem.h" #include "PacketHeaders.h" -#include "ParticleTreeRenderer.h" #include "Stars.h" #include "avatar/Avatar.h" #include "avatar/AvatarManager.h" @@ -60,6 +60,8 @@ #include "devices/Faceshift.h" #include "devices/SixenseManager.h" #include "devices/Visage.h" +#include "models/ModelTreeRenderer.h" +#include "particles/ParticleTreeRenderer.h" #include "renderer/AmbientOcclusionEffect.h" #include "renderer/GeometryCache.h" #include "renderer/GlowEffect.h" @@ -416,6 +418,8 @@ private: ParticleTreeRenderer _particles; ParticleCollisionSystem _particleCollisionSystem; + ModelTreeRenderer _models; + QByteArray _voxelsFilename; bool _wantToKillLocalVoxels; @@ -493,6 +497,7 @@ private: VoxelHideShowThread _voxelHideShowThread; VoxelEditPacketSender _voxelEditSender; ParticleEditPacketSender _particleEditSender; + ModelEditPacketSender _modelEditSender; int _packetsPerSecond; int _bytesPerSecond; diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index e194734928..296706c90e 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -280,8 +280,9 @@ Menu::Menu() : addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Shadows, 0, false); addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Metavoxels, 0, true); - addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::BuckyBalls, 0, true); + addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::BuckyBalls, 0, false); addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Particles, 0, true); + addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Models, 0, true); addActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::LodTools, Qt::SHIFT | Qt::Key_L, this, SLOT(lodTools())); QMenu* voxelOptionsMenu = developerMenu->addMenu("Voxel Options"); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 967312dca9..e99649438d 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -276,9 +276,6 @@ namespace MenuOption { const QString AudioSpatialProcessingWithDiffusions = "With Diffusions"; const QString AudioSpatialProcessingDontDistanceAttenuate = "Don't calculate distance attenuation"; const QString AudioSpatialProcessingAlternateDistanceAttenuate = "Alternate distance attenuation"; - - - const QString Avatars = "Avatars"; const QString Bandwidth = "Bandwidth Display"; const QString BandwidthDetails = "Bandwidth Details"; @@ -330,6 +327,7 @@ namespace MenuOption { const QString MetavoxelEditor = "Metavoxel Editor..."; const QString Metavoxels = "Metavoxels"; const QString Mirror = "Mirror"; + const QString Models = "Models"; const QString MoveWithLean = "Move with Lean"; const QString MuteAudio = "Mute Microphone"; const QString NameLocation = "Name this location"; diff --git a/interface/src/ParticleTreeRenderer.cpp b/interface/src/particles/ParticleTreeRenderer.cpp similarity index 100% rename from interface/src/ParticleTreeRenderer.cpp rename to interface/src/particles/ParticleTreeRenderer.cpp diff --git a/interface/src/ParticleTreeRenderer.h b/interface/src/particles/ParticleTreeRenderer.h similarity index 100% rename from interface/src/ParticleTreeRenderer.h rename to interface/src/particles/ParticleTreeRenderer.h diff --git a/libraries/networking/src/PacketHeaders.h b/libraries/networking/src/PacketHeaders.h index e69bc341be..c6b4e3b9e5 100644 --- a/libraries/networking/src/PacketHeaders.h +++ b/libraries/networking/src/PacketHeaders.h @@ -74,7 +74,7 @@ const QSet NON_VERIFIED_PACKETS = QSet() << PacketTypeDomainServerRequireDTLS << PacketTypeDomainConnectRequest << PacketTypeDomainList << PacketTypeDomainListRequest << PacketTypeCreateAssignment << PacketTypeRequestAssignment << PacketTypeStunResponse - << PacketTypeNodeJsonStats << PacketTypeVoxelQuery << PacketTypeParticleQuery; + << PacketTypeNodeJsonStats << PacketTypeVoxelQuery << PacketTypeParticleQuery << PacketTypeModelQuery; const int NUM_BYTES_MD5_HASH = 16; const int NUM_STATIC_HEADER_BYTES = sizeof(PacketVersion) + NUM_BYTES_RFC4122_UUID; diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index f7d01863cc..92376cf358 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -20,15 +20,14 @@ #include #include #include +#include #include #include +#include +#include #include #include #include -#include -#include - -#include #include "AnimationObject.h" #include "MenuItemProperties.h" @@ -205,6 +204,11 @@ void ScriptEngine::init() { qScriptRegisterMetaType(&_engine, ParticlePropertiesToScriptValue, ParticlePropertiesFromScriptValue); qScriptRegisterMetaType(&_engine, ParticleIDtoScriptValue, ParticleIDfromScriptValue); qScriptRegisterSequenceMetaType >(&_engine); + + qScriptRegisterMetaType(&_engine, ModelItemPropertiesToScriptValue, ModelItemPropertiesFromScriptValue); + qScriptRegisterMetaType(&_engine, ModelItemIDtoScriptValue, ModelItemIDfromScriptValue); + qScriptRegisterSequenceMetaType >(&_engine); + qScriptRegisterSequenceMetaType >(&_engine); qScriptRegisterSequenceMetaType >(&_engine); qScriptRegisterSequenceMetaType >(&_engine); @@ -225,6 +229,7 @@ void ScriptEngine::init() { registerGlobalObject("Script", this); registerGlobalObject("Audio", &_audioScriptingInterface); registerGlobalObject("Controller", _controllerScriptingInterface); + registerGlobalObject("Models", &_modelsScriptingInterface); registerGlobalObject("Particles", &_particlesScriptingInterface); registerGlobalObject("Quat", &_quatLibrary); registerGlobalObject("Vec3", &_vec3Library); From be3d307b273920724cd4d7aeaa939893262d6ca6 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Wed, 30 Apr 2014 13:48:09 -0700 Subject: [PATCH 04/13] added cmake support for STREAMABLE --- libraries/models/CMakeLists.txt | 8 +++++++- libraries/models/src/ModelItem.cpp | 1 + libraries/models/src/ModelItem.h | 2 -- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/libraries/models/CMakeLists.txt b/libraries/models/CMakeLists.txt index 5bd02714d2..062352e50c 100644 --- a/libraries/models/CMakeLists.txt +++ b/libraries/models/CMakeLists.txt @@ -14,8 +14,11 @@ set(TARGET_NAME models) find_package(Qt5Widgets REQUIRED) +include(${MACRO_DIR}/AutoMTC.cmake) +auto_mtc(${TARGET_NAME} "${ROOT_DIR}") + include(${MACRO_DIR}/SetupHifiLibrary.cmake) -setup_hifi_library(${TARGET_NAME}) +setup_hifi_library(${TARGET_NAME} "${AUTOMTC_SRC}") include(${MACRO_DIR}/IncludeGLM.cmake) include_glm(${TARGET_NAME} "${ROOT_DIR}") @@ -26,6 +29,9 @@ link_hifi_library(octree ${TARGET_NAME} "${ROOT_DIR}") link_hifi_library(fbx ${TARGET_NAME} "${ROOT_DIR}") link_hifi_library(networking ${TARGET_NAME} "${ROOT_DIR}") +# for streamable +link_hifi_library(metavoxels ${TARGET_NAME} "${ROOT_DIR}") + # link ZLIB and GnuTLS find_package(ZLIB) find_package(GnuTLS REQUIRED) diff --git a/libraries/models/src/ModelItem.cpp b/libraries/models/src/ModelItem.cpp index 2ebff1137a..605b5b209e 100644 --- a/libraries/models/src/ModelItem.cpp +++ b/libraries/models/src/ModelItem.cpp @@ -1348,3 +1348,4 @@ void ModelItemIDfromScriptValue(const QScriptValue &object, ModelItemID& id) { } + diff --git a/libraries/models/src/ModelItem.h b/libraries/models/src/ModelItem.h index d691a4e98b..c03dcd51d2 100644 --- a/libraries/models/src/ModelItem.h +++ b/libraries/models/src/ModelItem.h @@ -357,6 +357,4 @@ protected: static std::map _tokenIDsToIDs; }; - - #endif // hifi_ModelItem_h From f53df4c59785a6eb9cf3205821a3e5aaf73162cc Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Wed, 30 Apr 2014 14:18:35 -0700 Subject: [PATCH 05/13] removed velocity, gravity, etc from models --- libraries/models/src/ModelItem.cpp | 525 ++-------------------- libraries/models/src/ModelItem.h | 92 +--- libraries/models/src/ModelTreeElement.cpp | 11 - 3 files changed, 50 insertions(+), 578 deletions(-) diff --git a/libraries/models/src/ModelItem.cpp b/libraries/models/src/ModelItem.cpp index 605b5b209e..1a4d32581b 100644 --- a/libraries/models/src/ModelItem.cpp +++ b/libraries/models/src/ModelItem.cpp @@ -1,6 +1,6 @@ // // ModelItem.cpp -// libraries/particles/src +// libraries/models/src // // Created by Brad Hefta-Gaub on 12/4/13. // Copyright 2013 High Fidelity, Inc. @@ -29,10 +29,8 @@ #include "ModelTree.h" uint32_t ModelItem::_nextID = 0; -//VoxelEditPacketSender* ModelItem::_voxelEditSender = NULL; -//ModelItemEditPacketSender* ModelItem::_particleEditSender = NULL; -// for locally created particles +// for locally created models std::map ModelItem::_tokenIDsToIDs; uint32_t ModelItem::_nextCreatorTokenID = 0; @@ -58,41 +56,32 @@ void ModelItem::handleAddModelResponse(const QByteArray& packet) { memcpy(&creatorTokenID, dataAt, sizeof(creatorTokenID)); dataAt += sizeof(creatorTokenID); - uint32_t particleID; - memcpy(&particleID, dataAt, sizeof(particleID)); - dataAt += sizeof(particleID); + uint32_t modelItemID; + memcpy(&modelItemID, dataAt, sizeof(modelItemID)); + dataAt += sizeof(modelItemID); // add our token to id mapping - _tokenIDsToIDs[creatorTokenID] = particleID; + _tokenIDsToIDs[creatorTokenID] = modelItemID; } ModelItem::ModelItem() { rgbColor noColor = { 0, 0, 0 }; - init(glm::vec3(0,0,0), 0, noColor, glm::vec3(0,0,0), - MODEL_DEFAULT_GRAVITY, MODEL_DEFAULT_DAMPING, MODEL_DEFAULT_LIFETIME, MODEL_NOT_IN_HAND, MODEL_DEFAULT_SCRIPT, NEW_MODEL); + init(glm::vec3(0,0,0), 0, noColor, NEW_MODEL); } -ModelItem::ModelItem(const ModelItemID& particleID, const ModelItemProperties& properties) { - _id = particleID.id; - _creatorTokenID = particleID.creatorTokenID; +ModelItem::ModelItem(const ModelItemID& modelItemID, const ModelItemProperties& properties) { + _id = modelItemID.id; + _creatorTokenID = modelItemID.creatorTokenID; // init values with defaults before calling setProperties uint64_t now = usecTimestampNow(); _lastEdited = now; _lastUpdated = now; - _created = now; // will get updated as appropriate in setAge() _position = glm::vec3(0,0,0); _radius = 0; - _mass = 1.0f; rgbColor noColor = { 0, 0, 0 }; memcpy(_color, noColor, sizeof(_color)); - _velocity = glm::vec3(0,0,0); - _damping = MODEL_DEFAULT_DAMPING; - _lifetime = MODEL_DEFAULT_LIFETIME; - _gravity = MODEL_DEFAULT_GRAVITY; - _script = MODEL_DEFAULT_SCRIPT; - _inHand = MODEL_NOT_IN_HAND; _shouldDie = false; _modelURL = MODEL_DEFAULT_MODEL_URL; _modelTranslation = MODEL_DEFAULT_MODEL_TRANSLATION; @@ -106,8 +95,7 @@ ModelItem::ModelItem(const ModelItemID& particleID, const ModelItemProperties& p ModelItem::~ModelItem() { } -void ModelItem::init(glm::vec3 position, float radius, rgbColor color, glm::vec3 velocity, glm::vec3 gravity, - float damping, float lifetime, bool inHand, QString updateScript, uint32_t id) { +void ModelItem::init(glm::vec3 position, float radius, rgbColor color, uint32_t id) { if (id == NEW_MODEL) { _id = _nextID; _nextID++; @@ -117,18 +105,10 @@ void ModelItem::init(glm::vec3 position, float radius, rgbColor color, glm::vec3 quint64 now = usecTimestampNow(); _lastEdited = now; _lastUpdated = now; - _created = now; // will get updated as appropriate in setAge() _position = position; _radius = radius; - _mass = 1.0f; memcpy(_color, color, sizeof(_color)); - _velocity = velocity; - _damping = damping; - _lifetime = lifetime; - _gravity = gravity; - _script = updateScript; - _inHand = inHand; _shouldDie = false; _modelURL = MODEL_DEFAULT_MODEL_URL; _modelTranslation = MODEL_DEFAULT_MODEL_TRANSLATION; @@ -136,21 +116,12 @@ void ModelItem::init(glm::vec3 position, float radius, rgbColor color, glm::vec3 _modelScale = MODEL_DEFAULT_MODEL_SCALE; } -void ModelItem::setMass(float value) { - if (value > 0.0f) { - _mass = value; - } -} - bool ModelItem::appendModelData(OctreePacketData* packetData) const { bool success = packetData->appendValue(getID()); //qDebug("ModelItem::appendModelData()... getID()=%d", getID()); - if (success) { - success = packetData->appendValue(getAge()); - } if (success) { success = packetData->appendValue(getLastUpdated()); } @@ -166,31 +137,9 @@ bool ModelItem::appendModelData(OctreePacketData* packetData) const { if (success) { success = packetData->appendColor(getColor()); } - if (success) { - success = packetData->appendValue(getVelocity()); - } - if (success) { - success = packetData->appendValue(getGravity()); - } - if (success) { - success = packetData->appendValue(getDamping()); - } - if (success) { - success = packetData->appendValue(getLifetime()); - } - if (success) { - success = packetData->appendValue(getInHand()); - } if (success) { success = packetData->appendValue(getShouldDie()); } - if (success) { - uint16_t scriptLength = _script.size() + 1; // include NULL - success = packetData->appendValue(scriptLength); - if (success) { - success = packetData->appendRawData((const unsigned char*)qPrintable(_script), scriptLength); - } - } // modelURL if (success) { @@ -224,12 +173,7 @@ int ModelItem::expectedBytes() { + sizeof(quint64) // lasted edited + sizeof(float) // radius + sizeof(glm::vec3) // position - + sizeof(rgbColor) // color - + sizeof(glm::vec3) // velocity - + sizeof(glm::vec3) // gravity - + sizeof(float) // damping - + sizeof(float) // lifetime - + sizeof(bool); // inhand + + sizeof(rgbColor); // color // potentially more... return expectedBytes; } @@ -246,13 +190,6 @@ int ModelItem::readModelDataFromBuffer(const unsigned char* data, int bytesLeftT dataAt += sizeof(_id); bytesRead += sizeof(_id); - // age - float age; - memcpy(&age, dataAt, sizeof(age)); - dataAt += sizeof(age); - bytesRead += sizeof(age); - setAge(age); - // _lastUpdated memcpy(&_lastUpdated, dataAt, sizeof(_lastUpdated)); dataAt += sizeof(_lastUpdated); @@ -280,46 +217,11 @@ int ModelItem::readModelDataFromBuffer(const unsigned char* data, int bytesLeftT dataAt += sizeof(_color); bytesRead += sizeof(_color); - // velocity - memcpy(&_velocity, dataAt, sizeof(_velocity)); - dataAt += sizeof(_velocity); - bytesRead += sizeof(_velocity); - - // gravity - memcpy(&_gravity, dataAt, sizeof(_gravity)); - dataAt += sizeof(_gravity); - bytesRead += sizeof(_gravity); - - // damping - memcpy(&_damping, dataAt, sizeof(_damping)); - dataAt += sizeof(_damping); - bytesRead += sizeof(_damping); - - // lifetime - memcpy(&_lifetime, dataAt, sizeof(_lifetime)); - dataAt += sizeof(_lifetime); - bytesRead += sizeof(_lifetime); - - // inHand - memcpy(&_inHand, dataAt, sizeof(_inHand)); - dataAt += sizeof(_inHand); - bytesRead += sizeof(_inHand); - // shouldDie memcpy(&_shouldDie, dataAt, sizeof(_shouldDie)); dataAt += sizeof(_shouldDie); bytesRead += sizeof(_shouldDie); - // script - uint16_t scriptLength; - memcpy(&scriptLength, dataAt, sizeof(scriptLength)); - dataAt += sizeof(scriptLength); - bytesRead += sizeof(scriptLength); - QString tempString((const char*)dataAt); - _script = tempString; - dataAt += scriptLength; - bytesRead += scriptLength; - // modelURL uint16_t modelURLLength; memcpy(&modelURLLength, dataAt, sizeof(modelURLLength)); @@ -372,7 +274,7 @@ ModelItem ModelItem::fromEditPacket(const unsigned char* data, int length, int& bool isNewModelItem = (editID == NEW_MODEL); - // special case for handling "new" particles + // 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 @@ -383,18 +285,17 @@ ModelItem ModelItem::fromEditPacket(const unsigned char* data, int length, int& newModelItem.setCreatorTokenID(creatorTokenID); newModelItem._newlyCreated = true; - newModelItem.setAge(0); // this guy is new! } else { - // look up the existing particle + // look up the existing modelItem const ModelItem* existingModelItem = tree->findModelByID(editID, true); // copy existing properties before over-writing with new properties if (existingModelItem) { newModelItem = *existingModelItem; } else { - // the user attempted to edit a particle that doesn't exist - qDebug() << "user attempted to edit a particle that doesn't exist..."; + // the user attempted to edit a modelItem that doesn't exist + qDebug() << "user attempted to edit a modelItem that doesn't exist..."; valid = false; return newModelItem; } @@ -442,42 +343,6 @@ ModelItem ModelItem::fromEditPacket(const unsigned char* data, int length, int& processedBytes += sizeof(newModelItem._color); } - // velocity - if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_VELOCITY) == MODEL_PACKET_CONTAINS_VELOCITY)) { - memcpy(&newModelItem._velocity, dataAt, sizeof(newModelItem._velocity)); - dataAt += sizeof(newModelItem._velocity); - processedBytes += sizeof(newModelItem._velocity); - } - - // gravity - if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_GRAVITY) == MODEL_PACKET_CONTAINS_GRAVITY)) { - memcpy(&newModelItem._gravity, dataAt, sizeof(newModelItem._gravity)); - dataAt += sizeof(newModelItem._gravity); - processedBytes += sizeof(newModelItem._gravity); - } - - // damping - if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_DAMPING) == MODEL_PACKET_CONTAINS_DAMPING)) { - memcpy(&newModelItem._damping, dataAt, sizeof(newModelItem._damping)); - dataAt += sizeof(newModelItem._damping); - processedBytes += sizeof(newModelItem._damping); - } - - // lifetime - if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_LIFETIME) == MODEL_PACKET_CONTAINS_LIFETIME)) { - memcpy(&newModelItem._lifetime, dataAt, sizeof(newModelItem._lifetime)); - dataAt += sizeof(newModelItem._lifetime); - processedBytes += sizeof(newModelItem._lifetime); - } - - // TODO: make inHand and shouldDie into single bits - // inHand - if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_INHAND) == MODEL_PACKET_CONTAINS_INHAND)) { - memcpy(&newModelItem._inHand, dataAt, sizeof(newModelItem._inHand)); - dataAt += sizeof(newModelItem._inHand); - processedBytes += sizeof(newModelItem._inHand); - } - // shouldDie if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_SHOULDDIE) == MODEL_PACKET_CONTAINS_SHOULDDIE)) { memcpy(&newModelItem._shouldDie, dataAt, sizeof(newModelItem._shouldDie)); @@ -485,18 +350,6 @@ ModelItem ModelItem::fromEditPacket(const unsigned char* data, int length, int& processedBytes += sizeof(newModelItem._shouldDie); } - // script - if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_SCRIPT) == MODEL_PACKET_CONTAINS_SCRIPT)) { - uint16_t scriptLength; - memcpy(&scriptLength, dataAt, sizeof(scriptLength)); - dataAt += sizeof(scriptLength); - processedBytes += sizeof(scriptLength); - QString tempString((const char*)dataAt); - newModelItem._script = tempString; - dataAt += scriptLength; - processedBytes += scriptLength; - } - // modelURL if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_MODEL_URL) == MODEL_PACKET_CONTAINS_MODEL_URL)) { uint16_t modelURLLength; @@ -542,13 +395,10 @@ ModelItem ModelItem::fromEditPacket(const unsigned char* data, int length, int& void ModelItem::debugDump() const { qDebug("ModelItem id :%u", _id); - qDebug(" age:%f", getAge()); qDebug(" edited ago:%f", getEditedAgo()); qDebug(" should die:%s", debug::valueOf(getShouldDie())); qDebug(" position:%f,%f,%f", _position.x, _position.y, _position.z); qDebug(" radius:%f", getRadius()); - qDebug(" velocity:%f,%f,%f", _velocity.x, _velocity.y, _velocity.z); - qDebug(" gravity:%f,%f,%f", _gravity.x, _gravity.y, _gravity.z); qDebug(" color:%d,%d,%d", _color[0], _color[1], _color[2]); } @@ -559,15 +409,15 @@ bool ModelItem::encodeModelEditMessageDetails(PacketType command, ModelItemID id unsigned char* copyAt = bufferOut; sizeOut = 0; - // get the octal code for the particle + // 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 particle will go matters for - // particle servers with different jurisdictions, but for now, we'll send everything to the root, since the + // 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, @@ -589,7 +439,7 @@ bool ModelItem::encodeModelEditMessageDetails(PacketType command, ModelItemID id copyAt += sizeof(id.id); sizeOut += sizeof(id.id); - // special case for handling "new" particles + // 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 @@ -604,7 +454,7 @@ bool ModelItem::encodeModelEditMessageDetails(PacketType command, ModelItemID id copyAt += sizeof(lastEdited); sizeOut += sizeof(lastEdited); - // For new particles, all remaining items are mandatory, for an edited particle, All of the remaining items are + // 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) { @@ -637,46 +487,6 @@ bool ModelItem::encodeModelEditMessageDetails(PacketType command, ModelItemID id sizeOut += sizeof(color); } - // velocity - if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_VELOCITY) == MODEL_PACKET_CONTAINS_VELOCITY)) { - glm::vec3 velocity = properties.getVelocity() / (float)TREE_SCALE; - memcpy(copyAt, &velocity, sizeof(velocity)); - copyAt += sizeof(velocity); - sizeOut += sizeof(velocity); - } - - // gravity - if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_GRAVITY) == MODEL_PACKET_CONTAINS_GRAVITY)) { - glm::vec3 gravity = properties.getGravity() / (float)TREE_SCALE; - memcpy(copyAt, &gravity, sizeof(gravity)); - copyAt += sizeof(gravity); - sizeOut += sizeof(gravity); - } - - // damping - if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_DAMPING) == MODEL_PACKET_CONTAINS_DAMPING)) { - float damping = properties.getDamping(); - memcpy(copyAt, &damping, sizeof(damping)); - copyAt += sizeof(damping); - sizeOut += sizeof(damping); - } - - // lifetime - if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_LIFETIME) == MODEL_PACKET_CONTAINS_LIFETIME)) { - float lifetime = properties.getLifetime(); - memcpy(copyAt, &lifetime, sizeof(lifetime)); - copyAt += sizeof(lifetime); - sizeOut += sizeof(lifetime); - } - - // inHand - if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_INHAND) == MODEL_PACKET_CONTAINS_INHAND)) { - bool inHand = properties.getInHand(); - memcpy(copyAt, &inHand, sizeof(inHand)); - copyAt += sizeof(inHand); - sizeOut += sizeof(inHand); - } - // shoulDie if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_SHOULDDIE) == MODEL_PACKET_CONTAINS_SHOULDDIE)) { bool shouldDie = properties.getShouldDie(); @@ -685,17 +495,6 @@ bool ModelItem::encodeModelEditMessageDetails(PacketType command, ModelItemID id sizeOut += sizeof(shouldDie); } - // script - if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_SCRIPT) == MODEL_PACKET_CONTAINS_SCRIPT)) { - uint16_t scriptLength = properties.getScript().size() + 1; - memcpy(copyAt, &scriptLength, sizeof(scriptLength)); - copyAt += sizeof(scriptLength); - sizeOut += sizeof(scriptLength); - memcpy(copyAt, qPrintable(properties.getScript()), scriptLength); - copyAt += scriptLength; - sizeOut += scriptLength; - } - // modelURL if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_MODEL_URL) == MODEL_PACKET_CONTAINS_MODEL_URL)) { uint16_t urlLength = properties.getModelURL().size() + 1; @@ -754,7 +553,7 @@ void ModelItem::adjustEditPacketForClockSkew(unsigned char* codeColorBuffer, ssi uint32_t id; memcpy(&id, dataAt, sizeof(id)); dataAt += sizeof(id); - // special case for handling "new" particles + // special case for handling "new" modelItems if (id == NEW_MODEL) { // 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 @@ -775,88 +574,13 @@ void ModelItem::adjustEditPacketForClockSkew(unsigned char* codeColorBuffer, ssi } } -// HALTING_* params are determined using expected acceleration of gravity over some timescale. -// This is a HACK for particles that bounce in a 1.0 gravitational field and should eventually be made more universal. -const float HALTING_MODEL_PERIOD = 0.0167f; // ~1/60th of a second -const float HALTING_MODEL_SPEED = 9.8 * HALTING_MODEL_PERIOD / (float)(TREE_SCALE); - -void ModelItem::applyHardCollision(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. - // - // if elasticity = 0.0, collision is inelastic (vel normal to collision is lost) - // if elasticity = 1.0, collision is 100% elastic. - // - glm::vec3 position = getPosition(); - glm::vec3 velocity = getVelocity(); - - const float EPSILON = 0.0f; - glm::vec3 relativeVelocity = collisionInfo._addedVelocity - velocity; - float velocityDotPenetration = glm::dot(relativeVelocity, collisionInfo._penetration); - if (velocityDotPenetration < EPSILON) { - // particle is moving into collision surface - // - // TODO: do something smarter here by comparing the mass of the particle vs that of the other thing - // (other's mass could be stored in the Collision Info). The smaller mass should surrender more - // position offset and should slave more to the other's velocity in the static-friction case. - position -= collisionInfo._penetration; - - if (glm::length(relativeVelocity) < HALTING_MODEL_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 + collisionInfo._elasticity) * direction; // dynamic reflection - velocity += glm::clamp(collisionInfo._damping, 0.0f, 1.0f) * (relativeVelocity - glm::dot(relativeVelocity, direction) * direction); // dynamic friction - } - } - - // change the local particle too... - setPosition(position); - setVelocity(velocity); -} - void ModelItem::update(const quint64& 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... - bool isInHand = getInHand(); - bool shouldDie = (getAge() > getLifetime()) || getShouldDie(); - setShouldDie(shouldDie); - - //executeUpdateScripts(); // allow the javascript to alter our state - - // If the ball is in hand, it doesn't move or have gravity effect it - if (!isInHand) { - _position += _velocity * timeElapsed; - - // handle bounces off the ground... - if (_position.y <= 0) { - _velocity = _velocity * glm::vec3(1,-1,1); - _position.y = 0; - } - - // handle gravity.... - _velocity += _gravity * timeElapsed; - - // handle damping - glm::vec3 dampingResistance = _velocity * _damping; - _velocity -= dampingResistance * timeElapsed; - //qDebug("applying damping to ModelItem timeElapsed=%f",timeElapsed); - } -} - -void ModelItem::setAge(float age) { - quint64 ageInUsecs = age * USECS_PER_SECOND; - _created = usecTimestampNow() - ageInUsecs; + setShouldDie(getShouldDie()); } void ModelItem::copyChangedProperties(const ModelItem& other) { - float age = getAge(); *this = other; - setAge(age); } ModelItemProperties ModelItem::getProperties() const { @@ -873,12 +597,6 @@ ModelItemProperties::ModelItemProperties() : _position(0), _color(), _radius(MODEL_DEFAULT_RADIUS), - _velocity(0), - _gravity(MODEL_DEFAULT_GRAVITY), - _damping(MODEL_DEFAULT_DAMPING), - _lifetime(MODEL_DEFAULT_LIFETIME), - _script(""), - _inHand(false), _shouldDie(false), _modelURL(""), _modelScale(MODEL_DEFAULT_MODEL_SCALE), @@ -892,12 +610,6 @@ ModelItemProperties::ModelItemProperties() : _positionChanged(false), _colorChanged(false), _radiusChanged(false), - _velocityChanged(false), - _gravityChanged(false), - _dampingChanged(false), - _lifetimeChanged(false), - _scriptChanged(false), - _inHandChanged(false), _shouldDieChanged(false), _modelURLChanged(false), _modelScaleChanged(false), @@ -922,30 +634,6 @@ uint16_t ModelItemProperties::getChangedBits() const { changedBits += MODEL_PACKET_CONTAINS_COLOR; } - if (_velocityChanged) { - changedBits += MODEL_PACKET_CONTAINS_VELOCITY; - } - - if (_gravityChanged) { - changedBits += MODEL_PACKET_CONTAINS_GRAVITY; - } - - if (_dampingChanged) { - changedBits += MODEL_PACKET_CONTAINS_DAMPING; - } - - if (_lifetimeChanged) { - changedBits += MODEL_PACKET_CONTAINS_LIFETIME; - } - - if (_inHandChanged) { - changedBits += MODEL_PACKET_CONTAINS_INHAND; - } - - if (_scriptChanged) { - changedBits += MODEL_PACKET_CONTAINS_SCRIPT; - } - if (_shouldDieChanged) { changedBits += MODEL_PACKET_CONTAINS_SHOULDDIE; } @@ -981,16 +669,6 @@ QScriptValue ModelItemProperties::copyToScriptValue(QScriptEngine* engine) const properties.setProperty("radius", _radius); - QScriptValue velocity = vec3toScriptValue(engine, _velocity); - properties.setProperty("velocity", velocity); - - QScriptValue gravity = vec3toScriptValue(engine, _gravity); - properties.setProperty("gravity", gravity); - - properties.setProperty("damping", _damping); - properties.setProperty("lifetime", _lifetime); - properties.setProperty("script", _script); - properties.setProperty("inHand", _inHand); properties.setProperty("shouldDie", _shouldDie); properties.setProperty("modelURL", _modelURL); @@ -1060,80 +738,6 @@ void ModelItemProperties::copyFromScriptValue(const QScriptValue &object) { } } - QScriptValue velocity = object.property("velocity"); - if (velocity.isValid()) { - QScriptValue x = velocity.property("x"); - QScriptValue y = velocity.property("y"); - QScriptValue z = velocity.property("z"); - if (x.isValid() && y.isValid() && z.isValid()) { - glm::vec3 newVelocity; - newVelocity.x = x.toVariant().toFloat(); - newVelocity.y = y.toVariant().toFloat(); - newVelocity.z = z.toVariant().toFloat(); - if (_defaultSettings || newVelocity != _velocity) { - _velocity = newVelocity; - _velocityChanged = true; - } - } - } - - QScriptValue gravity = object.property("gravity"); - if (gravity.isValid()) { - QScriptValue x = gravity.property("x"); - QScriptValue y = gravity.property("y"); - QScriptValue z = gravity.property("z"); - if (x.isValid() && y.isValid() && z.isValid()) { - glm::vec3 newGravity; - newGravity.x = x.toVariant().toFloat(); - newGravity.y = y.toVariant().toFloat(); - newGravity.z = z.toVariant().toFloat(); - if (_defaultSettings || newGravity != _gravity) { - _gravity = newGravity; - _gravityChanged = true; - } - } - } - - QScriptValue damping = object.property("damping"); - if (damping.isValid()) { - float newDamping; - newDamping = damping.toVariant().toFloat(); - if (_defaultSettings || newDamping != _damping) { - _damping = newDamping; - _dampingChanged = true; - } - } - - QScriptValue lifetime = object.property("lifetime"); - if (lifetime.isValid()) { - float newLifetime; - newLifetime = lifetime.toVariant().toFloat(); - if (_defaultSettings || newLifetime != _lifetime) { - _lifetime = newLifetime; - _lifetimeChanged = true; - } - } - - QScriptValue script = object.property("script"); - if (script.isValid()) { - QString newScript; - newScript = script.toVariant().toString(); - if (_defaultSettings || newScript != _script) { - _script = newScript; - _scriptChanged = true; - } - } - - QScriptValue inHand = object.property("inHand"); - if (inHand.isValid()) { - bool newInHand; - newInHand = inHand.toVariant().toBool(); - if (_defaultSettings || newInHand != _inHand) { - _inHand = newInHand; - _inHandChanged = true; - } - } - QScriptValue shouldDie = object.property("shouldDie"); if (shouldDie.isValid()) { bool newShouldDie; @@ -1204,75 +808,45 @@ void ModelItemProperties::copyFromScriptValue(const QScriptValue &object) { _lastEdited = usecTimestampNow(); } -void ModelItemProperties::copyToModelItem(ModelItem& particle) const { +void ModelItemProperties::copyToModelItem(ModelItem& modelItem) const { bool somethingChanged = false; if (_positionChanged) { - particle.setPosition(_position / (float) TREE_SCALE); + modelItem.setPosition(_position / (float) TREE_SCALE); somethingChanged = true; } if (_colorChanged) { - particle.setColor(_color); + modelItem.setColor(_color); somethingChanged = true; } if (_radiusChanged) { - particle.setRadius(_radius / (float) TREE_SCALE); - somethingChanged = true; - } - - if (_velocityChanged) { - particle.setVelocity(_velocity / (float) TREE_SCALE); - somethingChanged = true; - } - - if (_gravityChanged) { - particle.setGravity(_gravity / (float) TREE_SCALE); - somethingChanged = true; - } - - if (_dampingChanged) { - particle.setDamping(_damping); - somethingChanged = true; - } - - if (_lifetimeChanged) { - particle.setLifetime(_lifetime); - somethingChanged = true; - } - - if (_scriptChanged) { - particle.setScript(_script); - somethingChanged = true; - } - - if (_inHandChanged) { - particle.setInHand(_inHand); + modelItem.setRadius(_radius / (float) TREE_SCALE); somethingChanged = true; } if (_shouldDieChanged) { - particle.setShouldDie(_shouldDie); + modelItem.setShouldDie(_shouldDie); somethingChanged = true; } if (_modelURLChanged) { - particle.setModelURL(_modelURL); + modelItem.setModelURL(_modelURL); somethingChanged = true; } if (_modelScaleChanged) { - particle.setModelScale(_modelScale); + modelItem.setModelScale(_modelScale); somethingChanged = true; } if (_modelTranslationChanged) { - particle.setModelTranslation(_modelTranslation); + modelItem.setModelTranslation(_modelTranslation); somethingChanged = true; } if (_modelRotationChanged) { - particle.setModelRotation(_modelRotation); + modelItem.setModelRotation(_modelRotation); somethingChanged = true; } @@ -1284,38 +858,27 @@ void ModelItemProperties::copyToModelItem(ModelItem& particle) const { qDebug() << "ModelItemProperties::copyToModelItem() AFTER update... edited AGO=" << elapsed << "now=" << now << " _lastEdited=" << _lastEdited; } - particle.setLastEdited(_lastEdited); + modelItem.setLastEdited(_lastEdited); } } -void ModelItemProperties::copyFromModelItem(const ModelItem& particle) { - _position = particle.getPosition() * (float) TREE_SCALE; - _color = particle.getXColor(); - _radius = particle.getRadius() * (float) TREE_SCALE; - _velocity = particle.getVelocity() * (float) TREE_SCALE; - _gravity = particle.getGravity() * (float) TREE_SCALE; - _damping = particle.getDamping(); - _lifetime = particle.getLifetime(); - _script = particle.getScript(); - _inHand = particle.getInHand(); - _shouldDie = particle.getShouldDie(); - _modelURL = particle.getModelURL(); - _modelScale = particle.getModelScale(); - _modelTranslation = particle.getModelTranslation(); - _modelRotation = particle.getModelRotation(); +void ModelItemProperties::copyFromModelItem(const ModelItem& modelItem) { + _position = modelItem.getPosition() * (float) TREE_SCALE; + _color = modelItem.getXColor(); + _radius = modelItem.getRadius() * (float) TREE_SCALE; + _shouldDie = modelItem.getShouldDie(); + _modelURL = modelItem.getModelURL(); + _modelScale = modelItem.getModelScale(); + _modelTranslation = modelItem.getModelTranslation(); + _modelRotation = modelItem.getModelRotation(); - _id = particle.getID(); + _id = modelItem.getID(); _idSet = true; _positionChanged = false; _colorChanged = false; _radiusChanged = false; - _velocityChanged = false; - _gravityChanged = false; - _dampingChanged = false; - _lifetimeChanged = false; - _scriptChanged = false; - _inHandChanged = false; + _shouldDieChanged = false; _modelURLChanged = false; _modelScaleChanged = false; diff --git a/libraries/models/src/ModelItem.h b/libraries/models/src/ModelItem.h index c03dcd51d2..caaaf7d0d3 100644 --- a/libraries/models/src/ModelItem.h +++ b/libraries/models/src/ModelItem.h @@ -39,12 +39,6 @@ const uint32_t UNKNOWN_MODEL_ID = 0xFFFFFFFF; const uint16_t MODEL_PACKET_CONTAINS_RADIUS = 1; const uint16_t MODEL_PACKET_CONTAINS_POSITION = 2; const uint16_t MODEL_PACKET_CONTAINS_COLOR = 4; -const uint16_t MODEL_PACKET_CONTAINS_VELOCITY = 8; -const uint16_t MODEL_PACKET_CONTAINS_GRAVITY = 16; -const uint16_t MODEL_PACKET_CONTAINS_DAMPING = 32; -const uint16_t MODEL_PACKET_CONTAINS_LIFETIME = 64; -const uint16_t MODEL_PACKET_CONTAINS_INHAND = 128; -const uint16_t MODEL_PACKET_CONTAINS_SCRIPT = 256; const uint16_t MODEL_PACKET_CONTAINS_SHOULDDIE = 512; const uint16_t MODEL_PACKET_CONTAINS_MODEL_URL = 1024; const uint16_t MODEL_PACKET_CONTAINS_MODEL_TRANSLATION = 1024; @@ -67,7 +61,7 @@ const bool MODEL_NOT_IN_HAND = !MODEL_IN_HAND; // it's not in a hand /// A collection of properties of a particle used in the scripting API. Translates between the actual properties of a particle /// and a JavaScript style hash/QScriptValue storing a set of properties. Used in scripting to set/get the complete set of /// particle properties via JavaScript hashes/QScriptValues -/// all units for position, velocity, gravity, radius, etc are in meter units +/// all units for position, radius, etc are in meter units class ModelItemProperties { public: ModelItemProperties(); @@ -81,13 +75,8 @@ public: const glm::vec3& getPosition() const { return _position; } xColor getColor() const { return _color; } float getRadius() const { return _radius; } - const glm::vec3& getVelocity() const { return _velocity; } - const glm::vec3& getGravity() const { return _gravity; } - float getDamping() const { return _damping; } - float getLifetime() const { return _lifetime; } - const QString& getScript() const { return _script; } - bool getInHand() const { return _inHand; } bool getShouldDie() const { return _shouldDie; } + const QString& getModelURL() const { return _modelURL; } float getModelScale() const { return _modelScale; } const glm::vec3& getModelTranslation() const { return _modelTranslation; } @@ -98,19 +87,9 @@ public: /// set position in meter units void setPosition(const glm::vec3& value) { _position = value; _positionChanged = true; } - - /// set velocity in meter units - void setVelocity(const glm::vec3& value) { _velocity = value; _velocityChanged = true; } void setColor(const xColor& value) { _color = value; _colorChanged = true; } void setRadius(float value) { _radius = value; _radiusChanged = true; } - - /// set gravity in meter units - void setGravity(const glm::vec3& value) { _gravity = value; _gravityChanged = true; } - void setInHand(bool inHand) { _inHand = inHand; _inHandChanged = true; } - void setDamping(float value) { _damping = value; _dampingChanged = true; } void setShouldDie(bool shouldDie) { _shouldDie = shouldDie; _shouldDieChanged = true; } - void setLifetime(float value) { _lifetime = value; _lifetimeChanged = true; } - void setScript(const QString& updateScript) { _script = updateScript; _scriptChanged = true; } // model related properties void setModelURL(const QString& url) { _modelURL = url; _modelURLChanged = true; } @@ -126,13 +105,8 @@ private: glm::vec3 _position; xColor _color; float _radius; - glm::vec3 _velocity; - glm::vec3 _gravity; - float _damping; - float _lifetime; - QString _script; - bool _inHand; - bool _shouldDie; + bool _shouldDie; /// to delete it + QString _modelURL; float _modelScale; glm::vec3 _modelTranslation; @@ -145,13 +119,8 @@ private: bool _positionChanged; bool _colorChanged; bool _radiusChanged; - bool _velocityChanged; - bool _gravityChanged; - bool _dampingChanged; - bool _lifetimeChanged; - bool _scriptChanged; - bool _inHandChanged; bool _shouldDieChanged; + bool _modelURLChanged; bool _modelScaleChanged; bool _modelTranslationChanged; @@ -201,9 +170,7 @@ public: static ModelItem fromEditPacket(const unsigned char* data, int length, int& processedBytes, ModelTree* tree, bool& valid); virtual ~ModelItem(); - virtual void init(glm::vec3 position, float radius, rgbColor color, glm::vec3 velocity, - glm::vec3 gravity = MODEL_DEFAULT_GRAVITY, float damping = MODEL_DEFAULT_DAMPING, float lifetime = MODEL_DEFAULT_LIFETIME, - bool inHand = MODEL_NOT_IN_HAND, QString updateScript = MODEL_DEFAULT_SCRIPT, uint32_t id = NEW_MODEL); + virtual void init(glm::vec3 position, float radius, rgbColor color, uint32_t id = NEW_MODEL); /// get position in domain scale units (0.0 - 1.0) const glm::vec3& getPosition() const { return _position; } @@ -213,17 +180,6 @@ public: /// get radius in domain scale units (0.0 - 1.0) float getRadius() const { return _radius; } - float getMass() const { return _mass; } - - /// get velocity in domain scale units (0.0 - 1.0) - const glm::vec3& getVelocity() const { return _velocity; } - - /// get gravity in domain scale units (0.0 - 1.0) - const glm::vec3& getGravity() const { return _gravity; } - - bool getInHand() const { return _inHand; } - float getDamping() const { return _damping; } - float getLifetime() const { return _lifetime; } // model related properties bool hasModel() const { return !_modelURL.isEmpty(); } @@ -243,20 +199,16 @@ public: void setLastEdited(quint64 lastEdited) { _lastEdited = lastEdited; } /// lifetime of the particle in seconds - float getAge() const { return static_cast(usecTimestampNow() - _created) / static_cast(USECS_PER_SECOND); } float getEditedAgo() const { return static_cast(usecTimestampNow() - _lastEdited) / static_cast(USECS_PER_SECOND); } uint32_t getID() const { return _id; } void setID(uint32_t id) { _id = id; } bool getShouldDie() const { return _shouldDie; } - QString getScript() const { return _script; } uint32_t getCreatorTokenID() const { return _creatorTokenID; } bool isNewlyCreated() const { return _newlyCreated; } /// set position in domain scale units (0.0 - 1.0) void setPosition(const glm::vec3& value) { _position = value; } - /// set velocity in domain scale units (0.0 - 1.0) - void setVelocity(const glm::vec3& value) { _velocity = value; } void setColor(const rgbColor& value) { memcpy(_color, value, sizeof(_color)); } void setColor(const xColor& value) { _color[RED_INDEX] = value.red; @@ -265,15 +217,8 @@ public: } /// set radius in domain scale units (0.0 - 1.0) void setRadius(float value) { _radius = value; } - void setMass(float value); - /// set gravity in domain scale units (0.0 - 1.0) - void setGravity(const glm::vec3& value) { _gravity = value; } - void setInHand(bool inHand) { _inHand = inHand; } - void setDamping(float value) { _damping = value; } void setShouldDie(bool shouldDie) { _shouldDie = shouldDie; } - void setLifetime(float value) { _lifetime = value; } - void setScript(QString updateScript) { _script = updateScript; } void setCreatorTokenID(uint32_t creatorTokenID) { _creatorTokenID = creatorTokenID; } // model related properties @@ -302,40 +247,18 @@ public: // similar to assignment/copy, but it handles keeping lifetime accurate void copyChangedProperties(const ModelItem& other); - static VoxelEditPacketSender* getVoxelEditPacketSender() { return _voxelEditSender; } - static ModelEditPacketSender* getModelEditPacketSender() { return _particleEditSender; } - - static void setVoxelEditPacketSender(VoxelEditPacketSender* senderInterface) - { _voxelEditSender = senderInterface; } - - static void setModelEditPacketSender(ModelEditPacketSender* senderInterface) - { _particleEditSender = senderInterface; } - - // these methods allow you to create particles, and later edit them. static uint32_t getIDfromCreatorTokenID(uint32_t creatorTokenID); static uint32_t getNextCreatorTokenID(); static void handleAddModelResponse(const QByteArray& packet); protected: - static VoxelEditPacketSender* _voxelEditSender; - static ModelEditPacketSender* _particleEditSender; - - void setAge(float age); - glm::vec3 _position; rgbColor _color; float _radius; - float _mass; - glm::vec3 _velocity; uint32_t _id; static uint32_t _nextID; bool _shouldDie; - glm::vec3 _gravity; - float _damping; - float _lifetime; - QString _script; - bool _inHand; // model related items QString _modelURL; @@ -349,9 +272,6 @@ protected: quint64 _lastUpdated; quint64 _lastEdited; - // this doesn't go on the wire, we send it as lifetime - quint64 _created; - // used by the static interfaces for creator token ids static uint32_t _nextCreatorTokenID; static std::map _tokenIDsToIDs; diff --git a/libraries/models/src/ModelTreeElement.cpp b/libraries/models/src/ModelTreeElement.cpp index b799d377df..687199827c 100644 --- a/libraries/models/src/ModelTreeElement.cpp +++ b/libraries/models/src/ModelTreeElement.cpp @@ -107,17 +107,6 @@ bool ModelTreeElement::findSpherePenetration(const glm::vec3& center, float radi return false; } - // We've considered making "inHand" models not collide, if we want to do that, - // we should change this setting... but now, we do allow inHand models to collide - const bool IN_HAND_PARTICLES_DONT_COLLIDE = false; - if (IN_HAND_PARTICLES_DONT_COLLIDE) { - // don't penetrate if the model is "inHand" -- they don't collide - if (model.getInHand()) { - ++modelItr; - continue; - } - } - if (findSphereSpherePenetration(center, radius, modelCenter, modelRadius, penetration)) { // return true on first valid model penetration *penetratedObject = (void*)(&model); From 3322d1f7ffc5187e5f785cec03213d8db1c07c25 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Wed, 30 Apr 2014 14:18:57 -0700 Subject: [PATCH 06/13] example JS for models --- examples/editModelExample.js | 79 ++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 examples/editModelExample.js diff --git a/examples/editModelExample.js b/examples/editModelExample.js new file mode 100644 index 0000000000..8b5ca54dbf --- /dev/null +++ b/examples/editModelExample.js @@ -0,0 +1,79 @@ +// +// editModelExample.js +// examples +// +// Created by Brad Hefta-Gaub on 12/31/13. +// Copyright 2014 High Fidelity, Inc. +// +// This is an example script that demonstrates creating and editing a model +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +var count = 0; +var moveUntil = 2000; +var stopAfter = moveUntil + 100; + +var originalProperties = { + position: { x: 10, + y: 0, + z: 0 }, + + radius : 0.1, + + color: { red: 0, + green: 255, + blue: 0 }, +}; + +var positionDelta = { x: 0.05, y: 0, z: 0 }; + + +var modelID = Models.addModel(originalProperties); + +function moveModel(deltaTime) { + if (count >= moveUntil) { + + // delete it... + if (count == moveUntil) { + print("calling Models.deleteModel()"); + Models.deleteModel(modelID); + } + + // stop it... + if (count >= stopAfter) { + print("calling Script.stop()"); + Script.stop(); + } + + count++; + return; // break early + } + + print("count =" + count); + count++; + + print("modelID.creatorTokenID = " + modelID.creatorTokenID); + + var newProperties = { + position: { + x: originalProperties.position.x + (count * positionDelta.x), + y: originalProperties.position.y + (count * positionDelta.y), + z: originalProperties.position.z + (count * positionDelta.z) + }, + radius : 0.25, + + }; + + + //print("modelID = " + modelID); + print("newProperties.position = " + newProperties.position.x + "," + newProperties.position.y+ "," + newProperties.position.z); + + Models.editModel(modelID, newProperties); +} + + +// register the call back so it fires before each data send +Script.update.connect(moveModel); + From e886090e766848af6845f7fc960c8a692b78ef20 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Wed, 30 Apr 2014 14:40:49 -0700 Subject: [PATCH 07/13] removed dead code --- libraries/models/src/ModelItem.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/libraries/models/src/ModelItem.h b/libraries/models/src/ModelItem.h index caaaf7d0d3..0fc5d31ed5 100644 --- a/libraries/models/src/ModelItem.h +++ b/libraries/models/src/ModelItem.h @@ -238,8 +238,6 @@ public: static void adjustEditPacketForClockSkew(unsigned char* codeColorBuffer, ssize_t length, int clockSkew); - void applyHardCollision(const CollisionInfo& collisionInfo); - void update(const quint64& now); void debugDump() const; From 85484affa9fd56e7643255650a239521dd1f9a52 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Wed, 30 Apr 2014 17:55:54 -0700 Subject: [PATCH 08/13] hacking --- examples/editModelExample.js | 15 ++++++++- interface/src/renderer/Model.cpp | 39 +++++++++++++++++++++- interface/src/renderer/Model.h | 10 ++++++ libraries/models/src/ModelItem.cpp | 52 ------------------------------ libraries/models/src/ModelItem.h | 15 --------- 5 files changed, 62 insertions(+), 69 deletions(-) diff --git a/examples/editModelExample.js b/examples/editModelExample.js index 8b5ca54dbf..6a95383b48 100644 --- a/examples/editModelExample.js +++ b/examples/editModelExample.js @@ -15,6 +15,11 @@ var count = 0; var moveUntil = 2000; var stopAfter = moveUntil + 100; +var pitch = 90.0; +var yaw = 0.0; +var roll = 180.0; +var rotation = Quat.fromPitchYawRollDegrees(pitch, yaw, roll) + var originalProperties = { position: { x: 10, y: 0, @@ -25,9 +30,17 @@ var originalProperties = { color: { red: 0, green: 255, blue: 0 }, + + //modelURL: "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/Feisar_Ship.FBX", + //modelURL: "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/birarda/birarda_head.fbx", + modelURL: "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/pug.fbx", + //modelURL: "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/newInvader16x16-large-purple.svo", + //modelURL: "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/mino_full.fst", + + //modelRotation: rotation }; -var positionDelta = { x: 0.05, y: 0, z: 0 }; +var positionDelta = { x: 0, y: 0, z: 0 }; var modelID = Models.addModel(originalProperties); diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index 3484ac5fc8..5f325fd934 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -34,6 +34,9 @@ static int vec3VectorTypeId = qRegisterMetaType >(); Model::Model(QObject* parent) : QObject(parent), _scale(1.0f, 1.0f, 1.0f), + _scaleToFit(false), + _scaleToFitLargestDimension(0.0f), + _scaledToFit(false), _shapesAreDirty(true), _boundingRadius(0.f), _boundingShape(), @@ -357,6 +360,15 @@ Extents Model::getBindExtents() const { return scaledExtents; } +Extents Model::getMeshExtents() const { + if (!isActive()) { + return Extents(); + } + const Extents& extents = _geometry->getFBXGeometry().meshExtents; + Extents scaledExtents = { extents.minimum * _scale, extents.maximum * _scale }; + return scaledExtents; +} + bool Model::getJointState(int index, glm::quat& rotation) const { if (index == -1 || index >= _jointStates.size()) { return false; @@ -770,9 +782,34 @@ void Blender::run() { Q_ARG(const QVector&, vertices), Q_ARG(const QVector&, normals)); } +void Model::setScaleToFit(bool scaleToFit, float largestDimension) { + if (_scaleToFit != scaleToFit || _scaleToFitLargestDimension != largestDimension) { + _scaleToFit = scaleToFit; + _scaleToFitLargestDimension = largestDimension; + _scaledToFit = false; // force rescaling + } +} + +void Model::checkScaleToFit() { + Extents modelMeshExtents = getMeshExtents(); + + // size is our "target size in world space" + // we need to set our model scale so that the extents of the mesh, fit in a cube that size... + glm::vec3 dimensions = modelMeshExtents.maximum - modelMeshExtents.minimum; + float maxDimension = glm::max(glm::max(dimensions.x, dimensions.y), dimensions.z); + float maxScale = _scaleToFitLargestDimension / maxDimension; + glm::vec3 scale(maxScale, maxScale, maxScale); + setScale(scale); + _scaledToFit = true; +} + void Model::simulate(float deltaTime, bool fullUpdate) { - fullUpdate = updateGeometry() || fullUpdate; + fullUpdate = updateGeometry() || fullUpdate || (_scaleToFit && !_scaledToFit); if (isActive() && fullUpdate) { + // check for scale to fit + if (_scaleToFit && !_scaledToFit) { + checkScaleToFit(); + } simulateInternal(deltaTime); } } diff --git a/interface/src/renderer/Model.h b/interface/src/renderer/Model.h index 64295cb915..3cf8963b0b 100644 --- a/interface/src/renderer/Model.h +++ b/interface/src/renderer/Model.h @@ -39,6 +39,7 @@ public: void setRotation(const glm::quat& rotation) { _rotation = rotation; } const glm::quat& getRotation() const { return _rotation; } + void setScaleToFit(bool scaleToFit, float largestDimension = 0.0f); void setScale(const glm::vec3& scale); const glm::vec3& getScale() const { return _scale; } @@ -80,6 +81,9 @@ public: /// Returns the extents of the model in its bind pose. Extents getBindExtents() const; + /// Returns the extents of the model's mesh + Extents getMeshExtents() const; + /// Returns a reference to the shared geometry. const QSharedPointer& getGeometry() const { return _geometry; } @@ -203,6 +207,10 @@ protected: glm::quat _rotation; glm::vec3 _scale; glm::vec3 _offset; + + bool _scaleToFit; /// If you set scaleToFit, we will calculate scale based on MeshExtents + float _scaleToFitLargestDimension; /// this is the dimension that scale to fit will use + bool _scaledToFit; /// have we scaled to fit class JointState { public: @@ -229,6 +237,8 @@ protected: // returns 'true' if needs fullUpdate after geometry change bool updateGeometry(); + + void checkScaleToFit(); void simulateInternal(float deltaTime); diff --git a/libraries/models/src/ModelItem.cpp b/libraries/models/src/ModelItem.cpp index 1a4d32581b..b357715a13 100644 --- a/libraries/models/src/ModelItem.cpp +++ b/libraries/models/src/ModelItem.cpp @@ -86,7 +86,6 @@ ModelItem::ModelItem(const ModelItemID& modelItemID, const ModelItemProperties& _modelURL = MODEL_DEFAULT_MODEL_URL; _modelTranslation = MODEL_DEFAULT_MODEL_TRANSLATION; _modelRotation = MODEL_DEFAULT_MODEL_ROTATION; - _modelScale = MODEL_DEFAULT_MODEL_SCALE; setProperties(properties); } @@ -113,7 +112,6 @@ void ModelItem::init(glm::vec3 position, float radius, rgbColor color, uint32_t _modelURL = MODEL_DEFAULT_MODEL_URL; _modelTranslation = MODEL_DEFAULT_MODEL_TRANSLATION; _modelRotation = MODEL_DEFAULT_MODEL_ROTATION; - _modelScale = MODEL_DEFAULT_MODEL_SCALE; } bool ModelItem::appendModelData(OctreePacketData* packetData) const { @@ -150,11 +148,6 @@ bool ModelItem::appendModelData(OctreePacketData* packetData) const { } } - // modelScale - if (success) { - success = packetData->appendValue(getModelScale()); - } - // modelTranslation if (success) { success = packetData->appendValue(getModelTranslation()); @@ -232,11 +225,6 @@ int ModelItem::readModelDataFromBuffer(const unsigned char* data, int bytesLeftT dataAt += modelURLLength; bytesRead += modelURLLength; - // modelScale - memcpy(&_modelScale, dataAt, sizeof(_modelScale)); - dataAt += sizeof(_modelScale); - bytesRead += sizeof(_modelScale); - // modelTranslation memcpy(&_modelTranslation, dataAt, sizeof(_modelTranslation)); dataAt += sizeof(_modelTranslation); @@ -362,13 +350,6 @@ ModelItem ModelItem::fromEditPacket(const unsigned char* data, int length, int& processedBytes += modelURLLength; } - // modelScale - if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_MODEL_SCALE) == MODEL_PACKET_CONTAINS_MODEL_SCALE)) { - memcpy(&newModelItem._modelScale, dataAt, sizeof(newModelItem._modelScale)); - dataAt += sizeof(newModelItem._modelScale); - processedBytes += sizeof(newModelItem._modelScale); - } - // modelTranslation if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_MODEL_TRANSLATION) == MODEL_PACKET_CONTAINS_MODEL_TRANSLATION)) { memcpy(&newModelItem._modelTranslation, dataAt, sizeof(newModelItem._modelTranslation)); @@ -506,14 +487,6 @@ bool ModelItem::encodeModelEditMessageDetails(PacketType command, ModelItemID id sizeOut += urlLength; } - // modelScale - if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_MODEL_SCALE) == MODEL_PACKET_CONTAINS_MODEL_SCALE)) { - float modelScale = properties.getModelScale(); - memcpy(copyAt, &modelScale, sizeof(modelScale)); - copyAt += sizeof(modelScale); - sizeOut += sizeof(modelScale); - } - // modelTranslation if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_MODEL_TRANSLATION) == MODEL_PACKET_CONTAINS_MODEL_TRANSLATION)) { glm::vec3 modelTranslation = properties.getModelTranslation(); // should this be relative to TREE_SCALE?? @@ -599,7 +572,6 @@ ModelItemProperties::ModelItemProperties() : _radius(MODEL_DEFAULT_RADIUS), _shouldDie(false), _modelURL(""), - _modelScale(MODEL_DEFAULT_MODEL_SCALE), _modelTranslation(MODEL_DEFAULT_MODEL_TRANSLATION), _modelRotation(MODEL_DEFAULT_MODEL_ROTATION), @@ -612,7 +584,6 @@ ModelItemProperties::ModelItemProperties() : _radiusChanged(false), _shouldDieChanged(false), _modelURLChanged(false), - _modelScaleChanged(false), _modelTranslationChanged(false), _modelRotationChanged(false), _defaultSettings(true) @@ -642,10 +613,6 @@ uint16_t ModelItemProperties::getChangedBits() const { changedBits += MODEL_PACKET_CONTAINS_MODEL_URL; } - if (_modelScaleChanged) { - changedBits += MODEL_PACKET_CONTAINS_MODEL_SCALE; - } - if (_modelTranslationChanged) { changedBits += MODEL_PACKET_CONTAINS_MODEL_TRANSLATION; } @@ -673,8 +640,6 @@ QScriptValue ModelItemProperties::copyToScriptValue(QScriptEngine* engine) const properties.setProperty("modelURL", _modelURL); - properties.setProperty("modelScale", _modelScale); - QScriptValue modelTranslation = vec3toScriptValue(engine, _modelTranslation); properties.setProperty("modelTranslation", modelTranslation); @@ -758,16 +723,6 @@ void ModelItemProperties::copyFromScriptValue(const QScriptValue &object) { } } - QScriptValue modelScale = object.property("modelScale"); - if (modelScale.isValid()) { - float newModelScale; - newModelScale = modelScale.toVariant().toFloat(); - if (_defaultSettings || newModelScale != _modelScale) { - _modelScale = newModelScale; - _modelScaleChanged = true; - } - } - QScriptValue modelTranslation = object.property("modelTranslation"); if (modelTranslation.isValid()) { QScriptValue x = modelTranslation.property("x"); @@ -835,11 +790,6 @@ void ModelItemProperties::copyToModelItem(ModelItem& modelItem) const { somethingChanged = true; } - if (_modelScaleChanged) { - modelItem.setModelScale(_modelScale); - somethingChanged = true; - } - if (_modelTranslationChanged) { modelItem.setModelTranslation(_modelTranslation); somethingChanged = true; @@ -868,7 +818,6 @@ void ModelItemProperties::copyFromModelItem(const ModelItem& modelItem) { _radius = modelItem.getRadius() * (float) TREE_SCALE; _shouldDie = modelItem.getShouldDie(); _modelURL = modelItem.getModelURL(); - _modelScale = modelItem.getModelScale(); _modelTranslation = modelItem.getModelTranslation(); _modelRotation = modelItem.getModelRotation(); @@ -881,7 +830,6 @@ void ModelItemProperties::copyFromModelItem(const ModelItem& modelItem) { _shouldDieChanged = false; _modelURLChanged = false; - _modelScaleChanged = false; _modelTranslationChanged = false; _modelRotationChanged = false; _defaultSettings = false; diff --git a/libraries/models/src/ModelItem.h b/libraries/models/src/ModelItem.h index 0fc5d31ed5..8d273a06f0 100644 --- a/libraries/models/src/ModelItem.h +++ b/libraries/models/src/ModelItem.h @@ -43,20 +43,12 @@ const uint16_t MODEL_PACKET_CONTAINS_SHOULDDIE = 512; const uint16_t MODEL_PACKET_CONTAINS_MODEL_URL = 1024; const uint16_t MODEL_PACKET_CONTAINS_MODEL_TRANSLATION = 1024; const uint16_t MODEL_PACKET_CONTAINS_MODEL_ROTATION = 2048; -const uint16_t MODEL_PACKET_CONTAINS_MODEL_SCALE = 4096; -const float MODEL_DEFAULT_LIFETIME = 10.0f; // particles live for 10 seconds by default -const float MODEL_DEFAULT_DAMPING = 0.99f; const float MODEL_DEFAULT_RADIUS = 0.1f / TREE_SCALE; const float MODEL_MINIMUM_PARTICLE_ELEMENT_SIZE = (1.0f / 100000.0f) / TREE_SCALE; // smallest size container -const glm::vec3 MODEL_DEFAULT_GRAVITY(0, (-9.8f / TREE_SCALE), 0); -const QString MODEL_DEFAULT_SCRIPT(""); const QString MODEL_DEFAULT_MODEL_URL(""); const glm::vec3 MODEL_DEFAULT_MODEL_TRANSLATION(0, 0, 0); const glm::quat MODEL_DEFAULT_MODEL_ROTATION(0, 0, 0, 0); -const float MODEL_DEFAULT_MODEL_SCALE = 1.0f; -const bool MODEL_IN_HAND = true; // it's in a hand -const bool MODEL_NOT_IN_HAND = !MODEL_IN_HAND; // it's not in a hand /// A collection of properties of a particle used in the scripting API. Translates between the actual properties of a particle /// and a JavaScript style hash/QScriptValue storing a set of properties. Used in scripting to set/get the complete set of @@ -78,7 +70,6 @@ public: bool getShouldDie() const { return _shouldDie; } const QString& getModelURL() const { return _modelURL; } - float getModelScale() const { return _modelScale; } const glm::vec3& getModelTranslation() const { return _modelTranslation; } const glm::quat& getModelRotation() const { return _modelRotation; } @@ -93,7 +84,6 @@ public: // model related properties void setModelURL(const QString& url) { _modelURL = url; _modelURLChanged = true; } - void setModelScale(float scale) { _modelScale = scale; _modelScaleChanged = true; } void setModelTranslation(const glm::vec3& translation) { _modelTranslation = translation; _modelTranslationChanged = true; } void setModelRotation(const glm::quat& rotation) { _modelRotation = rotation; _modelRotationChanged = true; } @@ -108,7 +98,6 @@ private: bool _shouldDie; /// to delete it QString _modelURL; - float _modelScale; glm::vec3 _modelTranslation; glm::quat _modelRotation; @@ -122,7 +111,6 @@ private: bool _shouldDieChanged; bool _modelURLChanged; - bool _modelScaleChanged; bool _modelTranslationChanged; bool _modelRotationChanged; bool _defaultSettings; @@ -184,7 +172,6 @@ public: // model related properties bool hasModel() const { return !_modelURL.isEmpty(); } const QString& getModelURL() const { return _modelURL; } - float getModelScale() const { return _modelScale; } const glm::vec3& getModelTranslation() const { return _modelTranslation; } const glm::quat& getModelRotation() const { return _modelRotation; } @@ -223,7 +210,6 @@ public: // model related properties void setModelURL(const QString& url) { _modelURL = url; } - void setModelScale(float scale) { _modelScale = scale; } void setModelTranslation(const glm::vec3& translation) { _modelTranslation = translation; } void setModelRotation(const glm::quat& rotation) { _modelRotation = rotation; } @@ -260,7 +246,6 @@ protected: // model related items QString _modelURL; - float _modelScale; glm::vec3 _modelTranslation; glm::quat _modelRotation; From bb6444f5a949e5729ae5f6f0f93a1c4e36382c18 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Thu, 1 May 2014 07:30:56 -0700 Subject: [PATCH 09/13] more hacking got it working --- examples/editModelExample.js | 3 ++- interface/src/renderer/Model.cpp | 8 ++++++++ interface/src/renderer/Model.h | 3 +++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/examples/editModelExample.js b/examples/editModelExample.js index 6a95383b48..96f348c45e 100644 --- a/examples/editModelExample.js +++ b/examples/editModelExample.js @@ -33,9 +33,10 @@ var originalProperties = { //modelURL: "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/Feisar_Ship.FBX", //modelURL: "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/birarda/birarda_head.fbx", - modelURL: "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/pug.fbx", + //modelURL: "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/pug.fbx", //modelURL: "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/newInvader16x16-large-purple.svo", //modelURL: "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/mino_full.fst", + modelURL: "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/minotaur/mino_full.fbx", //modelRotation: rotation }; diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index 5f325fd934..2f27c76c24 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -369,6 +369,14 @@ Extents Model::getMeshExtents() const { return scaledExtents; } +Extents Model::getUnscaledMeshExtents() const { + if (!isActive()) { + return Extents(); + } + const Extents& extents = _geometry->getFBXGeometry().meshExtents; + return extents; +} + bool Model::getJointState(int index, glm::quat& rotation) const { if (index == -1 || index >= _jointStates.size()) { return false; diff --git a/interface/src/renderer/Model.h b/interface/src/renderer/Model.h index 3cf8963b0b..4fb2b5afaf 100644 --- a/interface/src/renderer/Model.h +++ b/interface/src/renderer/Model.h @@ -84,6 +84,9 @@ public: /// Returns the extents of the model's mesh Extents getMeshExtents() const; + /// Returns the unscaled extents of the model's mesh + Extents getUnscaledMeshExtents() const; + /// Returns a reference to the shared geometry. const QSharedPointer& getGeometry() const { return _geometry; } From 293963c546454be665729d1f1f9786e15cdfa1e9 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Thu, 1 May 2014 09:02:41 -0700 Subject: [PATCH 10/13] implement snap to center in model --- examples/editModelExample.js | 3 +-- interface/src/renderer/Model.cpp | 33 +++++++++++++++++++++++++++++--- interface/src/renderer/Model.h | 17 ++++++++++++++-- 3 files changed, 46 insertions(+), 7 deletions(-) diff --git a/examples/editModelExample.js b/examples/editModelExample.js index 96f348c45e..24f29d1d88 100644 --- a/examples/editModelExample.js +++ b/examples/editModelExample.js @@ -35,10 +35,9 @@ var originalProperties = { //modelURL: "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/birarda/birarda_head.fbx", //modelURL: "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/pug.fbx", //modelURL: "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/newInvader16x16-large-purple.svo", - //modelURL: "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/mino_full.fst", modelURL: "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/minotaur/mino_full.fbx", - //modelRotation: rotation + modelRotation: rotation }; var positionDelta = { x: 0, y: 0, z: 0 }; diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index 2f27c76c24..eec6184ec2 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -37,6 +37,8 @@ Model::Model(QObject* parent) : _scaleToFit(false), _scaleToFitLargestDimension(0.0f), _scaledToFit(false), + _snapModelToCenter(false), + _snappedToCenter(false), _shapesAreDirty(true), _boundingRadius(0.f), _boundingShape(), @@ -63,6 +65,13 @@ Model::SkinLocations Model::_skinNormalMapLocations; Model::SkinLocations Model::_skinShadowLocations; void Model::setScale(const glm::vec3& scale) { + setScaleInternal(scale); + // if anyone sets scale manually, then we are no longer scaled to fit + _scaleToFit = false; + _scaledToFit = false; +} + +void Model::setScaleInternal(const glm::vec3& scale) { float scaleLength = glm::length(_scale); float relativeDeltaScale = glm::length(_scale - scale) / scaleLength; @@ -798,7 +807,7 @@ void Model::setScaleToFit(bool scaleToFit, float largestDimension) { } } -void Model::checkScaleToFit() { +void Model::scaleToFit() { Extents modelMeshExtents = getMeshExtents(); // size is our "target size in world space" @@ -807,16 +816,34 @@ void Model::checkScaleToFit() { float maxDimension = glm::max(glm::max(dimensions.x, dimensions.y), dimensions.z); float maxScale = _scaleToFitLargestDimension / maxDimension; glm::vec3 scale(maxScale, maxScale, maxScale); - setScale(scale); + setScaleInternal(scale); _scaledToFit = true; } +void Model::setSnapModelToCenter(bool snapModelToCenter) { + if (_snapModelToCenter != snapModelToCenter) { + _snapModelToCenter = snapModelToCenter; + _snappedToCenter = false; // force re-centering + } +} + +void Model::snapToCenter() { + Extents modelMeshExtents = getUnscaledMeshExtents(); + glm::vec3 halfDimensions = (modelMeshExtents.maximum - modelMeshExtents.minimum) * 0.5f; + glm::vec3 offset = -modelMeshExtents.minimum - halfDimensions; + _offset = offset; + _snappedToCenter = true; +} + void Model::simulate(float deltaTime, bool fullUpdate) { fullUpdate = updateGeometry() || fullUpdate || (_scaleToFit && !_scaledToFit); if (isActive() && fullUpdate) { // check for scale to fit if (_scaleToFit && !_scaledToFit) { - checkScaleToFit(); + scaleToFit(); + } + if (_snapModelToCenter && !_snappedToCenter) { + snapToCenter(); } simulateInternal(deltaTime); } diff --git a/interface/src/renderer/Model.h b/interface/src/renderer/Model.h index 4fb2b5afaf..f7667654ab 100644 --- a/interface/src/renderer/Model.h +++ b/interface/src/renderer/Model.h @@ -39,11 +39,19 @@ public: void setRotation(const glm::quat& rotation) { _rotation = rotation; } const glm::quat& getRotation() const { return _rotation; } + /// enables/disables scale to fit behavior, the model will be automatically scaled to the specified largest dimension void setScaleToFit(bool scaleToFit, float largestDimension = 0.0f); + bool getScaleToFit() const { return _scaleToFit; } /// is scale to fit enabled + bool getIsScaledToFit() const { return _scaledToFit; } /// is model scaled to fit + bool getScaleToFitDimension() const { return _scaleToFitLargestDimension; } /// the dimension model is scaled to + + void setSnapModelToCenter(bool snapModelToCenter); + bool getSnapModelToCenter() { return _snapModelToCenter; } + void setScale(const glm::vec3& scale); const glm::vec3& getScale() const { return _scale; } - void setOffset(const glm::vec3& offset) { _offset = offset; } + void setOffset(const glm::vec3& offset) { _offset = offset; _snapModelToCenter = false; _snappedToCenter = false; } const glm::vec3& getOffset() const { return _offset; } void setPupilDilation(float dilation) { _pupilDilation = dilation; } @@ -215,6 +223,9 @@ protected: float _scaleToFitLargestDimension; /// this is the dimension that scale to fit will use bool _scaledToFit; /// have we scaled to fit + bool _snapModelToCenter; /// is the model's offset automatically adjusted to center around 0,0,0 in model space + bool _snappedToCenter; + class JointState { public: glm::vec3 translation; // translation relative to parent @@ -241,7 +252,9 @@ protected: // returns 'true' if needs fullUpdate after geometry change bool updateGeometry(); - void checkScaleToFit(); + void setScaleInternal(const glm::vec3& scale); + void scaleToFit(); + void snapToCenter(); void simulateInternal(float deltaTime); From f7da070d774cbda796536f78868da8d88e0f0ac4 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Thu, 1 May 2014 11:29:17 -0700 Subject: [PATCH 11/13] more work, cleanup names, remove translation use snap to center --- interface/src/renderer/Model.cpp | 11 +++- interface/src/renderer/Model.h | 4 +- .../models/src/ModelEditPacketSender.cpp | 2 +- libraries/models/src/ModelEditPacketSender.h | 6 +- libraries/models/src/ModelItem.cpp | 60 ------------------- libraries/models/src/ModelItem.h | 44 ++++++-------- libraries/models/src/ModelTree.cpp | 8 +-- 7 files changed, 37 insertions(+), 98 deletions(-) diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index eec6184ec2..6bc5afd4fe 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -82,6 +82,15 @@ void Model::setScaleInternal(const glm::vec3& scale) { } } +void Model::setOffset(const glm::vec3& offset) { + _offset = offset; + + // if someone manually sets our offset, then we are no longer snapped to center + _snapModelToCenter = false; + _snappedToCenter = false; +} + + void Model::initSkinProgram(ProgramObject& program, Model::SkinLocations& locations) { program.bind(); locations.clusterMatrices = program.uniformLocation("clusterMatrices"); @@ -836,7 +845,7 @@ void Model::snapToCenter() { } void Model::simulate(float deltaTime, bool fullUpdate) { - fullUpdate = updateGeometry() || fullUpdate || (_scaleToFit && !_scaledToFit); + fullUpdate = updateGeometry() || fullUpdate || (_scaleToFit && !_scaledToFit) || (_snapModelToCenter && !_snappedToCenter); if (isActive() && fullUpdate) { // check for scale to fit if (_scaleToFit && !_scaledToFit) { diff --git a/interface/src/renderer/Model.h b/interface/src/renderer/Model.h index f7667654ab..4105b229b3 100644 --- a/interface/src/renderer/Model.h +++ b/interface/src/renderer/Model.h @@ -51,7 +51,7 @@ public: void setScale(const glm::vec3& scale); const glm::vec3& getScale() const { return _scale; } - void setOffset(const glm::vec3& offset) { _offset = offset; _snapModelToCenter = false; _snappedToCenter = false; } + void setOffset(const glm::vec3& offset); const glm::vec3& getOffset() const { return _offset; } void setPupilDilation(float dilation) { _pupilDilation = dilation; } @@ -224,7 +224,7 @@ protected: bool _scaledToFit; /// have we scaled to fit bool _snapModelToCenter; /// is the model's offset automatically adjusted to center around 0,0,0 in model space - bool _snappedToCenter; + bool _snappedToCenter; /// are we currently snapped to center class JointState { public: diff --git a/libraries/models/src/ModelEditPacketSender.cpp b/libraries/models/src/ModelEditPacketSender.cpp index ad14e1f11b..5059b8891b 100644 --- a/libraries/models/src/ModelEditPacketSender.cpp +++ b/libraries/models/src/ModelEditPacketSender.cpp @@ -1,6 +1,6 @@ // // ModelEditPacketSender.cpp -// libraries/particles/src +// libraries/models/src // // Created by Brad Hefta-Gaub on 8/12/13. // Copyright 2013 High Fidelity, Inc. diff --git a/libraries/models/src/ModelEditPacketSender.h b/libraries/models/src/ModelEditPacketSender.h index b4b19adae1..287d6982a0 100644 --- a/libraries/models/src/ModelEditPacketSender.h +++ b/libraries/models/src/ModelEditPacketSender.h @@ -1,6 +1,6 @@ // // ModelEditPacketSender.h -// libraries/particles/src +// libraries/models/src // // Created by Brad Hefta-Gaub on 8/12/13. // Copyright 2013 High Fidelity, Inc. @@ -20,7 +20,7 @@ class ModelEditPacketSender : public OctreeEditPacketSender { Q_OBJECT public: - /// Send particle add message immediately + /// Send model add message immediately /// NOTE: ModelItemProperties assumes that all distances are in meter units void sendEditModelMessage(PacketType type, ModelItemID modelID, const ModelItemProperties& properties); @@ -30,7 +30,7 @@ public: /// NOTE: ModelItemProperties assumes that all distances are in meter units void queueModelEditMessage(PacketType type, ModelItemID modelID, const ModelItemProperties& properties); - // My server type is the particle server + // My server type is the model server virtual unsigned char getMyNodeType() const { return NodeType::ModelServer; } virtual void adjustEditPacketForClockSkew(unsigned char* codeColorBuffer, ssize_t length, int clockSkew); }; diff --git a/libraries/models/src/ModelItem.cpp b/libraries/models/src/ModelItem.cpp index b357715a13..af8500bf25 100644 --- a/libraries/models/src/ModelItem.cpp +++ b/libraries/models/src/ModelItem.cpp @@ -84,7 +84,6 @@ ModelItem::ModelItem(const ModelItemID& modelItemID, const ModelItemProperties& memcpy(_color, noColor, sizeof(_color)); _shouldDie = false; _modelURL = MODEL_DEFAULT_MODEL_URL; - _modelTranslation = MODEL_DEFAULT_MODEL_TRANSLATION; _modelRotation = MODEL_DEFAULT_MODEL_ROTATION; setProperties(properties); @@ -110,7 +109,6 @@ void ModelItem::init(glm::vec3 position, float radius, rgbColor color, uint32_t memcpy(_color, color, sizeof(_color)); _shouldDie = false; _modelURL = MODEL_DEFAULT_MODEL_URL; - _modelTranslation = MODEL_DEFAULT_MODEL_TRANSLATION; _modelRotation = MODEL_DEFAULT_MODEL_ROTATION; } @@ -148,10 +146,6 @@ bool ModelItem::appendModelData(OctreePacketData* packetData) const { } } - // modelTranslation - if (success) { - success = packetData->appendValue(getModelTranslation()); - } // modelRotation if (success) { success = packetData->appendValue(getModelRotation()); @@ -225,11 +219,6 @@ int ModelItem::readModelDataFromBuffer(const unsigned char* data, int bytesLeftT dataAt += modelURLLength; bytesRead += modelURLLength; - // modelTranslation - memcpy(&_modelTranslation, dataAt, sizeof(_modelTranslation)); - dataAt += sizeof(_modelTranslation); - bytesRead += sizeof(_modelTranslation); - // modelRotation int bytes = unpackOrientationQuatFromBytes(dataAt, _modelRotation); dataAt += bytes; @@ -350,13 +339,6 @@ ModelItem ModelItem::fromEditPacket(const unsigned char* data, int length, int& processedBytes += modelURLLength; } - // modelTranslation - if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_MODEL_TRANSLATION) == MODEL_PACKET_CONTAINS_MODEL_TRANSLATION)) { - memcpy(&newModelItem._modelTranslation, dataAt, sizeof(newModelItem._modelTranslation)); - dataAt += sizeof(newModelItem._modelTranslation); - processedBytes += sizeof(newModelItem._modelTranslation); - } - // modelRotation if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_MODEL_ROTATION) == MODEL_PACKET_CONTAINS_MODEL_ROTATION)) { int bytes = unpackOrientationQuatFromBytes(dataAt, newModelItem._modelRotation); @@ -487,14 +469,6 @@ bool ModelItem::encodeModelEditMessageDetails(PacketType command, ModelItemID id sizeOut += urlLength; } - // modelTranslation - if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_MODEL_TRANSLATION) == MODEL_PACKET_CONTAINS_MODEL_TRANSLATION)) { - glm::vec3 modelTranslation = properties.getModelTranslation(); // should this be relative to TREE_SCALE?? - memcpy(copyAt, &modelTranslation, sizeof(modelTranslation)); - copyAt += sizeof(modelTranslation); - sizeOut += sizeof(modelTranslation); - } - // modelRotation if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_MODEL_ROTATION) == MODEL_PACKET_CONTAINS_MODEL_ROTATION)) { int bytes = packOrientationQuatToBytes(copyAt, properties.getModelRotation()); @@ -572,7 +546,6 @@ ModelItemProperties::ModelItemProperties() : _radius(MODEL_DEFAULT_RADIUS), _shouldDie(false), _modelURL(""), - _modelTranslation(MODEL_DEFAULT_MODEL_TRANSLATION), _modelRotation(MODEL_DEFAULT_MODEL_ROTATION), _id(UNKNOWN_MODEL_ID), @@ -584,7 +557,6 @@ ModelItemProperties::ModelItemProperties() : _radiusChanged(false), _shouldDieChanged(false), _modelURLChanged(false), - _modelTranslationChanged(false), _modelRotationChanged(false), _defaultSettings(true) { @@ -613,10 +585,6 @@ uint16_t ModelItemProperties::getChangedBits() const { changedBits += MODEL_PACKET_CONTAINS_MODEL_URL; } - if (_modelTranslationChanged) { - changedBits += MODEL_PACKET_CONTAINS_MODEL_TRANSLATION; - } - if (_modelRotationChanged) { changedBits += MODEL_PACKET_CONTAINS_MODEL_ROTATION; } @@ -640,9 +608,6 @@ QScriptValue ModelItemProperties::copyToScriptValue(QScriptEngine* engine) const properties.setProperty("modelURL", _modelURL); - QScriptValue modelTranslation = vec3toScriptValue(engine, _modelTranslation); - properties.setProperty("modelTranslation", modelTranslation); - QScriptValue modelRotation = quatToScriptValue(engine, _modelRotation); properties.setProperty("modelRotation", modelRotation); @@ -723,24 +688,6 @@ void ModelItemProperties::copyFromScriptValue(const QScriptValue &object) { } } - QScriptValue modelTranslation = object.property("modelTranslation"); - if (modelTranslation.isValid()) { - QScriptValue x = modelTranslation.property("x"); - QScriptValue y = modelTranslation.property("y"); - QScriptValue z = modelTranslation.property("z"); - if (x.isValid() && y.isValid() && z.isValid()) { - glm::vec3 newModelTranslation; - newModelTranslation.x = x.toVariant().toFloat(); - newModelTranslation.y = y.toVariant().toFloat(); - newModelTranslation.z = z.toVariant().toFloat(); - if (_defaultSettings || newModelTranslation != _modelTranslation) { - _modelTranslation = newModelTranslation; - _modelTranslationChanged = true; - } - } - } - - QScriptValue modelRotation = object.property("modelRotation"); if (modelRotation.isValid()) { QScriptValue x = modelRotation.property("x"); @@ -790,11 +737,6 @@ void ModelItemProperties::copyToModelItem(ModelItem& modelItem) const { somethingChanged = true; } - if (_modelTranslationChanged) { - modelItem.setModelTranslation(_modelTranslation); - somethingChanged = true; - } - if (_modelRotationChanged) { modelItem.setModelRotation(_modelRotation); somethingChanged = true; @@ -818,7 +760,6 @@ void ModelItemProperties::copyFromModelItem(const ModelItem& modelItem) { _radius = modelItem.getRadius() * (float) TREE_SCALE; _shouldDie = modelItem.getShouldDie(); _modelURL = modelItem.getModelURL(); - _modelTranslation = modelItem.getModelTranslation(); _modelRotation = modelItem.getModelRotation(); _id = modelItem.getID(); @@ -830,7 +771,6 @@ void ModelItemProperties::copyFromModelItem(const ModelItem& modelItem) { _shouldDieChanged = false; _modelURLChanged = false; - _modelTranslationChanged = false; _modelRotationChanged = false; _defaultSettings = false; } diff --git a/libraries/models/src/ModelItem.h b/libraries/models/src/ModelItem.h index 8d273a06f0..317299be2d 100644 --- a/libraries/models/src/ModelItem.h +++ b/libraries/models/src/ModelItem.h @@ -1,6 +1,6 @@ // // ModelItem.h -// libraries/particles/src +// libraries/models/src // // Created by Brad Hefta-Gaub on 12/4/13. // Copyright 2013 High Fidelity, Inc. @@ -41,18 +41,16 @@ const uint16_t MODEL_PACKET_CONTAINS_POSITION = 2; const uint16_t MODEL_PACKET_CONTAINS_COLOR = 4; const uint16_t MODEL_PACKET_CONTAINS_SHOULDDIE = 512; const uint16_t MODEL_PACKET_CONTAINS_MODEL_URL = 1024; -const uint16_t MODEL_PACKET_CONTAINS_MODEL_TRANSLATION = 1024; const uint16_t MODEL_PACKET_CONTAINS_MODEL_ROTATION = 2048; const float MODEL_DEFAULT_RADIUS = 0.1f / TREE_SCALE; -const float MODEL_MINIMUM_PARTICLE_ELEMENT_SIZE = (1.0f / 100000.0f) / TREE_SCALE; // smallest size container +const float MINIMUM_MODEL_ELEMENT_SIZE = (1.0f / 100000.0f) / TREE_SCALE; // smallest size container const QString MODEL_DEFAULT_MODEL_URL(""); -const glm::vec3 MODEL_DEFAULT_MODEL_TRANSLATION(0, 0, 0); const glm::quat MODEL_DEFAULT_MODEL_ROTATION(0, 0, 0, 0); -/// A collection of properties of a particle used in the scripting API. Translates between the actual properties of a particle +/// A collection of properties of a model item used in the scripting API. Translates between the actual properties of a model /// and a JavaScript style hash/QScriptValue storing a set of properties. Used in scripting to set/get the complete set of -/// particle properties via JavaScript hashes/QScriptValues +/// model item properties via JavaScript hashes/QScriptValues /// all units for position, radius, etc are in meter units class ModelItemProperties { public: @@ -61,8 +59,8 @@ public: QScriptValue copyToScriptValue(QScriptEngine* engine) const; void copyFromScriptValue(const QScriptValue& object); - void copyToModelItem(ModelItem& particle) const; - void copyFromModelItem(const ModelItem& particle); + void copyToModelItem(ModelItem& modelItem) const; + void copyFromModelItem(const ModelItem& modelItem); const glm::vec3& getPosition() const { return _position; } xColor getColor() const { return _color; } @@ -70,7 +68,6 @@ public: bool getShouldDie() const { return _shouldDie; } const QString& getModelURL() const { return _modelURL; } - const glm::vec3& getModelTranslation() const { return _modelTranslation; } const glm::quat& getModelRotation() const { return _modelRotation; } quint64 getLastEdited() const { return _lastEdited; } @@ -84,11 +81,9 @@ public: // model related properties void setModelURL(const QString& url) { _modelURL = url; _modelURLChanged = true; } - void setModelTranslation(const glm::vec3& translation) { _modelTranslation = translation; - _modelTranslationChanged = true; } void setModelRotation(const glm::quat& rotation) { _modelRotation = rotation; _modelRotationChanged = true; } - /// used by ModelScriptingInterface to return ModelItemProperties for unknown particles + /// used by ModelScriptingInterface to return ModelItemProperties for unknown models void setIsUnknownID() { _id = UNKNOWN_MODEL_ID; _idSet = true; } private: @@ -98,7 +93,6 @@ private: bool _shouldDie; /// to delete it QString _modelURL; - glm::vec3 _modelTranslation; glm::quat _modelRotation; uint32_t _id; @@ -111,7 +105,6 @@ private: bool _shouldDieChanged; bool _modelURLChanged; - bool _modelTranslationChanged; bool _modelRotationChanged; bool _defaultSettings; }; @@ -120,9 +113,9 @@ QScriptValue ModelItemPropertiesToScriptValue(QScriptEngine* engine, const Model void ModelItemPropertiesFromScriptValue(const QScriptValue &object, ModelItemProperties& properties); -/// Abstract ID for editing particles. Used in ModelItem JS API - When particles are created in the JS api, they are given a -/// local creatorTokenID, the actual id for the particle is not known until the server responds to the creator with the -/// correct mapping. This class works with the scripting API an allows the developer to edit particles they created. +/// Abstract ID for editing model items. Used in ModelItem JS API - When models are created in the JS api, they are given a +/// local creatorTokenID, the actual id for the model is not known until the server responds to the creator with the +/// correct mapping. This class works with the scripting API an allows the developer to edit models they created. class ModelItemID { public: ModelItemID() : @@ -146,15 +139,15 @@ void ModelItemIDfromScriptValue(const QScriptValue &object, ModelItemID& propert -/// ModelItem class - this is the actual particle class. +/// ModelItem class - this is the actual model item class. class ModelItem { public: ModelItem(); - ModelItem(const ModelItemID& particleID, const ModelItemProperties& properties); + ModelItem(const ModelItemID& modelItemID, const ModelItemProperties& properties); - /// creates an NEW particle from an PACKET_TYPE_PARTICLE_ADD_OR_EDIT edit data buffer + /// creates an NEW model from an model add or edit message data buffer static ModelItem fromEditPacket(const unsigned char* data, int length, int& processedBytes, ModelTree* tree, bool& valid); virtual ~ModelItem(); @@ -172,20 +165,19 @@ public: // model related properties bool hasModel() const { return !_modelURL.isEmpty(); } const QString& getModelURL() const { return _modelURL; } - const glm::vec3& getModelTranslation() const { return _modelTranslation; } const glm::quat& getModelRotation() const { return _modelRotation; } ModelItemID getModelItemID() const { return ModelItemID(getID(), getCreatorTokenID(), getID() != UNKNOWN_MODEL_ID); } ModelItemProperties getProperties() const; - /// The last updated/simulated time of this particle from the time perspective of the authoritative server/source + /// The last updated/simulated time of this model from the time perspective of the authoritative server/source quint64 getLastUpdated() const { return _lastUpdated; } - /// The last edited time of this particle from the time perspective of the authoritative server/source + /// The last edited time of this model from the time perspective of the authoritative server/source quint64 getLastEdited() const { return _lastEdited; } void setLastEdited(quint64 lastEdited) { _lastEdited = lastEdited; } - /// lifetime of the particle in seconds + /// how long ago was this model edited in seconds float getEditedAgo() const { return static_cast(usecTimestampNow() - _lastEdited) / static_cast(USECS_PER_SECOND); } uint32_t getID() const { return _id; } void setID(uint32_t id) { _id = id; } @@ -210,7 +202,6 @@ public: // model related properties void setModelURL(const QString& url) { _modelURL = url; } - void setModelTranslation(const glm::vec3& translation) { _modelTranslation = translation; } void setModelRotation(const glm::quat& rotation) { _modelRotation = rotation; } void setProperties(const ModelItemProperties& properties); @@ -231,7 +222,7 @@ public: // similar to assignment/copy, but it handles keeping lifetime accurate void copyChangedProperties(const ModelItem& other); - // these methods allow you to create particles, and later edit them. + // these methods allow you to create models, and later edit them. static uint32_t getIDfromCreatorTokenID(uint32_t creatorTokenID); static uint32_t getNextCreatorTokenID(); static void handleAddModelResponse(const QByteArray& packet); @@ -246,7 +237,6 @@ protected: // model related items QString _modelURL; - glm::vec3 _modelTranslation; glm::quat _modelRotation; uint32_t _creatorTokenID; diff --git a/libraries/models/src/ModelTree.cpp b/libraries/models/src/ModelTree.cpp index 250e8855e2..236138e2a8 100644 --- a/libraries/models/src/ModelTree.cpp +++ b/libraries/models/src/ModelTree.cpp @@ -100,7 +100,7 @@ void ModelTree::storeModel(const ModelItem& model, const SharedNodePointer& send // if we didn't find it in the tree, then store it... if (!args.found) { glm::vec3 position = model.getPosition(); - float size = std::max(MODEL_MINIMUM_PARTICLE_ELEMENT_SIZE, model.getRadius()); + float size = std::max(MINIMUM_MODEL_ELEMENT_SIZE, model.getRadius()); ModelTreeElement* element = (ModelTreeElement*)getOrCreateChildElementAt(position.x, position.y, position.z, size); element->storeModel(model); @@ -150,7 +150,7 @@ void ModelTree::addModel(const ModelItemID& modelID, const ModelItemProperties& } ModelItem model(modelID, properties); glm::vec3 position = model.getPosition(); - float size = std::max(MODEL_MINIMUM_PARTICLE_ELEMENT_SIZE, model.getRadius()); + float size = std::max(MINIMUM_MODEL_ELEMENT_SIZE, model.getRadius()); ModelTreeElement* element = (ModelTreeElement*)getOrCreateChildElementAt(position.x, position.y, position.z, size); element->storeModel(model); @@ -403,8 +403,8 @@ int ModelTree::processEditPacketData(PacketType packetType, const unsigned char* } } 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 models + // TODO: wire in support here for server to get PacketTypeModelErase messages + // instead of using PacketTypeModelAddOrEdit messages to delete models case PacketTypeModelErase: processedBytes = 0; break; From 12daae03084bee3cf1f1a824fa3ab7c1b66119e3 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Thu, 1 May 2014 14:12:11 -0700 Subject: [PATCH 12/13] added missing files --- interface/src/models/ModelTreeRenderer.cpp | 123 +++++++++++++++++++++ interface/src/models/ModelTreeRenderer.h | 55 +++++++++ 2 files changed, 178 insertions(+) create mode 100644 interface/src/models/ModelTreeRenderer.cpp create mode 100644 interface/src/models/ModelTreeRenderer.h diff --git a/interface/src/models/ModelTreeRenderer.cpp b/interface/src/models/ModelTreeRenderer.cpp new file mode 100644 index 0000000000..e531c68ddc --- /dev/null +++ b/interface/src/models/ModelTreeRenderer.cpp @@ -0,0 +1,123 @@ +// +// ModelTreeRenderer.cpp +// interface/src +// +// Created by Brad Hefta-Gaub on 12/6/13. +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include + +#include "InterfaceConfig.h" + +#include "ModelTreeRenderer.h" + +ModelTreeRenderer::ModelTreeRenderer() : + OctreeRenderer() { +} + +ModelTreeRenderer::~ModelTreeRenderer() { + // delete the models in _modelsItemModels + foreach(Model* model, _modelsItemModels) { + delete model; + } + _modelsItemModels.clear(); +} + +void ModelTreeRenderer::init() { + OctreeRenderer::init(); +} + + +void ModelTreeRenderer::update() { + if (_tree) { + ModelTree* tree = static_cast(_tree); + tree->update(); + } +} + +void ModelTreeRenderer::render() { + OctreeRenderer::render(); +} + +Model* ModelTreeRenderer::getModel(const QString& url) { + Model* model = NULL; + + // if we don't already have this model then create it and initialize it + if (_modelsItemModels.find(url) == _modelsItemModels.end()) { + model = new Model(); + model->init(); + model->setURL(QUrl(url)); + _modelsItemModels[url] = model; + } else { + model = _modelsItemModels[url]; + } + return model; +} + +void ModelTreeRenderer::renderElement(OctreeElement* element, RenderArgs* args) { + // actually render it here... + // we need to iterate the actual modelItems of the element + ModelTreeElement* modelTreeElement = (ModelTreeElement*)element; + + const QList& modelItems = modelTreeElement->getModels(); + + uint16_t numberOfParticles = modelItems.size(); + + for (uint16_t i = 0; i < numberOfParticles; i++) { + const ModelItem& modelItem = modelItems[i]; + // render modelItem aspoints + glm::vec3 position = modelItem.getPosition() * (float)TREE_SCALE; + glColor3ub(modelItem.getColor()[RED_INDEX],modelItem.getColor()[GREEN_INDEX],modelItem.getColor()[BLUE_INDEX]); + float radius = modelItem.getRadius() * (float)TREE_SCALE; + //glm::vec3 center = position + glm::vec3(radius, radius, radius); // center it around the position + + bool drawAsModel = modelItem.hasModel(); + + args->_renderedItems++; + + if (drawAsModel) { + glPushMatrix(); + const float alpha = 1.0f; + + Model* model = getModel(modelItem.getModelURL()); + + model->setScaleToFit(true, radius * 2.0f); + model->setSnapModelToCenter(true); + + // set the rotation + glm::quat rotation = modelItem.getModelRotation(); + model->setRotation(rotation); + + // set the position + model->setTranslation(position); + + model->simulate(0.0f); + + + model->render(alpha); // TODO: should we allow modelItems to have alpha on their models? + + const bool wantDebugSphere = false; + if (wantDebugSphere) { + glPushMatrix(); + glTranslatef(position.x, position.y, position.z); + glutWireSphere(radius, 15, 15); + glPopMatrix(); + } + + glPopMatrix(); + } else { + glPushMatrix(); + glTranslatef(position.x, position.y, position.z); + glutSolidSphere(radius, 15, 15); + glPopMatrix(); + } + } +} + +void ModelTreeRenderer::processEraseMessage(const QByteArray& dataByteArray, const SharedNodePointer& sourceNode) { + static_cast(_tree)->processEraseMessage(dataByteArray, sourceNode); +} diff --git a/interface/src/models/ModelTreeRenderer.h b/interface/src/models/ModelTreeRenderer.h new file mode 100644 index 0000000000..2fe67ccad6 --- /dev/null +++ b/interface/src/models/ModelTreeRenderer.h @@ -0,0 +1,55 @@ +// +// ModelTreeRenderer.h +// interface/src +// +// Created by Brad Hefta-Gaub on 12/6/13. +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_ModelTreeRenderer_h +#define hifi_ModelTreeRenderer_h + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "renderer/Model.h" + +// Generic client side Octree renderer class. +class ModelTreeRenderer : public OctreeRenderer { +public: + ModelTreeRenderer(); + virtual ~ModelTreeRenderer(); + + virtual Octree* createTree() { return new ModelTree(true); } + virtual NodeType_t getMyNodeType() const { return NodeType::ParticleServer; } + virtual PacketType getMyQueryMessageType() const { return PacketTypeParticleQuery; } + virtual PacketType getExpectedPacketType() const { return PacketTypeParticleData; } + virtual void renderElement(OctreeElement* element, RenderArgs* args); + + void update(); + + ModelTree* getTree() { return (ModelTree*)_tree; } + + void processEraseMessage(const QByteArray& dataByteArray, const SharedNodePointer& sourceNode); + + virtual void init(); + virtual void render(); + +protected: + Model* getModel(const QString& url); + + QMap _modelsItemModels; +}; + +#endif // hifi_ModelTreeRenderer_h From 8938c1c38bd877cc6a5f0c047707d4a8d7cc84c0 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Thu, 1 May 2014 15:18:43 -0700 Subject: [PATCH 13/13] fixed bad particle reference --- interface/src/models/ModelTreeRenderer.cpp | 4 ++-- interface/src/models/ModelTreeRenderer.h | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/interface/src/models/ModelTreeRenderer.cpp b/interface/src/models/ModelTreeRenderer.cpp index e531c68ddc..0fa434622e 100644 --- a/interface/src/models/ModelTreeRenderer.cpp +++ b/interface/src/models/ModelTreeRenderer.cpp @@ -65,9 +65,9 @@ void ModelTreeRenderer::renderElement(OctreeElement* element, RenderArgs* args) const QList& modelItems = modelTreeElement->getModels(); - uint16_t numberOfParticles = modelItems.size(); + uint16_t numberOfModels = modelItems.size(); - for (uint16_t i = 0; i < numberOfParticles; i++) { + for (uint16_t i = 0; i < numberOfModels; i++) { const ModelItem& modelItem = modelItems[i]; // render modelItem aspoints glm::vec3 position = modelItem.getPosition() * (float)TREE_SCALE; diff --git a/interface/src/models/ModelTreeRenderer.h b/interface/src/models/ModelTreeRenderer.h index 2fe67ccad6..6c66350d8f 100644 --- a/interface/src/models/ModelTreeRenderer.h +++ b/interface/src/models/ModelTreeRenderer.h @@ -32,9 +32,9 @@ public: virtual ~ModelTreeRenderer(); virtual Octree* createTree() { return new ModelTree(true); } - virtual NodeType_t getMyNodeType() const { return NodeType::ParticleServer; } - virtual PacketType getMyQueryMessageType() const { return PacketTypeParticleQuery; } - virtual PacketType getExpectedPacketType() const { return PacketTypeParticleData; } + virtual NodeType_t getMyNodeType() const { return NodeType::ModelServer; } + virtual PacketType getMyQueryMessageType() const { return PacketTypeModelQuery; } + virtual PacketType getExpectedPacketType() const { return PacketTypeModelData; } virtual void renderElement(OctreeElement* element, RenderArgs* args); void update();