From 4bdc945f94bf0d2c8787c6397df68ac4221e114d Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Tue, 29 Apr 2014 20:12:16 +0200 Subject: [PATCH 01/38] Changed font sizes in script-editor to pixels, it should he readable in both Windows and OSX now --- interface/ui/scriptEditorWidget.ui | 52 ++++++++++++++++++++---------- 1 file changed, 35 insertions(+), 17 deletions(-) diff --git a/interface/ui/scriptEditorWidget.ui b/interface/ui/scriptEditorWidget.ui index 88761c91c5..363f99b635 100644 --- a/interface/ui/scriptEditorWidget.ui +++ b/interface/ui/scriptEditorWidget.ui @@ -18,8 +18,8 @@ - 541 - 238 + 690 + 328 @@ -35,20 +35,11 @@ Edit Script - + 0 - - 0 - - - 0 - - - 0 - - + 0 @@ -56,14 +47,17 @@ Courier - 9 + -1 50 false false - font: 9pt "Courier"; + font: 16px "Courier"; + + + false @@ -86,6 +80,9 @@ 0 + + font: 13px "Helvetica","Arial","sans-serif"; + Debug Log: @@ -93,8 +90,20 @@ + + + Helvetica,Arial,sans-serif + -1 + 50 + false + false + + + + font: 13px "Helvetica","Arial","sans-serif"; + - Run on the fly (Careful: Any valid change made to the code will run immediately) + Run on the fly (Careful: Any valid change made to the code will run immediately) @@ -115,12 +124,21 @@ + + + 0 + 0 + + - font: 8pt "Courier"; + font: 15px "Courier"; true + + + From df14e1e6e4c7402cb9f4ae5009b863673b0796fe Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Tue, 29 Apr 2014 21:51:20 +0200 Subject: [PATCH 02/38] Use same load/save dialog behavior as in main application for ScriptEditor, this will keep track of the last directory that was used to load a script. --- interface/src/Application.cpp | 23 ++++++++++++++++------- interface/src/Application.h | 3 +++ interface/src/ui/ScriptEditorWidget.cpp | 11 +++++++++-- interface/src/ui/ScriptEditorWindow.cpp | 5 ++++- 4 files changed, 32 insertions(+), 10 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 558ba31d80..228e40fdce 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -3438,9 +3438,9 @@ ScriptEngine* Application::loadScript(const QString& scriptName, bool loadScript return scriptEngine; } -void Application::loadDialog() { +QString Application::getPreviousScriptLocation() { QString suggestedName; - + if (_previousScriptLocation.isEmpty()) { QString desktopLocation = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation); // Temporary fix to Qt bug: http://stackoverflow.com/questions/16194475 @@ -3450,14 +3450,23 @@ void Application::loadDialog() { } else { suggestedName = _previousScriptLocation; } + + return suggestedName; +} - QString fileNameString = QFileDialog::getOpenFileName(_glWidget, tr("Open Script"), suggestedName, +void Application::setPreviousScriptLocation(QString previousScriptLocation) { + _previousScriptLocation = previousScriptLocation; + QMutexLocker locker(&_settingsMutex); + _settings->setValue("LastScriptLocation", _previousScriptLocation); +} + +void Application::loadDialog() { + + QString fileNameString = QFileDialog::getOpenFileName(_glWidget, tr("Open Script"), + getPreviousScriptLocation(), tr("JavaScript Files (*.js)")); if (!fileNameString.isEmpty()) { - _previousScriptLocation = fileNameString; - QMutexLocker locker(&_settingsMutex); - _settings->setValue("LastScriptLocation", _previousScriptLocation); - + setPreviousScriptLocation(fileNameString); loadScript(fileNameString); } } diff --git a/interface/src/Application.h b/interface/src/Application.h index 325770a8df..8929c06134 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -124,6 +124,9 @@ public: void restoreSizeAndPosition(); ScriptEngine* loadScript(const QString& fileNameString, bool loadScriptFromEditor = false); void loadScripts(); + QString getPreviousScriptLocation(); + void setPreviousScriptLocation(QString previousScriptLocation); + void storeSizeAndPosition(); void clearScriptsBeforeRunning(); void saveScripts(); diff --git a/interface/src/ui/ScriptEditorWidget.cpp b/interface/src/ui/ScriptEditorWidget.cpp index 3f9b0137ef..95d60e3b4f 100644 --- a/interface/src/ui/ScriptEditorWidget.cpp +++ b/interface/src/ui/ScriptEditorWidget.cpp @@ -128,8 +128,15 @@ bool ScriptEditorWidget::save() { } bool ScriptEditorWidget::saveAs() { - QString fileName = QFileDialog::getSaveFileName(this, tr("Save script"), QString(), tr("Javascript (*.js)")); - return !fileName.isEmpty() ? saveFile(fileName) : false; + QString fileName = QFileDialog::getSaveFileName(this, tr("Save script"), + Application::getInstance()->getPreviousScriptLocation(), + tr("JavaScript Files (*.js)")); + if (!fileName.isEmpty()) { + Application::getInstance()->setPreviousScriptLocation(fileName); + return saveFile(fileName); + } else { + return false; + } } void ScriptEditorWidget::setScriptFile(const QString& scriptPath) { diff --git a/interface/src/ui/ScriptEditorWindow.cpp b/interface/src/ui/ScriptEditorWindow.cpp index 0c34959353..d118f30d0b 100644 --- a/interface/src/ui/ScriptEditorWindow.cpp +++ b/interface/src/ui/ScriptEditorWindow.cpp @@ -73,8 +73,11 @@ void ScriptEditorWindow::loadScriptMenu(const QString& scriptName) { } void ScriptEditorWindow::loadScriptClicked() { - QString scriptName = QFileDialog::getOpenFileName(this, tr("Interface"), QString(), tr("Javascript (*.js)")); + QString scriptName = QFileDialog::getOpenFileName(this, tr("Interface"), + Application::getInstance()->getPreviousScriptLocation(), + tr("JavaScript Files (*.js)")); if (!scriptName.isEmpty()) { + Application::getInstance()->setPreviousScriptLocation(scriptName); addScriptEditorWidget("loading...")->loadFile(scriptName); updateButtons(); } From fc2ed9f9fb5a67da4b1b55d0c0d35a76c9d07f8f Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Tue, 29 Apr 2014 21:51:20 +0200 Subject: [PATCH 03/38] Use same load/save dialog behavior as in main application for ScriptEditor, this will keep track of the last directory that was used to load a script. --- interface/src/Application.cpp | 21 ++++++++++++++------- interface/src/Application.h | 3 +++ interface/src/ui/ScriptEditorWidget.cpp | 11 +++++++++-- interface/src/ui/ScriptEditorWindow.cpp | 5 ++++- 4 files changed, 30 insertions(+), 10 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 558ba31d80..72cf2c5e51 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -3438,9 +3438,8 @@ ScriptEngine* Application::loadScript(const QString& scriptName, bool loadScript return scriptEngine; } -void Application::loadDialog() { +QString Application::getPreviousScriptLocation() { QString suggestedName; - if (_previousScriptLocation.isEmpty()) { QString desktopLocation = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation); // Temporary fix to Qt bug: http://stackoverflow.com/questions/16194475 @@ -3450,14 +3449,22 @@ void Application::loadDialog() { } else { suggestedName = _previousScriptLocation; } + return suggestedName; +} - QString fileNameString = QFileDialog::getOpenFileName(_glWidget, tr("Open Script"), suggestedName, +void Application::setPreviousScriptLocation(QString previousScriptLocation) { + _previousScriptLocation = previousScriptLocation; + QMutexLocker locker(&_settingsMutex); + _settings->setValue("LastScriptLocation", _previousScriptLocation); +} + +void Application::loadDialog() { + + QString fileNameString = QFileDialog::getOpenFileName(_glWidget, tr("Open Script"), + getPreviousScriptLocation(), tr("JavaScript Files (*.js)")); if (!fileNameString.isEmpty()) { - _previousScriptLocation = fileNameString; - QMutexLocker locker(&_settingsMutex); - _settings->setValue("LastScriptLocation", _previousScriptLocation); - + setPreviousScriptLocation(fileNameString); loadScript(fileNameString); } } diff --git a/interface/src/Application.h b/interface/src/Application.h index 325770a8df..9b460f846a 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -124,6 +124,9 @@ public: void restoreSizeAndPosition(); ScriptEngine* loadScript(const QString& fileNameString, bool loadScriptFromEditor = false); void loadScripts(); + QString getPreviousScriptLocation(); + void setPreviousScriptLocation(QString previousScriptLocation); + void storeSizeAndPosition(); void clearScriptsBeforeRunning(); void saveScripts(); diff --git a/interface/src/ui/ScriptEditorWidget.cpp b/interface/src/ui/ScriptEditorWidget.cpp index 3f9b0137ef..95d60e3b4f 100644 --- a/interface/src/ui/ScriptEditorWidget.cpp +++ b/interface/src/ui/ScriptEditorWidget.cpp @@ -128,8 +128,15 @@ bool ScriptEditorWidget::save() { } bool ScriptEditorWidget::saveAs() { - QString fileName = QFileDialog::getSaveFileName(this, tr("Save script"), QString(), tr("Javascript (*.js)")); - return !fileName.isEmpty() ? saveFile(fileName) : false; + QString fileName = QFileDialog::getSaveFileName(this, tr("Save script"), + Application::getInstance()->getPreviousScriptLocation(), + tr("JavaScript Files (*.js)")); + if (!fileName.isEmpty()) { + Application::getInstance()->setPreviousScriptLocation(fileName); + return saveFile(fileName); + } else { + return false; + } } void ScriptEditorWidget::setScriptFile(const QString& scriptPath) { diff --git a/interface/src/ui/ScriptEditorWindow.cpp b/interface/src/ui/ScriptEditorWindow.cpp index 0c34959353..d118f30d0b 100644 --- a/interface/src/ui/ScriptEditorWindow.cpp +++ b/interface/src/ui/ScriptEditorWindow.cpp @@ -73,8 +73,11 @@ void ScriptEditorWindow::loadScriptMenu(const QString& scriptName) { } void ScriptEditorWindow::loadScriptClicked() { - QString scriptName = QFileDialog::getOpenFileName(this, tr("Interface"), QString(), tr("Javascript (*.js)")); + QString scriptName = QFileDialog::getOpenFileName(this, tr("Interface"), + Application::getInstance()->getPreviousScriptLocation(), + tr("JavaScript Files (*.js)")); if (!scriptName.isEmpty()) { + Application::getInstance()->setPreviousScriptLocation(scriptName); addScriptEditorWidget("loading...")->loadFile(scriptName); updateButtons(); } From d93f91a628b03911513f379d52bc6f19487b7e8b Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Tue, 29 Apr 2014 22:17:45 +0200 Subject: [PATCH 04/38] Resized ScriptEditorWindow --- interface/ui/scriptEditorWindow.ui | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/interface/ui/scriptEditorWindow.ui b/interface/ui/scriptEditorWindow.ui index 9e1b08de3e..f5b0d84235 100644 --- a/interface/ui/scriptEditorWindow.ui +++ b/interface/ui/scriptEditorWindow.ui @@ -9,8 +9,8 @@ 0 0 - 706 - 682 + 780 + 717 From 5ea512f65aaa13ae3175d08d30aecaa4db65b578 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Tue, 29 Apr 2014 23:53:46 +0200 Subject: [PATCH 05/38] ScriptEditorWindow not modal, but yet always on foreground --- interface/src/ui/ScriptEditorWindow.cpp | 1 + interface/ui/scriptEditorWindow.ui | 13 ++----------- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/interface/src/ui/ScriptEditorWindow.cpp b/interface/src/ui/ScriptEditorWindow.cpp index d118f30d0b..2d52165612 100644 --- a/interface/src/ui/ScriptEditorWindow.cpp +++ b/interface/src/ui/ScriptEditorWindow.cpp @@ -34,6 +34,7 @@ ScriptEditorWindow::ScriptEditorWindow() : _saveMenu(new QMenu) { _ScriptEditorWindowUI->setupUi(this); + this->setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint); show(); addScriptEditorWidget("New script"); connect(_loadMenu, SIGNAL(aboutToShow()), this, SLOT(loadMenuAboutToShow())); diff --git a/interface/ui/scriptEditorWindow.ui b/interface/ui/scriptEditorWindow.ui index f5b0d84235..9103fc1f57 100644 --- a/interface/ui/scriptEditorWindow.ui +++ b/interface/ui/scriptEditorWindow.ui @@ -3,7 +3,7 @@ ScriptEditorWindow - Qt::WindowModal + Qt::NonModal @@ -29,16 +29,7 @@ 0 - - 0 - - - 0 - - - 0 - - + 0 From 87260a4c5813ada502fc95d57d7178ff9e45e659 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Wed, 30 Apr 2014 00:58:22 +0200 Subject: [PATCH 06/38] ability to load a script thats running from an external link into the ScriptEditor , followed by a save dialog --- interface/src/ui/ScriptEditorWidget.cpp | 41 +++++++++++++++++-------- interface/src/ui/ScriptEditorWindow.cpp | 6 ++++ interface/src/ui/ScriptEditorWindow.h | 3 ++ 3 files changed, 37 insertions(+), 13 deletions(-) diff --git a/interface/src/ui/ScriptEditorWidget.cpp b/interface/src/ui/ScriptEditorWidget.cpp index 95d60e3b4f..08a68b1d8c 100644 --- a/interface/src/ui/ScriptEditorWidget.cpp +++ b/interface/src/ui/ScriptEditorWidget.cpp @@ -101,21 +101,36 @@ bool ScriptEditorWidget::saveFile(const QString &scriptPath) { } void ScriptEditorWidget::loadFile(const QString& scriptPath) { - QFile file(scriptPath); - if (!file.open(QFile::ReadOnly | QFile::Text)) { - QMessageBox::warning(this, tr("Interface"), tr("Cannot read script %1:\n%2.").arg(scriptPath).arg(file.errorString())); - return; + QUrl url(scriptPath); + + // if the scheme length is one or lower, maybe they typed in a file, let's try + const int WINDOWS_DRIVE_LETTER_SIZE = 1; + if (url.scheme().size() <= WINDOWS_DRIVE_LETTER_SIZE) { + QFile file(scriptPath); + if (!file.open(QFile::ReadOnly | QFile::Text)) { + QMessageBox::warning(this, tr("Interface"), tr("Cannot read script %1:\n%2.").arg(scriptPath).arg(file.errorString())); + return; + } + QTextStream in(&file); + _scriptEditorWidgetUI->scriptEdit->setPlainText(in.readAll()); + setScriptFile(scriptPath); + + disconnect(this, SLOT(onScriptError(const QString&))); + disconnect(this, SLOT(onScriptPrint(const QString&))); + } else { + QNetworkAccessManager* networkManager = new QNetworkAccessManager(this); + QNetworkReply* reply = networkManager->get(QNetworkRequest(url)); + qDebug() << "Downloading included script at" << scriptPath; + QEventLoop loop; + QObject::connect(reply, SIGNAL(finished()), &loop, SLOT(quit())); + loop.exec(); + _scriptEditorWidgetUI->scriptEdit->setPlainText(reply->readAll()); + if (!saveAs()) { + emit static_cast(this->parent()->parent()->parent())->terminateCurrentTab(); + } } - QTextStream in(&file); - _scriptEditorWidgetUI->scriptEdit->setPlainText(in.readAll()); - - setScriptFile(scriptPath); - - disconnect(this, SLOT(onScriptError(const QString&))); - disconnect(this, SLOT(onScriptPrint(const QString&))); - - _scriptEngine = Application::getInstance()->getScriptEngine(scriptPath); + _scriptEngine = Application::getInstance()->getScriptEngine(_currentScript); if (_scriptEngine != NULL) { connect(_scriptEngine, SIGNAL(runningStateChanged()), this, SIGNAL(runningStateChanged())); connect(_scriptEngine, SIGNAL(errorMessage(const QString&)), this, SLOT(onScriptError(const QString&))); diff --git a/interface/src/ui/ScriptEditorWindow.cpp b/interface/src/ui/ScriptEditorWindow.cpp index 2d52165612..9f90606f5b 100644 --- a/interface/src/ui/ScriptEditorWindow.cpp +++ b/interface/src/ui/ScriptEditorWindow.cpp @@ -202,3 +202,9 @@ void ScriptEditorWindow::updateScriptNameOrStatus() { } } } + +void ScriptEditorWindow::terminateCurrentTab() { + if (_ScriptEditorWindowUI->tabWidget->currentIndex() != -1) { + _ScriptEditorWindowUI->tabWidget->removeTab(_ScriptEditorWindowUI->tabWidget->currentIndex()); + } +} diff --git a/interface/src/ui/ScriptEditorWindow.h b/interface/src/ui/ScriptEditorWindow.h index 0bf5015ccf..c934e44073 100644 --- a/interface/src/ui/ScriptEditorWindow.h +++ b/interface/src/ui/ScriptEditorWindow.h @@ -37,6 +37,9 @@ private: void setRunningState(bool run); void setScriptName(const QString& scriptName); +public slots: + void terminateCurrentTab(); + private slots: void loadScriptMenu(const QString& scriptName); void loadScriptClicked(); From c20381a0eafb8ead7797a1e828774afffce2643e Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Wed, 30 Apr 2014 08:24:04 +0200 Subject: [PATCH 07/38] EditorWindow on-top behavior fixed --- interface/src/ui/ScriptEditorWidget.cpp | 2 +- interface/src/ui/ScriptEditorWindow.cpp | 3 ++- interface/src/ui/ScriptEditorWindow.h | 5 ++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/interface/src/ui/ScriptEditorWidget.cpp b/interface/src/ui/ScriptEditorWidget.cpp index 08a68b1d8c..33e014ac07 100644 --- a/interface/src/ui/ScriptEditorWidget.cpp +++ b/interface/src/ui/ScriptEditorWidget.cpp @@ -126,7 +126,7 @@ void ScriptEditorWidget::loadFile(const QString& scriptPath) { loop.exec(); _scriptEditorWidgetUI->scriptEdit->setPlainText(reply->readAll()); if (!saveAs()) { - emit static_cast(this->parent()->parent()->parent())->terminateCurrentTab(); + static_cast(this->parent()->parent()->parent())->terminateCurrentTab(); } } diff --git a/interface/src/ui/ScriptEditorWindow.cpp b/interface/src/ui/ScriptEditorWindow.cpp index 9f90606f5b..ec5070ac20 100644 --- a/interface/src/ui/ScriptEditorWindow.cpp +++ b/interface/src/ui/ScriptEditorWindow.cpp @@ -34,7 +34,7 @@ ScriptEditorWindow::ScriptEditorWindow() : _saveMenu(new QMenu) { _ScriptEditorWindowUI->setupUi(this); - this->setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint); + this->setWindowFlags(Qt::Tool); show(); addScriptEditorWidget("New script"); connect(_loadMenu, SIGNAL(aboutToShow()), this, SLOT(loadMenuAboutToShow())); @@ -206,5 +206,6 @@ void ScriptEditorWindow::updateScriptNameOrStatus() { void ScriptEditorWindow::terminateCurrentTab() { if (_ScriptEditorWindowUI->tabWidget->currentIndex() != -1) { _ScriptEditorWindowUI->tabWidget->removeTab(_ScriptEditorWindowUI->tabWidget->currentIndex()); + this->raise(); } } diff --git a/interface/src/ui/ScriptEditorWindow.h b/interface/src/ui/ScriptEditorWindow.h index c934e44073..360e902cc2 100644 --- a/interface/src/ui/ScriptEditorWindow.h +++ b/interface/src/ui/ScriptEditorWindow.h @@ -25,6 +25,8 @@ public: ScriptEditorWindow(); ~ScriptEditorWindow(); + void terminateCurrentTab(); + protected: void closeEvent(QCloseEvent* event); @@ -37,9 +39,6 @@ private: void setRunningState(bool run); void setScriptName(const QString& scriptName); -public slots: - void terminateCurrentTab(); - private slots: void loadScriptMenu(const QString& scriptName); void loadScriptClicked(); From aac42058ac5e152e2077f0d7ea9aace380faf123 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Wed, 30 Apr 2014 09:25:52 -0700 Subject: [PATCH 08/38] 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 09/38] 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 b9f296ddb3892404153d77e2224ccd441eed9a7f Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 30 Apr 2014 09:38:30 -0700 Subject: [PATCH 10/38] Add WindowScriptingInterface and LocationScriptingInterface --- .../scripting/LocationScriptingInterface.cpp | 49 +++++++++++ .../scripting/LocationScriptingInterface.h | 46 ++++++++++ .../scripting/WindowScriptingInterface.cpp | 85 +++++++++++++++++++ .../src/scripting/WindowScriptingInterface.h | 43 ++++++++++ 4 files changed, 223 insertions(+) create mode 100644 interface/src/scripting/LocationScriptingInterface.cpp create mode 100644 interface/src/scripting/LocationScriptingInterface.h create mode 100644 interface/src/scripting/WindowScriptingInterface.cpp create mode 100644 interface/src/scripting/WindowScriptingInterface.h diff --git a/interface/src/scripting/LocationScriptingInterface.cpp b/interface/src/scripting/LocationScriptingInterface.cpp new file mode 100644 index 0000000000..44ff94aa1f --- /dev/null +++ b/interface/src/scripting/LocationScriptingInterface.cpp @@ -0,0 +1,49 @@ +// +// LocationScriptingInterface.cpp +// interface/src/scripting +// +// Created by Ryan Huffman 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 "NodeList.h" + +#include "LocationScriptingInterface.h" + +LocationScriptingInterface* LocationScriptingInterface::getInstance() { + static LocationScriptingInterface sharedInstance; + return &sharedInstance; +} + +QString LocationScriptingInterface::getHref() { + return getProtocol() + "//" + getHostname() + getPathname(); +} + +QString LocationScriptingInterface::getPathname() { + const glm::vec3& position = Application::getInstance()->getAvatar()->getPosition(); + QString path; + path.sprintf("/%.4f,%.4f,%.4f", position.x, position.y, position.z); + return path; +} + +QString LocationScriptingInterface::getHostname() { + return NodeList::getInstance()->getDomainHandler().getHostname(); +} + +void LocationScriptingInterface::assign(const QString& url) { + QMetaObject::invokeMethod(Menu::getInstance(), "goToURL", Q_ARG(const QString&, url)); +} + +QScriptValue LocationScriptingInterface::locationGetter(QScriptContext* context, QScriptEngine* engine) { + return engine->newQObject(getInstance()); +} + +QScriptValue LocationScriptingInterface::locationSetter(QScriptContext* context, QScriptEngine* engine) { + LocationScriptingInterface::getInstance()->assign(context->argument(0).toString()); + return QScriptValue::UndefinedValue; +} diff --git a/interface/src/scripting/LocationScriptingInterface.h b/interface/src/scripting/LocationScriptingInterface.h new file mode 100644 index 0000000000..36b6d97561 --- /dev/null +++ b/interface/src/scripting/LocationScriptingInterface.h @@ -0,0 +1,46 @@ +// +// LocationScriptingInterface.h +// interface/src/scripting +// +// Created by Ryan Huffman 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_LocationScriptingInterface_h +#define hifi_LocationScriptingInterface_h + +#include +#include +#include +#include +#include + +#include "Application.h" + +class LocationScriptingInterface : public QObject { + Q_OBJECT + Q_PROPERTY(QString href READ getHref) + Q_PROPERTY(QString protocol READ getProtocol) + Q_PROPERTY(QString hostname READ getHostname) + Q_PROPERTY(QString pathname READ getPathname) + LocationScriptingInterface() { }; +public: + static LocationScriptingInterface* getInstance(); + + QString getHref(); + QString getProtocol() { return CUSTOM_URL_SCHEME; }; + QString getPathname(); + QString getHostname(); + + static QScriptValue locationGetter(QScriptContext* context, QScriptEngine* engine); + static QScriptValue locationSetter(QScriptContext* context, QScriptEngine* engine); + +public slots: + void assign(const QString& url); + +}; + +#endif // hifi_LocationScriptingInterface_h diff --git a/interface/src/scripting/WindowScriptingInterface.cpp b/interface/src/scripting/WindowScriptingInterface.cpp new file mode 100644 index 0000000000..6272b689d9 --- /dev/null +++ b/interface/src/scripting/WindowScriptingInterface.cpp @@ -0,0 +1,85 @@ +// +// WindowScriptingInterface.cpp +// interface/src/scripting +// +// Created by Ryan Huffman 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 "Application.h" +#include "Menu.h" + +#include "WindowScriptingInterface.h" + +WindowScriptingInterface* WindowScriptingInterface::getInstance() { + static WindowScriptingInterface sharedInstance; + return &sharedInstance; +} + +QScriptValue WindowScriptingInterface::alert(const QString& message) { + QScriptValue retVal; + QMetaObject::invokeMethod(this, "showAlert", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QScriptValue, retVal), Q_ARG(const QString&, message)); + return retVal; +} + +QScriptValue WindowScriptingInterface::confirm(const QString& message) { + bool retVal; + QMetaObject::invokeMethod(this, "showConfirm", Qt::BlockingQueuedConnection, + Q_RETURN_ARG(bool, retVal), Q_ARG(const QString&, message)); + return QScriptValue(retVal); +} + +QScriptValue WindowScriptingInterface::prompt(const QString& message, const QString& defaultText) { + QScriptValue retVal; + QMetaObject::invokeMethod(this, "showPrompt", Qt::BlockingQueuedConnection, + Q_RETURN_ARG(QScriptValue, retVal), + Q_ARG(const QString&, message), Q_ARG(const QString&, defaultText)); + return retVal; +} + +/// Display an alert box +/// \param const QString& message message to display +/// \return QScriptValue::UndefinedValue +QScriptValue WindowScriptingInterface::showAlert(const QString& message) { + QMessageBox::warning(Application::getInstance()->getWindow(), "", message); + return QScriptValue::UndefinedValue; +} + +/// Display a confirmation box with the options 'Yes' and 'No' +/// \param const QString& message message to display +/// \return QScriptValue `true` if 'Yes' was clicked, `false` otherwise +QScriptValue WindowScriptingInterface::showConfirm(const QString& message) { + QMessageBox::StandardButton response = QMessageBox::question(Application::getInstance()->getWindow(), "", message); + return QScriptValue(response == QMessageBox::Yes); +} + +/// Display a prompt with a text box +/// \param const QString& message message to display +/// \param const QString& defaultText default text in the text box +/// \return QScriptValue string text value in text box if the dialog was accepted, `null` otherwise. +QScriptValue WindowScriptingInterface::showPrompt(const QString& message, const QString& defaultText) { + QInputDialog promptDialog(Application::getInstance()->getWindow()); + promptDialog.setWindowTitle(""); + promptDialog.setLabelText(message); + promptDialog.setTextValue(defaultText); + + if (promptDialog.exec() == QDialog::Accepted) { + return QScriptValue(promptDialog.textValue()); + } + + return QScriptValue::NullValue; +} + +int WindowScriptingInterface::getInnerWidth() { + return Application::getInstance()->getWindow()->geometry().width(); +} + +int WindowScriptingInterface::getInnerHeight() { + return Application::getInstance()->getWindow()->geometry().height(); +} diff --git a/interface/src/scripting/WindowScriptingInterface.h b/interface/src/scripting/WindowScriptingInterface.h new file mode 100644 index 0000000000..a97733eaf4 --- /dev/null +++ b/interface/src/scripting/WindowScriptingInterface.h @@ -0,0 +1,43 @@ +// +// WindowScriptingInterface.cpp +// interface/src/scripting +// +// Created by Ryan Huffman 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_WindowScriptingInterface_h +#define hifi_WindowScriptingInterface_h + +#include +#include +#include +#include + +#include "LocationScriptingInterface.h" + +class WindowScriptingInterface : public QObject { + Q_OBJECT + Q_PROPERTY(int innerWidth READ getInnerWidth) + Q_PROPERTY(int innerHeight READ getInnerHeight) + WindowScriptingInterface() { }; +public: + static WindowScriptingInterface* getInstance(); + int getInnerWidth(); + int getInnerHeight(); + +public slots: + QScriptValue alert(const QString& message); + QScriptValue confirm(const QString& message); + QScriptValue prompt(const QString& message, const QString& defaultText=""); + +private slots: + QScriptValue showAlert(const QString& message); + QScriptValue showConfirm(const QString& message); + QScriptValue showPrompt(const QString& message, const QString& defaultText); +}; + +#endif // hifi_WindowScriptingInterface_h From 0570849df5909e0aaf592a2bff21f0a6fd66c559 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 30 Apr 2014 09:38:57 -0700 Subject: [PATCH 11/38] Add Window and location to global script object --- interface/src/Application.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 97b5c05f25..d630f2eb83 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -77,6 +77,8 @@ #include "scripting/ClipboardScriptingInterface.h" #include "scripting/MenuScriptingInterface.h" #include "scripting/SettingsScriptingInterface.h" +#include "scripting/WindowScriptingInterface.h" +#include "scripting/LocationScriptingInterface.h" #include "ui/InfoView.h" #include "ui/Snapshot.h" @@ -3406,6 +3408,18 @@ ScriptEngine* Application::loadScript(const QString& scriptName, bool loadScript connect(scriptEngine, SIGNAL(finished(const QString&)), clipboardScriptable, SLOT(deleteLater())); scriptEngine->registerGlobalObject("Overlays", &_overlays); + + QScriptEngine &qScriptEngine = scriptEngine->getEngine(); + QScriptValue getLocationFunction = qScriptEngine.newFunction(LocationScriptingInterface::locationGetter); + QScriptValue setLocationFunction = qScriptEngine.newFunction(LocationScriptingInterface::locationSetter, 1); + qScriptEngine.globalObject().setProperty("location", getLocationFunction, QScriptValue::PropertyGetter); + qScriptEngine.globalObject().setProperty("location", setLocationFunction, QScriptValue::PropertySetter); + + QScriptValue windowValue = qScriptEngine.newQObject(WindowScriptingInterface::getInstance()); + qScriptEngine.globalObject().setProperty("Window", windowValue); + windowValue.setProperty("location", getLocationFunction, QScriptValue::PropertyGetter); + windowValue.setProperty("location", setLocationFunction, QScriptValue::PropertySetter); + scriptEngine->registerGlobalObject("Menu", MenuScriptingInterface::getInstance()); scriptEngine->registerGlobalObject("Settings", SettingsScriptingInterface::getInstance()); scriptEngine->registerGlobalObject("AudioDevice", AudioDeviceScriptingInterface::getInstance()); From ff51b22bfeb2a95da2a3f76e464ed128f00c8136 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 30 Apr 2014 09:39:48 -0700 Subject: [PATCH 12/38] Add Menu::goToUrl --- interface/src/Menu.cpp | 70 ++++++++++++++++++++++-------------------- interface/src/Menu.h | 1 + 2 files changed, 38 insertions(+), 33 deletions(-) diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 44117df55c..ca969832b3 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -927,44 +927,48 @@ void Menu::goTo() { int dialogReturn = gotoDialog.exec(); if (dialogReturn == QDialog::Accepted && !gotoDialog.textValue().isEmpty()) { QString desiredDestination = gotoDialog.textValue(); - - if (desiredDestination.startsWith(CUSTOM_URL_SCHEME + "//")) { - QStringList urlParts = desiredDestination.remove(0, CUSTOM_URL_SCHEME.length() + 2).split('/', QString::SkipEmptyParts); - - if (urlParts.count() > 1) { - // if url has 2 or more parts, the first one is domain name - QString domain = urlParts[0]; - - // second part is either a destination coordinate or - // a place name - QString destination = urlParts[1]; - - // any third part is an avatar orientation. - QString orientation = urlParts.count() > 2 ? urlParts[2] : QString(); - - goToDomain(domain); - - // goto either @user, #place, or x-xx,y-yy,z-zz - // style co-ordinate. - goTo(destination); - - if (!orientation.isEmpty()) { - // location orientation - goToOrientation(orientation); - } - } else if (urlParts.count() == 1) { - // location coordinates or place name - QString destination = urlParts[0]; - goTo(destination); - } - - } else { - goToUser(gotoDialog.textValue()); + if (!goToURL(desiredDestination)) {; + goTo(desiredDestination); } } sendFakeEnterEvent(); } +bool Menu::goToURL(QString location) { + if (location.startsWith(CUSTOM_URL_SCHEME + "//")) { + QStringList urlParts = location.remove(0, CUSTOM_URL_SCHEME.length() + 2).split('/', QString::SkipEmptyParts); + + if (urlParts.count() > 1) { + // if url has 2 or more parts, the first one is domain name + QString domain = urlParts[0]; + + // second part is either a destination coordinate or + // a place name + QString destination = urlParts[1]; + + // any third part is an avatar orientation. + QString orientation = urlParts.count() > 2 ? urlParts[2] : QString(); + + goToDomain(domain); + + // goto either @user, #place, or x-xx,y-yy,z-zz + // style co-ordinate. + goTo(destination); + + if (!orientation.isEmpty()) { + // location orientation + goToOrientation(orientation); + } + } else if (urlParts.count() == 1) { + // location coordinates or place name + QString destination = urlParts[0]; + goTo(destination); + } + return true; + } + return false; +} + void Menu::goToUser(const QString& user) { LocationManager* manager = &LocationManager::getInstance(); manager->goTo(user); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index bc70f8f83f..c2f4937dc2 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -152,6 +152,7 @@ public slots: void importSettings(); void exportSettings(); void goTo(); + bool goToURL(QString location); void goToUser(const QString& user); void pasteToVoxel(); void openUrl(const QUrl& url); From 0477706cb91582354d317058ded8f5dea193002f Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 30 Apr 2014 09:40:32 -0700 Subject: [PATCH 13/38] Add ScriptEngine::getEngine() to get internal QScriptEngine --- libraries/script-engine/src/ScriptEngine.cpp | 1 + libraries/script-engine/src/ScriptEngine.h | 3 +++ 2 files changed, 4 insertions(+) diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index ed05658538..aae9e85294 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index 9ea99276d3..fec885ac1d 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -69,6 +69,8 @@ public: bool isListeningToAudioStream() const { return _isListeningToAudioStream; } void setIsListeningToAudioStream(bool isListeningToAudioStream) { _isListeningToAudioStream = isListeningToAudioStream; } + QScriptEngine& getEngine() { return _engine; } + void setAvatarSound(Sound* avatarSound) { _avatarSound = avatarSound; } bool isPlayingAvatarSound() const { return _avatarSound != NULL; } @@ -136,6 +138,7 @@ private: Vec3 _vec3Library; ScriptUUID _uuidLibrary; AnimationCache _animationCache; + }; #endif // hifi_ScriptEngine_h From 9a44b2b2fbb0f156b497ceb052334652413557ce Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 30 Apr 2014 09:40:50 -0700 Subject: [PATCH 14/38] Remove unneeded include in WindowScriptingInterface --- interface/src/scripting/WindowScriptingInterface.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/interface/src/scripting/WindowScriptingInterface.h b/interface/src/scripting/WindowScriptingInterface.h index a97733eaf4..f6a91cd597 100644 --- a/interface/src/scripting/WindowScriptingInterface.h +++ b/interface/src/scripting/WindowScriptingInterface.h @@ -17,8 +17,6 @@ #include #include -#include "LocationScriptingInterface.h" - class WindowScriptingInterface : public QObject { Q_OBJECT Q_PROPERTY(int innerWidth READ getInnerWidth) From 45beeaa8d5a854a3b2efde293f8d2ede5da2610f Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 30 Apr 2014 09:45:53 -0700 Subject: [PATCH 15/38] Add dialog + location + window JS examples --- examples/dialogExample.js | 7 +++++++ examples/locationExample.js | 11 +++++++++++ examples/windowExample.js | 12 ++++++++++++ 3 files changed, 30 insertions(+) create mode 100644 examples/dialogExample.js create mode 100644 examples/locationExample.js create mode 100644 examples/windowExample.js diff --git a/examples/dialogExample.js b/examples/dialogExample.js new file mode 100644 index 0000000000..144b681950 --- /dev/null +++ b/examples/dialogExample.js @@ -0,0 +1,7 @@ +Window.alert("This is an alert box"); + +var confirmed = Window.confirm("This is a confirmation dialog") +Window.alert("Your response was: " + confirmed); + +var prompt = Window.prompt("This is a prompt dialog", "This is the default text"); +Window.alert("Your response was: " + prompt); diff --git a/examples/locationExample.js b/examples/locationExample.js new file mode 100644 index 0000000000..7530a3e3b6 --- /dev/null +++ b/examples/locationExample.js @@ -0,0 +1,11 @@ +var goto = Window.prompt("Where would you like to go? (ex. @username, #location, 1.0,500.3,100.2)"); +var url = "hifi://" + goto; +print("Going to: " + url); +location = url; + +// If the destination location is a user or named location the new location may not be set by the time execution reaches here +// because it requires an asynchronous lookup. Coordinate changes should be be reflected immediately, though. (ex: hifi://0,0,0) +print("URL: " + location.href); +print("Protocol: " + location.protocol); +print("Hostname: " + location.hostname); +print("Pathname: " + location.pathname); diff --git a/examples/windowExample.js b/examples/windowExample.js new file mode 100644 index 0000000000..afde83591d --- /dev/null +++ b/examples/windowExample.js @@ -0,0 +1,12 @@ +var width = 0, + height = 0; + +function onUpdate(dt) { + if (width != Window.innerWidth || height != Window.innerHeight) { + width = Window.innerWidth; + height = Window.innerHeight; + print("New window dimensions: " + width + ", " + height); + } +} + +Script.update.connect(onUpdate); From 0182a6d8954d390773f1cbba6a53a41b95ec7d94 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Wed, 30 Apr 2014 11:12:49 -0700 Subject: [PATCH 16/38] 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 9e684a0bc88a8998e5632e18ca84f33ff3b2d638 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 30 Apr 2014 11:17:36 -0700 Subject: [PATCH 17/38] Add registerGetterSetter to remove need for getEngine() --- interface/src/Application.cpp | 15 ++++++--------- libraries/script-engine/src/ScriptEngine.cpp | 18 +++++++++++++++++- libraries/script-engine/src/ScriptEngine.h | 6 +++--- 3 files changed, 26 insertions(+), 13 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 3e64fcf3ea..6f101c062c 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -3407,16 +3407,13 @@ ScriptEngine* Application::loadScript(const QString& scriptName, bool loadScript scriptEngine->registerGlobalObject("Overlays", &_overlays); - QScriptEngine &qScriptEngine = scriptEngine->getEngine(); - QScriptValue getLocationFunction = qScriptEngine.newFunction(LocationScriptingInterface::locationGetter); - QScriptValue setLocationFunction = qScriptEngine.newFunction(LocationScriptingInterface::locationSetter, 1); - qScriptEngine.globalObject().setProperty("location", getLocationFunction, QScriptValue::PropertyGetter); - qScriptEngine.globalObject().setProperty("location", setLocationFunction, QScriptValue::PropertySetter); + QScriptValue windowValue = scriptEngine->registerGlobalObject("Window", WindowScriptingInterface::getInstance()); + scriptEngine->registerGetterSetter("location", LocationScriptingInterface::locationGetter, + LocationScriptingInterface::locationSetter, windowValue); - QScriptValue windowValue = qScriptEngine.newQObject(WindowScriptingInterface::getInstance()); - qScriptEngine.globalObject().setProperty("Window", windowValue); - windowValue.setProperty("location", getLocationFunction, QScriptValue::PropertyGetter); - windowValue.setProperty("location", setLocationFunction, QScriptValue::PropertySetter); + // register `location` on the global object. + scriptEngine->registerGetterSetter("location", LocationScriptingInterface::locationGetter, + LocationScriptingInterface::locationSetter); scriptEngine->registerGlobalObject("Menu", MenuScriptingInterface::getInstance()); scriptEngine->registerGlobalObject("Settings", SettingsScriptingInterface::getInstance()); diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index a4629b6555..f2a3da14d0 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -248,10 +248,26 @@ void ScriptEngine::init() { _particlesScriptingInterface.getParticlePacketSender()->setProcessCallIntervalHint(SCRIPT_DATA_CALLBACK_USECS); } -void ScriptEngine::registerGlobalObject(const QString& name, QObject* object) { +QScriptValue ScriptEngine::registerGlobalObject(const QString& name, QObject* object) { if (object) { QScriptValue value = _engine.newQObject(object); _engine.globalObject().setProperty(name, value); + return value; + } + return QScriptValue::NullValue; +} + +void ScriptEngine::registerGetterSetter(const QString& name, QScriptEngine::FunctionSignature getter, + QScriptEngine::FunctionSignature setter, QScriptValue object) { + QScriptValue setterFunction = _engine.newFunction(setter, 1); + QScriptValue getterFunction = _engine.newFunction(getter); + + if (object.isNull()) { + object.setProperty(name, setterFunction, QScriptValue::PropertySetter); + object.setProperty(name, getterFunction, QScriptValue::PropertyGetter); + } else { + _engine.globalObject().setProperty(name, setterFunction, QScriptValue::PropertySetter); + _engine.globalObject().setProperty(name, getterFunction, QScriptValue::PropertyGetter); } } diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index fec885ac1d..61c3c559c5 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -58,7 +58,9 @@ public: const QString& getScriptName() const { return _scriptName; } void cleanupMenuItems(); - void registerGlobalObject(const QString& name, QObject* object); /// registers a global object by name + QScriptValue registerGlobalObject(const QString& name, QObject* object); /// registers a global object by name + void registerGetterSetter(const QString& name, QScriptEngine::FunctionSignature getter, + QScriptEngine::FunctionSignature setter, QScriptValue object = QScriptValue::NullValue); Q_INVOKABLE void setIsAvatar(bool isAvatar); bool isAvatar() const { return _isAvatar; } @@ -69,8 +71,6 @@ public: bool isListeningToAudioStream() const { return _isListeningToAudioStream; } void setIsListeningToAudioStream(bool isListeningToAudioStream) { _isListeningToAudioStream = isListeningToAudioStream; } - QScriptEngine& getEngine() { return _engine; } - void setAvatarSound(Sound* avatarSound) { _avatarSound = avatarSound; } bool isPlayingAvatarSound() const { return _avatarSound != NULL; } From 701fcd3542bec46bffa2511a75d48c00ba4d0b3b Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 30 Apr 2014 11:29:01 -0700 Subject: [PATCH 18/38] Fix invalid check for null object in registerGetterSetter --- libraries/script-engine/src/ScriptEngine.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index f2a3da14d0..82209b1f5b 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -262,7 +262,7 @@ void ScriptEngine::registerGetterSetter(const QString& name, QScriptEngine::Func QScriptValue setterFunction = _engine.newFunction(setter, 1); QScriptValue getterFunction = _engine.newFunction(getter); - if (object.isNull()) { + if (!object.isNull()) { object.setProperty(name, setterFunction, QScriptValue::PropertySetter); object.setProperty(name, getterFunction, QScriptValue::PropertyGetter); } else { From be3d307b273920724cd4d7aeaa939893262d6ca6 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Wed, 30 Apr 2014 13:48:09 -0700 Subject: [PATCH 19/38] 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 6c3e1c6aa51f89beaa4e2034fe9ee0e2857843b4 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 30 Apr 2014 13:54:41 -0700 Subject: [PATCH 20/38] Update LoginDialog to be type Qt::WindowModality Currently the login window will cover other windows, in particular the "welcome" window that pops up for new users. This allows those windows to be interacted with while still blocking interaction with the main window. --- interface/src/ui/LoginDialog.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/interface/src/ui/LoginDialog.cpp b/interface/src/ui/LoginDialog.cpp index 993f355c47..40029b6785 100644 --- a/interface/src/ui/LoginDialog.cpp +++ b/interface/src/ui/LoginDialog.cpp @@ -36,6 +36,7 @@ LoginDialog::LoginDialog(QWidget* parent) : _ui->errorLabel->setVisible(false); setModal(true); + setWindowModality(Qt::WindowModal); setHideOnBlur(false); connect(&AccountManager::getInstance(), &AccountManager::loginComplete, From f53df4c59785a6eb9cf3205821a3e5aaf73162cc Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Wed, 30 Apr 2014 14:18:35 -0700 Subject: [PATCH 21/38] 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 22/38] 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 cf1baaf6eabaaf314011deea17f24db8ed1ae88c Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Wed, 30 Apr 2014 23:32:41 +0200 Subject: [PATCH 23/38] - Highlighting Fixes ( Single line comments in quoted text don't occur anymore. / Better number recognition) - Line numbers in ScriptEditor --- interface/src/ScriptHighlighting.cpp | 19 +++- interface/src/ScriptHighlighting.h | 1 + interface/src/ui/ScriptEditBox.cpp | 107 ++++++++++++++++++++++ interface/src/ui/ScriptEditBox.h | 38 ++++++++ interface/src/ui/ScriptEditorWidget.h | 1 - interface/src/ui/ScriptLineNumberArea.cpp | 28 ++++++ interface/src/ui/ScriptLineNumberArea.h | 31 +++++++ interface/ui/scriptEditorWidget.ui | 23 ++++- 8 files changed, 240 insertions(+), 8 deletions(-) create mode 100644 interface/src/ui/ScriptEditBox.cpp create mode 100644 interface/src/ui/ScriptEditBox.h create mode 100644 interface/src/ui/ScriptLineNumberArea.cpp create mode 100644 interface/src/ui/ScriptLineNumberArea.h diff --git a/interface/src/ScriptHighlighting.cpp b/interface/src/ScriptHighlighting.cpp index 3ab1394097..3d63ba55a8 100644 --- a/interface/src/ScriptHighlighting.cpp +++ b/interface/src/ScriptHighlighting.cpp @@ -22,6 +22,7 @@ ScriptHighlighting::ScriptHighlighting(QTextDocument* parent) : _numberRegex = QRegExp("[0-9]+(\\.[0-9]+){0,1}"); _singleLineComment = QRegExp("//[^\n]*"); _truefalseRegex = QRegExp("\\b(true|false)\\b"); + _alphacharRegex = QRegExp("[A-Za-z]"); } void ScriptHighlighting::highlightBlock(const QString& text) { @@ -60,7 +61,19 @@ void ScriptHighlighting::formatComments(const QString& text) { int index = _singleLineComment.indexIn(text); while (index >= 0) { int length = _singleLineComment.matchedLength(); - setFormat(index, length, Qt::lightGray); + int quoted_index = _qoutedTextRegex.indexIn(text); + bool valid = true; + while (quoted_index >= 0 && valid) { + int quoted_length = _qoutedTextRegex.matchedLength(); + if (quoted_index <= index && index <= (quoted_index + quoted_length)) { + valid = false; + } + quoted_index = _qoutedTextRegex.indexIn(text, quoted_index + quoted_length); + } + + if (valid) { + setFormat(index, length, Qt::lightGray); + } index = _singleLineComment.indexIn(text, index + length); } } @@ -78,7 +91,9 @@ void ScriptHighlighting::formatNumbers(const QString& text){ int index = _numberRegex.indexIn(text); while (index >= 0) { int length = _numberRegex.matchedLength(); - setFormat(index, length, Qt::green); + if (index == 0 || _alphacharRegex.indexIn(text, index - 1) != (index - 1)) { + setFormat(index, length, Qt::green); + } index = _numberRegex.indexIn(text, index + length); } } diff --git a/interface/src/ScriptHighlighting.h b/interface/src/ScriptHighlighting.h index d86d6f4d77..232d594308 100644 --- a/interface/src/ScriptHighlighting.h +++ b/interface/src/ScriptHighlighting.h @@ -34,6 +34,7 @@ protected: void formatTrueFalse(const QString& text); private: + QRegExp _alphacharRegex; QRegExp _keywordRegex; QRegExp _qoutedTextRegex; QRegExp _multiLineCommentBegin; diff --git a/interface/src/ui/ScriptEditBox.cpp b/interface/src/ui/ScriptEditBox.cpp new file mode 100644 index 0000000000..4c2b6564ba --- /dev/null +++ b/interface/src/ui/ScriptEditBox.cpp @@ -0,0 +1,107 @@ +// +// ScriptEditBox.cpp +// interface/src/ui +// +// Created by Thijs Wenker on 4/30/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 "ScriptEditBox.h" +#include "ScriptLineNumberArea.h" +#include "Application.h" + +ScriptEditBox::ScriptEditBox(QWidget* parent) : + QPlainTextEdit(parent) +{ + _scriptLineNumberArea = new ScriptLineNumberArea(this); + + connect(this, SIGNAL(blockCountChanged(int)), this, SLOT(updateLineNumberAreaWidth(int))); + connect(this, SIGNAL(updateRequest(QRect, int)), this, SLOT(updateLineNumberArea(QRect, int))); + connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(highlightCurrentLine())); + + updateLineNumberAreaWidth(0); + highlightCurrentLine(); +} + +int ScriptEditBox::lineNumberAreaWidth() { + int digits = 1; + const int SPACER_PIXELS = 3; + const int BASE_TEN = 10; + int max = qMax(1, blockCount()); + while (max >= BASE_TEN) { + max /= BASE_TEN; + ++digits; + } + return SPACER_PIXELS + fontMetrics().width(QLatin1Char('H')) * digits; +} + +void ScriptEditBox::updateLineNumberAreaWidth(int) { + setViewportMargins(lineNumberAreaWidth(), 0, 0, 0); +} + +void ScriptEditBox::updateLineNumberArea(const QRect& rect, int dy) { + if (dy) { + _scriptLineNumberArea->scroll(0, dy); + } else { + _scriptLineNumberArea->update(0, rect.y(), _scriptLineNumberArea->width(), rect.height()); + } + + if (rect.contains(viewport()->rect())) { + updateLineNumberAreaWidth(0); + } +} + +void ScriptEditBox::resizeEvent(QResizeEvent* e) { + QPlainTextEdit::resizeEvent(e); + + QRect cr = contentsRect(); + _scriptLineNumberArea->setGeometry(QRect(cr.left(), cr.top(), lineNumberAreaWidth(), cr.height())); +} + +void ScriptEditBox::highlightCurrentLine() { + QList extraSelections; + + if (!isReadOnly()) { + QTextEdit::ExtraSelection selection; + + QColor lineColor = QColor(Qt::gray).lighter(); + + selection.format.setBackground(lineColor); + selection.format.setProperty(QTextFormat::FullWidthSelection, true); + selection.cursor = textCursor(); + selection.cursor.clearSelection(); + extraSelections.append(selection); + } + + setExtraSelections(extraSelections); +} + +void ScriptEditBox::lineNumberAreaPaintEvent(QPaintEvent* event) +{ + QPainter painter(_scriptLineNumberArea); + painter.fillRect(event->rect(), Qt::lightGray); + QTextBlock block = firstVisibleBlock(); + int blockNumber = block.blockNumber(); + int top = (int) blockBoundingGeometry(block).translated(contentOffset()).top(); + int bottom = top + (int) blockBoundingRect(block).height(); + + while (block.isValid() && top <= event->rect().bottom()) { + if (block.isVisible() && bottom >= event->rect().top()) { + QFont font = painter.font(); + font.setBold(this->textCursor().blockNumber() == block.blockNumber()); + painter.setFont(font); + QString number = QString::number(blockNumber + 1); + painter.setPen(Qt::black); + painter.drawText(0, top, _scriptLineNumberArea->width(), fontMetrics().height(), + Qt::AlignRight, number); + } + + block = block.next(); + top = bottom; + bottom = top + (int) blockBoundingRect(block).height(); + ++blockNumber; + } +} diff --git a/interface/src/ui/ScriptEditBox.h b/interface/src/ui/ScriptEditBox.h new file mode 100644 index 0000000000..ea00ec02b6 --- /dev/null +++ b/interface/src/ui/ScriptEditBox.h @@ -0,0 +1,38 @@ +// +// ScriptEditBox.h +// interface/src/ui +// +// Created by Thijs Wenker on 4/30/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_ScriptEditBox_h +#define hifi_ScriptEditBox_h + +#include + +class ScriptEditBox : public QPlainTextEdit { + Q_OBJECT + +public: + ScriptEditBox(QWidget* parent = NULL); + + void lineNumberAreaPaintEvent(QPaintEvent* event); + int lineNumberAreaWidth(); + +protected: + void resizeEvent(QResizeEvent* event); + +private slots: + void updateLineNumberAreaWidth(int); + void highlightCurrentLine(); + void updateLineNumberArea(const QRect&, int); + +private: + QWidget* _scriptLineNumberArea; +}; + +#endif // hifi_ScriptEditBox_h diff --git a/interface/src/ui/ScriptEditorWidget.h b/interface/src/ui/ScriptEditorWidget.h index 3e50280a62..1a96661cf7 100644 --- a/interface/src/ui/ScriptEditorWidget.h +++ b/interface/src/ui/ScriptEditorWidget.h @@ -13,7 +13,6 @@ #define hifi_ScriptEditorWidget_h #include -#include "ScriptEditorWidget.h" #include "ScriptEngine.h" namespace Ui { diff --git a/interface/src/ui/ScriptLineNumberArea.cpp b/interface/src/ui/ScriptLineNumberArea.cpp new file mode 100644 index 0000000000..9173c72375 --- /dev/null +++ b/interface/src/ui/ScriptLineNumberArea.cpp @@ -0,0 +1,28 @@ +// +// ScriptLineNumberArea.cpp +// interface/src/ui +// +// Created by Thijs Wenker on 4/30/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 "ScriptLineNumberArea.h" + +#include "Application.h" + +ScriptLineNumberArea::ScriptLineNumberArea(ScriptEditBox* scriptEditBox) : + QWidget(scriptEditBox) +{ + _scriptEditBox = scriptEditBox; +} + +QSize ScriptLineNumberArea::sizeHint() { + return QSize(_scriptEditBox->lineNumberAreaWidth(), 0); +} + +void ScriptLineNumberArea::paintEvent(QPaintEvent* event) { + _scriptEditBox->lineNumberAreaPaintEvent(event); +} diff --git a/interface/src/ui/ScriptLineNumberArea.h b/interface/src/ui/ScriptLineNumberArea.h new file mode 100644 index 0000000000..75be2048f0 --- /dev/null +++ b/interface/src/ui/ScriptLineNumberArea.h @@ -0,0 +1,31 @@ +// +// ScriptLineNumberArea.h +// interface/src/ui +// +// Created by Thijs Wenker on 4/30/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_ScriptLineNumberArea_h +#define hifi_ScriptLineNumberArea_h + +#include +#include "ScriptEditBox.h" + +class ScriptLineNumberArea : public QWidget { + +public: + ScriptLineNumberArea(ScriptEditBox* scriptEditBox); + QSize sizeHint(); + +protected: + void paintEvent(QPaintEvent* event); + +private: + ScriptEditBox* _scriptEditBox; +}; + +#endif // hifi_ScriptLineNumberArea_h diff --git a/interface/ui/scriptEditorWidget.ui b/interface/ui/scriptEditorWidget.ui index 363f99b635..8aeeff363f 100644 --- a/interface/ui/scriptEditorWidget.ui +++ b/interface/ui/scriptEditorWidget.ui @@ -39,11 +39,20 @@ 0 - + + 0 + + + 0 + + + 0 + + 0 - + Courier @@ -56,9 +65,6 @@ font: 16px "Courier"; - - false - @@ -144,6 +150,13 @@ + + + ScriptEditBox + QTextEdit +
ui/ScriptEditBox.h
+
+
From e886090e766848af6845f7fc960c8a692b78ef20 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Wed, 30 Apr 2014 14:40:49 -0700 Subject: [PATCH 24/38] 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 3a64ce733a540e4dd98824c68b19be414be380cc Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Wed, 30 Apr 2014 16:55:51 -0700 Subject: [PATCH 25/38] Fixed warning --- interface/src/avatar/Avatar.h | 2 +- interface/src/avatar/MyAvatar.cpp | 2 +- interface/src/avatar/MyAvatar.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h index df1340b79a..4263e606a5 100755 --- a/interface/src/avatar/Avatar.h +++ b/interface/src/avatar/Avatar.h @@ -185,7 +185,7 @@ protected: float getPelvisToHeadLength() const; void renderDisplayName(); - virtual void renderBody(RenderMode renderMode, float glowLevel); + virtual void renderBody(RenderMode renderMode, float glowLevel = 0.0f); virtual bool shouldRenderHead(const glm::vec3& cameraPosition, RenderMode renderMode) const; virtual void updateJointMappings(); diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 40e350dcb7..8e0eb6f78f 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -618,7 +618,7 @@ void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) { _billboardValid = false; } -void MyAvatar::renderBody(RenderMode renderMode) { +void MyAvatar::renderBody(RenderMode renderMode, float glowLevel) { if (!(_skeletonModel.isRenderable() && getHead()->getFaceModel().isRenderable())) { return; // wait until both models are loaded } diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index a5312b0016..28ac2141bb 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -42,7 +42,7 @@ public: void moveWithLean(); void render(const glm::vec3& cameraPosition, RenderMode renderMode = NORMAL_RENDER_MODE); - void renderBody(RenderMode renderMode); + void renderBody(RenderMode renderMode, float glowLevel = 0.0f); bool shouldRenderHead(const glm::vec3& cameraPosition, RenderMode renderMode) const; void renderDebugBodyPoints(); void renderHeadMouse() const; From 85484affa9fd56e7643255650a239521dd1f9a52 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Wed, 30 Apr 2014 17:55:54 -0700 Subject: [PATCH 26/38] 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 8e63352d54ed534fb87ba8b9d931836a3e3f7486 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 30 Apr 2014 19:00:06 -0700 Subject: [PATCH 27/38] Fix Window.confirm() not working --- interface/src/scripting/WindowScriptingInterface.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/interface/src/scripting/WindowScriptingInterface.cpp b/interface/src/scripting/WindowScriptingInterface.cpp index 6272b689d9..3366e28f6e 100644 --- a/interface/src/scripting/WindowScriptingInterface.cpp +++ b/interface/src/scripting/WindowScriptingInterface.cpp @@ -29,10 +29,10 @@ QScriptValue WindowScriptingInterface::alert(const QString& message) { } QScriptValue WindowScriptingInterface::confirm(const QString& message) { - bool retVal; + QScriptValue retVal; QMetaObject::invokeMethod(this, "showConfirm", Qt::BlockingQueuedConnection, - Q_RETURN_ARG(bool, retVal), Q_ARG(const QString&, message)); - return QScriptValue(retVal); + Q_RETURN_ARG(QScriptValue, retVal), Q_ARG(const QString&, message)); + return retVal; } QScriptValue WindowScriptingInterface::prompt(const QString& message, const QString& defaultText) { From a7dd19183e0d612516fd031b2dc798ffd998c09f Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 30 Apr 2014 19:02:28 -0700 Subject: [PATCH 28/38] Add default values for alert, confirm, and prompt --- interface/src/scripting/WindowScriptingInterface.h | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/interface/src/scripting/WindowScriptingInterface.h b/interface/src/scripting/WindowScriptingInterface.h index f6a91cd597..af9b15a96d 100644 --- a/interface/src/scripting/WindowScriptingInterface.h +++ b/interface/src/scripting/WindowScriptingInterface.h @@ -12,9 +12,8 @@ #ifndef hifi_WindowScriptingInterface_h #define hifi_WindowScriptingInterface_h -#include -#include #include +#include #include class WindowScriptingInterface : public QObject { @@ -28,9 +27,9 @@ public: int getInnerHeight(); public slots: - QScriptValue alert(const QString& message); - QScriptValue confirm(const QString& message); - QScriptValue prompt(const QString& message, const QString& defaultText=""); + QScriptValue alert(const QString& message = ""); + QScriptValue confirm(const QString& message = ""); + QScriptValue prompt(const QString& message = "", const QString& defaultText = ""); private slots: QScriptValue showAlert(const QString& message); From 33ca387c8678e44874b4665594300b73fa7cc21c Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 30 Apr 2014 21:12:04 -0700 Subject: [PATCH 29/38] Update goToURL to work with domains and user/location If the path starts with # or @ it's a location or user, respectively, otherwise it is a domain. --- interface/src/Menu.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 5c396f169c..7ab2639065 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -931,9 +931,14 @@ bool Menu::goToURL(QString location) { goToOrientation(orientation); } } else if (urlParts.count() == 1) { - // location coordinates or place name QString destination = urlParts[0]; - goTo(destination); + + // If this starts with # or @, treat it as a user/location, otherwise treat it as a domain + if (destination[0] == '#' || destination[0] == '@') { + goTo(destination); + } else { + goToDomain(destination); + } } return true; } From 223d9cf32e47a5fc2996fa754ea5ff3eb8a5f295 Mon Sep 17 00:00:00 2001 From: Philip Rosedale Date: Wed, 30 Apr 2014 21:29:50 -0700 Subject: [PATCH 30/38] =?UTF-8?q?Added=20back=20ability=20to=20see/debug?= =?UTF-8?q?=20head=20and=20eye=20gaze=20position=20with=20=E2=80=98Head=20?= =?UTF-8?q?Mouse=E2=80=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- interface/src/Application.cpp | 2 +- interface/src/avatar/MyAvatar.cpp | 67 +++++++++++++------------------ interface/src/avatar/MyAvatar.h | 4 +- 3 files changed, 31 insertions(+), 42 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 7ee4fdc90d..ba73ffd63a 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2704,7 +2704,7 @@ void Application::displayOverlay() { if (Menu::getInstance()->isOptionChecked(MenuOption::HeadMouse)) { - _myAvatar->renderHeadMouse(); + _myAvatar->renderHeadMouse(_glWidget->width(), _glWidget->height()); } // Display stats and log text onscreen diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 40e350dcb7..c2d6d331be 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -82,11 +82,8 @@ MyAvatar::~MyAvatar() { } void MyAvatar::reset() { - // TODO? resurrect headMouse stuff? - //_headMouseX = _glWidget->width() / 2; - //_headMouseY = _glWidget->height() / 2; _skeletonModel.reset(); - getHead()->reset(); + getHead()->reset(); getHand()->reset(); _oculusYawOffset = 0.0f; @@ -103,23 +100,7 @@ void MyAvatar::update(float deltaTime) { // Faceshift drive is enabled, set the avatar drive based on the head position moveWithLean(); } - - // Update head mouse from faceshift if active - Faceshift* faceshift = Application::getInstance()->getFaceshift(); - if (faceshift->isActive()) { - // TODO? resurrect headMouse stuff? - //glm::vec3 headVelocity = faceshift->getHeadAngularVelocity(); - //// sets how quickly head angular rotation moves the head mouse - //const float HEADMOUSE_FACESHIFT_YAW_SCALE = 40.0f; - //const float HEADMOUSE_FACESHIFT_PITCH_SCALE = 30.0f; - //_headMouseX -= headVelocity.y * HEADMOUSE_FACESHIFT_YAW_SCALE; - //_headMouseY -= headVelocity.x * HEADMOUSE_FACESHIFT_PITCH_SCALE; - // - //// Constrain head-driven mouse to edges of screen - //_headMouseX = glm::clamp(_headMouseX, 0, _glWidget->width()); - //_headMouseY = glm::clamp(_headMouseY, 0, _glWidget->height()); - } - + // Get audio loudness data from audio input device Audio* audio = Application::getInstance()->getAudio(); head->setAudioLoudness(audio->getLastInputLoudness()); @@ -422,32 +403,41 @@ void MyAvatar::render(const glm::vec3& cameraPosition, RenderMode renderMode) { } } -void MyAvatar::renderHeadMouse() const { - // TODO? resurrect headMouse stuff? - /* +void MyAvatar::renderHeadMouse(int screenWidth, int screenHeight) const { + + Faceshift* faceshift = Application::getInstance()->getFaceshift(); + // Display small target box at center or head mouse target that can also be used to measure LOD + float headPitch = getHead()->getFinalPitch(); + float headYaw = getHead()->getFinalYaw(); + // + // It should be noted that the following constant is a function + // how far the viewer's head is away from both the screen and the size of the screen, + // which are both things we cannot know without adding a calibration phase. + // + const float PIXELS_PER_VERTICAL_DEGREE = 20.0f; + float aspectRatio = (float) screenWidth / (float) screenHeight; + int headMouseX = screenWidth / 2.f - headYaw * aspectRatio * PIXELS_PER_VERTICAL_DEGREE; + int headMouseY = screenHeight / 2.f - headPitch * PIXELS_PER_VERTICAL_DEGREE; + glColor3f(1.0f, 1.0f, 1.0f); glDisable(GL_LINE_SMOOTH); const int PIXEL_BOX = 16; glBegin(GL_LINES); - glVertex2f(_headMouseX - PIXEL_BOX/2, _headMouseY); - glVertex2f(_headMouseX + PIXEL_BOX/2, _headMouseY); - glVertex2f(_headMouseX, _headMouseY - PIXEL_BOX/2); - glVertex2f(_headMouseX, _headMouseY + PIXEL_BOX/2); + glVertex2f(headMouseX - PIXEL_BOX/2, headMouseY); + glVertex2f(headMouseX + PIXEL_BOX/2, headMouseY); + glVertex2f(headMouseX, headMouseY - PIXEL_BOX/2); + glVertex2f(headMouseX, headMouseY + PIXEL_BOX/2); glEnd(); glEnable(GL_LINE_SMOOTH); - glColor3f(1.0f, 0.0f, 0.0f); - glPointSize(3.0f); - glDisable(GL_POINT_SMOOTH); - glBegin(GL_POINTS); - glVertex2f(_headMouseX - 1, _headMouseY + 1); - glEnd(); // If Faceshift is active, show eye pitch and yaw as separate pointer - if (_faceshift.isActive()) { - const float EYE_TARGET_PIXELS_PER_DEGREE = 40.0; - int eyeTargetX = (_glWidget->width() / 2) - _faceshift.getEstimatedEyeYaw() * EYE_TARGET_PIXELS_PER_DEGREE; - int eyeTargetY = (_glWidget->height() / 2) - _faceshift.getEstimatedEyePitch() * EYE_TARGET_PIXELS_PER_DEGREE; + if (faceshift->isActive()) { + float avgEyePitch = faceshift->getEstimatedEyePitch(); + float avgEyeYaw = faceshift->getEstimatedEyeYaw(); + int eyeTargetX = (screenWidth / 2) - avgEyeYaw * aspectRatio * PIXELS_PER_VERTICAL_DEGREE; + int eyeTargetY = (screenHeight / 2) - avgEyePitch * PIXELS_PER_VERTICAL_DEGREE; + glColor3f(0.0f, 1.0f, 1.0f); glDisable(GL_LINE_SMOOTH); glBegin(GL_LINES); @@ -458,7 +448,6 @@ void MyAvatar::renderHeadMouse() const { glEnd(); } - */ } void MyAvatar::setLocalGravity(glm::vec3 gravity) { diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index a5312b0016..0f0fdbf6cd 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -45,7 +45,7 @@ public: void renderBody(RenderMode renderMode); bool shouldRenderHead(const glm::vec3& cameraPosition, RenderMode renderMode) const; void renderDebugBodyPoints(); - void renderHeadMouse() const; + void renderHeadMouse(int screenWidth, int screenHeight) const; // setters void setMousePressed(bool mousePressed) { _mousePressed = mousePressed; } @@ -123,7 +123,7 @@ private: glm::vec3 _gravity; glm::vec3 _environmentGravity; float _distanceToNearestAvatar; // How close is the nearest avatar? - + // motion stuff glm::vec3 _lastCollisionPosition; bool _speedBrakes; From bb6444f5a949e5729ae5f6f0f93a1c4e36382c18 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Thu, 1 May 2014 07:30:56 -0700 Subject: [PATCH 31/38] 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 32/38] 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 5a5e6285361462a2fb85193d3eae75763049fd92 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Thu, 1 May 2014 10:51:58 -0700 Subject: [PATCH 33/38] Add Window.browse to scripting environment --- .../scripting/WindowScriptingInterface.cpp | 36 ++++++++++++++++++- .../src/scripting/WindowScriptingInterface.h | 2 ++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/interface/src/scripting/WindowScriptingInterface.cpp b/interface/src/scripting/WindowScriptingInterface.cpp index 3366e28f6e..144415d6ee 100644 --- a/interface/src/scripting/WindowScriptingInterface.cpp +++ b/interface/src/scripting/WindowScriptingInterface.cpp @@ -9,8 +9,10 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include +#include +#include #include +#include #include "Application.h" #include "Menu.h" @@ -43,6 +45,14 @@ QScriptValue WindowScriptingInterface::prompt(const QString& message, const QStr return retVal; } +QScriptValue WindowScriptingInterface::browse(const QString& title, const QString& directory, const QString& nameFilter) { + QScriptValue retVal; + QMetaObject::invokeMethod(this, "showBrowse", Qt::BlockingQueuedConnection, + Q_RETURN_ARG(QScriptValue, retVal), + Q_ARG(const QString&, title), Q_ARG(const QString&, directory), Q_ARG(const QString&, nameFilter)); + return retVal; +} + /// Display an alert box /// \param const QString& message message to display /// \return QScriptValue::UndefinedValue @@ -76,6 +86,30 @@ QScriptValue WindowScriptingInterface::showPrompt(const QString& message, const return QScriptValue::NullValue; } +/// Display a file dialog. If `directory` is an invalid file or directory the browser will start at the current +/// working directory. +/// \param const QString& title title of the window +/// \param const QString& directory directory to start the file browser at +/// \param const QString& nameFilter filter to filter filenames by - see `QFileDialog` +/// \return QScriptValue file path as a string if one was selected, otherwise `QScriptValue::NullValue` +QScriptValue WindowScriptingInterface::showBrowse(const QString& title, const QString& directory, const QString& nameFilter) { + // On OS X `directory` does not work as expected unless a file is included in the path, so we append a bogus + // filename if the directory is valid. + QString path = ""; + QFileInfo fileInfo = QFileInfo(directory); + if (fileInfo.isDir()) { + fileInfo.setFile(directory, "__HIFI_INVALID_FILE__"); + path = fileInfo.filePath(); + } + + QFileDialog fileDialog(Application::getInstance()->getWindow(), title, path, nameFilter); + fileDialog.setFileMode(QFileDialog::ExistingFile); + if (fileDialog.exec()) { + return QScriptValue(fileDialog.selectedFiles().first()); + } + return QScriptValue::NullValue; +} + int WindowScriptingInterface::getInnerWidth() { return Application::getInstance()->getWindow()->geometry().width(); } diff --git a/interface/src/scripting/WindowScriptingInterface.h b/interface/src/scripting/WindowScriptingInterface.h index af9b15a96d..b97113af9c 100644 --- a/interface/src/scripting/WindowScriptingInterface.h +++ b/interface/src/scripting/WindowScriptingInterface.h @@ -30,11 +30,13 @@ public slots: QScriptValue alert(const QString& message = ""); QScriptValue confirm(const QString& message = ""); QScriptValue prompt(const QString& message = "", const QString& defaultText = ""); + QScriptValue browse(const QString& title = "", const QString& directory = "", const QString& nameFilter = ""); private slots: QScriptValue showAlert(const QString& message); QScriptValue showConfirm(const QString& message); QScriptValue showPrompt(const QString& message, const QString& defaultText); + QScriptValue showBrowse(const QString& title, const QString& directory, const QString& nameFilter); }; #endif // hifi_WindowScriptingInterface_h From eccedda2843e79306a2ab35d607e9580120b64b6 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Thu, 1 May 2014 10:56:23 -0700 Subject: [PATCH 34/38] Add fileBrowserExample.js --- examples/fileBrowserExample.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 examples/fileBrowserExample.js diff --git a/examples/fileBrowserExample.js b/examples/fileBrowserExample.js new file mode 100644 index 0000000000..e9b461ab22 --- /dev/null +++ b/examples/fileBrowserExample.js @@ -0,0 +1,13 @@ +var file = Window.browse("File Browser Example", "/"); +if (file === null) { + Window.alert("No file was selected"); +} else { + Window.alert("Selected file: " + file); +} + +file = Window.browse("Relative Directory Example", "./images", "PNG or JPG files(*.png *.jpg);;SVG files (*.svg)"); +if (file === null) { + Window.alert("No file was selected"); +} else { + Window.alert("Selected file: " + file); +} From f7da070d774cbda796536f78868da8d88e0f0ac4 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Thu, 1 May 2014 11:29:17 -0700 Subject: [PATCH 35/38] 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 966774815643251d941f7f179eb7eecba19a5a5f Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Thu, 1 May 2014 21:07:46 +0200 Subject: [PATCH 36/38] - Style fixes - ScriptHightlighting qouting RegEx fix ( "hello" + " " + "world" // the text between two quote entities isn't red anymore ) --- interface/src/Application.cpp | 2 +- interface/src/Application.h | 2 +- interface/src/ScriptHighlighting.cpp | 2 +- interface/src/ui/ScriptEditBox.cpp | 27 +++++++++--------- interface/src/ui/ScriptEditBox.h | 4 +-- interface/src/ui/ScriptEditorWidget.cpp | 37 +++++++++++++++---------- interface/src/ui/ScriptEditorWindow.cpp | 16 +++++------ 7 files changed, 50 insertions(+), 40 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index aa28d2768c..058408af66 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -3461,7 +3461,7 @@ QString Application::getPreviousScriptLocation() { return suggestedName; } -void Application::setPreviousScriptLocation(QString previousScriptLocation) { +void Application::setPreviousScriptLocation(const QString& previousScriptLocation) { _previousScriptLocation = previousScriptLocation; QMutexLocker locker(&_settingsMutex); _settings->setValue("LastScriptLocation", _previousScriptLocation); diff --git a/interface/src/Application.h b/interface/src/Application.h index fb15019c19..9b613f7ed7 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -126,7 +126,7 @@ public: ScriptEngine* loadScript(const QString& fileNameString, bool loadScriptFromEditor = false); void loadScripts(); QString getPreviousScriptLocation(); - void setPreviousScriptLocation(QString previousScriptLocation); + void setPreviousScriptLocation(const QString& previousScriptLocation); void storeSizeAndPosition(); void clearScriptsBeforeRunning(); void saveScripts(); diff --git a/interface/src/ScriptHighlighting.cpp b/interface/src/ScriptHighlighting.cpp index 3d63ba55a8..125ab1e967 100644 --- a/interface/src/ScriptHighlighting.cpp +++ b/interface/src/ScriptHighlighting.cpp @@ -16,7 +16,7 @@ ScriptHighlighting::ScriptHighlighting(QTextDocument* parent) : QSyntaxHighlighter(parent) { _keywordRegex = QRegExp("\\b(break|case|catch|continue|debugger|default|delete|do|else|finally|for|function|if|in|instanceof|new|return|switch|this|throw|try|typeof|var|void|while|with)\\b"); - _qoutedTextRegex = QRegExp("\".*\""); + _qoutedTextRegex = QRegExp("\"[^\"]*(\"){0,1}"); _multiLineCommentBegin = QRegExp("/\\*"); _multiLineCommentEnd = QRegExp("\\*/"); _numberRegex = QRegExp("[0-9]+(\\.[0-9]+){0,1}"); diff --git a/interface/src/ui/ScriptEditBox.cpp b/interface/src/ui/ScriptEditBox.cpp index 4c2b6564ba..acabaa3c8f 100644 --- a/interface/src/ui/ScriptEditBox.cpp +++ b/interface/src/ui/ScriptEditBox.cpp @@ -18,9 +18,9 @@ ScriptEditBox::ScriptEditBox(QWidget* parent) : { _scriptLineNumberArea = new ScriptLineNumberArea(this); - connect(this, SIGNAL(blockCountChanged(int)), this, SLOT(updateLineNumberAreaWidth(int))); - connect(this, SIGNAL(updateRequest(QRect, int)), this, SLOT(updateLineNumberArea(QRect, int))); - connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(highlightCurrentLine())); + connect(this, &ScriptEditBox::blockCountChanged, this, &ScriptEditBox::updateLineNumberAreaWidth); + connect(this, &ScriptEditBox::updateRequest, this, &ScriptEditBox::updateLineNumberArea); + connect(this, &ScriptEditBox::cursorPositionChanged, this, &ScriptEditBox::highlightCurrentLine); updateLineNumberAreaWidth(0); highlightCurrentLine(); @@ -33,18 +33,18 @@ int ScriptEditBox::lineNumberAreaWidth() { int max = qMax(1, blockCount()); while (max >= BASE_TEN) { max /= BASE_TEN; - ++digits; + digits++; } return SPACER_PIXELS + fontMetrics().width(QLatin1Char('H')) * digits; } -void ScriptEditBox::updateLineNumberAreaWidth(int) { +void ScriptEditBox::updateLineNumberAreaWidth(int blockCount) { setViewportMargins(lineNumberAreaWidth(), 0, 0, 0); } -void ScriptEditBox::updateLineNumberArea(const QRect& rect, int dy) { - if (dy) { - _scriptLineNumberArea->scroll(0, dy); +void ScriptEditBox::updateLineNumberArea(const QRect& rect, int deltaY) { + if (deltaY) { + _scriptLineNumberArea->scroll(0, deltaY); } else { _scriptLineNumberArea->update(0, rect.y(), _scriptLineNumberArea->width(), rect.height()); } @@ -54,11 +54,12 @@ void ScriptEditBox::updateLineNumberArea(const QRect& rect, int dy) { } } -void ScriptEditBox::resizeEvent(QResizeEvent* e) { - QPlainTextEdit::resizeEvent(e); +void ScriptEditBox::resizeEvent(QResizeEvent* event) { + QPlainTextEdit::resizeEvent(event); - QRect cr = contentsRect(); - _scriptLineNumberArea->setGeometry(QRect(cr.left(), cr.top(), lineNumberAreaWidth(), cr.height())); + QRect localContentsRect = contentsRect(); + _scriptLineNumberArea->setGeometry(QRect(localContentsRect.left(), localContentsRect.top(), lineNumberAreaWidth(), + localContentsRect.height())); } void ScriptEditBox::highlightCurrentLine() { @@ -102,6 +103,6 @@ void ScriptEditBox::lineNumberAreaPaintEvent(QPaintEvent* event) block = block.next(); top = bottom; bottom = top + (int) blockBoundingRect(block).height(); - ++blockNumber; + blockNumber++; } } diff --git a/interface/src/ui/ScriptEditBox.h b/interface/src/ui/ScriptEditBox.h index ea00ec02b6..41d881b904 100644 --- a/interface/src/ui/ScriptEditBox.h +++ b/interface/src/ui/ScriptEditBox.h @@ -27,9 +27,9 @@ protected: void resizeEvent(QResizeEvent* event); private slots: - void updateLineNumberAreaWidth(int); + void updateLineNumberAreaWidth(int blockCount); void highlightCurrentLine(); - void updateLineNumberArea(const QRect&, int); + void updateLineNumberArea(const QRect& rect, int deltaY); private: QWidget* _scriptLineNumberArea; diff --git a/interface/src/ui/ScriptEditorWidget.cpp b/interface/src/ui/ScriptEditorWidget.cpp index 33e014ac07..07c6e72226 100644 --- a/interface/src/ui/ScriptEditorWidget.cpp +++ b/interface/src/ui/ScriptEditorWidget.cpp @@ -32,8 +32,10 @@ ScriptEditorWidget::ScriptEditorWidget() : { _scriptEditorWidgetUI->setupUi(this); - connect(_scriptEditorWidgetUI->scriptEdit->document(), SIGNAL(modificationChanged(bool)), this, SIGNAL(scriptModified())); - connect(_scriptEditorWidgetUI->scriptEdit->document(), SIGNAL(contentsChanged()), this, SLOT(onScriptModified())); + connect(_scriptEditorWidgetUI->scriptEdit->document(), &QTextDocument::modificationChanged, this, + &ScriptEditorWidget::scriptModified); + connect(_scriptEditorWidgetUI->scriptEdit->document(), &QTextDocument::contentsChanged, this, + &ScriptEditorWidget::onScriptModified); // remove the title bar (see the Qt docs on setTitleBarWidget) setTitleBarWidget(new QWidget()); @@ -68,16 +70,19 @@ bool ScriptEditorWidget::setRunning(bool run) { return false; } // Clean-up old connections. - disconnect(this, SLOT(onScriptError(const QString&))); - disconnect(this, SLOT(onScriptPrint(const QString&))); + if (_scriptEngine != NULL) { + disconnect(_scriptEngine, &ScriptEngine::runningStateChanged, this, &ScriptEditorWidget::runningStateChanged); + disconnect(_scriptEngine, &ScriptEngine::errorMessage, this, &ScriptEditorWidget::onScriptError); + disconnect(_scriptEngine, &ScriptEngine::printedMessage, this, &ScriptEditorWidget::onScriptPrint); + } if (run) { _scriptEngine = Application::getInstance()->loadScript(_currentScript, true); - connect(_scriptEngine, SIGNAL(runningStateChanged()), this, SIGNAL(runningStateChanged())); + connect(_scriptEngine, &ScriptEngine::runningStateChanged, this, &ScriptEditorWidget::runningStateChanged); // Make new connections. - connect(_scriptEngine, SIGNAL(errorMessage(const QString&)), this, SLOT(onScriptError(const QString&))); - connect(_scriptEngine, SIGNAL(printedMessage(const QString&)), this, SLOT(onScriptPrint(const QString&))); + connect(_scriptEngine, &ScriptEngine::errorMessage, this, &ScriptEditorWidget::onScriptError); + connect(_scriptEngine, &ScriptEngine::printedMessage, this, &ScriptEditorWidget::onScriptPrint); } else { Application::getInstance()->stopScript(_currentScript); _scriptEngine = NULL; @@ -108,21 +113,25 @@ void ScriptEditorWidget::loadFile(const QString& scriptPath) { if (url.scheme().size() <= WINDOWS_DRIVE_LETTER_SIZE) { QFile file(scriptPath); if (!file.open(QFile::ReadOnly | QFile::Text)) { - QMessageBox::warning(this, tr("Interface"), tr("Cannot read script %1:\n%2.").arg(scriptPath).arg(file.errorString())); + QMessageBox::warning(this, tr("Interface"), tr("Cannot read script %1:\n%2.").arg(scriptPath) + .arg(file.errorString())); return; } QTextStream in(&file); _scriptEditorWidgetUI->scriptEdit->setPlainText(in.readAll()); setScriptFile(scriptPath); - disconnect(this, SLOT(onScriptError(const QString&))); - disconnect(this, SLOT(onScriptPrint(const QString&))); + if (_scriptEngine != NULL) { + disconnect(_scriptEngine, &ScriptEngine::runningStateChanged, this, &ScriptEditorWidget::runningStateChanged); + disconnect(_scriptEngine, &ScriptEngine::errorMessage, this, &ScriptEditorWidget::onScriptError); + disconnect(_scriptEngine, &ScriptEngine::printedMessage, this, &ScriptEditorWidget::onScriptPrint); + } } else { QNetworkAccessManager* networkManager = new QNetworkAccessManager(this); QNetworkReply* reply = networkManager->get(QNetworkRequest(url)); qDebug() << "Downloading included script at" << scriptPath; QEventLoop loop; - QObject::connect(reply, SIGNAL(finished()), &loop, SLOT(quit())); + QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit); loop.exec(); _scriptEditorWidgetUI->scriptEdit->setPlainText(reply->readAll()); if (!saveAs()) { @@ -132,9 +141,9 @@ void ScriptEditorWidget::loadFile(const QString& scriptPath) { _scriptEngine = Application::getInstance()->getScriptEngine(_currentScript); if (_scriptEngine != NULL) { - connect(_scriptEngine, SIGNAL(runningStateChanged()), this, SIGNAL(runningStateChanged())); - connect(_scriptEngine, SIGNAL(errorMessage(const QString&)), this, SLOT(onScriptError(const QString&))); - connect(_scriptEngine, SIGNAL(printedMessage(const QString&)), this, SLOT(onScriptPrint(const QString&))); + connect(_scriptEngine, &ScriptEngine::runningStateChanged, this, &ScriptEditorWidget::runningStateChanged); + connect(_scriptEngine, &ScriptEngine::errorMessage, this, &ScriptEditorWidget::onScriptError); + connect(_scriptEngine, &ScriptEngine::printedMessage, this, &ScriptEditorWidget::onScriptPrint); } } diff --git a/interface/src/ui/ScriptEditorWindow.cpp b/interface/src/ui/ScriptEditorWindow.cpp index ec5070ac20..41732d18c6 100644 --- a/interface/src/ui/ScriptEditorWindow.cpp +++ b/interface/src/ui/ScriptEditorWindow.cpp @@ -37,17 +37,17 @@ ScriptEditorWindow::ScriptEditorWindow() : this->setWindowFlags(Qt::Tool); show(); addScriptEditorWidget("New script"); - connect(_loadMenu, SIGNAL(aboutToShow()), this, SLOT(loadMenuAboutToShow())); + connect(_loadMenu, &QMenu::aboutToShow, this, &ScriptEditorWindow::loadMenuAboutToShow); _ScriptEditorWindowUI->loadButton->setMenu(_loadMenu); _saveMenu->addAction("Save as..", this, SLOT(saveScriptAsClicked()), Qt::CTRL | Qt::SHIFT | Qt::Key_S); _ScriptEditorWindowUI->saveButton->setMenu(_saveMenu); - connect(new QShortcut(QKeySequence("Ctrl+N"), this), SIGNAL(activated()), this, SLOT(newScriptClicked())); - connect(new QShortcut(QKeySequence("Ctrl+S"), this), SIGNAL(activated()), this, SLOT(saveScriptClicked())); - connect(new QShortcut(QKeySequence("Ctrl+O"), this), SIGNAL(activated()), this, SLOT(loadScriptClicked())); - connect(new QShortcut(QKeySequence("F5"), this), SIGNAL(activated()), this, SLOT(toggleRunScriptClicked())); + connect(new QShortcut(QKeySequence("Ctrl+N"), this), &QShortcut::activated, this, &ScriptEditorWindow::newScriptClicked); + connect(new QShortcut(QKeySequence("Ctrl+S"), this), &QShortcut::activated, this,&ScriptEditorWindow::saveScriptClicked); + connect(new QShortcut(QKeySequence("Ctrl+O"), this), &QShortcut::activated, this, &ScriptEditorWindow::loadScriptClicked); + connect(new QShortcut(QKeySequence("F5"), this), &QShortcut::activated, this, &ScriptEditorWindow::toggleRunScriptClicked); } ScriptEditorWindow::~ScriptEditorWindow() { @@ -130,9 +130,9 @@ void ScriptEditorWindow::saveScriptAsClicked() { ScriptEditorWidget* ScriptEditorWindow::addScriptEditorWidget(QString title) { ScriptEditorWidget* newScriptEditorWidget = new ScriptEditorWidget(); - connect(newScriptEditorWidget, SIGNAL(scriptnameChanged()), this, SLOT(updateScriptNameOrStatus())); - connect(newScriptEditorWidget, SIGNAL(scriptModified()), this, SLOT(updateScriptNameOrStatus())); - connect(newScriptEditorWidget, SIGNAL(runningStateChanged()), this, SLOT(updateButtons())); + connect(newScriptEditorWidget, &ScriptEditorWidget::scriptnameChanged, this, &ScriptEditorWindow::updateScriptNameOrStatus); + connect(newScriptEditorWidget, &ScriptEditorWidget::scriptModified, this, &ScriptEditorWindow::updateScriptNameOrStatus); + connect(newScriptEditorWidget, &ScriptEditorWidget::runningStateChanged, this, &ScriptEditorWindow::updateButtons); _ScriptEditorWindowUI->tabWidget->addTab(newScriptEditorWidget, title); _ScriptEditorWindowUI->tabWidget->setCurrentWidget(newScriptEditorWidget); newScriptEditorWidget->setUpdatesEnabled(true); From 12daae03084bee3cf1f1a824fa3ab7c1b66119e3 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Thu, 1 May 2014 14:12:11 -0700 Subject: [PATCH 37/38] 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 38/38] 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();