From aac73c36650f329271c155aba554aae1c5e8034f Mon Sep 17 00:00:00 2001 From: Mika Impola Date: Thu, 15 May 2014 21:44:45 +0300 Subject: [PATCH 01/39] Script to make your avatar sit down. --- examples/sit.js | 141 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 examples/sit.js diff --git a/examples/sit.js b/examples/sit.js new file mode 100644 index 0000000000..d67f6f6bb5 --- /dev/null +++ b/examples/sit.js @@ -0,0 +1,141 @@ +// +// sit.js +// examples +// +// Created by Mika Impola on February 8, 2014 +// 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 +// + + +var buttonImageUrl = "https://worklist-prod.s3.amazonaws.com/attachment/0aca88e1-9bd8-5c1d.svg"; + +var windowDimensions = Controller.getViewportDimensions(); + +var buttonWidth = 37; +var buttonHeight = 45; +var buttonPadding = 10; + +var buttonPositionX = windowDimensions.x - buttonPadding - buttonWidth; +var buttonPositionY = (windowDimensions.y - buttonHeight) / 2 ; + +var sitDownButton = Overlays.addOverlay("image", { + x: buttonPositionX, y: buttonPositionY, width: buttonWidth, height: buttonHeight, + subImage: { x: 0, y: buttonHeight, width: buttonWidth, height: buttonHeight}, + imageURL: buttonImageUrl, + visible: true, + alpha: 1.0 + }); +var standUpButton = Overlays.addOverlay("image", { + x: buttonPositionX, y: buttonPositionY, width: buttonWidth, height: buttonHeight, + subImage: { x: buttonWidth, y: buttonHeight, width: buttonWidth, height: buttonHeight}, + imageURL: buttonImageUrl, + visible: false, + alpha: 1.0 + }); + +var passedTime = 0.0; +var startPosition = null; +var animationLenght = 2.0; + +// This is the pose we would like to end up +var pose = [ + {joint:"RightUpLeg", rotation: {x:100.0, y:15.0, z:0.0}}, + {joint:"RightLeg", rotation: {x:-130.0, y:15.0, z:0.0}}, + {joint:"RightFoot", rotation: {x:30, y:15.0, z:0.0}}, + {joint:"LeftUpLeg", rotation: {x:100.0, y:-15.0, z:0.0}}, + {joint:"LeftLeg", rotation: {x:-130.0, y:-15.0, z:0.0}}, + {joint:"LeftFoot", rotation: {x:30, y:15.0, z:0.0}}, + + {joint:"Spine2", rotation: {x:20, y:0.0, z:0.0}}, + + {joint:"RightShoulder", rotation: {x:0.0, y:40.0, z:0.0}}, + {joint:"LeftShoulder", rotation: {x:0.0, y:-40.0, z:0.0}} + +]; + +var startPoseAndTransition = []; + +function storeStartPoseAndTransition() { + for (var i = 0; i < pose.length; i++){ + var startRotation = Quat.safeEulerAngles(MyAvatar.getJointRotation(pose[i].joint)); + var transitionVector = Vec3.subtract( pose[i].rotation, startRotation ); + startPoseAndTransition.push({joint: pose[i].joint, start: startRotation, transition: transitionVector}); + } +} + +function updateJoints(factor){ + for (var i = 0; i < startPoseAndTransition.length; i++){ + var scaledTransition = Vec3.multiply(startPoseAndTransition[i].transition, factor); + var rotation = Vec3.sum(startPoseAndTransition[i].start, scaledTransition); + MyAvatar.setJointData(startPoseAndTransition[i].joint, Quat.fromVec3Degrees( rotation )); + } +} + +var sittingDownAnimation = function(deltaTime) { + + passedTime += deltaTime; + var factor = passedTime/animationLenght; + + if ( passedTime <= animationLenght ) { + updateJoints(factor); + + var pos = { x: startPosition.x - 0.3 * factor, y: startPosition.y - 0.5 * factor, z: startPosition.z}; + MyAvatar.position = pos; + } +} + +var standingUpAnimation = function(deltaTime){ + + passedTime += deltaTime; + var factor = 1 - passedTime/animationLenght; + + if ( passedTime <= animationLenght ) { + + updateJoints(factor); + + var pos = { x: startPosition.x + 0.3 * (passedTime/animationLenght), y: startPosition.y + 0.5 * (passedTime/animationLenght), z: startPosition.z}; + MyAvatar.position = pos; + } +} + +Controller.mousePressEvent.connect(function(event){ + + var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y}); + + if (clickedOverlay == sitDownButton) { + passedTime = 0.0; + startPosition = MyAvatar.position; + storeStartPoseAndTransition(); + try{ + Script.update.disconnect(standingUpAnimation); + } catch(e){ + // no need to handle. if it wasn't connected no harm done + } + Script.update.connect(sittingDownAnimation); + Overlays.editOverlay(sitDownButton, { visible: false }); + Overlays.editOverlay(standUpButton, { visible: true }); + } else if (clickedOverlay == standUpButton) { + passedTime = 0.0; + startPosition = MyAvatar.position; + try{ + Script.update.disconnect(sittingDownAnimation); + } catch (e){} + Script.update.connect(standingUpAnimation); + Overlays.editOverlay(standUpButton, { visible: false }); + Overlays.editOverlay(sitDownButton, { visible: true }); + } +}) + + +Script.scriptEnding.connect(function() { + + for (var i = 0; i < pose.length; i++){ + MyAvatar.clearJointData(pose[i][0]); + } + + Overlays.deleteOverlay(sitDownButton); + Overlays.deleteOverlay(standUpButton); +}); From c7acc80ad0c89ba7d3909a7400710197303f8157 Mon Sep 17 00:00:00 2001 From: Mohammed Nafees Date: Fri, 16 May 2014 08:36:56 +0530 Subject: [PATCH 02/39] Fixes the log window to prevent halting of Interface when the log is long and the window is opened --- interface/src/AbstractLoggerInterface.h | 8 +- interface/src/Application.cpp | 111 ++++++++++++------------ interface/src/FileLogger.cpp | 4 +- interface/src/FileLogger.h | 4 +- interface/src/ui/LogDialog.cpp | 8 +- 5 files changed, 68 insertions(+), 67 deletions(-) diff --git a/interface/src/AbstractLoggerInterface.h b/interface/src/AbstractLoggerInterface.h index f6cf136a71..fe45346e4c 100644 --- a/interface/src/AbstractLoggerInterface.h +++ b/interface/src/AbstractLoggerInterface.h @@ -20,12 +20,12 @@ class AbstractLoggerInterface : public QObject { Q_OBJECT public: - AbstractLoggerInterface(QObject* parent = NULL) : QObject(parent) {}; - inline bool extraDebugging() { return _extraDebugging; }; - inline void setExtraDebugging(bool debugging) { _extraDebugging = debugging; }; + AbstractLoggerInterface(QObject* parent = NULL) : QObject(parent) {} + inline bool extraDebugging() { return _extraDebugging; } + inline void setExtraDebugging(bool debugging) { _extraDebugging = debugging; } virtual void addMessage(QString) = 0; - virtual QStringList getLogData() = 0; + virtual QString getLogData() = 0; virtual void locateLog() = 0; signals: diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 91e95bb4e3..891f2d480a 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -175,7 +175,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : { // init GnuTLS for DTLS with domain-servers DTLSClientSession::globalInit(); - + // read the ApplicationInfo.ini file for Name/Version/Domain information QSettings applicationInfo(Application::resourcesPath() + "info/ApplicationInfo.ini", QSettings::IniFormat); @@ -237,10 +237,10 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : connect(&nodeList->getDomainHandler(), SIGNAL(hostnameChanged(const QString&)), SLOT(domainChanged(const QString&))); connect(&nodeList->getDomainHandler(), SIGNAL(connectedToDomain(const QString&)), SLOT(connectedToDomain(const QString&))); - + // update our location every 5 seconds in the data-server, assuming that we are authenticated with one const float DATA_SERVER_LOCATION_CHANGE_UPDATE_MSECS = 5.0f * 1000.0f; - + QTimer* locationUpdateTimer = new QTimer(this); connect(locationUpdateTimer, &QTimer::timeout, this, &Application::updateLocationInServer); locationUpdateTimer->start(DATA_SERVER_LOCATION_CHANGE_UPDATE_MSECS); @@ -336,7 +336,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : // when -url in command line, teleport to location urlGoTo(argc, constArgv); - + // For now we're going to set the PPS for outbound packets to be super high, this is // probably not the right long term solution. But for now, we're going to do this to // allow you to move a particle around in your hand @@ -363,23 +363,23 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : // clear the scripts, and set out script to our default scripts clearScriptsBeforeRunning(); loadScript("http://public.highfidelity.io/scripts/defaultScripts.js"); - + QMutexLocker locker(&_settingsMutex); _settings->setValue("firstRun",QVariant(false)); } else { // do this as late as possible so that all required subsystems are inialized loadScripts(); - + QMutexLocker locker(&_settingsMutex); _previousScriptLocation = _settings->value("LastScriptLocation", QVariant("")).toString(); } - + connect(_window, &MainWindow::windowGeometryChanged, _runningScriptsWidget, &RunningScriptsWidget::setBoundary); - - //When -url in command line, teleport to location - urlGoTo(argc, constArgv); - + + //When -url in command line, teleport to location + urlGoTo(argc, constArgv); + // call the OAuthWebviewHandler static getter so that its instance lives in our thread OAuthWebViewHandler::getInstance(); // make sure the High Fidelity root CA is in our list of trusted certs @@ -392,11 +392,11 @@ Application::~Application() { // make sure we don't call the idle timer any more delete idleTimer; - + _sharedVoxelSystem.changeTree(new VoxelTree); - + saveSettings(); - + delete _voxelImporter; // let the avatar mixer know we're out @@ -430,14 +430,14 @@ Application::~Application() { delete _glWidget; AccountManager::getInstance().destroy(); - + DTLSClientSession::globalDeinit(); } void Application::saveSettings() { Menu::getInstance()->saveSettings(); _rearMirrorTools->saveSettings(_settings); - + if (_voxelImporter) { _voxelImporter->saveSettings(_settings); } @@ -519,7 +519,7 @@ void Application::initializeGL() { _voxelHideShowThread.initialize(_enableProcessVoxelsThread); _particleEditSender.initialize(_enableProcessVoxelsThread); _modelEditSender.initialize(_enableProcessVoxelsThread); - + if (_enableProcessVoxelsThread) { qDebug("Voxel parsing thread created."); } @@ -584,7 +584,7 @@ void Application::paintGL() { _myCamera.setDistance(MIRROR_FULLSCREEN_DISTANCE * _myAvatar->getScale() * _scaleMirror); _myCamera.setTargetPosition(_myAvatar->getPosition() + glm::vec3(0, headHeight + (_raiseMirror * _myAvatar->getScale()), 0)); _myCamera.setTargetRotation(_myAvatar->getWorldAlignedOrientation() * glm::quat(glm::vec3(0.0f, PI + _rotateMirror, 0.0f))); - + // if the head would intersect the near clip plane, we must push the camera out glm::vec3 relativePosition = glm::inverse(_myCamera.getTargetRotation()) * (eyePosition - _myCamera.getTargetPosition()); @@ -593,7 +593,7 @@ void Application::paintGL() { pushback = relativePosition.z + pushbackRadius - _myCamera.getDistance(); pushbackFocalLength = _myCamera.getDistance(); } - + // handle pushback, if any if (pushbackFocalLength > 0.0f) { const float PUSHBACK_DECAY = 0.5f; @@ -1278,7 +1278,7 @@ void Application::dropEvent(QDropEvent *event) { void Application::sendPingPackets() { QByteArray pingPacket = NodeList::getInstance()->constructPingPacket(); - controlledBroadcastToNodes(pingPacket, NodeSet() + controlledBroadcastToNodes(pingPacket, NodeSet() << NodeType::VoxelServer << NodeType::ParticleServer << NodeType::ModelServer << NodeType::AudioMixer << NodeType::AvatarMixer << NodeType::MetavoxelServer); @@ -1289,7 +1289,7 @@ void Application::timer() { if (Menu::getInstance()->isOptionChecked(MenuOption::TestPing)) { sendPingPackets(); } - + float diffTime = (float)_timerStart.nsecsElapsed() / 1000000000.0f; _fps = (float)_frameCount / diffTime; @@ -1693,7 +1693,7 @@ void Application::init() { connect(_rearMirrorTools, SIGNAL(restoreView()), SLOT(restoreMirrorView())); connect(_rearMirrorTools, SIGNAL(shrinkView()), SLOT(shrinkMirrorView())); connect(_rearMirrorTools, SIGNAL(resetView()), SLOT(resetSensors())); - + // set up our audio reflector _audioReflector.setMyAvatar(getAvatar()); _audioReflector.setVoxels(_voxels.getTree()); @@ -1702,7 +1702,7 @@ void Application::init() { connect(getAudio(), &Audio::processInboundAudio, &_audioReflector, &AudioReflector::processInboundAudio,Qt::DirectConnection); connect(getAudio(), &Audio::processLocalAudio, &_audioReflector, &AudioReflector::processLocalAudio,Qt::DirectConnection); - connect(getAudio(), &Audio::preProcessOriginalInboundAudio, &_audioReflector, + connect(getAudio(), &Audio::preProcessOriginalInboundAudio, &_audioReflector, &AudioReflector::preProcessOriginalInboundAudio,Qt::DirectConnection); // save settings when avatar changes @@ -1817,7 +1817,7 @@ void Application::updateMyAvatarLookAtPosition() { PerformanceWarning warn(showWarnings, "Application::updateMyAvatarLookAtPosition()"); FaceTracker* tracker = getActiveFaceTracker(); - + bool isLookingAtSomeone = false; glm::vec3 lookAtSpot; if (_myCamera.getMode() == CAMERA_MODE_MIRROR) { @@ -1852,7 +1852,7 @@ void Application::updateMyAvatarLookAtPosition() { glm::distance(_mouseRayOrigin, _myAvatar->getHead()->calculateAverageEyePosition())); lookAtSpot = _mouseRayOrigin + _mouseRayDirection * qMax(minEyeDistance, distance); */ - + } // // Deflect the eyes a bit to match the detected Gaze from 3D camera if active @@ -1872,7 +1872,7 @@ void Application::updateMyAvatarLookAtPosition() { eyePitch * pitchSign * deflection, eyeYaw * deflection, 0.0f))) * glm::inverse(_myCamera.getRotation()) * (lookAtSpot - origin); } - + _myAvatar->getHead()->setLookAtPosition(lookAtSpot); } @@ -1924,7 +1924,7 @@ void Application::updateCamera(float deltaTime) { PerformanceWarning warn(showWarnings, "Application::updateCamera()"); if (!OculusManager::isConnected() && !TV3DManager::isConnected() && - Menu::getInstance()->isOptionChecked(MenuOption::OffAxisProjection)) { + Menu::getInstance()->isOptionChecked(MenuOption::OffAxisProjection)) { FaceTracker* tracker = getActiveFaceTracker(); if (tracker) { const float EYE_OFFSET_SCALE = 0.025f; @@ -2479,7 +2479,7 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) { // disable specular lighting for ground and voxels glMaterialfv(GL_FRONT, GL_SPECULAR, NO_SPECULAR_COLOR); - + // draw the audio reflector overlay _audioReflector.render(); @@ -2644,7 +2644,7 @@ void Application::displayOverlay() { const float LOG2_LOUDNESS_FLOOR = 11.f; float audioLevel = 0.f; float loudness = _audio.getLastInputLoudness() + 1.f; - + _trailingAudioLoudness = AUDIO_METER_AVERAGING * _trailingAudioLoudness + (1.f - AUDIO_METER_AVERAGING) * loudness; float log2loudness = log(_trailingAudioLoudness) / LOG2; @@ -2657,7 +2657,7 @@ void Application::displayOverlay() { audioLevel = AUDIO_METER_SCALE_WIDTH; } bool isClipping = ((_audio.getTimeSinceLastClip() > 0.f) && (_audio.getTimeSinceLastClip() < CLIPPING_INDICATOR_TIME)); - + if ((_audio.getTimeSinceLastClip() > 0.f) && (_audio.getTimeSinceLastClip() < CLIPPING_INDICATOR_TIME)) { const float MAX_MAGNITUDE = 0.7f; float magnitude = MAX_MAGNITUDE * (1 - _audio.getTimeSinceLastClip() / CLIPPING_INDICATOR_TIME); @@ -2762,7 +2762,7 @@ void Application::displayOverlay() { // give external parties a change to hook in emit renderingOverlay(); - + _overlays.render2D(); glPopMatrix(); @@ -2838,7 +2838,7 @@ void Application::renderRearViewMirror(const QRect& region, bool billboard) { // save absolute translations glm::vec3 absoluteSkeletonTranslation = _myAvatar->getSkeletonModel().getTranslation(); glm::vec3 absoluteFaceTranslation = _myAvatar->getHead()->getFaceModel().getTranslation(); - + // get the eye positions relative to the neck and use them to set the face translation glm::vec3 leftEyePosition, rightEyePosition; _myAvatar->getHead()->getFaceModel().setTranslation(glm::vec3()); @@ -3104,7 +3104,7 @@ void Application::uploadModel(ModelType modelType) { thread->connect(uploader, SIGNAL(destroyed()), SLOT(quit())); thread->connect(thread, SIGNAL(finished()), SLOT(deleteLater())); uploader->connect(thread, SIGNAL(started()), SLOT(send())); - + thread->start(); } @@ -3121,28 +3121,28 @@ void Application::updateWindowTitle(){ } void Application::updateLocationInServer() { - + AccountManager& accountManager = AccountManager::getInstance(); - + if (accountManager.isLoggedIn()) { - + static QJsonObject lastLocationObject; - + // construct a QJsonObject given the user's current address information QJsonObject updatedLocationObject; - + QJsonObject addressObject; addressObject.insert("position", QString(createByteArray(_myAvatar->getPosition()))); addressObject.insert("orientation", QString(createByteArray(glm::degrees(safeEulerAngles(_myAvatar->getOrientation()))))); addressObject.insert("domain", NodeList::getInstance()->getDomainHandler().getHostname()); - + updatedLocationObject.insert("address", addressObject); - + if (updatedLocationObject != lastLocationObject) { - + accountManager.authenticatedRequest("/api/v1/users/address", QNetworkAccessManager::PutOperation, JSONCallbackParameters(), QJsonDocument(updatedLocationObject).toJson()); - + lastLocationObject = updatedLocationObject; } } @@ -3167,7 +3167,7 @@ void Application::domainChanged(const QString& domainHostname) { // reset the voxels renderer _voxels.killLocalVoxels(); - + // reset the auth URL for OAuth web view handler OAuthWebViewHandler::getInstance().clearLastAuthorizationURL(); } @@ -3388,7 +3388,7 @@ void Application::loadScripts() { loadScript(string); } } - + QMutexLocker locker(&_settingsMutex); _settings->endArray(); } @@ -3613,9 +3613,12 @@ void Application::loadScriptURLDialog() { void Application::toggleLogDialog() { if (! _logDialog) { _logDialog = new LogDialog(_glWidget, getLogger()); - _logDialog->show(); + } + + if (_logDialog->isVisible()) { + _logDialog->hide(); } else { - _logDialog->close(); + _logDialog->show(); } } @@ -3649,7 +3652,7 @@ void Application::parseVersionXml() { QObject* sender = QObject::sender(); QXmlStreamReader xml(qobject_cast(sender)); - + while (!xml.atEnd() && !xml.hasError()) { if (xml.tokenType() == QXmlStreamReader::StartElement && xml.name() == operatingSystem) { while (!(xml.tokenType() == QXmlStreamReader::EndElement && xml.name() == operatingSystem)) { @@ -3666,7 +3669,7 @@ void Application::parseVersionXml() { } xml.readNext(); } - + if (!shouldSkipVersion(latestVersion) && applicationVersion() != latestVersion) { new UpdateDialog(_glWidget, releaseNotes, latestVersion, downloadUrl); } @@ -3718,24 +3721,24 @@ void Application::urlGoTo(int argc, const char * constArgv[]) { } else 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(); - + Menu::goToDomain(domain); - + // goto either @user, #place, or x-xx,y-yy,z-zz // style co-ordinate. Menu::goTo(destination); - + if (!orientation.isEmpty()) { // location orientation Menu::goToOrientation(orientation); } - } + } } } diff --git a/interface/src/FileLogger.cpp b/interface/src/FileLogger.cpp index c4e75b21b2..cb3d43925d 100644 --- a/interface/src/FileLogger.cpp +++ b/interface/src/FileLogger.cpp @@ -23,7 +23,7 @@ const QString LOGS_DIRECTORY = "Logs"; FileLogger::FileLogger(QObject* parent) : AbstractLoggerInterface(parent), - _logData(NULL) + _logData("") { setExtraDebugging(false); @@ -36,7 +36,7 @@ FileLogger::FileLogger(QObject* parent) : void FileLogger::addMessage(QString message) { QMutexLocker locker(&_mutex); emit logReceived(message); - _logData.append(message); + _logData += message; QFile file(_fileName); if (file.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text)) { diff --git a/interface/src/FileLogger.h b/interface/src/FileLogger.h index 5da86044ab..3dbbfd26cd 100644 --- a/interface/src/FileLogger.h +++ b/interface/src/FileLogger.h @@ -22,11 +22,11 @@ public: FileLogger(QObject* parent = NULL); virtual void addMessage(QString); - virtual QStringList getLogData() { return _logData; }; + virtual QString getLogData() { return _logData; } virtual void locateLog(); private: - QStringList _logData; + QString _logData; QString _fileName; QMutex _mutex; diff --git a/interface/src/ui/LogDialog.cpp b/interface/src/ui/LogDialog.cpp index 5db704b230..ef690461be 100644 --- a/interface/src/ui/LogDialog.cpp +++ b/interface/src/ui/LogDialog.cpp @@ -57,6 +57,8 @@ LogDialog::LogDialog(QWidget* parent, AbstractLoggerInterface* logger) : QDialog resize(INITIAL_WIDTH, static_cast(screen.height() * INITIAL_HEIGHT_RATIO)); move(screen.center() - rect().center()); setMinimumWidth(MINIMAL_WIDTH); + + connect(_logger, SIGNAL(logReceived(QString)), this, SLOT(appendLogLine(QString)), Qt::QueuedConnection); } LogDialog::~LogDialog() { @@ -105,7 +107,6 @@ void LogDialog::initControls() { } void LogDialog::showEvent(QShowEvent*) { - connect(_logger, SIGNAL(logReceived(QString)), this, SLOT(appendLogLine(QString)), Qt::QueuedConnection); showLogData(); } @@ -146,10 +147,7 @@ void LogDialog::handleSearchTextChanged(const QString searchText) { void LogDialog::showLogData() { _logTextBox->clear(); - QStringList _logData = _logger->getLogData(); - for (int i = 0; i < _logData.size(); ++i) { - appendLogLine(_logData[i]); - } + _logTextBox->insertPlainText(_logger->getLogData()); } KeywordHighlighter::KeywordHighlighter(QTextDocument *parent) : QSyntaxHighlighter(parent), keywordFormat() { From f23dd0fecfac2d59264985994adbc7acba241414 Mon Sep 17 00:00:00 2001 From: Mohammed Nafees Date: Sat, 17 May 2014 01:17:55 +0530 Subject: [PATCH 03/39] Fix scrolling of log data when Extra Debugging is enabled --- interface/src/ui/LogDialog.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/ui/LogDialog.cpp b/interface/src/ui/LogDialog.cpp index ef690461be..7136ed2d25 100644 --- a/interface/src/ui/LogDialog.cpp +++ b/interface/src/ui/LogDialog.cpp @@ -123,7 +123,6 @@ void LogDialog::appendLogLine(QString logLine) { if (logLine.contains(_searchTerm, Qt::CaseInsensitive)) { _logTextBox->appendPlainText(logLine.simplified()); } - _logTextBox->ensureCursorVisible(); } } @@ -148,6 +147,7 @@ void LogDialog::handleSearchTextChanged(const QString searchText) { void LogDialog::showLogData() { _logTextBox->clear(); _logTextBox->insertPlainText(_logger->getLogData()); + _logTextBox->ensureCursorVisible(); } KeywordHighlighter::KeywordHighlighter(QTextDocument *parent) : QSyntaxHighlighter(parent), keywordFormat() { From fd3e871242126eef2473401f8468f3f9dffcd853 Mon Sep 17 00:00:00 2001 From: Mika Impola Date: Sat, 17 May 2014 21:04:27 +0300 Subject: [PATCH 04/39] Updating button position when window is resized. --- examples/sit.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/examples/sit.js b/examples/sit.js index d67f6f6bb5..f503438792 100644 --- a/examples/sit.js +++ b/examples/sit.js @@ -129,6 +129,18 @@ Controller.mousePressEvent.connect(function(event){ } }) +function update(deltaTime){ + var newWindowDimensions = Controller.getViewportDimensions(); + if( newWindowDimensions.x != windowDimensions.x || newWindowDimensions.y != windowDimensions.y ){ + windowDimensions = newWindowDimensions; + var newX = windowDimensions.x - buttonPadding - buttonWidth; + var newY = (windowDimensions.y - buttonHeight) / 2 ; + Overlays.editOverlay( standUpButton, {x: newX, y: newY} ); + Overlays.editOverlay( sitDownButton, {x: newX, y: newY} ); + } +} + +Script.update.connect(update); Script.scriptEnding.connect(function() { From cc1bd5d63bd061744a262645baa8f9d49ceab9d9 Mon Sep 17 00:00:00 2001 From: Mika Impola Date: Sat, 17 May 2014 21:24:29 +0300 Subject: [PATCH 05/39] Fixed cropping of the image. --- examples/sit.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/sit.js b/examples/sit.js index f503438792..1df877dba6 100644 --- a/examples/sit.js +++ b/examples/sit.js @@ -15,7 +15,7 @@ var buttonImageUrl = "https://worklist-prod.s3.amazonaws.com/attachment/0aca88e1 var windowDimensions = Controller.getViewportDimensions(); var buttonWidth = 37; -var buttonHeight = 45; +var buttonHeight = 46; var buttonPadding = 10; var buttonPositionX = windowDimensions.x - buttonPadding - buttonWidth; From 4f4b4c08bb32fa2bbe74a57450187a730f117373 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Mon, 19 May 2014 12:29:49 -0700 Subject: [PATCH 06/39] Restore specular lighting--but, per the OpenGL spec, shut off specular contribution if the surface isn't facing the light. --- interface/resources/shaders/model.frag | 7 +++++-- interface/resources/shaders/model_normal_map.frag | 9 ++++++--- .../resources/shaders/model_normal_specular_map.frag | 9 ++++++--- interface/resources/shaders/model_specular_map.frag | 9 ++++++--- 4 files changed, 23 insertions(+), 11 deletions(-) diff --git a/interface/resources/shaders/model.frag b/interface/resources/shaders/model.frag index 3964bd5b97..a9d93f2f6a 100644 --- a/interface/resources/shaders/model.frag +++ b/interface/resources/shaders/model.frag @@ -20,11 +20,14 @@ varying vec4 normal; void main(void) { // compute the base color based on OpenGL lighting model vec4 normalizedNormal = normalize(normal); + float diffuse = dot(normalizedNormal, gl_LightSource[0].position); + float facingLight = step(0.0, diffuse); vec4 base = gl_Color * (gl_FrontLightModelProduct.sceneColor + gl_FrontLightProduct[0].ambient + - gl_FrontLightProduct[0].diffuse * max(0.0, dot(normalizedNormal, gl_LightSource[0].position))); + gl_FrontLightProduct[0].diffuse * (diffuse * facingLight)); // compute the specular component (sans exponent) - float specular = max(0.0, dot(gl_LightSource[0].position, normalizedNormal)); + float specular = facingLight * max(0.0, dot(normalize(gl_LightSource[0].position + vec4(0.0, 0.0, 1.0, 0.0)), + normalizedNormal)); // modulate texture by base color and add specular contribution gl_FragColor = base * texture2D(diffuseMap, gl_TexCoord[0].st) + diff --git a/interface/resources/shaders/model_normal_map.frag b/interface/resources/shaders/model_normal_map.frag index a4f7a887c5..392be1f1cf 100644 --- a/interface/resources/shaders/model_normal_map.frag +++ b/interface/resources/shaders/model_normal_map.frag @@ -32,12 +32,15 @@ void main(void) { // compute the base color based on OpenGL lighting model vec4 viewNormal = vec4(normalizedTangent * localNormal.x + normalizedBitangent * localNormal.y + normalizedNormal * localNormal.z, 0.0); + float diffuse = dot(viewNormal, gl_LightSource[0].position); + float facingLight = step(0.0, diffuse); vec4 base = gl_Color * (gl_FrontLightModelProduct.sceneColor + gl_FrontLightProduct[0].ambient + - gl_FrontLightProduct[0].diffuse * max(0.0, dot(viewNormal, gl_LightSource[0].position))); + gl_FrontLightProduct[0].diffuse * (diffuse * facingLight)); // compute the specular component (sans exponent) - float specular = max(0.0, dot(gl_LightSource[0].position, viewNormal)); - + float specular = facingLight * max(0.0, dot(normalize(gl_LightSource[0].position + vec4(0.0, 0.0, 1.0, 0.0)), + viewNormal)); + // modulate texture by base color and add specular contribution gl_FragColor = base * texture2D(diffuseMap, gl_TexCoord[0].st) + vec4(pow(specular, gl_FrontMaterial.shininess) * gl_FrontLightProduct[0].specular.rgb, 0.0); diff --git a/interface/resources/shaders/model_normal_specular_map.frag b/interface/resources/shaders/model_normal_specular_map.frag index f5b9d2b06b..dbbb343c62 100644 --- a/interface/resources/shaders/model_normal_specular_map.frag +++ b/interface/resources/shaders/model_normal_specular_map.frag @@ -35,12 +35,15 @@ void main(void) { // compute the base color based on OpenGL lighting model vec4 viewNormal = vec4(normalizedTangent * localNormal.x + normalizedBitangent * localNormal.y + normalizedNormal * localNormal.z, 0.0); + float diffuse = dot(viewNormal, gl_LightSource[0].position); + float facingLight = step(0.0, diffuse); vec4 base = gl_Color * (gl_FrontLightModelProduct.sceneColor + gl_FrontLightProduct[0].ambient + - gl_FrontLightProduct[0].diffuse * max(0.0, dot(viewNormal, gl_LightSource[0].position))); + gl_FrontLightProduct[0].diffuse * (diffuse * facingLight)); // compute the specular component (sans exponent) - float specular = max(0.0, dot(gl_LightSource[0].position, viewNormal)); - + float specular = facingLight * max(0.0, dot(normalize(gl_LightSource[0].position + vec4(0.0, 0.0, 1.0, 0.0)), + viewNormal)); + // modulate texture by base color and add specular contribution gl_FragColor = base * texture2D(diffuseMap, gl_TexCoord[0].st) + vec4(pow(specular, gl_FrontMaterial.shininess) * gl_FrontLightProduct[0].specular.rgb * texture2D(specularMap, gl_TexCoord[0].st).rgb, 0.0); diff --git a/interface/resources/shaders/model_specular_map.frag b/interface/resources/shaders/model_specular_map.frag index 4e2f3d0c98..b955b5cfa6 100644 --- a/interface/resources/shaders/model_specular_map.frag +++ b/interface/resources/shaders/model_specular_map.frag @@ -23,12 +23,15 @@ varying vec4 normal; void main(void) { // compute the base color based on OpenGL lighting model vec4 normalizedNormal = normalize(normal); + float diffuse = dot(normalizedNormal, gl_LightSource[0].position); + float facingLight = step(0.0, diffuse); vec4 base = gl_Color * (gl_FrontLightModelProduct.sceneColor + gl_FrontLightProduct[0].ambient + - gl_FrontLightProduct[0].diffuse * max(0.0, dot(normalizedNormal, gl_LightSource[0].position))); + gl_FrontLightProduct[0].diffuse * (diffuse * facingLight)); // compute the specular component (sans exponent) - float specular = max(0.0, dot(gl_LightSource[0].position, normalizedNormal)); - + float specular = facingLight * max(0.0, dot(normalize(gl_LightSource[0].position + vec4(0.0, 0.0, 1.0, 0.0)), + normalizedNormal)); + // modulate texture by base color and add specular contribution gl_FragColor = base * texture2D(diffuseMap, gl_TexCoord[0].st) + vec4(pow(specular, gl_FrontMaterial.shininess) * gl_FrontLightProduct[0].specular.rgb * texture2D(specularMap, gl_TexCoord[0].st).rgb, 0.0); From e066d552a0b699c1dc0066369bca618945212ce6 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Mon, 19 May 2014 12:48:26 -0700 Subject: [PATCH 07/39] Added missing unlock that was causing a hang. --- interface/src/ui/ModelsBrowser.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/interface/src/ui/ModelsBrowser.cpp b/interface/src/ui/ModelsBrowser.cpp index f65829a8ac..4296a096a0 100644 --- a/interface/src/ui/ModelsBrowser.cpp +++ b/interface/src/ui/ModelsBrowser.cpp @@ -351,6 +351,7 @@ bool ModelHandler::parseHeaders(QNetworkReply* reply) { QList items = _model.findItems(QFileInfo(reply->url().toString()).baseName()); if (items.isEmpty() || items.first()->text() == DO_NOT_MODIFY_TAG) { + _lock.unlock(); return false; } From 5da656e3f56b5681c2b41b9ef5949eb47869d22c Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Mon, 19 May 2014 16:55:33 -0700 Subject: [PATCH 08/39] Working on animation dialog. --- interface/src/Menu.cpp | 11 +++ interface/src/Menu.h | 4 + interface/src/avatar/MyAvatar.cpp | 29 ++++++ interface/src/avatar/MyAvatar.h | 16 ++++ interface/src/ui/AnimationsDialog.cpp | 127 ++++++++++++++++++++++++++ interface/src/ui/AnimationsDialog.h | 71 ++++++++++++++ 6 files changed, 258 insertions(+) create mode 100644 interface/src/ui/AnimationsDialog.cpp create mode 100644 interface/src/ui/AnimationsDialog.h diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index bff08d5221..998f8acf72 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -37,6 +37,7 @@ #include "Menu.h" #include "scripting/MenuScriptingInterface.h" #include "Util.h" +#include "ui/AnimationsDialog.h" #include "ui/AttachmentsDialog.h" #include "ui/InfoView.h" #include "ui/MetavoxelEditor.h" @@ -193,6 +194,7 @@ Menu::Menu() : QAction::PreferencesRole); addActionToQMenuAndActionHash(editMenu, MenuOption::Attachments, 0, this, SLOT(editAttachments())); + addActionToQMenuAndActionHash(editMenu, MenuOption::Animations, 0, this, SLOT(editAnimations())); addDisabledActionAndSeparator(editMenu, "Physics"); QObject* avatar = appInstance->getAvatar(); @@ -862,6 +864,15 @@ void Menu::editAttachments() { } } +void Menu::editAnimations() { + if (!_animationsDialog) { + _animationsDialog = new AnimationsDialog(); + _animationsDialog->show(); + } else { + _animationsDialog->close(); + } +} + void Menu::goToDomain(const QString newDomain) { if (NodeList::getInstance()->getDomainHandler().getHostname() != newDomain) { // send a node kill request, indicating to other clients that they should play the "disappeared" effect diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 0a21a27960..c881ab05b0 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -64,6 +64,7 @@ struct ViewFrustumOffset { class QSettings; +class AnimationsDialog; class AttachmentsDialog; class BandwidthDialog; class LodToolsDialog; @@ -176,6 +177,7 @@ private slots: void aboutApp(); void editPreferences(); void editAttachments(); + void editAnimations(); void goToDomainDialog(); void goToLocation(); void nameLocation(); @@ -260,6 +262,7 @@ private: QAction* _loginAction; QPointer _preferencesDialog; QPointer _attachmentsDialog; + QPointer _animationsDialog; QAction* _chatAction; QString _snapshotsLocation; }; @@ -269,6 +272,7 @@ namespace MenuOption { const QString AlignForearmsWithWrists = "Align Forearms with Wrists"; const QString AlternateIK = "Alternate IK"; const QString AmbientOcclusion = "Ambient Occlusion"; + const QString Animations = "Animations..."; const QString Atmosphere = "Atmosphere"; const QString Attachments = "Attachments..."; const QString AudioNoiseReduction = "Audio Noise Reduction"; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 7c051d8984..a7a5b4e985 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -461,6 +461,15 @@ void MyAvatar::saveData(QSettings* settings) { } settings->endArray(); + settings->beginWriteArray("animationData"); + for (int i = 0; i < _animationData.size(); i++) { + settings->setArrayIndex(i); + const AnimationData& animation = _animationData.at(i); + settings->setValue("url", animation.url); + settings->setValue("fps", animation.fps); + } + settings->endArray(); + settings->setValue("displayName", _displayName); settings->endGroup(); @@ -511,6 +520,18 @@ void MyAvatar::loadData(QSettings* settings) { settings->endArray(); setAttachmentData(attachmentData); + QVector animationData; + int animationCount = settings->beginReadArray("animationData"); + for (int i = 0; i < animationCount; i++) { + settings->setArrayIndex(i); + AnimationData animation; + animation.url = settings->value("url").toUrl(); + animation.fps = loadSetting(settings, "fps", 30.0f); + animationData.append(animation); + } + settings->endArray(); + setAnimationData(animationData); + setDisplayName(settings->value("displayName").toString()); settings->endGroup(); @@ -577,6 +598,10 @@ AttachmentData MyAvatar::loadAttachmentData(const QUrl& modelURL, const QString& return attachment; } +void MyAvatar::setAnimationData(const QVector& animationData) { + _animationData = animationData; +} + int MyAvatar::parseDataAtOffset(const QByteArray& packet, int offset) { qDebug() << "Error: ignoring update packet for MyAvatar" << " packetLength = " << packet.size() @@ -1541,3 +1566,7 @@ void MyAvatar::applyCollision(const glm::vec3& contactPoint, const glm::vec3& pe getHead()->addLeanDeltas(sideways, forward); } } + +AnimationData::AnimationData() : + fps(30.0f) { +} diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 2e75ac984d..92dc447e56 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -16,6 +16,8 @@ #include "Avatar.h" +class AnimationData; + enum AvatarHandState { HAND_STATE_NULL = 0, @@ -69,6 +71,9 @@ public: void saveAttachmentData(const AttachmentData& attachment) const; AttachmentData loadAttachmentData(const QUrl& modelURL, const QString& jointName = QString()) const; + void setAnimationData(const QVector& animationData); + const QVector& getAnimationData() const { return _animationData; } + // Set what driving keys are being pressed to control thrust levels void setDriveKeys(int key, float val) { _driveKeys[key] = val; }; bool getDriveKeys(int key) { return _driveKeys[key] != 0.f; }; @@ -151,6 +156,8 @@ private: bool _billboardValid; float _oculusYawOffset; + QVector _animationData; + // private methods void updateOrientation(float deltaTime); void updateMotorFromKeyboard(float deltaTime, bool walking); @@ -167,4 +174,13 @@ private: void setGravity(const glm::vec3& gravity); }; +/// Describes an animation being run on the avatar. +class AnimationData { +public: + QUrl url; + float fps; + + AnimationData(); +}; + #endif // hifi_MyAvatar_h diff --git a/interface/src/ui/AnimationsDialog.cpp b/interface/src/ui/AnimationsDialog.cpp new file mode 100644 index 0000000000..5285c294ab --- /dev/null +++ b/interface/src/ui/AnimationsDialog.cpp @@ -0,0 +1,127 @@ +// +// AnimationsDialog.cpp +// interface/src/ui +// +// Created by Andrzej Kapolka on 5/19/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 +#include +#include +#include +#include +#include + +#include "AnimationsDialog.h" +#include "Application.h" + +AnimationsDialog::AnimationsDialog() : + QDialog(Application::getInstance()->getWindow()) { + + setWindowTitle("Edit Animations"); + setAttribute(Qt::WA_DeleteOnClose); + + QVBoxLayout* layout = new QVBoxLayout(); + setLayout(layout); + + QScrollArea* area = new QScrollArea(); + layout->addWidget(area); + area->setWidgetResizable(true); + QWidget* container = new QWidget(); + container->setLayout(_animations = new QVBoxLayout()); + container->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Preferred); + area->setWidget(container); + _animations->addStretch(1); + + foreach (const AnimationData& data, Application::getInstance()->getAvatar()->getAnimationData()) { + addAnimation(data); + } + + QPushButton* newAnimation = new QPushButton("New Animation"); + connect(newAnimation, SIGNAL(clicked(bool)), SLOT(addAnimation())); + layout->addWidget(newAnimation); + + QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok); + layout->addWidget(buttons); + connect(buttons, SIGNAL(accepted()), SLOT(deleteLater())); + _ok = buttons->button(QDialogButtonBox::Ok); + + setMinimumSize(600, 600); +} + +void AnimationsDialog::setVisible(bool visible) { + QDialog::setVisible(visible); + + // un-default the OK button + if (visible) { + _ok->setDefault(false); + } +} + +void AnimationsDialog::updateAnimationData() { + QVector data; + for (int i = 0; i < _animations->count() - 1; i++) { + data.append(static_cast(_animations->itemAt(i)->widget())->getAnimationData()); + } + Application::getInstance()->getAvatar()->setAnimationData(data); +} + +void AnimationsDialog::addAnimation(const AnimationData& data) { + _animations->insertWidget(_animations->count() - 1, new AnimationPanel(this, data)); +} + +AnimationPanel::AnimationPanel(AnimationsDialog* dialog, const AnimationData& data) : + _dialog(dialog), + _applying(false) { + setFrameStyle(QFrame::StyledPanel); + + QFormLayout* layout = new QFormLayout(); + layout->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow); + setLayout(layout); + + QHBoxLayout* urlBox = new QHBoxLayout(); + layout->addRow("URL:", urlBox); + urlBox->addWidget(_url = new QLineEdit(data.url.toString()), 1); + dialog->connect(_url, SIGNAL(returnPressed()), SLOT(updateAnimationData())); + QPushButton* chooseURL = new QPushButton("Choose"); + urlBox->addWidget(chooseURL); + connect(chooseURL, SIGNAL(clicked(bool)), SLOT(chooseURL())); + + layout->addRow("FPS:", _fps = new QDoubleSpinBox()); + _fps->setSingleStep(0.01); + _fps->setMaximum(FLT_MAX); + _fps->setValue(data.fps); + dialog->connect(_fps, SIGNAL(valueChanged(double)), SLOT(updateAnimationData())); + + QPushButton* remove = new QPushButton("Delete"); + layout->addRow(remove); + connect(remove, SIGNAL(clicked(bool)), SLOT(deleteLater())); + dialog->connect(remove, SIGNAL(clicked(bool)), SLOT(updateAnimationData()), Qt::QueuedConnection); +} + +AnimationData AnimationPanel::getAnimationData() const { + AnimationData data; + data.url = _url->text(); + data.fps = _fps->value(); + return data; +} + +void AnimationPanel::chooseURL() { + QString directory = Application::getInstance()->lockSettings()->value("animation_directory").toString(); + Application::getInstance()->unlockSettings(); + QString filename = QFileDialog::getOpenFileName(this, "Choose Animation", directory, "Animation files (*.fbx)"); + if (filename.isEmpty()) { + return; + } + Application::getInstance()->lockSettings()->setValue("animation_directory", QFileInfo(filename).path()); + Application::getInstance()->unlockSettings(); + _url->setText(QUrl::fromLocalFile(filename).toString()); + emit _url->returnPressed(); +} + diff --git a/interface/src/ui/AnimationsDialog.h b/interface/src/ui/AnimationsDialog.h new file mode 100644 index 0000000000..3d969aa32a --- /dev/null +++ b/interface/src/ui/AnimationsDialog.h @@ -0,0 +1,71 @@ +// +// AnimationsDialog.h +// interface/src/ui +// +// Created by Andrzej Kapolka on 5/19/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_AnimationsDialog_h +#define hifi_AnimationsDialog_h + +#include +#include + +#include "avatar/MyAvatar.h" + +class QDoubleSpinner; +class QLineEdit; +class QPushButton; +class QVBoxLayout; + +/// Allows users to edit the avatar animations. +class AnimationsDialog : public QDialog { + Q_OBJECT + +public: + + AnimationsDialog(); + + virtual void setVisible(bool visible); + +public slots: + + void updateAnimationData(); + +private slots: + + void addAnimation(const AnimationData& animation = AnimationData()); + +private: + + QVBoxLayout* _animations; + QPushButton* _ok; +}; + +/// A panel controlling a single animation. +class AnimationPanel : public QFrame { + Q_OBJECT + +public: + + AnimationPanel(AnimationsDialog* dialog, const AnimationData& data = AnimationData()); + + AnimationData getAnimationData() const; + +private slots: + + void chooseURL(); + +private: + + AnimationsDialog* _dialog; + QLineEdit* _url; + QDoubleSpinBox* _fps; + bool _applying; +}; + +#endif // hifi_AnimationsDialog_h From 62e7a31602448df9713171b9ab90c97e9af355b2 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Mon, 19 May 2014 18:09:33 -0700 Subject: [PATCH 09/39] More work on animation configuration. --- interface/src/Application.h | 1 + interface/src/avatar/MyAvatar.cpp | 9 ++++++++- interface/src/avatar/MyAvatar.h | 13 +++++++++++++ interface/src/renderer/Model.cpp | 30 ++++++++++++++++++++++++++++-- interface/src/renderer/Model.h | 21 +++++++++++++++++++++ 5 files changed, 71 insertions(+), 3 deletions(-) diff --git a/interface/src/Application.h b/interface/src/Application.h index 5460093cbd..1968ef4fee 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -217,6 +217,7 @@ public: QNetworkAccessManager* getNetworkAccessManager() { return _networkAccessManager; } GeometryCache* getGeometryCache() { return &_geometryCache; } + AnimationCache* getAnimationCache() { return &_animationCache; } TextureCache* getTextureCache() { return &_textureCache; } GlowEffect* getGlowEffect() { return &_glowEffect; } ControllerScriptingInterface* getControllerScriptingInterface() { return &_controllerScriptingInterface; } diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index a7a5b4e985..fd4b64ee5d 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -599,7 +599,10 @@ AttachmentData MyAvatar::loadAttachmentData(const QUrl& modelURL, const QString& } void MyAvatar::setAnimationData(const QVector& animationData) { - _animationData = animationData; + // exit early if no change + if (_animationData != animationData) { + _animationData = animationData; + } } int MyAvatar::parseDataAtOffset(const QByteArray& packet, int offset) { @@ -1570,3 +1573,7 @@ void MyAvatar::applyCollision(const glm::vec3& contactPoint, const glm::vec3& pe AnimationData::AnimationData() : fps(30.0f) { } + +bool AnimationData::operator==(const AnimationData& other) const { + return url == other.url && fps == other.fps; +} diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 92dc447e56..4e076e22bb 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -14,6 +14,8 @@ #include +#include + #include "Avatar.h" class AnimationData; @@ -158,6 +160,15 @@ private: QVector _animationData; + class AnimationState { + public: + AnimationPointer animation; + QVector jointMappings; + float frameIndex; + }; + + QVector _animationStates; + // private methods void updateOrientation(float deltaTime); void updateMotorFromKeyboard(float deltaTime, bool walking); @@ -181,6 +192,8 @@ public: float fps; AnimationData(); + + bool operator==(const AnimationData& other) const; }; #endif // hifi_MyAvatar_h diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index 90ae9e5e46..7e1ba69e27 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -594,6 +594,14 @@ QStringList Model::getJointNames() const { return isActive() ? _geometry->getFBXGeometry().getJointNames() : QStringList(); } +void Model::startAnimation(const QUrl& url, float fps, bool loop, float offset) { + AnimationState state = { Application::getInstance()->getAnimationCache()->getAnimation(url), fps, loop, offset }; + _animationStates.append(state); +} + +void Model::stopAnimation() { + _animationStates.clear(); +} void Model::clearShapes() { for (int i = 0; i < _jointShapes.size(); ++i) { @@ -1006,6 +1014,22 @@ void Model::simulate(float deltaTime, bool fullUpdate) { } void Model::simulateInternal(float deltaTime) { + // update animations + const FBXGeometry& geometry = _geometry->getFBXGeometry(); + for (int i = 0; i < _animationStates.size(); i++) { + AnimationState& state = _animationStates[i]; + if (!(state.animation && state.animation->isLoaded())) { + continue; + } + const FBXGeometry& animationGeometry = state.animation->getGeometry(); + if (state.jointMappings.isEmpty()) { + for (int j = 0; j < geometry.joints.size(); j++) { + state.jointMappings.append(animationGeometry.jointIndices.value(geometry.joints.at(j).name) - 1); + } + } + + } + // NOTE: this is a recursive call that walks all attachments, and their attachments // update the world space transforms for all joints for (int i = 0; i < _jointStates.size(); i++) { @@ -1013,8 +1037,6 @@ void Model::simulateInternal(float deltaTime) { } _shapesAreDirty = true; - const FBXGeometry& geometry = _geometry->getFBXGeometry(); - // update the attachment transforms and simulate them for (int i = 0; i < _attachments.size(); i++) { const FBXAttachment& attachment = geometry.attachments.at(i); @@ -1426,6 +1448,10 @@ void Model::deleteGeometry() { _meshStates.clear(); clearShapes(); + for (int i = 0; i < _animationStates.size(); i++) { + _animationStates[i].jointMappings.clear(); + } + if (_geometry) { _geometry->clearLoadPriority(this); } diff --git a/interface/src/renderer/Model.h b/interface/src/renderer/Model.h index 5b2839baa2..5b8e640401 100644 --- a/interface/src/renderer/Model.h +++ b/interface/src/renderer/Model.h @@ -17,6 +17,8 @@ #include +#include + #include "GeometryCache.h" #include "InterfaceConfig.h" #include "ProgramObject.h" @@ -185,6 +187,23 @@ public: QStringList getJointNames() const; + class AnimationState { + public: + AnimationPointer animation; + float fps; + bool loop; + float offset; + QVector jointMappings; + }; + + const QVector& getAnimationStates() const { return _animationStates; } + + /// Starts playing the animation at the specified URL. + void startAnimation(const QUrl& url, float fps = 30.0f, bool loop = true, float offset = 0.0f); + + /// Stops playing all animations. + void stopAnimation(); + void clearShapes(); void rebuildShapes(); void resetShapePositions(); @@ -260,6 +279,8 @@ protected: QVector _meshStates; + QVector _animationStates; + // returns 'true' if needs fullUpdate after geometry change bool updateGeometry(); From 1278470a91de5f7945132bc646d5029e38e6bc28 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Tue, 20 May 2014 13:31:08 -0700 Subject: [PATCH 10/39] properly handle FSTs that have translations and rotations --- interface/src/renderer/Model.cpp | 17 ++++++++++++----- libraries/fbx/src/FBXReader.cpp | 1 - libraries/fbx/src/FBXReader.h | 2 -- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index 90ae9e5e46..a53d0486ec 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -435,8 +435,12 @@ Extents Model::getMeshExtents() const { return Extents(); } const Extents& extents = _geometry->getFBXGeometry().meshExtents; - glm::vec3 scale = _scale * _geometry->getFBXGeometry().fstScaled; - Extents scaledExtents = { extents.minimum * scale, extents.maximum * scale }; + + // even though our caller asked for "unscaled" we need to include any fst scaling, translation, and rotation, which + // is captured in the offset matrix + glm::vec3 minimum = glm::vec3(_geometry->getFBXGeometry().offset * glm::vec4(extents.minimum, 1.0)); + glm::vec3 maximum = glm::vec3(_geometry->getFBXGeometry().offset * glm::vec4(extents.maximum, 1.0)); + Extents scaledExtents = { minimum * _scale, maximum * _scale }; return scaledExtents; } @@ -447,9 +451,12 @@ Extents Model::getUnscaledMeshExtents() const { const Extents& extents = _geometry->getFBXGeometry().meshExtents; - // even though our caller asked for "unscaled" we need to include any fst scaling - float scale = _geometry->getFBXGeometry().fstScaled; - Extents scaledExtents = { extents.minimum * scale, extents.maximum * scale }; + // even though our caller asked for "unscaled" we need to include any fst scaling, translation, and rotation, which + // is captured in the offset matrix + glm::vec3 minimum = glm::vec3(_geometry->getFBXGeometry().offset * glm::vec4(extents.minimum, 1.0)); + glm::vec3 maximum = glm::vec3(_geometry->getFBXGeometry().offset * glm::vec4(extents.maximum, 1.0)); + Extents scaledExtents = { minimum, maximum }; + return scaledExtents; } diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp index 44ef3f3aab..897c9b6f98 100644 --- a/libraries/fbx/src/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -1399,7 +1399,6 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) // get offset transform from mapping float offsetScale = mapping.value("scale", 1.0f).toFloat(); - geometry.fstScaled = offsetScale; glm::quat offsetRotation = glm::quat(glm::radians(glm::vec3(mapping.value("rx").toFloat(), mapping.value("ry").toFloat(), mapping.value("rz").toFloat()))); geometry.offset = glm::translate(glm::vec3(mapping.value("tx").toFloat(), mapping.value("ty").toFloat(), diff --git a/libraries/fbx/src/FBXReader.h b/libraries/fbx/src/FBXReader.h index 2aabdab6fa..38251e4065 100644 --- a/libraries/fbx/src/FBXReader.h +++ b/libraries/fbx/src/FBXReader.h @@ -211,8 +211,6 @@ public: Extents bindExtents; Extents meshExtents; - float fstScaled; - QVector animationFrames; QVector attachments; From 8a1fdf3486a65e554f0cf692b75c073ffd4339fd Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 20 May 2014 14:38:51 -0700 Subject: [PATCH 11/39] pass wallet UUID from AC to DS, cleanup noisy timer --- assignment-client/src/AssignmentClient.cpp | 11 ++++++++ domain-server/src/DomainServer.cpp | 32 ++++++++++++---------- libraries/networking/src/Assignment.cpp | 16 +++++++++-- libraries/networking/src/Assignment.h | 4 +++ 4 files changed, 47 insertions(+), 16 deletions(-) diff --git a/assignment-client/src/AssignmentClient.cpp b/assignment-client/src/AssignmentClient.cpp index b3a92f2f54..009bd42e88 100644 --- a/assignment-client/src/AssignmentClient.cpp +++ b/assignment-client/src/AssignmentClient.cpp @@ -67,9 +67,20 @@ AssignmentClient::AssignmentClient(int &argc, char **argv) : if (argumentIndex != -1) { assignmentPool = argumentList[argumentIndex + 1]; } + // setup our _requestAssignment member variable from the passed arguments _requestAssignment = Assignment(Assignment::RequestCommand, requestAssignmentType, assignmentPool); + // check if we were passed a wallet UUID on the command line + // this would represent where the user running AC wants funds sent to + + const QString ASSIGNMENT_WALLET_DESTINATION_ID_OPTION = "--wallet"; + if ((argumentIndex = argumentList.indexOf(ASSIGNMENT_WALLET_DESTINATION_ID_OPTION)) != -1) { + QUuid walletUUID = QString(argumentList[argumentIndex + 1]); + qDebug() << "The destination wallet UUID for credits is" << uuidStringWithoutCurlyBraces(walletUUID); + _requestAssignment.setWalletUUID(walletUUID); + } + // create a NodeList as an unassigned client NodeList* nodeList = NodeList::createInstance(NodeType::Unassigned); diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index f6af63fd7a..bcce081d94 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -564,14 +564,21 @@ void DomainServer::readAvailableDatagrams() { Assignment requestAssignment(receivedPacket); // Suppress these for Assignment::AgentType to once per 5 seconds - static quint64 lastNoisyMessage = usecTimestampNow(); - quint64 timeNow = usecTimestampNow(); - const quint64 NOISY_TIME_ELAPSED = 5 * USECS_PER_SECOND; - bool noisyMessage = false; - if (requestAssignment.getType() != Assignment::AgentType || (timeNow - lastNoisyMessage) > NOISY_TIME_ELAPSED) { + static QElapsedTimer noisyMessageTimer; + static bool wasNoisyTimerStarted = false; + + if (!wasNoisyTimerStarted) { + noisyMessageTimer.start(); + wasNoisyTimerStarted = true; + } + + const quint64 NOISY_MESSAGE_INTERVAL_MSECS = 5 * 1000; + + if (requestAssignment.getType() != Assignment::AgentType + || noisyMessageTimer.elapsed() > NOISY_MESSAGE_INTERVAL_MSECS) { qDebug() << "Received a request for assignment type" << requestAssignment.getType() - << "from" << senderSockAddr; - noisyMessage = true; + << "from" << senderSockAddr; + noisyMessageTimer.restart(); } SharedAssignmentPointer assignmentToDeploy = deployableAssignmentForRequest(requestAssignment); @@ -589,16 +596,13 @@ void DomainServer::readAvailableDatagrams() { nodeList->getNodeSocket().writeDatagram(assignmentPacket, senderSockAddr.getAddress(), senderSockAddr.getPort()); } else { - if (requestAssignment.getType() != Assignment::AgentType || (timeNow - lastNoisyMessage) > NOISY_TIME_ELAPSED) { + if (requestAssignment.getType() != Assignment::AgentType + || noisyMessageTimer.elapsed() > NOISY_MESSAGE_INTERVAL_MSECS) { qDebug() << "Unable to fulfill assignment request of type" << requestAssignment.getType() - << "from" << senderSockAddr; - noisyMessage = true; + << "from" << senderSockAddr; + noisyMessageTimer.restart(); } } - - if (noisyMessage) { - lastNoisyMessage = timeNow; - } } else if (!_isUsingDTLS) { // not using DTLS, process datagram normally processDatagram(receivedPacket, senderSockAddr); diff --git a/libraries/networking/src/Assignment.cpp b/libraries/networking/src/Assignment.cpp index 925ed2930f..fd6ecd4602 100644 --- a/libraries/networking/src/Assignment.cpp +++ b/libraries/networking/src/Assignment.cpp @@ -63,7 +63,8 @@ Assignment::Assignment(Assignment::Command command, Assignment::Type type, const _pool(pool), _location(location), _payload(), - _isStatic(false) + _isStatic(false), + _walletUUID() { if (_command == Assignment::CreateCommand) { // this is a newly created assignment, generate a random UUID @@ -74,7 +75,8 @@ Assignment::Assignment(Assignment::Command command, Assignment::Type type, const Assignment::Assignment(const QByteArray& packet) : _pool(), _location(GlobalLocation), - _payload() + _payload(), + _walletUUID() { PacketType packetType = packetTypeForPacket(packet); @@ -104,6 +106,7 @@ Assignment::Assignment(const Assignment& otherAssignment) { _location = otherAssignment._location; _pool = otherAssignment._pool; _payload = otherAssignment._payload; + _walletUUID = otherAssignment._walletUUID; } Assignment& Assignment::operator=(const Assignment& rhsAssignment) { @@ -121,6 +124,7 @@ void Assignment::swap(Assignment& otherAssignment) { swap(_location, otherAssignment._location); swap(_pool, otherAssignment._pool); swap(_payload, otherAssignment._payload); + swap(_walletUUID, otherAssignment._walletUUID); } const char* Assignment::getTypeName() const { @@ -156,6 +160,10 @@ QDebug operator<<(QDebug debug, const Assignment &assignment) { QDataStream& operator<<(QDataStream &out, const Assignment& assignment) { out << (quint8) assignment._type << assignment._uuid << assignment._pool << assignment._payload; + if (assignment._command == Assignment::RequestCommand) { + out << assignment._walletUUID; + } + return out; } @@ -166,5 +174,9 @@ QDataStream& operator>>(QDataStream &in, Assignment& assignment) { in >> assignment._uuid >> assignment._pool >> assignment._payload; + if (assignment._command == Assignment::RequestCommand) { + in >> assignment._walletUUID; + } + return in; } diff --git a/libraries/networking/src/Assignment.h b/libraries/networking/src/Assignment.h index 1d97b08bb8..3898b84787 100644 --- a/libraries/networking/src/Assignment.h +++ b/libraries/networking/src/Assignment.h @@ -81,6 +81,9 @@ public: void setIsStatic(bool isStatic) { _isStatic = isStatic; } bool isStatic() const { return _isStatic; } + void setWalletUUID(const QUuid& walletUUID) { _walletUUID = walletUUID; } + const QUuid& getWalletUUID() const { return _walletUUID; } + const char* getTypeName() const; // implement parseData to return 0 so we can be a subclass of NodeData @@ -98,6 +101,7 @@ protected: Assignment::Location _location; /// the location of the assignment, allows a domain to preferentially use local ACs QByteArray _payload; /// an optional payload attached to this assignment, a maximum for 1024 bytes will be packed bool _isStatic; /// defines if this assignment needs to be re-queued in the domain-server if it stops being fulfilled + QUuid _walletUUID; /// the UUID for the wallet that should be paid for this assignment }; #endif // hifi_Assignment_h From e7425a81288edb25ee0a78683ae4a74b0ebfdb3b Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Tue, 20 May 2014 15:20:27 -0700 Subject: [PATCH 12/39] support for models in root element --- libraries/models/src/ModelTree.h | 1 + libraries/networking/src/PacketHeaders.cpp | 6 +++--- libraries/octree/src/Octree.cpp | 24 +++++++++++++++++++++- libraries/octree/src/Octree.h | 1 + 4 files changed, 28 insertions(+), 4 deletions(-) diff --git a/libraries/models/src/ModelTree.h b/libraries/models/src/ModelTree.h index 10ef62c0a0..b8df1c1a22 100644 --- a/libraries/models/src/ModelTree.h +++ b/libraries/models/src/ModelTree.h @@ -41,6 +41,7 @@ public: virtual int processEditPacketData(PacketType packetType, const unsigned char* packetData, int packetLength, const unsigned char* editData, int maxLength, const SharedNodePointer& senderNode); + virtual bool rootElementHasData() const { return true; } virtual void update(); void storeModel(const ModelItem& model, const SharedNodePointer& senderNode = SharedNodePointer()); diff --git a/libraries/networking/src/PacketHeaders.cpp b/libraries/networking/src/PacketHeaders.cpp index b9eee6e0c9..0f0b57635c 100644 --- a/libraries/networking/src/PacketHeaders.cpp +++ b/libraries/networking/src/PacketHeaders.cpp @@ -53,8 +53,6 @@ PacketVersion versionForPacketType(PacketType type) { return 1; case PacketTypeEnvironmentData: return 1; - case PacketTypeParticleData: - return 1; case PacketTypeDomainList: case PacketTypeDomainListRequest: return 3; @@ -66,8 +64,10 @@ PacketVersion versionForPacketType(PacketType type) { return 1; case PacketTypeOctreeStats: return 1; + case PacketTypeParticleData: + return 1; case PacketTypeModelData: - return 1; + return 2; default: return 0; } diff --git a/libraries/octree/src/Octree.cpp b/libraries/octree/src/Octree.cpp index 3042626a51..fd0cbcff7c 100644 --- a/libraries/octree/src/Octree.cpp +++ b/libraries/octree/src/Octree.cpp @@ -301,6 +301,13 @@ int Octree::readElementData(OctreeElement* destinationElement, const unsigned ch } } } + + // if this is the root, and there is more data to read, allow it to read it's element data... + if (destinationElement == _rootElement && rootElementHasData() && (bytesLeftToRead - bytesRead) > 0) { + // tell the element to read the subsequent data + bytesRead += _rootElement->readElementDataFromBuffer(nodeData + bytesRead, bytesLeftToRead - bytesRead, args); + } + return bytesRead; } @@ -1524,7 +1531,22 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElement* element, } } // end keepDiggingDeeper - // At this point all our BitMasks are complete... so let's output them to see how they compare... + // If we made it this far, then we've written all of our child data... if this element is the root + // element, then we also allow the root element to write out it's data... + if (continueThisLevel && element == _rootElement && rootElementHasData()) { + int bytesBeforeChild = packetData->getUncompressedSize(); + continueThisLevel = element->appendElementData(packetData, params); + int bytesAfterChild = packetData->getUncompressedSize(); + + if (continueThisLevel) { + bytesAtThisLevel += (bytesAfterChild - bytesBeforeChild); // keep track of byte count for this child + + if (params.stats) { + params.stats->colorSent(element); + } + } + } + // if we were unable to fit this level in our packet, then rewind and add it to the element bag for // sending later... if (continueThisLevel) { diff --git a/libraries/octree/src/Octree.h b/libraries/octree/src/Octree.h index 1e71884faa..81a9823dd5 100644 --- a/libraries/octree/src/Octree.h +++ b/libraries/octree/src/Octree.h @@ -211,6 +211,7 @@ public: const unsigned char* editData, int maxLength, const SharedNodePointer& sourceNode) { return 0; } virtual bool recurseChildrenWithData() const { return true; } + virtual bool rootElementHasData() const { return false; } virtual void update() { }; // nothing to do by default From 49a06456774b9c147ee3564ba3d976b1d5159fa7 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Tue, 20 May 2014 15:23:15 -0700 Subject: [PATCH 13/39] Animation bits. --- interface/src/avatar/MyAvatar.cpp | 54 ++++++------ interface/src/avatar/MyAvatar.h | 33 ++----- interface/src/renderer/GeometryCache.cpp | 16 ++++ interface/src/renderer/GeometryCache.h | 6 ++ interface/src/renderer/Model.cpp | 106 ++++++++++++++++++----- interface/src/renderer/Model.h | 65 ++++++++++---- interface/src/ui/AnimationsDialog.cpp | 47 +++++----- interface/src/ui/AnimationsDialog.h | 17 ++-- 8 files changed, 216 insertions(+), 128 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index fd4b64ee5d..e7638cc56d 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -423,6 +423,19 @@ void MyAvatar::setGravity(const glm::vec3& gravity) { } } +AnimationHandlePointer MyAvatar::addAnimationHandle() { + AnimationHandlePointer handle = _skeletonModel.createAnimationHandle(); + handle->setLoop(true); + handle->start(); + _animationHandles.append(handle); + return handle; +} + +void MyAvatar::removeAnimationHandle(const AnimationHandlePointer& handle) { + handle->stop(); + _animationHandles.removeOne(handle); +} + void MyAvatar::saveData(QSettings* settings) { settings->beginGroup("Avatar"); @@ -461,12 +474,12 @@ void MyAvatar::saveData(QSettings* settings) { } settings->endArray(); - settings->beginWriteArray("animationData"); - for (int i = 0; i < _animationData.size(); i++) { + settings->beginWriteArray("animationHandles"); + for (int i = 0; i < _animationHandles.size(); i++) { settings->setArrayIndex(i); - const AnimationData& animation = _animationData.at(i); - settings->setValue("url", animation.url); - settings->setValue("fps", animation.fps); + const AnimationHandlePointer& pointer = _animationHandles.at(i); + settings->setValue("url", pointer->getURL()); + settings->setValue("fps", pointer->getFPS()); } settings->endArray(); @@ -520,17 +533,20 @@ void MyAvatar::loadData(QSettings* settings) { settings->endArray(); setAttachmentData(attachmentData); - QVector animationData; - int animationCount = settings->beginReadArray("animationData"); + int animationCount = settings->beginReadArray("animationHandles"); + while (_animationHandles.size() > animationCount) { + _animationHandles.takeLast()->stop(); + } + while (_animationHandles.size() < animationCount) { + addAnimationHandle(); + } for (int i = 0; i < animationCount; i++) { settings->setArrayIndex(i); - AnimationData animation; - animation.url = settings->value("url").toUrl(); - animation.fps = loadSetting(settings, "fps", 30.0f); - animationData.append(animation); + const AnimationHandlePointer& handle = _animationHandles.at(i); + handle->setURL(settings->value("url").toUrl()); + handle->setFPS(loadSetting(settings, "fps", 30.0f)); } settings->endArray(); - setAnimationData(animationData); setDisplayName(settings->value("displayName").toString()); @@ -598,13 +614,6 @@ AttachmentData MyAvatar::loadAttachmentData(const QUrl& modelURL, const QString& return attachment; } -void MyAvatar::setAnimationData(const QVector& animationData) { - // exit early if no change - if (_animationData != animationData) { - _animationData = animationData; - } -} - int MyAvatar::parseDataAtOffset(const QByteArray& packet, int offset) { qDebug() << "Error: ignoring update packet for MyAvatar" << " packetLength = " << packet.size() @@ -1570,10 +1579,3 @@ void MyAvatar::applyCollision(const glm::vec3& contactPoint, const glm::vec3& pe } } -AnimationData::AnimationData() : - fps(30.0f) { -} - -bool AnimationData::operator==(const AnimationData& other) const { - return url == other.url && fps == other.fps; -} diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 4e076e22bb..50b8fceca3 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -14,12 +14,8 @@ #include -#include - #include "Avatar.h" -class AnimationData; - enum AvatarHandState { HAND_STATE_NULL = 0, @@ -66,6 +62,10 @@ public: glm::vec3 getUprightHeadPosition() const; bool getShouldRenderLocally() const { return _shouldRender; } + const QList& getAnimationHandles() const { return _animationHandles; } + AnimationHandlePointer addAnimationHandle(); + void removeAnimationHandle(const AnimationHandlePointer& handle); + // get/set avatar data void saveData(QSettings* settings); void loadData(QSettings* settings); @@ -73,9 +73,6 @@ public: void saveAttachmentData(const AttachmentData& attachment) const; AttachmentData loadAttachmentData(const QUrl& modelURL, const QString& jointName = QString()) const; - void setAnimationData(const QVector& animationData); - const QVector& getAnimationData() const { return _animationData; } - // Set what driving keys are being pressed to control thrust levels void setDriveKeys(int key, float val) { _driveKeys[key] = val; }; bool getDriveKeys(int key) { return _driveKeys[key] != 0.f; }; @@ -158,16 +155,7 @@ private: bool _billboardValid; float _oculusYawOffset; - QVector _animationData; - - class AnimationState { - public: - AnimationPointer animation; - QVector jointMappings; - float frameIndex; - }; - - QVector _animationStates; + QList _animationHandles; // private methods void updateOrientation(float deltaTime); @@ -185,15 +173,4 @@ private: void setGravity(const glm::vec3& gravity); }; -/// Describes an animation being run on the avatar. -class AnimationData { -public: - QUrl url; - float fps; - - AnimationData(); - - bool operator==(const AnimationData& other) const; -}; - #endif // hifi_MyAvatar_h diff --git a/interface/src/renderer/GeometryCache.cpp b/interface/src/renderer/GeometryCache.cpp index ec68c87a76..b5bd63ab87 100644 --- a/interface/src/renderer/GeometryCache.cpp +++ b/interface/src/renderer/GeometryCache.cpp @@ -404,6 +404,22 @@ QSharedPointer NetworkGeometry::getLODOrFallback(float distance return lod; } +uint qHash(const QWeakPointer& animation, uint seed = 0) { + return qHash(animation.data(), seed); +} + +QVector NetworkGeometry::getJointMappings(const AnimationPointer& animation) { + QVector mappings = _jointMappings.value(animation); + if (mappings.isEmpty() && isLoaded() && animation && animation->isLoaded()) { + const FBXGeometry& animationGeometry = animation->getGeometry(); + for (int i = 0; i < animationGeometry.joints.size(); i++) { + mappings.append(_geometry.jointIndices.value(animationGeometry.joints.at(i).name) - 1); + } + _jointMappings.insert(animation, mappings); + } + return mappings; +} + void NetworkGeometry::setLoadPriority(const QPointer& owner, float priority) { Resource::setLoadPriority(owner, priority); diff --git a/interface/src/renderer/GeometryCache.h b/interface/src/renderer/GeometryCache.h index deecfd56c5..41bedc5e05 100644 --- a/interface/src/renderer/GeometryCache.h +++ b/interface/src/renderer/GeometryCache.h @@ -22,6 +22,8 @@ #include +#include + class Model; class NetworkGeometry; class NetworkMesh; @@ -90,6 +92,8 @@ public: const FBXGeometry& getFBXGeometry() const { return _geometry; } const QVector& getMeshes() const { return _meshes; } + QVector getJointMappings(const AnimationPointer& animation); + virtual void setLoadPriority(const QPointer& owner, float priority); virtual void setLoadPriorities(const QHash, float>& priorities); virtual void clearLoadPriority(const QPointer& owner); @@ -117,6 +121,8 @@ private: QVector _meshes; QWeakPointer _lodParent; + + QHash, QVector > _jointMappings; }; /// The state associated with a single mesh part. diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index 7e1ba69e27..3914f46fe7 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -594,13 +594,15 @@ QStringList Model::getJointNames() const { return isActive() ? _geometry->getFBXGeometry().getJointNames() : QStringList(); } -void Model::startAnimation(const QUrl& url, float fps, bool loop, float offset) { - AnimationState state = { Application::getInstance()->getAnimationCache()->getAnimation(url), fps, loop, offset }; - _animationStates.append(state); +uint qHash(const WeakAnimationHandlePointer& handle, uint seed) { + return qHash(handle.data(), seed); } -void Model::stopAnimation() { - _animationStates.clear(); +AnimationHandlePointer Model::createAnimationHandle() { + AnimationHandlePointer handle(new AnimationHandle(this)); + handle->_self = handle; + _animationHandles.insert(handle); + return handle; } void Model::clearShapes() { @@ -1015,19 +1017,8 @@ void Model::simulate(float deltaTime, bool fullUpdate) { void Model::simulateInternal(float deltaTime) { // update animations - const FBXGeometry& geometry = _geometry->getFBXGeometry(); - for (int i = 0; i < _animationStates.size(); i++) { - AnimationState& state = _animationStates[i]; - if (!(state.animation && state.animation->isLoaded())) { - continue; - } - const FBXGeometry& animationGeometry = state.animation->getGeometry(); - if (state.jointMappings.isEmpty()) { - for (int j = 0; j < geometry.joints.size(); j++) { - state.jointMappings.append(animationGeometry.jointIndices.value(geometry.joints.at(j).name) - 1); - } - } - + foreach (const AnimationHandlePointer& handle, _runningAnimations) { + handle->simulate(deltaTime); } // NOTE: this is a recursive call that walks all attachments, and their attachments @@ -1038,6 +1029,7 @@ void Model::simulateInternal(float deltaTime) { _shapesAreDirty = true; // update the attachment transforms and simulate them + const FBXGeometry& geometry = _geometry->getFBXGeometry(); for (int i = 0; i < _attachments.size(); i++) { const FBXAttachment& attachment = geometry.attachments.at(i); Model* model = _attachments.at(i); @@ -1448,8 +1440,14 @@ void Model::deleteGeometry() { _meshStates.clear(); clearShapes(); - for (int i = 0; i < _animationStates.size(); i++) { - _animationStates[i].jointMappings.clear(); + for (QSet::iterator it = _animationHandles.begin(); it != _animationHandles.end(); ) { + AnimationHandlePointer handle = it->toStrongRef(); + if (handle) { + handle->_jointMappings.clear(); + it++; + } else { + it = _animationHandles.erase(it); + } } if (_geometry) { @@ -1647,3 +1645,71 @@ void Model::renderMeshes(float alpha, RenderMode mode, bool translucent) { activeProgram->release(); } } + +void AnimationHandle::setURL(const QUrl& url) { + _animation = Application::getInstance()->getAnimationCache()->getAnimation(_url = url); + _jointMappings.clear(); +} + +void AnimationHandle::start() { + if (!_model->_runningAnimations.contains(_self)) { + _model->_runningAnimations.append(_self); + } + _frameIndex = 0.0f; +} + +void AnimationHandle::stop() { + _model->_runningAnimations.removeOne(_self); +} + +AnimationHandle::AnimationHandle(Model* model) : + QObject(model), + _model(model), + _fps(30.0f), + _loop(false) { +} + +void AnimationHandle::simulate(float deltaTime) { + _frameIndex += deltaTime * _fps; + + // update the joint mappings if necessary/possible + if (_jointMappings.isEmpty()) { + if (_model->isActive()) { + _jointMappings = _model->getGeometry()->getJointMappings(_animation); + } + if (_jointMappings.isEmpty()) { + return; + } + } + + const FBXGeometry& animationGeometry = _animation->getGeometry(); + if (animationGeometry.animationFrames.isEmpty()) { + stop(); + return; + } + int ceilFrameIndex = (int)glm::ceil(_frameIndex); + if (!_loop && ceilFrameIndex >= animationGeometry.animationFrames.size()) { + const FBXAnimationFrame& frame = animationGeometry.animationFrames.last(); + for (int i = 0; i < _jointMappings.size(); i++) { + int mapping = _jointMappings.at(i); + if (mapping != -1) { + _model->_jointStates[mapping].rotation = frame.rotations.at(i); + } + } + stop(); + return; + } + const FBXAnimationFrame& ceilFrame = animationGeometry.animationFrames.at( + ceilFrameIndex % animationGeometry.animationFrames.size()); + const FBXAnimationFrame& floorFrame = animationGeometry.animationFrames.at( + (int)glm::floor(_frameIndex) % animationGeometry.animationFrames.size()); + float frameFraction = glm::fract(_frameIndex); + for (int i = 0; i < _jointMappings.size(); i++) { + int mapping = _jointMappings.at(i); + if (mapping != -1) { + _model->_jointStates[mapping].rotation = safeMix(floorFrame.rotations.at(i), + ceilFrame.rotations.at(i), frameFraction); + } + } +} + diff --git a/interface/src/renderer/Model.h b/interface/src/renderer/Model.h index 5b8e640401..b8b877ac96 100644 --- a/interface/src/renderer/Model.h +++ b/interface/src/renderer/Model.h @@ -24,8 +24,12 @@ #include "ProgramObject.h" #include "TextureCache.h" +class AnimationHandle; class Shape; +typedef QSharedPointer AnimationHandlePointer; +typedef QWeakPointer WeakAnimationHandlePointer; + /// A generic 3D model displaying geometry loaded from a URL. class Model : public QObject { Q_OBJECT @@ -187,22 +191,7 @@ public: QStringList getJointNames() const; - class AnimationState { - public: - AnimationPointer animation; - float fps; - bool loop; - float offset; - QVector jointMappings; - }; - - const QVector& getAnimationStates() const { return _animationStates; } - - /// Starts playing the animation at the specified URL. - void startAnimation(const QUrl& url, float fps = 30.0f, bool loop = true, float offset = 0.0f); - - /// Stops playing all animations. - void stopAnimation(); + AnimationHandlePointer createAnimationHandle(); void clearShapes(); void rebuildShapes(); @@ -279,8 +268,6 @@ protected: QVector _meshStates; - QVector _animationStates; - // returns 'true' if needs fullUpdate after geometry change bool updateGeometry(); @@ -320,6 +307,8 @@ protected: private: + friend class AnimationHandle; + void applyNextGeometry(); void deleteGeometry(); void renderMeshes(float alpha, RenderMode mode, bool translucent); @@ -343,6 +332,10 @@ private: QVector _attachments; + QSet _animationHandles; + + QList _runningAnimations; + static ProgramObject _program; static ProgramObject _normalMapProgram; static ProgramObject _specularMapProgram; @@ -378,4 +371,40 @@ Q_DECLARE_METATYPE(QPointer) Q_DECLARE_METATYPE(QWeakPointer) Q_DECLARE_METATYPE(QVector) +/// Represents a handle to a model animation. +class AnimationHandle : public QObject { + Q_OBJECT + +public: + + void setURL(const QUrl& url); + const QUrl& getURL() const { return _url; } + + void setFPS(float fps) { _fps = fps; } + float getFPS() const { return _fps; } + + void setLoop(bool loop) { _loop = loop; } + bool getLoop() const { return _loop; } + + void start(); + void stop(); + +private: + + friend class Model; + + AnimationHandle(Model* model); + + void simulate(float deltaTime); + + Model* _model; + WeakAnimationHandlePointer _self; + AnimationPointer _animation; + QUrl _url; + float _fps; + bool _loop; + QVector _jointMappings; + float _frameIndex; +}; + #endif // hifi_Model_h diff --git a/interface/src/ui/AnimationsDialog.cpp b/interface/src/ui/AnimationsDialog.cpp index 5285c294ab..2beea24578 100644 --- a/interface/src/ui/AnimationsDialog.cpp +++ b/interface/src/ui/AnimationsDialog.cpp @@ -39,8 +39,8 @@ AnimationsDialog::AnimationsDialog() : area->setWidget(container); _animations->addStretch(1); - foreach (const AnimationData& data, Application::getInstance()->getAvatar()->getAnimationData()) { - addAnimation(data); + foreach (const AnimationHandlePointer& handle, Application::getInstance()->getAvatar()->getAnimationHandles()) { + _animations->insertWidget(_animations->count() - 1, new AnimationPanel(this, handle)); } QPushButton* newAnimation = new QPushButton("New Animation"); @@ -64,20 +64,14 @@ void AnimationsDialog::setVisible(bool visible) { } } -void AnimationsDialog::updateAnimationData() { - QVector data; - for (int i = 0; i < _animations->count() - 1; i++) { - data.append(static_cast(_animations->itemAt(i)->widget())->getAnimationData()); - } - Application::getInstance()->getAvatar()->setAnimationData(data); +void AnimationsDialog::addAnimation() { + _animations->insertWidget(_animations->count() - 1, new AnimationPanel( + this, Application::getInstance()->getAvatar()->addAnimationHandle())); } -void AnimationsDialog::addAnimation(const AnimationData& data) { - _animations->insertWidget(_animations->count() - 1, new AnimationPanel(this, data)); -} - -AnimationPanel::AnimationPanel(AnimationsDialog* dialog, const AnimationData& data) : +AnimationPanel::AnimationPanel(AnimationsDialog* dialog, const AnimationHandlePointer& handle) : _dialog(dialog), + _handle(handle), _applying(false) { setFrameStyle(QFrame::StyledPanel); @@ -87,8 +81,8 @@ AnimationPanel::AnimationPanel(AnimationsDialog* dialog, const AnimationData& da QHBoxLayout* urlBox = new QHBoxLayout(); layout->addRow("URL:", urlBox); - urlBox->addWidget(_url = new QLineEdit(data.url.toString()), 1); - dialog->connect(_url, SIGNAL(returnPressed()), SLOT(updateAnimationData())); + urlBox->addWidget(_url = new QLineEdit(handle->getURL().toString()), 1); + connect(_url, SIGNAL(returnPressed()), SLOT(updateHandle())); QPushButton* chooseURL = new QPushButton("Choose"); urlBox->addWidget(chooseURL); connect(chooseURL, SIGNAL(clicked(bool)), SLOT(chooseURL())); @@ -96,20 +90,12 @@ AnimationPanel::AnimationPanel(AnimationsDialog* dialog, const AnimationData& da layout->addRow("FPS:", _fps = new QDoubleSpinBox()); _fps->setSingleStep(0.01); _fps->setMaximum(FLT_MAX); - _fps->setValue(data.fps); - dialog->connect(_fps, SIGNAL(valueChanged(double)), SLOT(updateAnimationData())); + _fps->setValue(handle->getFPS()); + connect(_fps, SIGNAL(valueChanged(double)), SLOT(updateHandle())); QPushButton* remove = new QPushButton("Delete"); layout->addRow(remove); - connect(remove, SIGNAL(clicked(bool)), SLOT(deleteLater())); - dialog->connect(remove, SIGNAL(clicked(bool)), SLOT(updateAnimationData()), Qt::QueuedConnection); -} - -AnimationData AnimationPanel::getAnimationData() const { - AnimationData data; - data.url = _url->text(); - data.fps = _fps->value(); - return data; + connect(remove, SIGNAL(clicked(bool)), SLOT(removeHandle())); } void AnimationPanel::chooseURL() { @@ -125,3 +111,12 @@ void AnimationPanel::chooseURL() { emit _url->returnPressed(); } +void AnimationPanel::updateHandle() { + _handle->setURL(_url->text()); + _handle->setFPS(_fps->value()); +} + +void AnimationPanel::removeHandle() { + Application::getInstance()->getAvatar()->removeAnimationHandle(_handle); + deleteLater(); +} diff --git a/interface/src/ui/AnimationsDialog.h b/interface/src/ui/AnimationsDialog.h index 3d969aa32a..d53b3dd6a5 100644 --- a/interface/src/ui/AnimationsDialog.h +++ b/interface/src/ui/AnimationsDialog.h @@ -32,14 +32,10 @@ public: virtual void setVisible(bool visible); -public slots: - - void updateAnimationData(); - private slots: - - void addAnimation(const AnimationData& animation = AnimationData()); - + + void addAnimation(); + private: QVBoxLayout* _animations; @@ -52,17 +48,18 @@ class AnimationPanel : public QFrame { public: - AnimationPanel(AnimationsDialog* dialog, const AnimationData& data = AnimationData()); - - AnimationData getAnimationData() const; + AnimationPanel(AnimationsDialog* dialog, const AnimationHandlePointer& handle); private slots: void chooseURL(); + void updateHandle(); + void removeHandle(); private: AnimationsDialog* _dialog; + AnimationHandlePointer _handle; QLineEdit* _url; QDoubleSpinBox* _fps; bool _applying; From b42e005cdbc3b205e460967493fd93e438d4555b Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 20 May 2014 15:28:45 -0700 Subject: [PATCH 14/39] add pending assignment purgatory for assignees --- domain-server/src/DomainServer.cpp | 48 ++++++++++++++----- domain-server/src/DomainServer.h | 3 ++ domain-server/src/PendingAssignedNodeData.cpp | 19 ++++++++ domain-server/src/PendingAssignedNodeData.h | 33 +++++++++++++ 4 files changed, 91 insertions(+), 12 deletions(-) create mode 100644 domain-server/src/PendingAssignedNodeData.cpp create mode 100644 domain-server/src/PendingAssignedNodeData.h diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index bcce081d94..816e014c23 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -35,6 +35,7 @@ DomainServer::DomainServer(int argc, char* argv[]) : _httpsManager(NULL), _allAssignments(), _unfulfilledAssignments(), + _pendingAssignedNodes(), _isUsingDTLS(false), _oauthProviderURL(), _oauthClientID(), @@ -339,9 +340,9 @@ void DomainServer::handleConnectRequest(const QByteArray& packet, const HifiSock QUuid packetUUID = uuidFromPacketHeader(packet); // check if this connect request matches an assignment in the queue - bool isFulfilledOrUnfulfilledAssignment = _allAssignments.contains(packetUUID); + bool isAssignment = _pendingAssignedNodes.contains(packetUUID); SharedAssignmentPointer matchingQueuedAssignment = SharedAssignmentPointer(); - if (isFulfilledOrUnfulfilledAssignment) { + if (isAssignment) { matchingQueuedAssignment = matchingQueuedAssignmentForCheckIn(packetUUID, nodeType); } @@ -371,8 +372,8 @@ void DomainServer::handleConnectRequest(const QByteArray& packet, const HifiSock } } - if ((!isFulfilledOrUnfulfilledAssignment && !STATICALLY_ASSIGNED_NODES.contains(nodeType)) - || (isFulfilledOrUnfulfilledAssignment && matchingQueuedAssignment)) { + if ((!isAssignment && !STATICALLY_ASSIGNED_NODES.contains(nodeType)) + || (isAssignment && matchingQueuedAssignment)) { // this was either not a static assignment or it was and we had a matching one in the queue // create a new session UUID for this node @@ -384,8 +385,8 @@ void DomainServer::handleConnectRequest(const QByteArray& packet, const HifiSock // if this was a static assignment set the UUID, set the sendingSockAddr DomainServerNodeData* nodeData = reinterpret_cast(newNode->getLinkedData()); - if (isFulfilledOrUnfulfilledAssignment) { - nodeData->setAssignmentUUID(packetUUID); + if (isAssignment) { + nodeData->setAssignmentUUID(matchingQueuedAssignment->getUUID()); } nodeData->setSendingSockAddr(senderSockAddr); @@ -589,12 +590,21 @@ void DomainServer::readAvailableDatagrams() { // give this assignment out, either the type matches or the requestor said they will take any assignmentPacket.resize(numAssignmentPacketHeaderBytes); + // setup a copy of this assignment that will have a unique UUID, for packaging purposes + Assignment uniqueAssignment(*assignmentToDeploy.data()); + uniqueAssignment.setUUID(QUuid::createUuid()); + QDataStream assignmentStream(&assignmentPacket, QIODevice::Append); - assignmentStream << *assignmentToDeploy.data(); + assignmentStream << uniqueAssignment; nodeList->getNodeSocket().writeDatagram(assignmentPacket, senderSockAddr.getAddress(), senderSockAddr.getPort()); + + // add the information for that deployed assignment to the hash of pending assigned nodes + PendingAssignedNodeData* pendingNodeData = new PendingAssignedNodeData(assignmentToDeploy->getUUID(), + requestAssignment.getWalletUUID()); + _pendingAssignedNodes.insert(uniqueAssignment.getUUID(), pendingNodeData); } else { if (requestAssignment.getType() != Assignment::AgentType || noisyMessageTimer.elapsed() > NOISY_MESSAGE_INTERVAL_MSECS) { @@ -1069,11 +1079,25 @@ void DomainServer::nodeKilled(SharedNodePointer node) { SharedAssignmentPointer DomainServer::matchingQueuedAssignmentForCheckIn(const QUuid& checkInUUID, NodeType_t nodeType) { QQueue::iterator i = _unfulfilledAssignments.begin(); - while (i != _unfulfilledAssignments.end()) { - if (i->data()->getType() == Assignment::typeForNodeType(nodeType) && i->data()->getUUID() == checkInUUID) { - return _unfulfilledAssignments.takeAt(i - _unfulfilledAssignments.begin()); - } else { - ++i; + PendingAssignedNodeData* pendingAssigneeData = _pendingAssignedNodes.take(checkInUUID); + + if (pendingAssigneeData) { + while (i != _unfulfilledAssignments.end()) { + if (i->data()->getType() == Assignment::typeForNodeType(nodeType) + && i->data()->getUUID() == pendingAssigneeData->getAssignmentUUID()) { + // we have an unfulfilled assignment to return + qDebug() << "Assignment deployed with" << uuidStringWithoutCurlyBraces(checkInUUID) + << "matches unfulfilled assignment" + << uuidStringWithoutCurlyBraces(pendingAssigneeData->getAssignmentUUID()); + + // first clear the pending assignee data + delete pendingAssigneeData; + + // return the matching assignment + return _unfulfilledAssignments.takeAt(i - _unfulfilledAssignments.begin()); + } else { + ++i; + } } } diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index 71809d9e16..63a3afa13d 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -24,6 +24,8 @@ #include #include +#include "PendingAssignedNodeData.h" + typedef QSharedPointer SharedAssignmentPointer; class DomainServer : public QCoreApplication, public HTTPSRequestHandler { @@ -85,6 +87,7 @@ private: QHash _allAssignments; QQueue _unfulfilledAssignments; + QHash _pendingAssignedNodes; QVariantMap _argumentVariantMap; diff --git a/domain-server/src/PendingAssignedNodeData.cpp b/domain-server/src/PendingAssignedNodeData.cpp new file mode 100644 index 0000000000..21b3aa4ca4 --- /dev/null +++ b/domain-server/src/PendingAssignedNodeData.cpp @@ -0,0 +1,19 @@ +// +// PendingAssignedNodeData.cpp +// domain-server/src +// +// Created by Stephen Birarda on 2014-05-20. +// 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 "PendingAssignedNodeData.h" + +PendingAssignedNodeData::PendingAssignedNodeData(const QUuid& assignmentUUID, const QUuid& walletUUID) : + _assignmentUUID(assignmentUUID), + _walletUUID(walletUUID) +{ + +} \ No newline at end of file diff --git a/domain-server/src/PendingAssignedNodeData.h b/domain-server/src/PendingAssignedNodeData.h new file mode 100644 index 0000000000..93d99a6f8f --- /dev/null +++ b/domain-server/src/PendingAssignedNodeData.h @@ -0,0 +1,33 @@ +// +// PendingAssignedNodeData.h +// domain-server/src +// +// Created by Stephen Birarda on 2014-05-20. +// 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_PendingAssignedNodeData_h +#define hifi_PendingAssignedNodeData_h + +#include +#include + +class PendingAssignedNodeData : public QObject { + Q_OBJECT +public: + PendingAssignedNodeData(const QUuid& assignmentUUID, const QUuid& walletUUID); + + void setAssignmentUUID(const QUuid& assignmentUUID) { _assignmentUUID = assignmentUUID; } + const QUuid& getAssignmentUUID() const { return _assignmentUUID; } + + void setWalletUUID(const QUuid& walletUUID) { _walletUUID = walletUUID; } + const QUuid& getWalletUUID() const { return _walletUUID; } +private: + QUuid _assignmentUUID; + QUuid _walletUUID; +}; + +#endif // hifi_PendingAssignedNodeData_h \ No newline at end of file From 412e870f98916163083e8c4fda35f8b84e365793 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Tue, 20 May 2014 15:32:50 -0700 Subject: [PATCH 15/39] add support for backward compatibility in old versions of models.svo --- libraries/models/src/ModelItem.h | 1 + libraries/models/src/ModelTreeElement.cpp | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/libraries/models/src/ModelItem.h b/libraries/models/src/ModelItem.h index 847e58e7c2..838dbc0fc8 100644 --- a/libraries/models/src/ModelItem.h +++ b/libraries/models/src/ModelItem.h @@ -56,6 +56,7 @@ const QString MODEL_DEFAULT_ANIMATION_URL(""); const float MODEL_DEFAULT_ANIMATION_FPS = 30.0f; const PacketVersion VERSION_MODELS_HAVE_ANIMATION = 1; +const PacketVersion VERSION_ROOT_ELEMENT_HAS_DATA = 2; /// 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 diff --git a/libraries/models/src/ModelTreeElement.cpp b/libraries/models/src/ModelTreeElement.cpp index 2f57818044..4929edb484 100644 --- a/libraries/models/src/ModelTreeElement.cpp +++ b/libraries/models/src/ModelTreeElement.cpp @@ -324,6 +324,14 @@ bool ModelTreeElement::removeModelWithID(uint32_t id) { int ModelTreeElement::readElementDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args) { + // If we're the root, but this bitstream doesn't support root elements with data, then + // return without reading any bytes + if (this == _myTree->getRoot() && args.bitstreamVersion < VERSION_ROOT_ELEMENT_HAS_DATA) { + qDebug() << "ROOT ELEMENT: no root data for " + "bitstreamVersion=" << (int)args.bitstreamVersion << " bytesLeftToRead=" << bytesLeftToRead; + return 0; + } + const unsigned char* dataAt = data; int bytesRead = 0; uint16_t numberOfModels = 0; From d3e5e3ccf180189e9ffdf3a6021a4a2d441584ea Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Tue, 20 May 2014 15:41:11 -0700 Subject: [PATCH 16/39] A couple explanatory comments. --- interface/src/renderer/Model.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index 3914f46fe7..439c5738f3 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -1689,6 +1689,7 @@ void AnimationHandle::simulate(float deltaTime) { } int ceilFrameIndex = (int)glm::ceil(_frameIndex); if (!_loop && ceilFrameIndex >= animationGeometry.animationFrames.size()) { + // passed the end; apply the last frame const FBXAnimationFrame& frame = animationGeometry.animationFrames.last(); for (int i = 0; i < _jointMappings.size(); i++) { int mapping = _jointMappings.at(i); @@ -1699,6 +1700,7 @@ void AnimationHandle::simulate(float deltaTime) { stop(); return; } + // blend between the closest two frames const FBXAnimationFrame& ceilFrame = animationGeometry.animationFrames.at( ceilFrameIndex % animationGeometry.animationFrames.size()); const FBXAnimationFrame& floorFrame = animationGeometry.animationFrames.at( From 13077e48f85f5aad79368db6ba6ab590461e6c2f Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 20 May 2014 15:45:08 -0700 Subject: [PATCH 17/39] handle types larger than 255 for packetVersionAndHashMatch --- libraries/networking/src/LimitedNodeList.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index 5692023ab1..b5a23f6b99 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -152,10 +152,11 @@ void LimitedNodeList::changeSendSocketBufferSize(int numSendBytes) { bool LimitedNodeList::packetVersionAndHashMatch(const QByteArray& packet) { PacketType checkType = packetTypeForPacket(packet); - if (packet[1] != versionForPacketType(checkType) + int numPacketTypeBytes = numBytesArithmeticCodingFromBuffer(packet.data()); + + if (packet[numPacketTypeBytes] != versionForPacketType(checkType) && checkType != PacketTypeStunResponse) { PacketType mismatchType = packetTypeForPacket(packet); - int numPacketTypeBytes = numBytesArithmeticCodingFromBuffer(packet.data()); static QMultiMap versionDebugSuppressMap; From 072343ce790dab3c362492bb27dc14b268ec4b8b Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 20 May 2014 15:50:09 -0700 Subject: [PATCH 18/39] associate assignment wallet UUID with node via DomainServerNodeData --- domain-server/src/DomainServer.cpp | 47 ++++++++++++---------- domain-server/src/DomainServerNodeData.cpp | 1 + domain-server/src/DomainServerNodeData.h | 4 ++ 3 files changed, 31 insertions(+), 21 deletions(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 816e014c23..56d645ed24 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -342,8 +342,19 @@ void DomainServer::handleConnectRequest(const QByteArray& packet, const HifiSock // check if this connect request matches an assignment in the queue bool isAssignment = _pendingAssignedNodes.contains(packetUUID); SharedAssignmentPointer matchingQueuedAssignment = SharedAssignmentPointer(); + PendingAssignedNodeData* pendingAssigneeData = NULL; if (isAssignment) { - matchingQueuedAssignment = matchingQueuedAssignmentForCheckIn(packetUUID, nodeType); + pendingAssigneeData = _pendingAssignedNodes.take(packetUUID); + if (pendingAssigneeData) { + matchingQueuedAssignment = matchingQueuedAssignmentForCheckIn(packetUUID, nodeType); + + if (matchingQueuedAssignment) { + qDebug() << "Assignment deployed with" << uuidStringWithoutCurlyBraces(packetUUID) + << "matches unfulfilled assignment" + << uuidStringWithoutCurlyBraces(matchingQueuedAssignment->getUUID()); + } + } + } if (!matchingQueuedAssignment && !_oauthProviderURL.isEmpty() && _argumentVariantMap.contains(ALLOWED_ROLES_CONFIG_KEY)) { @@ -387,6 +398,10 @@ void DomainServer::handleConnectRequest(const QByteArray& packet, const HifiSock if (isAssignment) { nodeData->setAssignmentUUID(matchingQueuedAssignment->getUUID()); + nodeData->setWalletUUID(pendingAssigneeData->getWalletUUID()); + + // now that we've pulled the wallet UUID and added the node to our list, delete the pending assignee data + delete pendingAssigneeData; } nodeData->setSendingSockAddr(senderSockAddr); @@ -1076,28 +1091,18 @@ void DomainServer::nodeKilled(SharedNodePointer node) { } } -SharedAssignmentPointer DomainServer::matchingQueuedAssignmentForCheckIn(const QUuid& checkInUUID, NodeType_t nodeType) { +SharedAssignmentPointer DomainServer::matchingQueuedAssignmentForCheckIn(const QUuid& assignmentUUID, NodeType_t nodeType) { QQueue::iterator i = _unfulfilledAssignments.begin(); - PendingAssignedNodeData* pendingAssigneeData = _pendingAssignedNodes.take(checkInUUID); - - if (pendingAssigneeData) { - while (i != _unfulfilledAssignments.end()) { - if (i->data()->getType() == Assignment::typeForNodeType(nodeType) - && i->data()->getUUID() == pendingAssigneeData->getAssignmentUUID()) { - // we have an unfulfilled assignment to return - qDebug() << "Assignment deployed with" << uuidStringWithoutCurlyBraces(checkInUUID) - << "matches unfulfilled assignment" - << uuidStringWithoutCurlyBraces(pendingAssigneeData->getAssignmentUUID()); - - // first clear the pending assignee data - delete pendingAssigneeData; - - // return the matching assignment - return _unfulfilledAssignments.takeAt(i - _unfulfilledAssignments.begin()); - } else { - ++i; - } + while (i != _unfulfilledAssignments.end()) { + if (i->data()->getType() == Assignment::typeForNodeType(nodeType) + && i->data()->getUUID() == assignmentUUID) { + // we have an unfulfilled assignment to return + + // return the matching assignment + return _unfulfilledAssignments.takeAt(i - _unfulfilledAssignments.begin()); + } else { + ++i; } } diff --git a/domain-server/src/DomainServerNodeData.cpp b/domain-server/src/DomainServerNodeData.cpp index a43e17ae60..bdf088c19e 100644 --- a/domain-server/src/DomainServerNodeData.cpp +++ b/domain-server/src/DomainServerNodeData.cpp @@ -20,6 +20,7 @@ DomainServerNodeData::DomainServerNodeData() : _sessionSecretHash(), _assignmentUUID(), + _walletUUID(), _statsJSONObject(), _sendingSockAddr(), _isAuthenticated(true) diff --git a/domain-server/src/DomainServerNodeData.h b/domain-server/src/DomainServerNodeData.h index 011bee57c1..a8935bea00 100644 --- a/domain-server/src/DomainServerNodeData.h +++ b/domain-server/src/DomainServerNodeData.h @@ -30,6 +30,9 @@ public: void setAssignmentUUID(const QUuid& assignmentUUID) { _assignmentUUID = assignmentUUID; } const QUuid& getAssignmentUUID() const { return _assignmentUUID; } + void setWalletUUID(const QUuid& walletUUID) { _walletUUID = walletUUID; } + const QUuid& getWalletUUID() const { return _walletUUID; } + void setSendingSockAddr(const HifiSockAddr& sendingSockAddr) { _sendingSockAddr = sendingSockAddr; } const HifiSockAddr& getSendingSockAddr() { return _sendingSockAddr; } @@ -42,6 +45,7 @@ private: QHash _sessionSecretHash; QUuid _assignmentUUID; + QUuid _walletUUID; QJsonObject _statsJSONObject; HifiSockAddr _sendingSockAddr; bool _isAuthenticated; From 8fb85110ac407d46720a2e72afd9c7169e2e5d78 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 20 May 2014 15:58:57 -0700 Subject: [PATCH 19/39] add a timer to initiate payment to assigned nodes --- domain-server/src/DomainServer.cpp | 23 ++++++++++++++++++++++- domain-server/src/DomainServer.h | 2 +- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 56d645ed24..df946b8dcb 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -57,6 +57,13 @@ DomainServer::DomainServer(int argc, char* argv[]) : setupNodeListAndAssignments(); _networkAccessManager = new QNetworkAccessManager(this); + + // setup a timer to send transactions to pay assigned nodes every 30 seconds + QTimer* nodePaymentTimer = new QTimer(this); + connect(nodePaymentTimer, &QTimer::timeout, this, &DomainServer::payAssignedNodes); + + const qint64 NODE_PAYMENT_INTERVAL_MSECS = 30 * 1000; + nodePaymentTimer->start(NODE_PAYMENT_INTERVAL_MSECS); } } @@ -343,10 +350,12 @@ void DomainServer::handleConnectRequest(const QByteArray& packet, const HifiSock bool isAssignment = _pendingAssignedNodes.contains(packetUUID); SharedAssignmentPointer matchingQueuedAssignment = SharedAssignmentPointer(); PendingAssignedNodeData* pendingAssigneeData = NULL; + if (isAssignment) { pendingAssigneeData = _pendingAssignedNodes.take(packetUUID); + if (pendingAssigneeData) { - matchingQueuedAssignment = matchingQueuedAssignmentForCheckIn(packetUUID, nodeType); + matchingQueuedAssignment = matchingQueuedAssignmentForCheckIn(pendingAssigneeData->getAssignmentUUID(), nodeType); if (matchingQueuedAssignment) { qDebug() << "Assignment deployed with" << uuidStringWithoutCurlyBraces(packetUUID) @@ -647,6 +656,18 @@ void DomainServer::readAvailableDatagrams() { } } +void DomainServer::payAssignedNodes() { + // enumerate the NodeList to find the assigned nodes + foreach (const SharedNodePointer& node, LimitedNodeList::getInstance()->getNodeHash()) { + DomainServerNodeData* nodeData = reinterpret_cast(node->getLinkedData()); + + if (!nodeData->getAssignmentUUID().isNull() && !nodeData->getWalletUUID().isNull()) { + // add a pending transaction for this node or increase the amount for the existing transaction + + } + } +} + void DomainServer::processDatagram(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr) { LimitedNodeList* nodeList = LimitedNodeList::getInstance(); diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index 63a3afa13d..281a10d88b 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -45,8 +45,8 @@ public slots: void nodeKilled(SharedNodePointer node); private slots: - void readAvailableDatagrams(); + void payAssignedNodes(); private: void setupNodeListAndAssignments(const QUuid& sessionUUID = QUuid::createUuid()); bool optionallySetupOAuth(); From 22a6e4679a8b0bfaceb6fdfddf23a8838386ec8c Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Tue, 20 May 2014 16:19:14 -0700 Subject: [PATCH 20/39] properly discard piggy back packets that don't match expected version --- interface/src/voxels/VoxelPacketProcessor.cpp | 17 +++++++++++++++++ libraries/networking/src/PacketHeaders.h | 4 ++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/interface/src/voxels/VoxelPacketProcessor.cpp b/interface/src/voxels/VoxelPacketProcessor.cpp index 8576f14b16..aedf9e6b41 100644 --- a/interface/src/voxels/VoxelPacketProcessor.cpp +++ b/interface/src/voxels/VoxelPacketProcessor.cpp @@ -61,6 +61,23 @@ void VoxelPacketProcessor::processPacket(const SharedNodePointer& sendingNode, c } // fall through to piggyback message voxelPacketType = packetTypeForPacket(mutablePacket); + PacketVersion packetVersion = mutablePacket[1]; + PacketVersion expectedVersion = versionForPacketType(voxelPacketType); + + // check version of piggyback packet against expected version + if (packetVersion != expectedVersion) { + static QMultiMap versionDebugSuppressMap; + + QUuid senderUUID = uuidFromPacketHeader(packet); + if (!versionDebugSuppressMap.contains(senderUUID, voxelPacketType)) { + qDebug() << "Packet version mismatch on" << voxelPacketType << "- Sender" + << senderUUID << "sent" << (int)packetVersion << "but" + << (int)expectedVersion << "expected."; + + versionDebugSuppressMap.insert(senderUUID, voxelPacketType); + } + } + if (Menu::getInstance()->isOptionChecked(MenuOption::Voxels)) { app->trackIncomingVoxelPacket(mutablePacket, sendingNode, wasStatsPacket); diff --git a/libraries/networking/src/PacketHeaders.h b/libraries/networking/src/PacketHeaders.h index d5b1e8301c..9c764f9f02 100644 --- a/libraries/networking/src/PacketHeaders.h +++ b/libraries/networking/src/PacketHeaders.h @@ -47,7 +47,7 @@ enum PacketType { PacketTypeVoxelSet, PacketTypeVoxelSetDestructive, PacketTypeVoxelErase, - PacketTypeOctreeStats, + PacketTypeOctreeStats, // 26 PacketTypeJurisdiction, PacketTypeJurisdictionRequest, PacketTypeParticleQuery, @@ -62,7 +62,7 @@ enum PacketType { PacketTypeDomainServerRequireDTLS, PacketTypeNodeJsonStats, PacketTypeModelQuery, - PacketTypeModelData, + PacketTypeModelData, // 41 PacketTypeModelAddOrEdit, PacketTypeModelErase, PacketTypeModelAddResponse, From 2665f3af67b45739c9022914c1aecfdaaeeeeb16 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Tue, 20 May 2014 16:31:03 -0700 Subject: [PATCH 21/39] properly bail on version mismatch --- interface/src/voxels/VoxelPacketProcessor.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/interface/src/voxels/VoxelPacketProcessor.cpp b/interface/src/voxels/VoxelPacketProcessor.cpp index aedf9e6b41..c0f27264f6 100644 --- a/interface/src/voxels/VoxelPacketProcessor.cpp +++ b/interface/src/voxels/VoxelPacketProcessor.cpp @@ -76,6 +76,7 @@ void VoxelPacketProcessor::processPacket(const SharedNodePointer& sendingNode, c versionDebugSuppressMap.insert(senderUUID, voxelPacketType); } + return; // bail since piggyback version doesn't match } From 99a3fde8fa7a2a0d76012e1d1e9ab5198ec2a66f Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 20 May 2014 16:57:19 -0700 Subject: [PATCH 22/39] accumulate credits to pay nodes, return in assignments JSON --- domain-server/src/DomainServer.cpp | 49 +++++++++++++++++++--- domain-server/src/DomainServer.h | 6 ++- domain-server/src/DomainServerNodeData.cpp | 3 +- domain-server/src/DomainServerNodeData.h | 5 +++ domain-server/src/WalletTransaction.cpp | 43 +++++++++++++++++++ domain-server/src/WalletTransaction.h | 42 +++++++++++++++++++ 6 files changed, 141 insertions(+), 7 deletions(-) create mode 100644 domain-server/src/WalletTransaction.cpp create mode 100644 domain-server/src/WalletTransaction.h diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index df946b8dcb..5f9864f45b 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -60,10 +60,10 @@ DomainServer::DomainServer(int argc, char* argv[]) : // setup a timer to send transactions to pay assigned nodes every 30 seconds QTimer* nodePaymentTimer = new QTimer(this); - connect(nodePaymentTimer, &QTimer::timeout, this, &DomainServer::payAssignedNodes); + connect(nodePaymentTimer, &QTimer::timeout, this, &DomainServer::setupPendingAssignmentCredits); - const qint64 NODE_PAYMENT_INTERVAL_MSECS = 30 * 1000; - nodePaymentTimer->start(NODE_PAYMENT_INTERVAL_MSECS); + const qint64 CREDIT_CHECK_INTERVAL = 5 * 1000; + nodePaymentTimer->start(CREDIT_CHECK_INTERVAL); } } @@ -656,14 +656,40 @@ void DomainServer::readAvailableDatagrams() { } } -void DomainServer::payAssignedNodes() { +void DomainServer::setupPendingAssignmentCredits() { // enumerate the NodeList to find the assigned nodes foreach (const SharedNodePointer& node, LimitedNodeList::getInstance()->getNodeHash()) { DomainServerNodeData* nodeData = reinterpret_cast(node->getLinkedData()); if (!nodeData->getAssignmentUUID().isNull() && !nodeData->getWalletUUID().isNull()) { - // add a pending transaction for this node or increase the amount for the existing transaction + // check if we have a non-finalized transaction for this node to add this amount to + TransactionHash::iterator i = _pendingAssignmentCredits.find(nodeData->getWalletUUID()); + WalletTransaction* existingTransaction = NULL; + while (i != _pendingAssignmentCredits.end() && i.key() == nodeData->getWalletUUID()) { + if (!i.value()->isFinalized()) { + existingTransaction = i.value(); + break; + } else { + ++i; + } + } + + qint64 elapsedMsecsSinceLastPayment = nodeData->getPaymentIntervalTimer().elapsed(); + nodeData->getPaymentIntervalTimer().restart(); + + const float CREDITS_PER_HOUR = 3; + const float CREDITS_PER_MSEC = CREDITS_PER_HOUR / (60 * 60 * 1000); + + float pendingCredits = elapsedMsecsSinceLastPayment * CREDITS_PER_MSEC; + + if (existingTransaction) { + existingTransaction->incrementAmount(pendingCredits); + } else { + // create a fresh transaction to pay this node, there is no transaction to append to + WalletTransaction* freshTransaction = new WalletTransaction(nodeData->getWalletUUID(), pendingCredits); + _pendingAssignmentCredits.insert(nodeData->getWalletUUID(), freshTransaction); + } } } } @@ -717,6 +743,7 @@ const char JSON_KEY_TYPE[] = "type"; const char JSON_KEY_PUBLIC_SOCKET[] = "public"; const char JSON_KEY_LOCAL_SOCKET[] = "local"; const char JSON_KEY_POOL[] = "pool"; +const char JSON_KEY_PENDING_CREDITS[] = "pending_credits"; const char JSON_KEY_WAKE_TIMESTAMP[] = "wake_timestamp"; QJsonObject DomainServer::jsonObjectForNode(const SharedNodePointer& node) { @@ -745,6 +772,18 @@ QJsonObject DomainServer::jsonObjectForNode(const SharedNodePointer& node) { SharedAssignmentPointer matchingAssignment = _allAssignments.value(nodeData->getAssignmentUUID()); if (matchingAssignment) { nodeJson[JSON_KEY_POOL] = matchingAssignment->getPool(); + + if (!nodeData->getWalletUUID().isNull()) { + TransactionHash::iterator i = _pendingAssignmentCredits.find(nodeData->getWalletUUID()); + double pendingCreditAmount = 0; + + while (i != _pendingAssignmentCredits.end() && i.key() == nodeData->getWalletUUID()) { + pendingCreditAmount += i.value()->getAmount(); + ++i; + } + + nodeJson[JSON_KEY_PENDING_CREDITS] = pendingCreditAmount; + } } return nodeJson; diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index 281a10d88b..cb011be16b 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -24,9 +24,12 @@ #include #include +#include "WalletTransaction.h" + #include "PendingAssignedNodeData.h" typedef QSharedPointer SharedAssignmentPointer; +typedef QMultiHash TransactionHash; class DomainServer : public QCoreApplication, public HTTPSRequestHandler { Q_OBJECT @@ -46,7 +49,7 @@ public slots: private slots: void readAvailableDatagrams(); - void payAssignedNodes(); + void setupPendingAssignmentCredits(); private: void setupNodeListAndAssignments(const QUuid& sessionUUID = QUuid::createUuid()); bool optionallySetupOAuth(); @@ -88,6 +91,7 @@ private: QHash _allAssignments; QQueue _unfulfilledAssignments; QHash _pendingAssignedNodes; + TransactionHash _pendingAssignmentCredits; QVariantMap _argumentVariantMap; diff --git a/domain-server/src/DomainServerNodeData.cpp b/domain-server/src/DomainServerNodeData.cpp index bdf088c19e..68903cc106 100644 --- a/domain-server/src/DomainServerNodeData.cpp +++ b/domain-server/src/DomainServerNodeData.cpp @@ -21,11 +21,12 @@ DomainServerNodeData::DomainServerNodeData() : _sessionSecretHash(), _assignmentUUID(), _walletUUID(), + _paymentIntervalTimer(), _statsJSONObject(), _sendingSockAddr(), _isAuthenticated(true) { - + _paymentIntervalTimer.start(); } void DomainServerNodeData::parseJSONStatsPacket(const QByteArray& statsPacket) { diff --git a/domain-server/src/DomainServerNodeData.h b/domain-server/src/DomainServerNodeData.h index a8935bea00..a7d7233874 100644 --- a/domain-server/src/DomainServerNodeData.h +++ b/domain-server/src/DomainServerNodeData.h @@ -12,6 +12,8 @@ #ifndef hifi_DomainServerNodeData_h #define hifi_DomainServerNodeData_h + +#include #include #include @@ -33,6 +35,8 @@ public: void setWalletUUID(const QUuid& walletUUID) { _walletUUID = walletUUID; } const QUuid& getWalletUUID() const { return _walletUUID; } + QElapsedTimer& getPaymentIntervalTimer() { return _paymentIntervalTimer; } + void setSendingSockAddr(const HifiSockAddr& sendingSockAddr) { _sendingSockAddr = sendingSockAddr; } const HifiSockAddr& getSendingSockAddr() { return _sendingSockAddr; } @@ -46,6 +50,7 @@ private: QHash _sessionSecretHash; QUuid _assignmentUUID; QUuid _walletUUID; + QElapsedTimer _paymentIntervalTimer; QJsonObject _statsJSONObject; HifiSockAddr _sendingSockAddr; bool _isAuthenticated; diff --git a/domain-server/src/WalletTransaction.cpp b/domain-server/src/WalletTransaction.cpp new file mode 100644 index 0000000000..400eeb0688 --- /dev/null +++ b/domain-server/src/WalletTransaction.cpp @@ -0,0 +1,43 @@ +// +// WalletTransaction.cpp +// domain-server/src +// +// Created by Stephen Birarda on 2014-05-20. +// 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 "WalletTransaction.h" + +WalletTransaction::WalletTransaction(const QUuid& destinationUUID, double amount) : + _uuid(QUuid::createUuid()), + _destinationUUID(destinationUUID), + _amount(amount), + _isFinalized(false) +{ + +} + +QJsonDocument WalletTransaction::postJson() { + QJsonObject rootObject; + QJsonObject transactionObject; + + const QString TRANSCATION_ID_KEY = "id"; + const QString TRANSACTION_DESTINATION_WALLET_ID_KEY = "destination_wallet_id"; + const QString TRANSACTION_AMOUNT_KEY = "amount"; + + transactionObject.insert(TRANSCATION_ID_KEY, uuidStringWithoutCurlyBraces(_uuid)); + transactionObject.insert(TRANSACTION_DESTINATION_WALLET_ID_KEY, uuidStringWithoutCurlyBraces(_destinationUUID)); + transactionObject.insert(TRANSACTION_AMOUNT_KEY, _amount); + + const QString ROOT_OBJECT_TRANSACTION_KEY = "transaction"; + rootObject.insert(ROOT_OBJECT_TRANSACTION_KEY, transactionObject); + + return QJsonDocument(rootObject); +} \ No newline at end of file diff --git a/domain-server/src/WalletTransaction.h b/domain-server/src/WalletTransaction.h new file mode 100644 index 0000000000..f704042804 --- /dev/null +++ b/domain-server/src/WalletTransaction.h @@ -0,0 +1,42 @@ +// +// WalletTransaction.h +// domain-server/src +// +// Created by Stephen Birarda on 2014-05-20. +// 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_WalletTransaction_h +#define hifi_WalletTransaction_h + +#include +#include +#include + +class WalletTransaction : public QObject { +public: + WalletTransaction(const QUuid& destinationUUID, double amount); + + const QUuid& getUUID() const { return _uuid; } + + void setDestinationUUID(const QUuid& destinationUUID) { _destinationUUID = destinationUUID; } + const QUuid& getDestinationUUID() const { return _destinationUUID; } + + double getAmount() const { return _amount; } + void setAmount(double amount) { _amount = amount; } + void incrementAmount(double increment) { _amount += increment; } + + bool isFinalized() const { return _isFinalized; } + + QJsonDocument postJson(); +private: + QUuid _uuid; + QUuid _destinationUUID; + double _amount; + bool _isFinalized; +}; + +#endif // hifi_WalletTransaction_h \ No newline at end of file From 5b3b19011ee87876f886e43d351322d8479ef333 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 20 May 2014 17:04:41 -0700 Subject: [PATCH 23/39] display pending credits in DS web page --- domain-server/resources/web/index.shtml | 1 + domain-server/resources/web/js/tables.js | 2 ++ 2 files changed, 3 insertions(+) diff --git a/domain-server/resources/web/index.shtml b/domain-server/resources/web/index.shtml index afd0af1679..b6ba8f67db 100644 --- a/domain-server/resources/web/index.shtml +++ b/domain-server/resources/web/index.shtml @@ -13,6 +13,7 @@ Public Local Uptime (s) + Pending Credits Kill? diff --git a/domain-server/resources/web/js/tables.js b/domain-server/resources/web/js/tables.js index a4884486c3..b564d9392f 100644 --- a/domain-server/resources/web/js/tables.js +++ b/domain-server/resources/web/js/tables.js @@ -42,6 +42,8 @@ $(document).ready(function(){ var uptimeSeconds = (Date.now() - data.wake_timestamp) / 1000; nodesTableBody += "" + uptimeSeconds.toLocaleString() + ""; + nodesTableBody += "" + (typeof data.pending_credits == 'number' ? data.pending_credits.toLocaleString() : 'N/A') + ""; + nodesTableBody += ""; nodesTableBody += ""; }); From 7124b4a196c245bd60b1695d06aafdc91ad5cf61 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Tue, 20 May 2014 17:10:53 -0700 Subject: [PATCH 24/39] Mask joints from animation when explicitly set. --- interface/src/renderer/Model.cpp | 14 +++++++++++--- interface/src/renderer/Model.h | 2 ++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index 439c5738f3..6349d7f9ee 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -1200,6 +1200,7 @@ bool Model::setJointRotation(int jointIndex, const glm::quat& rotation, bool fro state.rotation = state.rotation * glm::inverse(state.combinedRotation) * rotation * glm::inverse(fromBind ? _geometry->getFBXGeometry().joints.at(jointIndex).inverseBindRotation : _geometry->getFBXGeometry().joints.at(jointIndex).inverseDefaultRotation); + state.animationDisabled = true; return true; } @@ -1232,6 +1233,7 @@ bool Model::restoreJointPosition(int jointIndex, float percent) { const FBXJoint& joint = geometry.joints.at(index); state.rotation = safeMix(state.rotation, joint.rotation, percent); state.translation = glm::mix(state.translation, joint.translation, percent); + state.animationDisabled = false; } return true; } @@ -1265,6 +1267,7 @@ void Model::applyRotationDelta(int jointIndex, const glm::quat& delta, bool cons glm::quat newRotation = glm::quat(glm::clamp(eulers, joint.rotationMin, joint.rotationMax)); state.combinedRotation = state.combinedRotation * glm::inverse(state.rotation) * newRotation; state.rotation = newRotation; + state.animationDisabled = true; } const int BALL_SUBDIVISIONS = 10; @@ -1694,7 +1697,10 @@ void AnimationHandle::simulate(float deltaTime) { for (int i = 0; i < _jointMappings.size(); i++) { int mapping = _jointMappings.at(i); if (mapping != -1) { - _model->_jointStates[mapping].rotation = frame.rotations.at(i); + Model::JointState& state = _model->_jointStates[mapping]; + if (!state.animationDisabled) { + state.rotation = frame.rotations.at(i); + } } } stop(); @@ -1709,8 +1715,10 @@ void AnimationHandle::simulate(float deltaTime) { for (int i = 0; i < _jointMappings.size(); i++) { int mapping = _jointMappings.at(i); if (mapping != -1) { - _model->_jointStates[mapping].rotation = safeMix(floorFrame.rotations.at(i), - ceilFrame.rotations.at(i), frameFraction); + Model::JointState& state = _model->_jointStates[mapping]; + if (!state.animationDisabled) { + state.rotation = safeMix(floorFrame.rotations.at(i), ceilFrame.rotations.at(i), frameFraction); + } } } } diff --git a/interface/src/renderer/Model.h b/interface/src/renderer/Model.h index b8b877ac96..4d9d8d5010 100644 --- a/interface/src/renderer/Model.h +++ b/interface/src/renderer/Model.h @@ -12,6 +12,7 @@ #ifndef hifi_Model_h #define hifi_Model_h +#include #include #include @@ -251,6 +252,7 @@ protected: glm::quat rotation; // rotation relative to parent glm::mat4 transform; // rotation to world frame + translation in model frame glm::quat combinedRotation; // rotation from joint local to world frame + bool animationDisabled; // if true, animations do not affect this joint }; bool _shapesAreDirty; From f709ea61b5aa2220b6657af3e51480bc3c32b28e Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Tue, 20 May 2014 17:55:48 -0700 Subject: [PATCH 25/39] Need to initialize this to false. --- interface/src/renderer/Model.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index 6349d7f9ee..199932af80 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -118,6 +118,7 @@ QVector Model::createJointStates(const FBXGeometry& geometry) JointState state; state.translation = joint.translation; state.rotation = joint.rotation; + state.animationDisabled = false; jointStates.append(state); } From ee67c64b766b91777ee990ff97928f255c1f4405 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 21 May 2014 09:26:17 -0700 Subject: [PATCH 26/39] stub sending of transactions to server --- domain-server/src/DomainServer.cpp | 24 ++++++++++++++++++++---- domain-server/src/DomainServer.h | 1 + domain-server/src/WalletTransaction.h | 1 + 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 5f9864f45b..ab1430fc29 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -59,11 +59,17 @@ DomainServer::DomainServer(int argc, char* argv[]) : _networkAccessManager = new QNetworkAccessManager(this); // setup a timer to send transactions to pay assigned nodes every 30 seconds - QTimer* nodePaymentTimer = new QTimer(this); - connect(nodePaymentTimer, &QTimer::timeout, this, &DomainServer::setupPendingAssignmentCredits); + QTimer* creditSetupTimer = new QTimer(this); + connect(creditSetupTimer, &QTimer::timeout, this, &DomainServer::setupPendingAssignmentCredits); - const qint64 CREDIT_CHECK_INTERVAL = 5 * 1000; - nodePaymentTimer->start(CREDIT_CHECK_INTERVAL); + const qint64 CREDIT_CHECK_INTERVAL_MSECS = 5 * 1000; + creditSetupTimer->start(CREDIT_CHECK_INTERVAL_MSECS); + + QTimer* nodePaymentTimer = new QTimer(this); + connect(nodePaymentTimer, &QTimer::timeout, this, &DomainServer::sendPendingTransactionsToServer); + + const qint64 TRANSACTION_SEND_INTERVAL_MSECS = 30 * 1000; + nodePaymentTimer->start(TRANSACTION_SEND_INTERVAL_MSECS); } } @@ -694,6 +700,16 @@ void DomainServer::setupPendingAssignmentCredits() { } } +void DomainServer::sendPendingTransactionsToServer() { + // enumerate the pending transactions and send them to the server to complete payment + TransactionHash::iterator i = _pendingAssignmentCredits.begin(); + + while (i != _pendingAssignmentCredits.end()) { + + ++i; + } +} + void DomainServer::processDatagram(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr) { LimitedNodeList* nodeList = LimitedNodeList::getInstance(); diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index cb011be16b..fc8806d53e 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -50,6 +50,7 @@ public slots: private slots: void readAvailableDatagrams(); void setupPendingAssignmentCredits(); + void sendPendingTransactionsToServer(); private: void setupNodeListAndAssignments(const QUuid& sessionUUID = QUuid::createUuid()); bool optionallySetupOAuth(); diff --git a/domain-server/src/WalletTransaction.h b/domain-server/src/WalletTransaction.h index f704042804..6c9708906d 100644 --- a/domain-server/src/WalletTransaction.h +++ b/domain-server/src/WalletTransaction.h @@ -30,6 +30,7 @@ public: void incrementAmount(double increment) { _amount += increment; } bool isFinalized() const { return _isFinalized; } + void setIsFinalized(bool isFinalized) { _isFinalized = isFinalized; } QJsonDocument postJson(); private: From 1022f1bec44a14ab43bfa47140c652c1d0bc82b2 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Wed, 21 May 2014 09:40:49 -0700 Subject: [PATCH 27/39] Added a priority setting so that we can control the order in which animations are applied. --- interface/src/avatar/MyAvatar.cpp | 2 ++ interface/src/renderer/Model.cpp | 27 ++++++++++++++++++++++++--- interface/src/renderer/Model.h | 4 ++++ interface/src/ui/AnimationsDialog.cpp | 8 ++++++++ interface/src/ui/AnimationsDialog.h | 1 + 5 files changed, 39 insertions(+), 3 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index e7638cc56d..052b742e89 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -480,6 +480,7 @@ void MyAvatar::saveData(QSettings* settings) { const AnimationHandlePointer& pointer = _animationHandles.at(i); settings->setValue("url", pointer->getURL()); settings->setValue("fps", pointer->getFPS()); + settings->setValue("priority", pointer->getPriority()); } settings->endArray(); @@ -545,6 +546,7 @@ void MyAvatar::loadData(QSettings* settings) { const AnimationHandlePointer& handle = _animationHandles.at(i); handle->setURL(settings->value("url").toUrl()); handle->setFPS(loadSetting(settings, "fps", 30.0f)); + handle->setPriority(loadSetting(settings, "priority", 1.0f)); } settings->endArray(); diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index 199932af80..caf3b92a07 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -1651,13 +1651,33 @@ void Model::renderMeshes(float alpha, RenderMode mode, bool translucent) { } void AnimationHandle::setURL(const QUrl& url) { - _animation = Application::getInstance()->getAnimationCache()->getAnimation(_url = url); - _jointMappings.clear(); + if (_url != url) { + _animation = Application::getInstance()->getAnimationCache()->getAnimation(_url = url); + _jointMappings.clear(); + } +} + +static void insertSorted(QList& handles, const AnimationHandlePointer& handle) { + for (QList::iterator it = handles.begin(); it != handles.end(); it++) { + if (handle->getPriority() < (*it)->getPriority()) { + handles.insert(it, handle); + return; + } + } + handles.append(handle); +} + +void AnimationHandle::setPriority(float priority) { + if (_priority != priority) { + _priority = priority; + _model->_runningAnimations.removeOne(_self); + insertSorted(_model->_runningAnimations, _self); + } } void AnimationHandle::start() { if (!_model->_runningAnimations.contains(_self)) { - _model->_runningAnimations.append(_self); + insertSorted(_model->_runningAnimations, _self); } _frameIndex = 0.0f; } @@ -1670,6 +1690,7 @@ AnimationHandle::AnimationHandle(Model* model) : QObject(model), _model(model), _fps(30.0f), + _priority(1.0f), _loop(false) { } diff --git a/interface/src/renderer/Model.h b/interface/src/renderer/Model.h index 4d9d8d5010..63a1e5e435 100644 --- a/interface/src/renderer/Model.h +++ b/interface/src/renderer/Model.h @@ -384,6 +384,9 @@ public: void setFPS(float fps) { _fps = fps; } float getFPS() const { return _fps; } + + void setPriority(float priority); + float getPriority() const { return _priority; } void setLoop(bool loop) { _loop = loop; } bool getLoop() const { return _loop; } @@ -404,6 +407,7 @@ private: AnimationPointer _animation; QUrl _url; float _fps; + float _priority; bool _loop; QVector _jointMappings; float _frameIndex; diff --git a/interface/src/ui/AnimationsDialog.cpp b/interface/src/ui/AnimationsDialog.cpp index 2beea24578..a9341fa283 100644 --- a/interface/src/ui/AnimationsDialog.cpp +++ b/interface/src/ui/AnimationsDialog.cpp @@ -93,6 +93,13 @@ AnimationPanel::AnimationPanel(AnimationsDialog* dialog, const AnimationHandlePo _fps->setValue(handle->getFPS()); connect(_fps, SIGNAL(valueChanged(double)), SLOT(updateHandle())); + layout->addRow("Priority:", _priority = new QDoubleSpinBox()); + _priority->setSingleStep(0.01); + _priority->setMinimum(-FLT_MAX); + _priority->setMaximum(FLT_MAX); + _priority->setValue(handle->getPriority()); + connect(_priority, SIGNAL(valueChanged(double)), SLOT(updateHandle())); + QPushButton* remove = new QPushButton("Delete"); layout->addRow(remove); connect(remove, SIGNAL(clicked(bool)), SLOT(removeHandle())); @@ -114,6 +121,7 @@ void AnimationPanel::chooseURL() { void AnimationPanel::updateHandle() { _handle->setURL(_url->text()); _handle->setFPS(_fps->value()); + _handle->setPriority(_priority->value()); } void AnimationPanel::removeHandle() { diff --git a/interface/src/ui/AnimationsDialog.h b/interface/src/ui/AnimationsDialog.h index d53b3dd6a5..5491418f6d 100644 --- a/interface/src/ui/AnimationsDialog.h +++ b/interface/src/ui/AnimationsDialog.h @@ -62,6 +62,7 @@ private: AnimationHandlePointer _handle; QLineEdit* _url; QDoubleSpinBox* _fps; + QDoubleSpinBox* _priority; bool _applying; }; From 981f9df6b387c832363b53f0ba090e25bf43eca6 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 21 May 2014 09:53:09 -0700 Subject: [PATCH 28/39] add option to pass data server username and password to DS --- domain-server/src/DomainServer.cpp | 74 ++++++++++++++----- domain-server/src/DomainServer.h | 2 + libraries/shared/src/HifiConfigVariantMap.cpp | 6 +- 3 files changed, 61 insertions(+), 21 deletions(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index ab1430fc29..7b06acc9c1 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -50,26 +50,14 @@ DomainServer::DomainServer(int argc, char* argv[]) : _argumentVariantMap = HifiConfigVariantMap::mergeCLParametersWithJSONConfig(arguments()); - if (optionallyReadX509KeyAndCertificate() && optionallySetupOAuth()) { - // we either read a certificate and private key or were not passed one, good to load assignments - // and set up the node list + _networkAccessManager = new QNetworkAccessManager(this); + + if (optionallyReadX509KeyAndCertificate() && optionallySetupOAuth() && optionallyLoginAndSetupAssignmentPayment()) { + // we either read a certificate and private key or were not passed one + // and completed login or did not need to + qDebug() << "Setting up LimitedNodeList and assignments."; setupNodeListAndAssignments(); - - _networkAccessManager = new QNetworkAccessManager(this); - - // setup a timer to send transactions to pay assigned nodes every 30 seconds - QTimer* creditSetupTimer = new QTimer(this); - connect(creditSetupTimer, &QTimer::timeout, this, &DomainServer::setupPendingAssignmentCredits); - - const qint64 CREDIT_CHECK_INTERVAL_MSECS = 5 * 1000; - creditSetupTimer->start(CREDIT_CHECK_INTERVAL_MSECS); - - QTimer* nodePaymentTimer = new QTimer(this); - connect(nodePaymentTimer, &QTimer::timeout, this, &DomainServer::sendPendingTransactionsToServer); - - const qint64 TRANSACTION_SEND_INTERVAL_MSECS = 30 * 1000; - nodePaymentTimer->start(TRANSACTION_SEND_INTERVAL_MSECS); } } @@ -201,6 +189,56 @@ void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) { addStaticAssignmentsToQueue(); } +bool DomainServer::optionallyLoginAndSetupAssignmentPayment() { + // check if we have a username and password set via env + const QString HIFI_AUTH_ENABLED_OPTION = "hifi-auth"; + const QString HIFI_USERNAME_ENV_KEY = "DOMAIN_SERVER_USERNAME"; + const QString HIFI_PASSWORD_ENV_KEY = "DOMAIN_SERVER_PASSWORD"; + + if (_argumentVariantMap.contains(HIFI_AUTH_ENABLED_OPTION)) { + AccountManager& accountManager = AccountManager::getInstance(); + accountManager.setAuthURL(DEFAULT_NODE_AUTH_URL); + + if (!accountManager.hasValidAccessToken()) { + // we don't have a valid access token so we need to get one + QString username = QProcessEnvironment::systemEnvironment().value(HIFI_USERNAME_ENV_KEY); + QString password = QProcessEnvironment::systemEnvironment().value(HIFI_PASSWORD_ENV_KEY); + + if (!username.isEmpty() && !password.isEmpty()) { + accountManager.requestAccessToken(username, password); + + // connect to loginFailed signal from AccountManager so we can quit if that is the case + connect(&accountManager, &AccountManager::loginFailed, this, &DomainServer::loginFailed); + } else { + qDebug() << "Missing access-token or username and password combination. domain-server will now quit."; + QMetaObject::invokeMethod(this, "quit", Qt::QueuedConnection); + return false; + } + } + + // assume that the fact we are authing against HF data server means we will pay for assignments + // setup a timer to send transactions to pay assigned nodes every 30 seconds + QTimer* creditSetupTimer = new QTimer(this); + connect(creditSetupTimer, &QTimer::timeout, this, &DomainServer::setupPendingAssignmentCredits); + + const qint64 CREDIT_CHECK_INTERVAL_MSECS = 5 * 1000; + creditSetupTimer->start(CREDIT_CHECK_INTERVAL_MSECS); + + QTimer* nodePaymentTimer = new QTimer(this); + connect(nodePaymentTimer, &QTimer::timeout, this, &DomainServer::sendPendingTransactionsToServer); + + const qint64 TRANSACTION_SEND_INTERVAL_MSECS = 30 * 1000; + nodePaymentTimer->start(TRANSACTION_SEND_INTERVAL_MSECS); + } + + return true; +} + +void DomainServer::loginFailed() { + qDebug() << "Login to data server has failed. domain-server will now quit"; + QMetaObject::invokeMethod(this, "quit", Qt::QueuedConnection); +} + void DomainServer::parseAssignmentConfigs(QSet& excludedTypes) { // check for configs from the command line, these take precedence const QString ASSIGNMENT_CONFIG_REGEX_STRING = "config-([\\d]+)"; diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index fc8806d53e..645b023c78 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -48,6 +48,7 @@ public slots: void nodeKilled(SharedNodePointer node); private slots: + void loginFailed(); void readAvailableDatagrams(); void setupPendingAssignmentCredits(); void sendPendingTransactionsToServer(); @@ -55,6 +56,7 @@ private: void setupNodeListAndAssignments(const QUuid& sessionUUID = QUuid::createUuid()); bool optionallySetupOAuth(); bool optionallyReadX509KeyAndCertificate(); + bool optionallyLoginAndSetupAssignmentPayment(); void processDatagram(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr); diff --git a/libraries/shared/src/HifiConfigVariantMap.cpp b/libraries/shared/src/HifiConfigVariantMap.cpp index d20f4276f1..e8ab59ce2d 100644 --- a/libraries/shared/src/HifiConfigVariantMap.cpp +++ b/libraries/shared/src/HifiConfigVariantMap.cpp @@ -41,19 +41,19 @@ QVariantMap HifiConfigVariantMap::mergeCLParametersWithJSONConfig(const QStringL nextKeyIndex = argumentList.indexOf(dashedKeyRegex, keyIndex + 1); - if (nextKeyIndex == keyIndex + 1) { + if (nextKeyIndex == keyIndex + 1 || keyIndex == argumentList.size() - 1) { // there's no value associated with this option, it's a boolean // so add it to the variant map with NULL as value mergedMap.insertMulti(key, QVariant()); } else { - int maxIndex = (nextKeyIndex == -1) ? argumentList.size() : nextKeyIndex; + int maxIndex = (nextKeyIndex == -1) ? argumentList.size() - 1: nextKeyIndex; // there's at least one value associated with the option // pull the first value to start QString value = argumentList[keyIndex + 1]; // for any extra values, append them, with a space, to the value string - for (int i = keyIndex + 2; i < maxIndex; i++) { + for (int i = keyIndex + 2; i <= maxIndex; i++) { value += " " + argumentList[i]; } From 908eb5cc4f285af6d7ae30e54ef010a5699379b2 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 21 May 2014 10:04:48 -0700 Subject: [PATCH 29/39] use OAuth provider URL for transaction login --- domain-server/src/DomainServer.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 7b06acc9c1..cdb88ec7a4 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -131,7 +131,7 @@ bool DomainServer::optionallySetupOAuth() { _oauthClientSecret = QProcessEnvironment::systemEnvironment().value(OAUTH_CLIENT_SECRET_ENV); _hostname = _argumentVariantMap.value(REDIRECT_HOSTNAME_OPTION).toString(); - if (!_oauthProviderURL.isEmpty() || !_hostname.isEmpty() || !_oauthClientID.isEmpty()) { + if (!_oauthClientID.isEmpty()) { if (_oauthProviderURL.isEmpty() || _hostname.isEmpty() || _oauthClientID.isEmpty() @@ -191,13 +191,13 @@ void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) { bool DomainServer::optionallyLoginAndSetupAssignmentPayment() { // check if we have a username and password set via env - const QString HIFI_AUTH_ENABLED_OPTION = "hifi-auth"; const QString HIFI_USERNAME_ENV_KEY = "DOMAIN_SERVER_USERNAME"; const QString HIFI_PASSWORD_ENV_KEY = "DOMAIN_SERVER_PASSWORD"; - if (_argumentVariantMap.contains(HIFI_AUTH_ENABLED_OPTION)) { + if (!_oauthProviderURL.isEmpty()) { + AccountManager& accountManager = AccountManager::getInstance(); - accountManager.setAuthURL(DEFAULT_NODE_AUTH_URL); + accountManager.setAuthURL(_oauthProviderURL); if (!accountManager.hasValidAccessToken()) { // we don't have a valid access token so we need to get one From 9b3b5201c993342f5a45f45e46a35b1ccc698c9d Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Wed, 21 May 2014 10:39:26 -0700 Subject: [PATCH 30/39] Made voxelTools unselected by default to avoid accidental voxelEdit at login --- examples/editVoxels.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/examples/editVoxels.js b/examples/editVoxels.js index b9f5d925d9..9040306bf6 100644 --- a/examples/editVoxels.js +++ b/examples/editVoxels.js @@ -630,7 +630,7 @@ var trackAsDelete = false; var trackAsRecolor = false; var trackAsEyedropper = false; -var voxelToolSelected = true; +var voxelToolSelected = false; var recolorToolSelected = false; var eyedropperToolSelected = false; var pasteMode = false; @@ -848,7 +848,7 @@ function showPreviewLines() { } function showPreviewGuides() { - if (editToolsOn && !isImporting) { + if (editToolsOn && !isImporting && (voxelToolSelected || recolorToolSelected || eyedropperToolSelected)) { if (previewAsVoxel) { showPreviewVoxel(); @@ -964,7 +964,7 @@ function mousePressEvent(event) { if (clickedOverlay == voxelTool) { modeSwitchSound.play(0); - voxelToolSelected = true; + voxelToolSelected = !voxelToolSelected; recolorToolSelected = false; eyedropperToolSelected = false; moveTools(); @@ -972,7 +972,7 @@ function mousePressEvent(event) { } else if (clickedOverlay == recolorTool) { modeSwitchSound.play(1); voxelToolSelected = false; - recolorToolSelected = true; + recolorToolSelected = !recolorToolSelected; eyedropperToolSelected = false; moveTools(); clickedOnSomething = true; @@ -980,7 +980,7 @@ function mousePressEvent(event) { modeSwitchSound.play(2); voxelToolSelected = false; recolorToolSelected = false; - eyedropperToolSelected = true; + eyedropperToolSelected = !eyedropperToolSelected; moveTools(); clickedOnSomething = true; } else if (scaleSelector.clicked(event.x, event.y)) { @@ -1000,7 +1000,7 @@ function mousePressEvent(event) { } } } - if (clickedOnSomething || isImporting) { + if (clickedOnSomething || isImporting || (!voxelToolSelected && !recolorToolSelected && !eyedropperToolSelected)) { return; // no further processing } @@ -1344,7 +1344,7 @@ function moveTools() { recolorToolOffset = 2; } else if (eyedropperToolSelected) { eyedropperToolOffset = 2; - } else { + } else if (voxelToolSelected) { if (pasteMode) { voxelToolColor = pasteModeColor; } From 0dfd787034a8b812c7f1ce118e76acd8b528a1a4 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Wed, 21 May 2014 10:44:51 -0700 Subject: [PATCH 31/39] Added some basic method to allow scripts to run/stop animations on the avatar. --- interface/src/avatar/MyAvatar.cpp | 27 +++++++++++++++++++++++++++ interface/src/avatar/MyAvatar.h | 7 +++++++ interface/src/renderer/Model.cpp | 30 +++++++++++++++++------------- interface/src/renderer/Model.h | 12 +++++++++--- 4 files changed, 60 insertions(+), 16 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 052b742e89..3aec78cc82 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -436,6 +436,33 @@ void MyAvatar::removeAnimationHandle(const AnimationHandlePointer& handle) { _animationHandles.removeOne(handle); } +void MyAvatar::startAnimation(const QString& url, float fps, float priority, bool loop) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "startAnimation", Q_ARG(const QString&, url), + Q_ARG(float, fps), Q_ARG(float, priority), Q_ARG(bool, loop)); + return; + } + AnimationHandlePointer handle = _skeletonModel.createAnimationHandle(); + handle->setURL(url); + handle->setFPS(fps); + handle->setPriority(priority); + handle->setLoop(loop); + handle->start(); +} + +void MyAvatar::stopAnimation(const QString& url) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "stopAnimation", Q_ARG(const QString&, url)); + return; + } + foreach (const AnimationHandlePointer& handle, _skeletonModel.getRunningAnimations()) { + if (handle->getURL() == url) { + handle->stop(); + return; + } + } +} + void MyAvatar::saveData(QSettings* settings) { settings->beginGroup("Avatar"); diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 50b8fceca3..11493524ca 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -66,6 +66,13 @@ public: AnimationHandlePointer addAnimationHandle(); void removeAnimationHandle(const AnimationHandlePointer& handle); + /// Allows scripts to run animations. + Q_INVOKABLE void startAnimation(const QString& url, float fps = 30.0f, + float priority = 1.0f, bool loop = false); + + /// Stops an animation as identified by a URL. + Q_INVOKABLE void stopAnimation(const QString& url); + // get/set avatar data void saveData(QSettings* settings); void loadData(QSettings* settings); diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index caf3b92a07..f251d98e3e 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -1670,28 +1670,32 @@ static void insertSorted(QList& handles, const Animation void AnimationHandle::setPriority(float priority) { if (_priority != priority) { _priority = priority; + if (_running) { + _model->_runningAnimations.removeOne(_self); + insertSorted(_model->_runningAnimations, _self); + } + } +} + +void AnimationHandle::setRunning(bool running) { + if ((_running = running)) { + if (!_model->_runningAnimations.contains(_self)) { + insertSorted(_model->_runningAnimations, _self); + } + _frameIndex = 0.0f; + + } else { _model->_runningAnimations.removeOne(_self); - insertSorted(_model->_runningAnimations, _self); } } -void AnimationHandle::start() { - if (!_model->_runningAnimations.contains(_self)) { - insertSorted(_model->_runningAnimations, _self); - } - _frameIndex = 0.0f; -} - -void AnimationHandle::stop() { - _model->_runningAnimations.removeOne(_self); -} - AnimationHandle::AnimationHandle(Model* model) : QObject(model), _model(model), _fps(30.0f), _priority(1.0f), - _loop(false) { + _loop(false), + _running(false) { } void AnimationHandle::simulate(float deltaTime) { diff --git a/interface/src/renderer/Model.h b/interface/src/renderer/Model.h index 63a1e5e435..feef91f703 100644 --- a/interface/src/renderer/Model.h +++ b/interface/src/renderer/Model.h @@ -194,6 +194,8 @@ public: AnimationHandlePointer createAnimationHandle(); + const QList& getRunningAnimations() const { return _runningAnimations; } + void clearShapes(); void rebuildShapes(); void resetShapePositions(); @@ -376,7 +378,7 @@ Q_DECLARE_METATYPE(QVector) /// Represents a handle to a model animation. class AnimationHandle : public QObject { Q_OBJECT - + public: void setURL(const QUrl& url); @@ -391,8 +393,11 @@ public: void setLoop(bool loop) { _loop = loop; } bool getLoop() const { return _loop; } - void start(); - void stop(); + void setRunning(bool running); + bool isRunning() const { return _running; } + + void start() { setRunning(true); } + void stop() { setRunning(false); } private: @@ -409,6 +414,7 @@ private: float _fps; float _priority; bool _loop; + bool _running; QVector _jointMappings; float _frameIndex; }; From 1dddabb6915f74e209fa42a111b05c69583cea36 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 21 May 2014 10:58:41 -0700 Subject: [PATCH 32/39] send and confirm transactions to assigned nodes from DS --- domain-server/src/DomainServer.cpp | 69 +++++++++++++++++++-- domain-server/src/DomainServer.h | 2 + domain-server/src/WalletTransaction.cpp | 40 +++++++++--- domain-server/src/WalletTransaction.h | 3 + libraries/networking/src/AccountManager.cpp | 8 ++- 5 files changed, 109 insertions(+), 13 deletions(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index cdb88ec7a4..15b720a388 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -739,12 +739,55 @@ void DomainServer::setupPendingAssignmentCredits() { } void DomainServer::sendPendingTransactionsToServer() { - // enumerate the pending transactions and send them to the server to complete payment - TransactionHash::iterator i = _pendingAssignmentCredits.begin(); - while (i != _pendingAssignmentCredits.end()) { + AccountManager& accountManager = AccountManager::getInstance(); + + if (accountManager.hasValidAccessToken()) { - ++i; + // enumerate the pending transactions and send them to the server to complete payment + TransactionHash::iterator i = _pendingAssignmentCredits.begin(); + + JSONCallbackParameters transactionCallbackParams; + + transactionCallbackParams.jsonCallbackReceiver = this; + transactionCallbackParams.jsonCallbackMethod = "transactionJSONCallback"; + + while (i != _pendingAssignmentCredits.end()) { + accountManager.authenticatedRequest("api/v1/transactions", QNetworkAccessManager::PostOperation, + transactionCallbackParams, i.value()->postJson().toJson()); + + // set this transaction to finalized so we don't add additional credits to it + i.value()->setIsFinalized(true); + + ++i; + } + } + +} + +void DomainServer::transactionJSONCallback(const QJsonObject& data) { + // check if this was successful - if so we can remove it from our list of pending + if (data.value("status").toString() == "success") { + // create a dummy wallet transaction to unpack the JSON to + WalletTransaction dummyTransaction; + dummyTransaction.loadFromJson(data); + + TransactionHash::iterator i = _pendingAssignmentCredits.find(dummyTransaction.getDestinationUUID()); + + while (i != _pendingAssignmentCredits.end() && i.key() == dummyTransaction.getDestinationUUID()) { + if (i.value()->getUUID() == dummyTransaction.getUUID()) { + // we have a match - we can remove this from the hash of pending credits + // and delete it for clean up + + WalletTransaction* matchingTransaction = i.value(); + _pendingAssignmentCredits.erase(i); + delete matchingTransaction; + + break; + } else { + ++i; + } + } } } @@ -907,6 +950,24 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url connection->respond(HTTPConnection::StatusCode200, assignmentDocument.toJson(), qPrintable(JSON_MIME_TYPE)); // we've processed this request + return true; + } else if (url.path() == "/transactions.json") { + // enumerate our pending transactions and display them in an array + QJsonObject rootObject; + QJsonArray transactionArray; + + TransactionHash::iterator i = _pendingAssignmentCredits.begin(); + while (i != _pendingAssignmentCredits.end()) { + transactionArray.push_back(i.value()->toJson()); + ++i; + } + + rootObject["pending_transactions"] = transactionArray; + + // print out the created JSON + QJsonDocument transactionsDocument(rootObject); + connection->respond(HTTPConnection::StatusCode200, transactionsDocument.toJson(), qPrintable(JSON_MIME_TYPE)); + return true; } else if (url.path() == QString("%1.json").arg(URI_NODES)) { // setup the JSON diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index 645b023c78..aa55e07086 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -47,6 +47,8 @@ public slots: /// Called by NodeList to inform us a node has been killed void nodeKilled(SharedNodePointer node); + void transactionJSONCallback(const QJsonObject& data); + private slots: void loginFailed(); void readAvailableDatagrams(); diff --git a/domain-server/src/WalletTransaction.cpp b/domain-server/src/WalletTransaction.cpp index 400eeb0688..6ff57f063c 100644 --- a/domain-server/src/WalletTransaction.cpp +++ b/domain-server/src/WalletTransaction.cpp @@ -15,6 +15,15 @@ #include "WalletTransaction.h" +WalletTransaction::WalletTransaction() : + _uuid(), + _destinationUUID(), + _amount(), + _isFinalized(false) +{ + +} + WalletTransaction::WalletTransaction(const QUuid& destinationUUID, double amount) : _uuid(QUuid::createUuid()), _destinationUUID(destinationUUID), @@ -24,20 +33,35 @@ WalletTransaction::WalletTransaction(const QUuid& destinationUUID, double amount } +const QString TRANSACTION_ID_KEY = "id"; +const QString TRANSACTION_DESTINATION_WALLET_ID_KEY = "destination_wallet_id"; +const QString TRANSACTION_AMOUNT_KEY = "amount"; + +const QString ROOT_OBJECT_TRANSACTION_KEY = "transaction"; + QJsonDocument WalletTransaction::postJson() { QJsonObject rootObject; + + rootObject.insert(ROOT_OBJECT_TRANSACTION_KEY, toJson()); + + return QJsonDocument(rootObject); +} + +QJsonObject WalletTransaction::toJson() { QJsonObject transactionObject; - const QString TRANSCATION_ID_KEY = "id"; - const QString TRANSACTION_DESTINATION_WALLET_ID_KEY = "destination_wallet_id"; - const QString TRANSACTION_AMOUNT_KEY = "amount"; - - transactionObject.insert(TRANSCATION_ID_KEY, uuidStringWithoutCurlyBraces(_uuid)); + transactionObject.insert(TRANSACTION_ID_KEY, uuidStringWithoutCurlyBraces(_uuid)); transactionObject.insert(TRANSACTION_DESTINATION_WALLET_ID_KEY, uuidStringWithoutCurlyBraces(_destinationUUID)); transactionObject.insert(TRANSACTION_AMOUNT_KEY, _amount); - const QString ROOT_OBJECT_TRANSACTION_KEY = "transaction"; - rootObject.insert(ROOT_OBJECT_TRANSACTION_KEY, transactionObject); + return transactionObject; +} + +void WalletTransaction::loadFromJson(const QJsonObject& jsonObject) { + // pull the destination wallet and ID of the transaction to match it + QJsonObject transactionObject = jsonObject.value("data").toObject().value(ROOT_OBJECT_TRANSACTION_KEY).toObject(); - return QJsonDocument(rootObject); + _uuid = QUuid(transactionObject.value(TRANSACTION_ID_KEY).toString()); + _destinationUUID = QUuid(transactionObject.value(TRANSACTION_DESTINATION_WALLET_ID_KEY).toString()); + _amount = transactionObject.value(TRANSACTION_AMOUNT_KEY).toDouble(); } \ No newline at end of file diff --git a/domain-server/src/WalletTransaction.h b/domain-server/src/WalletTransaction.h index 6c9708906d..8f36d10302 100644 --- a/domain-server/src/WalletTransaction.h +++ b/domain-server/src/WalletTransaction.h @@ -18,6 +18,7 @@ class WalletTransaction : public QObject { public: + WalletTransaction(); WalletTransaction(const QUuid& destinationUUID, double amount); const QUuid& getUUID() const { return _uuid; } @@ -33,6 +34,8 @@ public: void setIsFinalized(bool isFinalized) { _isFinalized = isFinalized; } QJsonDocument postJson(); + QJsonObject toJson(); + void loadFromJson(const QJsonObject& jsonObject); private: QUuid _uuid; QUuid _destinationUUID; diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp index 547768ec48..aad2cfb386 100644 --- a/libraries/networking/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -136,7 +136,13 @@ void AccountManager::invokedRequest(const QString& path, QNetworkAccessManager:: QNetworkRequest authenticatedRequest; QUrl requestURL = _authURL; - requestURL.setPath(path); + + if (path.startsWith("/")) { + requestURL.setPath(path); + } else { + requestURL.setPath("/" + path); + } + requestURL.setQuery("access_token=" + _accountInfo.getAccessToken().token); authenticatedRequest.setUrl(requestURL); From b7727f0b16f6625bfdb399617a406332c388c5ee Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 21 May 2014 11:02:02 -0700 Subject: [PATCH 33/39] require a flag to enable payment to assigned nodes --- domain-server/src/DomainServer.cpp | 71 +++++++++++++++++------------- 1 file changed, 40 insertions(+), 31 deletions(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 15b720a388..6fca13f3c8 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -191,44 +191,53 @@ void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) { bool DomainServer::optionallyLoginAndSetupAssignmentPayment() { // check if we have a username and password set via env + const QString ASSIGNED_NODE_PAYMENT_OPTION = "pay-nodes"; const QString HIFI_USERNAME_ENV_KEY = "DOMAIN_SERVER_USERNAME"; const QString HIFI_PASSWORD_ENV_KEY = "DOMAIN_SERVER_PASSWORD"; - if (!_oauthProviderURL.isEmpty()) { - - AccountManager& accountManager = AccountManager::getInstance(); - accountManager.setAuthURL(_oauthProviderURL); - - if (!accountManager.hasValidAccessToken()) { - // we don't have a valid access token so we need to get one - QString username = QProcessEnvironment::systemEnvironment().value(HIFI_USERNAME_ENV_KEY); - QString password = QProcessEnvironment::systemEnvironment().value(HIFI_PASSWORD_ENV_KEY); + if (_argumentVariantMap.contains(ASSIGNED_NODE_PAYMENT_OPTION)) { + if (!_oauthProviderURL.isEmpty()) { - if (!username.isEmpty() && !password.isEmpty()) { - accountManager.requestAccessToken(username, password); + AccountManager& accountManager = AccountManager::getInstance(); + accountManager.setAuthURL(_oauthProviderURL); + + if (!accountManager.hasValidAccessToken()) { + // we don't have a valid access token so we need to get one + QString username = QProcessEnvironment::systemEnvironment().value(HIFI_USERNAME_ENV_KEY); + QString password = QProcessEnvironment::systemEnvironment().value(HIFI_PASSWORD_ENV_KEY); - // connect to loginFailed signal from AccountManager so we can quit if that is the case - connect(&accountManager, &AccountManager::loginFailed, this, &DomainServer::loginFailed); - } else { - qDebug() << "Missing access-token or username and password combination. domain-server will now quit."; - QMetaObject::invokeMethod(this, "quit", Qt::QueuedConnection); - return false; + if (!username.isEmpty() && !password.isEmpty()) { + accountManager.requestAccessToken(username, password); + + // connect to loginFailed signal from AccountManager so we can quit if that is the case + connect(&accountManager, &AccountManager::loginFailed, this, &DomainServer::loginFailed); + } else { + qDebug() << "Missing access-token or username and password combination. domain-server will now quit."; + QMetaObject::invokeMethod(this, "quit", Qt::QueuedConnection); + return false; + } } + + // assume that the fact we are authing against HF data server means we will pay for assignments + // setup a timer to send transactions to pay assigned nodes every 30 seconds + QTimer* creditSetupTimer = new QTimer(this); + connect(creditSetupTimer, &QTimer::timeout, this, &DomainServer::setupPendingAssignmentCredits); + + const qint64 CREDIT_CHECK_INTERVAL_MSECS = 5 * 1000; + creditSetupTimer->start(CREDIT_CHECK_INTERVAL_MSECS); + + QTimer* nodePaymentTimer = new QTimer(this); + connect(nodePaymentTimer, &QTimer::timeout, this, &DomainServer::sendPendingTransactionsToServer); + + const qint64 TRANSACTION_SEND_INTERVAL_MSECS = 30 * 1000; + nodePaymentTimer->start(TRANSACTION_SEND_INTERVAL_MSECS); + + } else { + qDebug() << "Missing OAuth provider URL, but assigned node payment was enabled. domain-server will now quit."; + QMetaObject::invokeMethod(this, "quit", Qt::QueuedConnection); + + return false; } - - // assume that the fact we are authing against HF data server means we will pay for assignments - // setup a timer to send transactions to pay assigned nodes every 30 seconds - QTimer* creditSetupTimer = new QTimer(this); - connect(creditSetupTimer, &QTimer::timeout, this, &DomainServer::setupPendingAssignmentCredits); - - const qint64 CREDIT_CHECK_INTERVAL_MSECS = 5 * 1000; - creditSetupTimer->start(CREDIT_CHECK_INTERVAL_MSECS); - - QTimer* nodePaymentTimer = new QTimer(this); - connect(nodePaymentTimer, &QTimer::timeout, this, &DomainServer::sendPendingTransactionsToServer); - - const qint64 TRANSACTION_SEND_INTERVAL_MSECS = 30 * 1000; - nodePaymentTimer->start(TRANSACTION_SEND_INTERVAL_MSECS); } return true; From 98d1146e90158d16f63b5491858515dcfb94cec9 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 21 May 2014 11:04:00 -0700 Subject: [PATCH 34/39] rename the pay for assignments option --- domain-server/src/DomainServer.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 6fca13f3c8..54cfa12130 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -191,11 +191,11 @@ void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) { bool DomainServer::optionallyLoginAndSetupAssignmentPayment() { // check if we have a username and password set via env - const QString ASSIGNED_NODE_PAYMENT_OPTION = "pay-nodes"; + const QString PAY_FOR_ASSIGNMENTS_OPTION = "pay-for-assignments"; const QString HIFI_USERNAME_ENV_KEY = "DOMAIN_SERVER_USERNAME"; const QString HIFI_PASSWORD_ENV_KEY = "DOMAIN_SERVER_PASSWORD"; - if (_argumentVariantMap.contains(ASSIGNED_NODE_PAYMENT_OPTION)) { + if (_argumentVariantMap.contains(PAY_FOR_ASSIGNMENTS_OPTION)) { if (!_oauthProviderURL.isEmpty()) { AccountManager& accountManager = AccountManager::getInstance(); From 0e15c82e49e60b61c8b6c1cd6e795e10b429655f Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Wed, 21 May 2014 11:58:20 -0700 Subject: [PATCH 35/39] Allow setting masked joints for each animation. --- interface/src/avatar/MyAvatar.cpp | 2 ++ interface/src/renderer/Model.cpp | 14 ++++++++++++++ interface/src/renderer/Model.h | 4 ++++ interface/src/ui/AnimationsDialog.cpp | 28 +++++++++++++++++++++++++++ interface/src/ui/AnimationsDialog.h | 3 +++ 5 files changed, 51 insertions(+) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 181814d127..a886ad4503 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -513,6 +513,7 @@ void MyAvatar::saveData(QSettings* settings) { settings->setValue("url", pointer->getURL()); settings->setValue("fps", pointer->getFPS()); settings->setValue("priority", pointer->getPriority()); + settings->setValue("maskedJoints", pointer->getMaskedJoints()); } settings->endArray(); @@ -579,6 +580,7 @@ void MyAvatar::loadData(QSettings* settings) { handle->setURL(settings->value("url").toUrl()); handle->setFPS(loadSetting(settings, "fps", 30.0f)); handle->setPriority(loadSetting(settings, "priority", 1.0f)); + handle->setMaskedJoints(settings->value("maskedJoints").toStringList()); } settings->endArray(); diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index 7bef1fd959..900d7ff951 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -1684,6 +1684,11 @@ void AnimationHandle::setPriority(float priority) { } } +void AnimationHandle::setMaskedJoints(const QStringList& maskedJoints) { + _maskedJoints = maskedJoints; + _jointMappings.clear(); +} + void AnimationHandle::setRunning(bool running) { if ((_running = running)) { if (!_model->_runningAnimations.contains(_self)) { @@ -1716,6 +1721,15 @@ void AnimationHandle::simulate(float deltaTime) { if (_jointMappings.isEmpty()) { return; } + if (!_maskedJoints.isEmpty()) { + const FBXGeometry& geometry = _model->getGeometry()->getFBXGeometry(); + for (int i = 0; i < _jointMappings.size(); i++) { + int& mapping = _jointMappings[i]; + if (mapping != -1 && _maskedJoints.contains(geometry.joints.at(mapping).name)) { + mapping = -1; + } + } + } } const FBXGeometry& animationGeometry = _animation->getGeometry(); diff --git a/interface/src/renderer/Model.h b/interface/src/renderer/Model.h index feef91f703..59ec50cac1 100644 --- a/interface/src/renderer/Model.h +++ b/interface/src/renderer/Model.h @@ -393,6 +393,9 @@ public: void setLoop(bool loop) { _loop = loop; } bool getLoop() const { return _loop; } + void setMaskedJoints(const QStringList& maskedJoints); + const QStringList& getMaskedJoints() const { return _maskedJoints; } + void setRunning(bool running); bool isRunning() const { return _running; } @@ -414,6 +417,7 @@ private: float _fps; float _priority; bool _loop; + QStringList _maskedJoints; bool _running; QVector _jointMappings; float _frameIndex; diff --git a/interface/src/ui/AnimationsDialog.cpp b/interface/src/ui/AnimationsDialog.cpp index a9341fa283..29837f67be 100644 --- a/interface/src/ui/AnimationsDialog.cpp +++ b/interface/src/ui/AnimationsDialog.cpp @@ -100,6 +100,13 @@ AnimationPanel::AnimationPanel(AnimationsDialog* dialog, const AnimationHandlePo _priority->setValue(handle->getPriority()); connect(_priority, SIGNAL(valueChanged(double)), SLOT(updateHandle())); + QHBoxLayout* maskedJointBox = new QHBoxLayout(); + layout->addRow("Masked Joints:", maskedJointBox); + maskedJointBox->addWidget(_maskedJoints = new QLineEdit(handle->getMaskedJoints().join(", ")), 1); + connect(_maskedJoints, SIGNAL(returnPressed()), SLOT(updateHandle())); + maskedJointBox->addWidget(_chooseMaskedJoints = new QPushButton("Choose")); + connect(_chooseMaskedJoints, SIGNAL(clicked(bool)), SLOT(chooseMaskedJoints())); + QPushButton* remove = new QPushButton("Delete"); layout->addRow(remove); connect(remove, SIGNAL(clicked(bool)), SLOT(removeHandle())); @@ -118,10 +125,31 @@ void AnimationPanel::chooseURL() { emit _url->returnPressed(); } +void AnimationPanel::chooseMaskedJoints() { + QMenu menu; + QStringList maskedJoints = _handle->getMaskedJoints(); + foreach (const QString& jointName, Application::getInstance()->getAvatar()->getJointNames()) { + QAction* action = menu.addAction(jointName); + action->setCheckable(true); + action->setChecked(maskedJoints.contains(jointName)); + } + QAction* action = menu.exec(_chooseMaskedJoints->mapToGlobal(QPoint(0, 0))); + if (action) { + if (action->isChecked()) { + maskedJoints.append(action->text()); + } else { + maskedJoints.removeOne(action->text()); + } + _handle->setMaskedJoints(maskedJoints); + _maskedJoints->setText(maskedJoints.join(", ")); + } +} + void AnimationPanel::updateHandle() { _handle->setURL(_url->text()); _handle->setFPS(_fps->value()); _handle->setPriority(_priority->value()); + _handle->setMaskedJoints(_maskedJoints->text().split(QRegExp("\\s*,\\s*"))); } void AnimationPanel::removeHandle() { diff --git a/interface/src/ui/AnimationsDialog.h b/interface/src/ui/AnimationsDialog.h index 5491418f6d..7693a1da97 100644 --- a/interface/src/ui/AnimationsDialog.h +++ b/interface/src/ui/AnimationsDialog.h @@ -53,6 +53,7 @@ public: private slots: void chooseURL(); + void chooseMaskedJoints(); void updateHandle(); void removeHandle(); @@ -63,6 +64,8 @@ private: QLineEdit* _url; QDoubleSpinBox* _fps; QDoubleSpinBox* _priority; + QLineEdit* _maskedJoints; + QPushButton* _chooseMaskedJoints; bool _applying; }; From 709a9d75f6cc6e0d7633cd2f663fbf7534b660ee Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Wed, 21 May 2014 12:03:47 -0700 Subject: [PATCH 36/39] Provide an option for the masked joints in the scriptable method, fix a couple of annoying warnings. --- interface/src/avatar/MyAvatar.cpp | 5 +++-- interface/src/avatar/MyAvatar.h | 2 +- interface/src/ui/NodeBounds.cpp | 2 -- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index a886ad4503..be88a80b90 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -441,10 +441,10 @@ void MyAvatar::removeAnimationHandle(const AnimationHandlePointer& handle) { _animationHandles.removeOne(handle); } -void MyAvatar::startAnimation(const QString& url, float fps, float priority, bool loop) { +void MyAvatar::startAnimation(const QString& url, float fps, float priority, bool loop, const QStringList& maskedJoints) { if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "startAnimation", Q_ARG(const QString&, url), - Q_ARG(float, fps), Q_ARG(float, priority), Q_ARG(bool, loop)); + Q_ARG(float, fps), Q_ARG(float, priority), Q_ARG(bool, loop), Q_ARG(const QStringList&, maskedJoints)); return; } AnimationHandlePointer handle = _skeletonModel.createAnimationHandle(); @@ -452,6 +452,7 @@ void MyAvatar::startAnimation(const QString& url, float fps, float priority, boo handle->setFPS(fps); handle->setPriority(priority); handle->setLoop(loop); + handle->setMaskedJoints(maskedJoints); handle->start(); } diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 11493524ca..9d6f22264f 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -68,7 +68,7 @@ public: /// Allows scripts to run animations. Q_INVOKABLE void startAnimation(const QString& url, float fps = 30.0f, - float priority = 1.0f, bool loop = false); + float priority = 1.0f, bool loop = false, const QStringList& maskedJoints = QStringList()); /// Stops an animation as identified by a URL. Q_INVOKABLE void stopAnimation(const QString& url); diff --git a/interface/src/ui/NodeBounds.cpp b/interface/src/ui/NodeBounds.cpp index 735dc66ddf..c4139f39c5 100644 --- a/interface/src/ui/NodeBounds.cpp +++ b/interface/src/ui/NodeBounds.cpp @@ -133,7 +133,6 @@ void NodeBounds::draw() { glTranslatef(selectedCenter.x, selectedCenter.y, selectedCenter.z); glScalef(selectedScale, selectedScale, selectedScale); - NodeType_t selectedNodeType = selectedNode->getType(); float red, green, blue; getColorForNodeType(selectedNode->getType(), red, green, blue); @@ -225,7 +224,6 @@ void NodeBounds::drawOverlay() { const int FONT = 2; const int PADDING = 10; const int MOUSE_OFFSET = 10; - const int BACKGROUND_OFFSET_Y = -20; const int BACKGROUND_BEVEL = 3; int mouseX = application->getMouseX(), From e3ac7c5eecb84cb1166fe6d53836340e789e80b1 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 21 May 2014 12:07:23 -0700 Subject: [PATCH 37/39] rename assignment payment method for clarity --- domain-server/src/DomainServer.cpp | 4 ++-- domain-server/src/DomainServer.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 54cfa12130..9ad36e6956 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -52,7 +52,7 @@ DomainServer::DomainServer(int argc, char* argv[]) : _networkAccessManager = new QNetworkAccessManager(this); - if (optionallyReadX509KeyAndCertificate() && optionallySetupOAuth() && optionallyLoginAndSetupAssignmentPayment()) { + if (optionallyReadX509KeyAndCertificate() && optionallySetupOAuth() && optionallySetupAssignmentPayment()) { // we either read a certificate and private key or were not passed one // and completed login or did not need to @@ -189,7 +189,7 @@ void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) { addStaticAssignmentsToQueue(); } -bool DomainServer::optionallyLoginAndSetupAssignmentPayment() { +bool DomainServer::optionallySetupAssignmentPayment() { // check if we have a username and password set via env const QString PAY_FOR_ASSIGNMENTS_OPTION = "pay-for-assignments"; const QString HIFI_USERNAME_ENV_KEY = "DOMAIN_SERVER_USERNAME"; diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index aa55e07086..b038850b3d 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -58,7 +58,7 @@ private: void setupNodeListAndAssignments(const QUuid& sessionUUID = QUuid::createUuid()); bool optionallySetupOAuth(); bool optionallyReadX509KeyAndCertificate(); - bool optionallyLoginAndSetupAssignmentPayment(); + bool optionallySetupAssignmentPayment(); void processDatagram(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr); From 906e082215fb4b8c988689d3b2ae7b10b981e7f5 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Wed, 21 May 2014 13:56:20 -0700 Subject: [PATCH 38/39] If there are no animation frames in the model, treat its default position as a single frame. --- libraries/fbx/src/FBXReader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp index d8663055cd..37a03bcdee 100644 --- a/libraries/fbx/src/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -1442,7 +1442,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) } // figure the number of animation frames from the curves - int frameCount = 0; + int frameCount = 1; foreach (const AnimationCurve& curve, animationCurves) { frameCount = qMax(frameCount, curve.values.size()); } From f876a72b37e1f171cecf2f1b16116433e4f4b60d Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Wed, 21 May 2014 15:12:36 -0700 Subject: [PATCH 39/39] fix crash in model server when you attempt to create an unreasonably small model --- libraries/models/src/ModelTreeElement.cpp | 5 +++++ libraries/octree/src/OctreeElement.cpp | 5 +++++ libraries/octree/src/OctreeElement.h | 2 ++ 3 files changed, 12 insertions(+) diff --git a/libraries/models/src/ModelTreeElement.cpp b/libraries/models/src/ModelTreeElement.cpp index 4929edb484..3caaf3a14c 100644 --- a/libraries/models/src/ModelTreeElement.cpp +++ b/libraries/models/src/ModelTreeElement.cpp @@ -95,6 +95,11 @@ bool ModelTreeElement::bestFitModelBounds(const ModelItem& model) const { if (_box.contains(clampedMin) && _box.contains(clampedMax)) { int childForMinimumPoint = getMyChildContainingPoint(clampedMin); int childForMaximumPoint = getMyChildContainingPoint(clampedMax); + + // if this is a really small box, then it's close enough! + if (_box.getScale() <= SMALLEST_REASONABLE_OCTREE_ELEMENT_SCALE) { + return true; + } // If I contain both the minimum and maximum point, but two different children of mine // contain those points, then I am the best fit for that model if (childForMinimumPoint != childForMaximumPoint) { diff --git a/libraries/octree/src/OctreeElement.cpp b/libraries/octree/src/OctreeElement.cpp index 0462a3b53d..90938ddff3 100644 --- a/libraries/octree/src/OctreeElement.cpp +++ b/libraries/octree/src/OctreeElement.cpp @@ -1412,6 +1412,11 @@ OctreeElement* OctreeElement::getOrCreateChildElementContaining(const AABox& box if (!child) { child = addChildAtIndex(childIndex); } + + // if we've made a really small child, then go ahead and use that one. + if (child->getScale() <= SMALLEST_REASONABLE_OCTREE_ELEMENT_SCALE) { + return child; + } // Now that we have the child to recurse down, let it answer the original question... return child->getOrCreateChildElementContaining(box); diff --git a/libraries/octree/src/OctreeElement.h b/libraries/octree/src/OctreeElement.h index 2485e49797..3b056fa789 100644 --- a/libraries/octree/src/OctreeElement.h +++ b/libraries/octree/src/OctreeElement.h @@ -32,6 +32,8 @@ class OctreePacketData; class ReadBitstreamToTreeParams; class VoxelSystem; +const float SMALLEST_REASONABLE_OCTREE_ELEMENT_SCALE = (1.0f / TREE_SCALE) / 10000.0f; // 1/10,000th of a meter + // Callers who want delete hook callbacks should implement this class class OctreeElementDeleteHook { public: