diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 56570a4b82..d17caf24f0 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -189,6 +189,12 @@ void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) { populateDefaultStaticAssignmentsExcludingTypes(parsedTypes); LimitedNodeList* nodeList = LimitedNodeList::createInstance(domainServerPort, domainServerDTLSPort); + + const QString DOMAIN_CONFIG_ID_KEY = "id"; + + // set our LimitedNodeList UUID to match the UUID from our config + // nodes will currently use this to add resources to data-web that relate to our domain + nodeList->setSessionUUID(_argumentVariantMap.value(DOMAIN_CONFIG_ID_KEY).toString()); connect(nodeList, &LimitedNodeList::nodeAdded, this, &DomainServer::nodeAdded); connect(nodeList, &LimitedNodeList::nodeKilled, this, &DomainServer::nodeKilled); diff --git a/examples/gracefulControls.js b/examples/gracefulControls.js new file mode 100644 index 0000000000..e2b603c5d7 --- /dev/null +++ b/examples/gracefulControls.js @@ -0,0 +1,216 @@ +// +// gracefulControls.js +// examples +// +// Created by Ryan Huffman on 9/11/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 +// + +var DEFAULT_PARAMETERS = { + // Coefficient to use for linear drag. Higher numbers will cause motion to + // slow down more quickly. + DRAG_COEFFICIENT: 0.9, + MAX_SPEED: 40.0, + ACCELERATION: 1.0, + + MOUSE_YAW_SCALE: -0.125, + MOUSE_PITCH_SCALE: -0.125, + MOUSE_SENSITIVITY: 0.5, + + // Damping frequency, adjust to change mouse look behavior + W: 4.2, +} + +var BRAKE_PARAMETERS = { + DRAG_COEFFICIENT: 4.9, + MAX_SPEED: DEFAULT_PARAMETERS.MAX_SPEED, + ACCELERATION: 0, + + W: 1.0, + MOUSE_YAW_SCALE: -0.125, + MOUSE_PITCH_SCALE: -0.125, + MOUSE_SENSITIVITY: 0.5, +} + +var movementParameters = DEFAULT_PARAMETERS; + +// Movement keys +var KEY_BRAKE = "q"; +var KEY_FORWARD = "w"; +var KEY_BACKWARD = "s"; +var KEY_LEFT = "a"; +var KEY_RIGHT = "d"; +var KEY_UP = "e"; +var KEY_DOWN = "c"; +var KEY_ENABLE = "SPACE"; +var CAPTURED_KEYS = [KEY_BRAKE, KEY_FORWARD, KEY_BACKWARD, KEY_LEFT, KEY_RIGHT, KEY_UP, KEY_DOWN, KEY_ENABLE]; + +// Global Variables +var keys = {}; +var velocity = { x: 0, y: 0, z: 0 }; +var velocityVertical = 0; +var enabled = false; + +var lastX = Window.getCursorPositionX(); +var lastY = Window.getCursorPositionY(); +var yawFromMouse = 0; +var pitchFromMouse = 0; + +var yawSpeed = 0; +var pitchSpeed = 0; + +function keyPressEvent(event) { + if (event.text == "ESC") { + disable(); + } else if (event.text == KEY_ENABLE) { + if (Window.hasFocus()) { + enable(); + } + } else if (event.text == KEY_BRAKE) { + movementParameters = BRAKE_PARAMETERS; + } + keys[event.text] = true; +} + +function keyReleaseEvent(event) { + if (event.text == KEY_BRAKE) { + movementParameters = DEFAULT_PARAMETERS; + } + + delete keys[event.text]; +} + +function update(dt) { + var maxMove = 3.0 * dt; + var targetVelocity = { x: 0, y: 0, z: 0 }; + var targetVelocityVertical = 0; + var acceleration = movementParameters.ACCELERATION; + + if (keys[KEY_FORWARD]) { + targetVelocity.z -= acceleration * dt; + } + if (keys[KEY_LEFT]) { + targetVelocity.x -= acceleration * dt; + } + if (keys[KEY_BACKWARD]) { + targetVelocity.z += acceleration * dt; + } + if (keys[KEY_RIGHT]) { + targetVelocity.x += acceleration * dt; + } + if (keys[KEY_UP]) { + targetVelocityVertical += acceleration * dt; + } + if (keys[KEY_DOWN]) { + targetVelocityVertical -= acceleration * dt; + } + + if (enabled && Window.hasFocus()) { + var x = Window.getCursorPositionX(); + var y = Window.getCursorPositionY(); + + yawFromMouse += ((x - lastX) * movementParameters.MOUSE_YAW_SCALE * movementParameters.MOUSE_SENSITIVITY); + pitchFromMouse += ((y - lastY) * movementParameters.MOUSE_PITCH_SCALE * movementParameters.MOUSE_SENSITIVITY); + pitchFromMouse = Math.max(-180, Math.min(180, pitchFromMouse)); + + resetCursorPosition(); + } + + // Here we use a linear damping model - http://en.wikipedia.org/wiki/Damping#Linear_damping + // Because we are using a critically damped model (no oscillation), ΞΆ = 1 and + // so we derive the formula: acceleration = -(2 * w0 * v) - (w0^2 * x) + var W = movementParameters.W; + yawAccel = (W * W * yawFromMouse) - (2 * W * yawSpeed); + pitchAccel = (W * W * pitchFromMouse) - (2 * W * pitchSpeed); + + yawSpeed += yawAccel * dt; + var yawMove = yawSpeed * dt; + var newOrientation = Quat.multiply(MyAvatar.orientation, Quat.fromVec3Degrees( { x: 0, y: yawMove, z: 0 } )); + MyAvatar.orientation = newOrientation; + yawFromMouse -= yawMove; + + pitchSpeed += pitchAccel * dt; + var pitchMove = pitchSpeed * dt; + var newPitch = MyAvatar.headPitch + pitchMove; + MyAvatar.headPitch = newPitch; + pitchFromMouse -= pitchMove; + + // If force isn't being applied in a direction, add drag; + if (targetVelocity.x == 0) { + targetVelocity.x -= (velocity.x * movementParameters.DRAG_COEFFICIENT * dt); + } + if (targetVelocity.z == 0) { + targetVelocity.z -= (velocity.z * movementParameters.DRAG_COEFFICIENT * dt); + } + velocity = Vec3.sum(velocity, targetVelocity); + + var maxSpeed = movementParameters.MAX_SPEED; + velocity.x = Math.max(-maxSpeed, Math.min(maxSpeed, velocity.x)); + velocity.z = Math.max(-maxSpeed, Math.min(maxSpeed, velocity.z)); + var v = Vec3.multiplyQbyV(MyAvatar.headOrientation, velocity); + + if (targetVelocityVertical == 0) { + targetVelocityVertical -= (velocityVertical * movementParameters.DRAG_COEFFICIENT * dt); + } + velocityVertical += targetVelocityVertical; + velocityVertical = Math.max(-maxSpeed, Math.min(maxSpeed, velocityVertical)); + v.y += velocityVertical; + + MyAvatar.setVelocity(v); +} + +function vecToString(vec) { + return vec.x + ", " + vec.y + ", " + vec.z; +} + +function scriptEnding() { + disable(); +} + +function resetCursorPosition() { + var newX = Window.x + Window.innerWidth / 2; + var newY = Window.y + Window.innerHeight / 2; + Window.setCursorPosition(newX, newY); + lastX = newX; + lastY = newY; +} + +function enable() { + if (!enabled) { + enabled = true; + + resetCursorPosition(); + + // Reset movement variables + yawFromMouse = 0; + pitchFromMouse = 0; + yawSpeed = 0; + pitchSpeed = 0; + velocityVertical = 0; + + for (var i = 0; i < CAPTURED_KEYS.length; i++) { + Controller.captureKeyEvents({ text: CAPTURED_KEYS[i] }); + } + Window.setCursorVisible(false); + Script.update.connect(update); + } +} + +function disable() { + if (enabled) { + enabled = false; + for (var i = 0; i < CAPTURED_KEYS.length; i++) { + Controller.releaseKeyEvents({ text: CAPTURED_KEYS[i] }); + } + Window.setCursorVisible(true); + Script.update.disconnect(update); + } +} + +Controller.keyPressEvent.connect(keyPressEvent); +Controller.keyReleaseEvent.connect(keyReleaseEvent); + +Script.scriptEnding.connect(scriptEnding); diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index be155dcddc..4a328562c7 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -51,6 +51,7 @@ #include #include +#include #include #include #include @@ -80,10 +81,10 @@ #include "scripting/AccountScriptingInterface.h" #include "scripting/AudioDeviceScriptingInterface.h" #include "scripting/ClipboardScriptingInterface.h" +#include "scripting/LocationScriptingInterface.h" #include "scripting/MenuScriptingInterface.h" #include "scripting/SettingsScriptingInterface.h" #include "scripting/WindowScriptingInterface.h" -#include "scripting/LocationScriptingInterface.h" #include "ui/InfoView.h" #include "ui/OAuthWebViewHandler.h" @@ -358,9 +359,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : Particle::setVoxelEditPacketSender(&_voxelEditSender); Particle::setParticleEditPacketSender(&_particleEditSender); - // 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 @@ -405,9 +403,15 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : connect(_window, &MainWindow::windowGeometryChanged, _runningScriptsWidget, &RunningScriptsWidget::setBoundary); + + AddressManager& addressManager = AddressManager::getInstance(); - //When -url in command line, teleport to location - urlGoTo(argc, constArgv); + // connect to the domainChangeRequired signal on AddressManager + connect(&addressManager, &AddressManager::possibleDomainChangeRequired, + this, &Application::changeDomainHostname); + + // when -url in command line, teleport to location + addressManager.handleLookupString(getCmdOption(argc, constArgv, "-url")); // call the OAuthWebviewHandler static getter so that its instance lives in our thread OAuthWebViewHandler::getInstance(); @@ -807,12 +811,14 @@ bool Application::event(QEvent* event) { // handle custom URL if (event->type() == QEvent::FileOpen) { QFileOpenEvent* fileEvent = static_cast(event); - bool isHifiSchemeURL = !fileEvent->url().isEmpty() && fileEvent->url().toLocalFile().startsWith(CUSTOM_URL_SCHEME); - if (isHifiSchemeURL) { - Menu::getInstance()->goToURL(fileEvent->url().toLocalFile()); + + if (!fileEvent->url().isEmpty()) { + AddressManager::getInstance().handleLookupString(fileEvent->url().toLocalFile()); } + return false; } + return QApplication::event(event); } @@ -912,7 +918,12 @@ void Application::keyPressEvent(QKeyEvent* event) { case Qt::Key_Return: case Qt::Key_Enter: - Menu::getInstance()->triggerOption(MenuOption::Chat); + if (isMeta) { + Menu::getInstance()->triggerOption(MenuOption::AddressBar); + } else { + Menu::getInstance()->triggerOption(MenuOption::Chat); + } + break; case Qt::Key_Up: @@ -1059,10 +1070,6 @@ void Application::keyPressEvent(QKeyEvent* event) { case Qt::Key_Equal: _myAvatar->resetSize(); break; - - case Qt::Key_At: - Menu::getInstance()->goTo(); - break; default: event->ignore(); break; @@ -1321,7 +1328,7 @@ void Application::dropEvent(QDropEvent *event) { SnapshotMetaData* snapshotData = Snapshot::parseSnapshotData(snapshotPath); if (snapshotData) { if (!snapshotData->getDomain().isEmpty()) { - Menu::getInstance()->goToDomain(snapshotData->getDomain()); + changeDomainHostname(snapshotData->getDomain()); } _myAvatar->setPosition(snapshotData->getLocation()); @@ -3377,31 +3384,53 @@ void Application::updateWindowTitle(){ #ifndef WIN32 // crashes with vs2013/win32 qDebug("Application title set to: %s", title.toStdString().c_str()); -#endif //!WIN32 +#endif _window->setWindowTitle(title); } void Application::updateLocationInServer() { AccountManager& accountManager = AccountManager::getInstance(); - - if (accountManager.isLoggedIn()) { + const QUuid& domainUUID = NodeList::getInstance()->getDomainHandler().getUUID(); + + if (accountManager.isLoggedIn() && !domainUUID.isNull()) { // construct a QJsonObject given the user's current address information - QJsonObject updatedLocationObject; + QJsonObject rootObject; - 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()); + QJsonObject locationObject; + + QString pathString = AddressManager::pathForPositionAndOrientation(_myAvatar->getPosition(), + true, + _myAvatar->getOrientation()); + + const QString LOCATION_KEY_IN_ROOT = "location"; + const QString PATH_KEY_IN_LOCATION = "path"; + const QString DOMAIN_ID_KEY_IN_LOCATION = "domain_id"; + + locationObject.insert(PATH_KEY_IN_LOCATION, pathString); + locationObject.insert(DOMAIN_ID_KEY_IN_LOCATION, domainUUID.toString()); - updatedLocationObject.insert("address", addressObject); + rootObject.insert(LOCATION_KEY_IN_ROOT, locationObject); - accountManager.authenticatedRequest("/api/v1/users/address", QNetworkAccessManager::PutOperation, - JSONCallbackParameters(), QJsonDocument(updatedLocationObject).toJson()); + accountManager.authenticatedRequest("/api/v1/users/location", QNetworkAccessManager::PutOperation, + JSONCallbackParameters(), QJsonDocument(rootObject).toJson()); } } +void Application::changeDomainHostname(const QString &newDomainHostname) { + NodeList* nodeList = NodeList::getInstance(); + + if (!nodeList->getDomainHandler().isCurrentHostname(newDomainHostname)) { + // tell the MyAvatar object to send a kill packet so that it dissapears from its old avatar mixer immediately + _myAvatar->sendKillAvatar(); + + // call the domain hostname change as a queued connection on the nodelist + QMetaObject::invokeMethod(&NodeList::getInstance()->getDomainHandler(), "setHostname", + Q_ARG(const QString&, newDomainHostname)); + } +} + void Application::domainChanged(const QString& domainHostname) { updateWindowTitle(); @@ -3785,12 +3814,11 @@ ScriptEngine* Application::loadScript(const QString& scriptFilename, bool isUser QScriptValue windowValue = scriptEngine->registerGlobalObject("Window", WindowScriptingInterface::getInstance()); scriptEngine->registerGetterSetter("location", LocationScriptingInterface::locationGetter, - LocationScriptingInterface::locationSetter, windowValue); - + LocationScriptingInterface::locationSetter, windowValue); // register `location` on the global object. scriptEngine->registerGetterSetter("location", LocationScriptingInterface::locationGetter, - LocationScriptingInterface::locationSetter); - + LocationScriptingInterface::locationSetter); + scriptEngine->registerGlobalObject("Menu", MenuScriptingInterface::getInstance()); scriptEngine->registerGlobalObject("Settings", SettingsScriptingInterface::getInstance()); scriptEngine->registerGlobalObject("AudioDevice", AudioDeviceScriptingInterface::getInstance()); @@ -3918,6 +3946,15 @@ void Application::uploadAttachment() { uploadModel(ATTACHMENT_MODEL); } +void Application::openUrl(const QUrl& url) { + if (url.scheme() == HIFI_URL_SCHEME) { + AddressManager::getInstance().handleLookupString(url.toString()); + } else { + // address manager did not handle - ask QDesktopServices to handle + QDesktopServices::openUrl(url); + } +} + void Application::domainSettingsReceived(const QJsonObject& domainSettingsObject) { // from the domain-handler, figure out the satoshi cost per voxel and per meter cubed @@ -4088,6 +4125,14 @@ void Application::skipVersion(QString latestVersion) { skipFile.write(latestVersion.toStdString().c_str()); } +void Application::setCursorVisible(bool visible) { + if (visible) { + restoreOverrideCursor(); + } else { + setOverrideCursor(Qt::BlankCursor); + } +} + void Application::takeSnapshot() { QMediaPlayer* player = new QMediaPlayer(); QFileInfo inf = QFileInfo(Application::resourcesPath() + "sounds/snap.wav"); @@ -4106,37 +4151,3 @@ void Application::takeSnapshot() { } _snapshotShareDialog->show(); } - -void Application::urlGoTo(int argc, const char * constArgv[]) { - //Gets the url (hifi://domain/destination/orientation) - QString customUrl = getCmdOption(argc, constArgv, "-url"); - if(customUrl.startsWith(CUSTOM_URL_SCHEME + "//")) { - QStringList urlParts = customUrl.remove(0, CUSTOM_URL_SCHEME.length() + 2).split('/', QString::SkipEmptyParts); - if (urlParts.count() == 1) { - // location coordinates or place name - QString domain = urlParts[0]; - Menu::goToDomain(domain); - } 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/Application.h b/interface/src/Application.h index fbcc67cf80..2466e54936 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -114,7 +114,6 @@ static const float NODE_KILLED_GREEN = 0.0f; static const float NODE_KILLED_BLUE = 0.0f; static const QString SNAPSHOT_EXTENSION = ".jpg"; -static const QString CUSTOM_URL_SCHEME = "hifi:"; static const float BILLBOARD_FIELD_OF_VIEW = 30.0f; // degrees static const float BILLBOARD_DISTANCE = 5.0f; // meters @@ -153,7 +152,6 @@ public: void initializeGL(); void paintGL(); void resizeGL(int width, int height); - void urlGoTo(int argc, const char * constArgv[]); void keyPressEvent(QKeyEvent* event); void keyReleaseEvent(QKeyEvent* event); @@ -299,6 +297,8 @@ public: QStringList getRunningScripts() { return _scriptEnginesHash.keys(); } ScriptEngine* getScriptEngine(QString scriptHash) { return _scriptEnginesHash.contains(scriptHash) ? _scriptEnginesHash[scriptHash] : NULL; } + void setCursorVisible(bool visible); + signals: /// Fired when we're simulating; allows external parties to hook in. @@ -314,6 +314,7 @@ signals: void importDone(); public slots: + void changeDomainHostname(const QString& newDomainHostname); void domainChanged(const QString& domainHostname); void updateWindowTitle(); void updateLocationInServer(); @@ -353,6 +354,8 @@ public slots: void uploadHead(); void uploadSkeleton(); void uploadAttachment(); + + void openUrl(const QUrl& url); void bumpSettings() { ++_numChangedSettings; } diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index ff5e50d66c..c39e9abd3d 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -30,6 +30,7 @@ #include #include +#include #include #include #include @@ -155,21 +156,6 @@ Menu::Menu() : appInstance, SLOT(toggleRunningScriptsWidget())); addDisabledActionAndSeparator(fileMenu, "Go"); - addActionToQMenuAndActionHash(fileMenu, - MenuOption::GoHome, - Qt::CTRL | Qt::Key_G, - appInstance->getAvatar(), - SLOT(goHome())); - addActionToQMenuAndActionHash(fileMenu, - MenuOption::GoToDomain, - Qt::CTRL | Qt::Key_D, - this, - SLOT(goToDomainDialog())); - addActionToQMenuAndActionHash(fileMenu, - MenuOption::GoToLocation, - Qt::CTRL | Qt::SHIFT | Qt::Key_L, - this, - SLOT(goToLocation())); addActionToQMenuAndActionHash(fileMenu, MenuOption::NameLocation, Qt::CTRL | Qt::Key_N, @@ -181,12 +167,10 @@ Menu::Menu() : this, SLOT(toggleLocationList())); addActionToQMenuAndActionHash(fileMenu, - MenuOption::GoTo, - Qt::Key_At, + MenuOption::AddressBar, + Qt::CTRL | Qt::Key_Enter, this, - SLOT(goTo())); - connect(&LocationManager::getInstance(), &LocationManager::multipleDestinationsFound, - this, &Menu::multipleDestinationsDecision); + SLOT(toggleAddressBar())); addDisabledActionAndSeparator(fileMenu, "Upload Avatar Model"); addActionToQMenuAndActionHash(fileMenu, MenuOption::UploadHead, 0, Application::getInstance(), SLOT(uploadHead())); @@ -1155,147 +1139,25 @@ void Menu::changePrivateKey() { sendFakeEnterEvent(); } -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 - Application::getInstance()->getAvatar()->sendKillAvatar(); +void Menu::toggleAddressBar() { - // give our nodeList the new domain-server hostname - NodeList::getInstance()->getDomainHandler().setHostname(newDomain); + QInputDialog addressBarDialog(Application::getInstance()->getWindow()); + addressBarDialog.setWindowTitle("Address Bar"); + addressBarDialog.setWindowFlags(Qt::Sheet); + addressBarDialog.setLabelText("place, domain, @user, example.com, /position/orientation"); + + addressBarDialog.resize(addressBarDialog.parentWidget()->size().width() * DIALOG_RATIO_OF_WINDOW, + addressBarDialog.size().height()); + + int dialogReturn = addressBarDialog.exec(); + if (dialogReturn == QDialog::Accepted && !addressBarDialog.textValue().isEmpty()) { + // let the AddressManger figure out what to do with this + AddressManager::getInstance().handleLookupString(addressBarDialog.textValue()); } -} - -void Menu::goToDomainDialog() { - - QString currentDomainHostname = NodeList::getInstance()->getDomainHandler().getHostname(); - - if (NodeList::getInstance()->getDomainHandler().getPort() != DEFAULT_DOMAIN_SERVER_PORT) { - // add the port to the currentDomainHostname string if it is custom - currentDomainHostname.append(QString(":%1").arg(NodeList::getInstance()->getDomainHandler().getPort())); - } - - QInputDialog domainDialog(Application::getInstance()->getWindow()); - domainDialog.setWindowTitle("Go to Domain"); - domainDialog.setLabelText("Domain server:"); - domainDialog.setTextValue(currentDomainHostname); - domainDialog.resize(domainDialog.parentWidget()->size().width() * DIALOG_RATIO_OF_WINDOW, domainDialog.size().height()); - - int dialogReturn = domainDialog.exec(); - if (dialogReturn == QDialog::Accepted) { - QString newHostname(DEFAULT_DOMAIN_HOSTNAME); - - if (domainDialog.textValue().size() > 0) { - // the user input a new hostname, use that - newHostname = domainDialog.textValue(); - } - - goToDomain(newHostname); - } - + sendFakeEnterEvent(); } -void Menu::goToOrientation(QString orientation) { - LocationManager::getInstance().goToOrientation(orientation); -} - -bool Menu::goToDestination(QString destination) { - return LocationManager::getInstance().goToDestination(destination); -} - -void Menu::goTo(QString destination) { - LocationManager::getInstance().goTo(destination); -} - -void Menu::goTo() { - - QInputDialog gotoDialog(Application::getInstance()->getWindow()); - gotoDialog.setWindowTitle("Go to"); - gotoDialog.setLabelText("Destination or URL:\n @user, #place, hifi://domain/location/orientation"); - QString destination = QString(); - - gotoDialog.setTextValue(destination); - gotoDialog.resize(gotoDialog.parentWidget()->size().width() * DIALOG_RATIO_OF_WINDOW, gotoDialog.size().height()); - - int dialogReturn = gotoDialog.exec(); - if (dialogReturn == QDialog::Accepted && !gotoDialog.textValue().isEmpty()) { - QString desiredDestination = gotoDialog.textValue(); - if (!goToURL(desiredDestination)) {; - goTo(desiredDestination); - } - } - sendFakeEnterEvent(); -} - -bool Menu::goToURL(QString location) { - if (location.startsWith(CUSTOM_URL_SCHEME + "/")) { - QStringList urlParts = location.remove(0, CUSTOM_URL_SCHEME.length()).split('/', QString::SkipEmptyParts); - - if (urlParts.count() > 1) { - // if url has 2 or more parts, the first one is domain name - QString domain = urlParts[0]; - - // second part is either a destination coordinate or - // a place name - QString destination = urlParts[1]; - - // any third part is an avatar orientation. - QString orientation = urlParts.count() > 2 ? urlParts[2] : QString(); - - goToDomain(domain); - - // goto either @user, #place, or x-xx,y-yy,z-zz - // style co-ordinate. - goTo(destination); - - if (!orientation.isEmpty()) { - // location orientation - goToOrientation(orientation); - } - } else if (urlParts.count() == 1) { - QString destination = urlParts[0]; - - // If this starts with # or @, treat it as a user/location, otherwise treat it as a domain - if (destination[0] == '#' || destination[0] == '@') { - goTo(destination); - } else { - goToDomain(destination); - } - } - return true; - } - return false; -} - -void Menu::goToUser(const QString& user) { - LocationManager::getInstance().goTo(user); -} - -/// Open a url, shortcutting any "hifi" scheme URLs to the local application. -void Menu::openUrl(const QUrl& url) { - if (url.scheme() == "hifi") { - goToURL(url.toString()); - } else { - QDesktopServices::openUrl(url); - } -} - -void Menu::multipleDestinationsDecision(const QJsonObject& userData, const QJsonObject& placeData) { - QMessageBox msgBox; - msgBox.setText("Both user and location exists with same name"); - msgBox.setInformativeText("Where you wanna go?"); - msgBox.setStandardButtons(QMessageBox::Ok | QMessageBox::Open); - msgBox.button(QMessageBox::Ok)->setText("User"); - msgBox.button(QMessageBox::Open)->setText("Place"); - int userResponse = msgBox.exec(); - - if (userResponse == QMessageBox::Ok) { - Application::getInstance()->getAvatar()->goToLocationFromAddress(userData["address"].toObject()); - } else if (userResponse == QMessageBox::Open) { - Application::getInstance()->getAvatar()->goToLocationFromAddress(placeData["address"].toObject()); - } -} - void Menu::muteEnvironment() { int headerSize = numBytesForPacketHeaderGivenPacketType(PacketTypeMuteEnvironment); int packetSize = headerSize + sizeof(glm::vec3) + sizeof(float); @@ -1320,43 +1182,13 @@ void Menu::muteEnvironment() { free(packet); } -void Menu::goToLocation() { - MyAvatar* myAvatar = Application::getInstance()->getAvatar(); - glm::vec3 avatarPos = myAvatar->getPosition(); - QString currentLocation = QString("%1, %2, %3").arg(QString::number(avatarPos.x), - QString::number(avatarPos.y), QString::number(avatarPos.z)); +void Menu::displayNameLocationResponse(const QString& errorString) { - QInputDialog coordinateDialog(Application::getInstance()->getWindow()); - coordinateDialog.setWindowTitle("Go to Location"); - coordinateDialog.setLabelText("Coordinate as x,y,z:"); - coordinateDialog.setTextValue(currentLocation); - coordinateDialog.resize(coordinateDialog.parentWidget()->size().width() * 0.30, coordinateDialog.size().height()); - - int dialogReturn = coordinateDialog.exec(); - if (dialogReturn == QDialog::Accepted && !coordinateDialog.textValue().isEmpty()) { - goToDestination(coordinateDialog.textValue()); - } - - sendFakeEnterEvent(); -} - -void Menu::namedLocationCreated(LocationManager::NamedLocationCreateResponse response) { - - if (response == LocationManager::Created) { - return; - } - - QMessageBox msgBox; - switch (response) { - case LocationManager::AlreadyExists: - msgBox.setText("That name has been already claimed, try something else."); - break; - default: - msgBox.setText("An unexpected error has occurred, please try again later."); - break; - } - - msgBox.exec(); + if (!errorString.isEmpty()) { + QMessageBox msgBox; + msgBox.setText(errorString); + msgBox.exec(); + } } void Menu::toggleLocationList() { @@ -1374,23 +1206,34 @@ void Menu::nameLocation() { // check if user is logged in or show login dialog if not AccountManager& accountManager = AccountManager::getInstance(); + if (!accountManager.isLoggedIn()) { QMessageBox msgBox; msgBox.setText("We need to tie this location to your username."); msgBox.setInformativeText("Please login first, then try naming the location again."); msgBox.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel); msgBox.button(QMessageBox::Ok)->setText("Login"); + if (msgBox.exec() == QMessageBox::Ok) { loginForCurrentDomain(); } return; } + + DomainHandler& domainHandler = NodeList::getInstance()->getDomainHandler(); + if (domainHandler.getUUID().isNull()) { + const QString UNREGISTERED_DOMAIN_MESSAGE = "This domain is not registered with High Fidelity." + "\n\nYou cannot create a global location in an unregistered domain."; + QMessageBox::critical(this, "Unregistered Domain", UNREGISTERED_DOMAIN_MESSAGE); + + return; + } QInputDialog nameDialog(Application::getInstance()->getWindow()); nameDialog.setWindowTitle("Name this location"); nameDialog.setLabelText("Name this location, then share that name with others.\n" - "When they come here, they'll be in the same location and orientation\n" + "When they come here, they'll have the same viewpoint\n" "(wherever you are standing and looking now) as you.\n\n" "Location name:"); @@ -1405,10 +1248,10 @@ void Menu::nameLocation() { MyAvatar* myAvatar = Application::getInstance()->getAvatar(); LocationManager* manager = new LocationManager(); - connect(manager, &LocationManager::creationCompleted, this, &Menu::namedLocationCreated); + connect(manager, &LocationManager::creationCompleted, this, &Menu::displayNameLocationResponse); NamedLocation* location = new NamedLocation(locationName, myAvatar->getPosition(), myAvatar->getOrientation(), - NodeList::getInstance()->getDomainHandler().getHostname()); + domainHandler.getUUID()); manager->createNamedLocation(location); } } diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 7ed963f473..307b011f74 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -164,11 +164,6 @@ public: int menuItemLocation = UNSPECIFIED_POSITION); void removeAction(QMenu* menu, const QString& actionName); - - bool static goToDestination(QString destination); - void static goToOrientation(QString orientation); - void static goToDomain(const QString newDomain); - void static goTo(QString destination); const QByteArray& getWalletPrivateKey() const { return _walletPrivateKey; } @@ -187,11 +182,8 @@ public slots: void saveSettings(QSettings* settings = NULL); void importSettings(); void exportSettings(); - void goTo(); - bool goToURL(QString location); - void goToUser(const QString& user); + void toggleAddressBar(); void pasteToVoxel(); - void openUrl(const QUrl& url); void toggleLoginMenuItem(); @@ -213,8 +205,6 @@ private slots: void editAttachments(); void editAnimations(); void changePrivateKey(); - void goToDomainDialog(); - void goToLocation(); void nameLocation(); void toggleLocationList(); void bandwidthDetailsClosed(); @@ -228,8 +218,7 @@ private slots: void toggleConsole(); void toggleChat(); void audioMuteToggled(); - void namedLocationCreated(LocationManager::NamedLocationCreateResponse response); - void multipleDestinationsDecision(const QJsonObject& userData, const QJsonObject& placeData); + void displayNameLocationResponse(const QString& errorString); void muteEnvironment(); private: @@ -318,6 +307,7 @@ private: namespace MenuOption { const QString AboutApp = "About Interface"; + const QString AddressBar = "Show Address Bar"; const QString AlignForearmsWithWrists = "Align Forearms with Wrists"; const QString AlternateIK = "Alternate IK"; const QString AmbientOcclusion = "Ambient Occlusion"; @@ -403,10 +393,7 @@ namespace MenuOption { const QString FullscreenMirror = "Fullscreen Mirror"; const QString GlowMode = "Cycle Glow Mode"; const QString GlowWhenSpeaking = "Glow When Speaking"; - const QString GoHome = "Go Home"; - const QString GoToDomain = "Go To Domain..."; - const QString GoTo = "Go To..."; - const QString GoToLocation = "Go To Location..."; + const QString GoToUser = "Go To User"; const QString HeadMouse = "Head Mouse"; const QString IncreaseAvatarSize = "Increase Avatar Size"; const QString IncreaseVoxelSize = "Increase Voxel Size"; diff --git a/interface/src/ModelUploader.cpp b/interface/src/ModelUploader.cpp index 12a4b145cc..91c1770e6f 100644 --- a/interface/src/ModelUploader.cpp +++ b/interface/src/ModelUploader.cpp @@ -48,6 +48,7 @@ static const QString TRANSLATION_Y_FIELD = "ty"; static const QString TRANSLATION_Z_FIELD = "tz"; static const QString JOINT_FIELD = "joint"; static const QString FREE_JOINT_FIELD = "freeJoint"; +static const QString BLENDSHAPE_FIELD = "bs"; static const QString S3_URL = "http://public.highfidelity.io"; static const QString MODEL_URL = "/api/v1/models"; @@ -192,6 +193,45 @@ bool ModelUploader::zip() { mapping.insertMulti(FREE_JOINT_FIELD, "RightForeArm"); } + // mixamo blendshapes + if (!mapping.contains(BLENDSHAPE_FIELD) && geometry.applicationName == "mixamo.com") { + QVariantHash blendshapes; + blendshapes.insert("EyeBlink_L", QVariantList() << "Blink_Left" << 1.0); + blendshapes.insert("EyeBlink_R", QVariantList() << "Blink_Right" << 1.0); + blendshapes.insert("EyeSquint_L", QVariantList() << "Squint_Left" << 1.0); + blendshapes.insert("EyeSquint_R", QVariantList() << "Squint_Right" << 1.0); + blendshapes.insert("EyeOpen_L", QVariantList() << "EyesWide_Left" << 1.0); + blendshapes.insert("EyeOpen_R", QVariantList() << "EyesWide_Right" << 1.0); + blendshapes.insert("BrowsD_L", QVariantList() << "BrowsDown_Left" << 1.0); + blendshapes.insert("BrowsD_R", QVariantList() << "BrowsDown_Right" << 1.0); + blendshapes.insert("BrowsU_L", QVariantList() << "BrowsUp_Left" << 1.0); + blendshapes.insert("BrowsU_R", QVariantList() << "BrowsUp_Right" << 1.0); + blendshapes.insert("JawFwd", QVariantList() << "JawForeward" << 1.0); + blendshapes.insert("JawOpen", QVariantList() << "Jaw_Down" << 1.0); + blendshapes.insert("JawLeft", QVariantList() << "Jaw_Left" << 1.0); + blendshapes.insert("JawRight", QVariantList() << "Jaw_Right" << 1.0); + blendshapes.insert("JawChew", QVariantList() << "Jaw_Up" << 1.0); + blendshapes.insert("MouthLeft", QVariantList() << "Midmouth_Left" << 1.0); + blendshapes.insert("MouthRight", QVariantList() << "Midmouth_Right" << 1.0); + blendshapes.insert("MouthFrown_L", QVariantList() << "Frown_Left" << 1.0); + blendshapes.insert("MouthFrown_R", QVariantList() << "Frown_Right" << 1.0); + blendshapes.insert("MouthSmile_L", QVariantList() << "Smile_Left" << 1.0); + blendshapes.insert("MouthSmile_R", QVariantList() << "Smile_Right" << 1.0); + blendshapes.insert("LipsUpperUp", QVariantList() << "UpperLipUp_Left" << 0.5); + blendshapes.insertMulti("LipsUpperUp", QVariantList() << "UpperLipUp_Right" << 0.5); + blendshapes.insert("Puff", QVariantList() << "CheekPuff_Left" << 0.5); + blendshapes.insertMulti("Puff", QVariantList() << "CheekPuff_Right" << 0.5); + blendshapes.insert("Sneer", QVariantList() << "NoseScrunch_Left" << 0.5); + blendshapes.insertMulti("Sneer", QVariantList() << "NoseScrunch_Right" << 0.5); + blendshapes.insert("CheekSquint_L", QVariantList() << "Squint_Left" << 1.0); + blendshapes.insert("CheekSquint_R", QVariantList() << "Squint_Right" << 1.0); + blendshapes.insert("LipsPucker", QVariantList() << "MouthNarrow_Left" << 0.5); + blendshapes.insertMulti("LipsPucker", QVariantList() << "MouthNarrow_Right" << 0.5); + blendshapes.insert("LipsLowerDown", QVariantList() << "LowerLipDown_Left" << 0.5); + blendshapes.insertMulti("LipsLowerDown", QVariantList() << "LowerLipDown_Right" << 0.5); + mapping.insert(BLENDSHAPE_FIELD, blendshapes); + } + // open the dialog to configure the rest ModelPropertiesDialog properties(_modelType, mapping, basePath, geometry); if (properties.exec() == QDialog::Rejected) { @@ -381,11 +421,11 @@ void ModelUploader::uploadSuccess(const QJsonObject& jsonResponse) { checkS3(); } -void ModelUploader::uploadFailed(QNetworkReply::NetworkError errorCode, const QString& errorString) { +void ModelUploader::uploadFailed(QNetworkReply& errorReply) { if (_progressDialog) { _progressDialog->reject(); } - qDebug() << "Model upload failed (" << errorCode << "): " << errorString; + qDebug() << "Model upload failed (" << errorReply.error() << "): " << errorReply.errorString(); QMessageBox::warning(NULL, QString("ModelUploader::uploadFailed()"), QString("There was a problem with your upload, please try again later."), diff --git a/interface/src/ModelUploader.h b/interface/src/ModelUploader.h index 2596120751..a1c7a27393 100644 --- a/interface/src/ModelUploader.h +++ b/interface/src/ModelUploader.h @@ -43,7 +43,7 @@ private slots: void checkJSON(const QJsonObject& jsonResponse); void uploadUpdate(qint64 bytesSent, qint64 bytesTotal); void uploadSuccess(const QJsonObject& jsonResponse); - void uploadFailed(QNetworkReply::NetworkError errorCode, const QString& errorString); + void uploadFailed(QNetworkReply& errorReply); void checkS3(); void processCheck(); diff --git a/interface/src/UserLocationsModel.cpp b/interface/src/UserLocationsModel.cpp index e84cae8f95..0fae0d8800 100644 --- a/interface/src/UserLocationsModel.cpp +++ b/interface/src/UserLocationsModel.cpp @@ -18,14 +18,13 @@ #include "Application.h" #include "UserLocationsModel.h" -static const QString PLACES_GET = "/api/v1/places"; -static const QString PLACES_UPDATE = "/api/v1/places/%1"; -static const QString PLACES_DELETE= "/api/v1/places/%1"; +static const QString LOCATIONS_GET = "/api/v1/locations"; +static const QString LOCATION_UPDATE_OR_DELETE = "/api/v1/locations/%1"; -UserLocation::UserLocation(QString id, QString name, QString location) : +UserLocation::UserLocation(const QString& id, const QString& name, const QString& address) : _id(id), _name(name), - _location(location), + _address(address), _previousName(name), _updating(false) { } @@ -35,10 +34,15 @@ void UserLocation::requestRename(const QString& newName) { _updating = true; JSONCallbackParameters callbackParams(this, "handleRenameResponse", this, "handleRenameError"); + QJsonObject jsonNameObject; - jsonNameObject.insert("name", QJsonValue(newName)); + jsonNameObject.insert("name", newName); + + QJsonObject locationObject; + locationObject.insert("location", jsonNameObject); + QJsonDocument jsonDocument(jsonNameObject); - AccountManager::getInstance().authenticatedRequest(PLACES_UPDATE.arg(_id), + AccountManager::getInstance().authenticatedRequest(LOCATION_UPDATE_OR_DELETE.arg(_id), QNetworkAccessManager::PutOperation, callbackParams, jsonDocument.toJson()); @@ -54,7 +58,9 @@ void UserLocation::handleRenameResponse(const QJsonObject& responseData) { QJsonValue status = responseData["status"]; if (!status.isUndefined() && status.toString() == "success") { - QString updatedName = responseData["data"].toObject()["name"].toString(); + qDebug() << responseData; + QString updatedName = responseData["data"].toObject()["location"].toObject()["name"].toString(); + qDebug() << "The updated name is" << updatedName; _name = updatedName; } else { _name = _previousName; @@ -75,10 +81,10 @@ void UserLocation::handleRenameResponse(const QJsonObject& responseData) { emit updated(_name); } -void UserLocation::handleRenameError(QNetworkReply::NetworkError error, const QString& errorString) { +void UserLocation::handleRenameError(QNetworkReply& errorReply) { _updating = false; - QString msg = "There was an error renaming location '" + _name + "': " + errorString; + QString msg = "There was an error renaming location '" + _name + "': " + errorReply.errorString(); qDebug() << msg; QMessageBox::warning(Application::getInstance()->getWindow(), "Error", msg); @@ -90,7 +96,7 @@ void UserLocation::requestDelete() { _updating = true; JSONCallbackParameters callbackParams(this, "handleDeleteResponse", this, "handleDeleteError"); - AccountManager::getInstance().authenticatedRequest(PLACES_DELETE.arg(_id), + AccountManager::getInstance().authenticatedRequest(LOCATION_UPDATE_OR_DELETE.arg(_id), QNetworkAccessManager::DeleteOperation, callbackParams); } @@ -109,10 +115,10 @@ void UserLocation::handleDeleteResponse(const QJsonObject& responseData) { } } -void UserLocation::handleDeleteError(QNetworkReply::NetworkError error, const QString& errorString) { +void UserLocation::handleDeleteError(QNetworkReply& errorReply) { _updating = false; - QString msg = "There was an error deleting location '" + _name + "': " + errorString; + QString msg = "There was an error deleting location '" + _name + "': " + errorReply.errorString(); qDebug() << msg; QMessageBox::warning(Application::getInstance()->getWindow(), "Error", msg); } @@ -153,7 +159,7 @@ void UserLocationsModel::refresh() { endResetModel(); JSONCallbackParameters callbackParams(this, "handleLocationsResponse"); - AccountManager::getInstance().authenticatedRequest(PLACES_GET, + AccountManager::getInstance().authenticatedRequest(LOCATIONS_GET, QNetworkAccessManager::GetOperation, callbackParams); } @@ -165,14 +171,13 @@ void UserLocationsModel::handleLocationsResponse(const QJsonObject& responseData QJsonValue status = responseData["status"]; if (!status.isUndefined() && status.toString() == "success") { beginResetModel(); - QJsonArray locations = responseData["data"].toObject()["places"].toArray(); + QJsonArray locations = responseData["data"].toObject()["locations"].toArray(); for (QJsonArray::const_iterator it = locations.constBegin(); it != locations.constEnd(); it++) { QJsonObject location = (*it).toObject(); - QJsonObject address = location["address"].toObject(); + QString locationAddress = "hifi://" + location["domain"].toObject()["name"].toString() + + location["path"].toString(); UserLocation* userLocation = new UserLocation(location["id"].toString(), location["name"].toString(), - "hifi://" + address["domain"].toString() - + "/" + address["position"].toString() - + "/" + address["orientation"].toString()); + locationAddress); _locations.append(userLocation); connect(userLocation, &UserLocation::deleted, this, &UserLocationsModel::removeLocation); connect(userLocation, &UserLocation::updated, this, &UserLocationsModel::update); @@ -214,8 +219,8 @@ QVariant UserLocationsModel::data(const QModelIndex& index, int role) const { return QVariant(); } else if (index.column() == NameColumn) { return _locations[index.row()]->name(); - } else if (index.column() == LocationColumn) { - return QVariant(_locations[index.row()]->location()); + } else if (index.column() == AddressColumn) { + return QVariant(_locations[index.row()]->address()); } } @@ -226,7 +231,7 @@ QVariant UserLocationsModel::headerData(int section, Qt::Orientation orientation if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { switch (section) { case NameColumn: return "Name"; - case LocationColumn: return "Location"; + case AddressColumn: return "Address"; default: return QVariant(); } } diff --git a/interface/src/UserLocationsModel.h b/interface/src/UserLocationsModel.h index d3f86faa5a..54518d72e1 100644 --- a/interface/src/UserLocationsModel.h +++ b/interface/src/UserLocationsModel.h @@ -20,20 +20,20 @@ class UserLocation : public QObject { Q_OBJECT public: - UserLocation(QString id, QString name, QString location); + UserLocation(const QString& id, const QString& name, const QString& address); bool isUpdating() { return _updating; } void requestRename(const QString& newName); void requestDelete(); - QString id() { return _id; } - QString name() { return _name; } - QString location() { return _location; } + const QString& id() { return _id; } + const QString& name() { return _name; } + const QString& address() { return _address; } public slots: void handleRenameResponse(const QJsonObject& responseData); - void handleRenameError(QNetworkReply::NetworkError error, const QString& errorString); + void handleRenameError(QNetworkReply& errorReply); void handleDeleteResponse(const QJsonObject& responseData); - void handleDeleteError(QNetworkReply::NetworkError error, const QString& errorString); + void handleDeleteError(QNetworkReply& errorReply); signals: void updated(const QString& name); @@ -42,7 +42,7 @@ signals: private: QString _id; QString _name; - QString _location; + QString _address; QString _previousName; bool _updating; @@ -65,7 +65,7 @@ public: enum Columns { NameColumn = 0, - LocationColumn + AddressColumn }; public slots: diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 282ee512de..48d58fb02c 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -21,6 +21,7 @@ #include #include +#include #include #include #include @@ -91,6 +92,9 @@ MyAvatar::MyAvatar() : Ragdoll* ragdoll = _skeletonModel.buildRagdoll(); _physicsSimulation.setRagdoll(ragdoll); _physicsSimulation.addEntity(&_voxelShapeManager); + + // connect to AddressManager signal for location jumps + connect(&AddressManager::getInstance(), &AddressManager::locationChangeRequired, this, &MyAvatar::goToLocation); } MyAvatar::~MyAvatar() { @@ -1763,11 +1767,6 @@ void MyAvatar::maybeUpdateBillboard() { sendBillboardPacket(); } -void MyAvatar::goHome() { - qDebug("Going Home!"); - slamPosition(START_LOCATION); -} - void MyAvatar::increaseSize() { if ((1.0f + SCALING_RATIO) * _targetScale < MAX_AVATAR_SCALE) { _targetScale *= (1.0f + SCALING_RATIO); @@ -1787,45 +1786,26 @@ void MyAvatar::resetSize() { qDebug("Reseted scale to %f", _targetScale); } -void MyAvatar::goToLocationFromResponse(const QJsonObject& jsonObject) { - QJsonObject locationObject = jsonObject["data"].toObject()["address"].toObject(); - bool isOnline = jsonObject["data"].toObject()["online"].toBool(); - if (isOnline ) { - goToLocationFromAddress(locationObject); - } else { - QMessageBox::warning(Application::getInstance()->getWindow(), "", "The user is not online."); +void MyAvatar::goToLocation(const glm::vec3& newPosition, bool hasOrientation, const glm::vec3& newOrientation) { + glm::quat quatOrientation = getOrientation(); + + qDebug().nospace() << "MyAvatar goToLocation - moving to " << newPosition.x << ", " + << newPosition.y << ", " << newPosition.z; + + if (hasOrientation) { + qDebug().nospace() << "MyAvatar goToLocation - new orientation is " + << newOrientation.x << ", " << newOrientation.y << ", " << newOrientation.z; + + // orient the user to face the target + glm::quat quatOrientation = glm::quat(glm::radians(newOrientation)) + * glm::angleAxis(PI, glm::vec3(0.0f, 1.0f, 0.0f)); + setOrientation(quatOrientation); } -} - -void MyAvatar::goToLocationFromAddress(const QJsonObject& locationObject) { - // send a node kill request, indicating to other clients that they should play the "disappeared" effect - sendKillAvatar(); - - QString positionString = locationObject["position"].toString(); - QString orientationString = locationObject["orientation"].toString(); - QString domainHostnameString = locationObject["domain"].toString(); - - qDebug() << "Changing domain to" << domainHostnameString << - ", position to" << positionString << - ", and orientation to" << orientationString; - - QStringList coordinateItems = positionString.split(','); - QStringList orientationItems = orientationString.split(','); - - NodeList::getInstance()->getDomainHandler().setHostname(domainHostnameString); - - // orient the user to face the target - glm::quat newOrientation = glm::quat(glm::radians(glm::vec3(orientationItems[0].toFloat(), - orientationItems[1].toFloat(), - orientationItems[2].toFloat()))) - * glm::angleAxis(PI, glm::vec3(0.0f, 1.0f, 0.0f)); - setOrientation(newOrientation); // move the user a couple units away const float DISTANCE_TO_USER = 2.0f; - glm::vec3 newPosition = glm::vec3(coordinateItems[0].toFloat(), coordinateItems[1].toFloat(), - coordinateItems[2].toFloat()) - newOrientation * IDENTITY_FRONT * DISTANCE_TO_USER; - slamPosition(newPosition); + glm::vec3 shiftedPosition = newPosition - quatOrientation * IDENTITY_FRONT * DISTANCE_TO_USER; + slamPosition(shiftedPosition); emit transformChanged(); } diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index f80689249d..758b1f92bb 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -150,19 +150,19 @@ public: const PlayerPointer getPlayer() const { return _player; } public slots: - void goHome(); void increaseSize(); void decreaseSize(); void resetSize(); - void goToLocationFromResponse(const QJsonObject& jsonObject); - void goToLocationFromAddress(const QJsonObject& jsonObject); + void goToLocation(const glm::vec3& newPosition, bool hasOrientation = false, const glm::vec3& newOrientation = glm::vec3()); // Set/Get update the thrust that will move the avatar around void addThrust(glm::vec3 newThrust) { _thrust += newThrust; }; glm::vec3 getThrust() { return _thrust; }; void setThrust(glm::vec3 newThrust) { _thrust = newThrust; } + void setVelocity(const glm::vec3 velocity) { _velocity = velocity; } + void updateMotionBehaviorsFromMenu(); glm::vec3 getLeftPalmPosition(); diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index e54a8e0f42..ebcf4520cb 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -60,6 +60,7 @@ void SkeletonModel::simulate(float deltaTime, bool fullUpdate) { setRotation(_owningAvatar->getOrientation() * refOrientation); const float MODEL_SCALE = 0.0006f; setScale(glm::vec3(1.0f, 1.0f, 1.0f) * _owningAvatar->getScale() * MODEL_SCALE); + setBlendshapeCoefficients(_owningAvatar->getHead()->getBlendshapeCoefficients()); Model::simulate(deltaTime, fullUpdate); diff --git a/interface/src/location/LocationManager.cpp b/interface/src/location/LocationManager.cpp index 67e1b899ee..92e8616478 100644 --- a/interface/src/location/LocationManager.cpp +++ b/interface/src/location/LocationManager.cpp @@ -9,43 +9,33 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include +#include + +#include -#include "Application.h" #include "LocationManager.h" -#include -const QString GET_USER_ADDRESS = "/api/v1/users/%1/address"; -const QString GET_PLACE_ADDRESS = "/api/v1/places/%1"; -const QString GET_ADDRESSES = "/api/v1/addresses/%1"; -const QString POST_PLACE_CREATE = "/api/v1/places/"; - - -LocationManager::LocationManager() { - -}; +const QString POST_LOCATION_CREATE = "/api/v1/locations/"; LocationManager& LocationManager::getInstance() { static LocationManager sharedInstance; return sharedInstance; } +const QString UNKNOWN_ERROR_MESSAGE = "Unknown error creating named location. Please try again!"; + void LocationManager::namedLocationDataReceived(const QJsonObject& data) { if (data.isEmpty()) { return; } if (data.contains("status") && data["status"].toString() == "success") { - emit creationCompleted(LocationManager::Created); + emit creationCompleted(QString()); } else { - emit creationCompleted(LocationManager::AlreadyExists); + emit creationCompleted(UNKNOWN_ERROR_MESSAGE); } } -void LocationManager::errorDataReceived(QNetworkReply::NetworkError error, const QString& message) { - emit creationCompleted(LocationManager::SystemError); -} - void LocationManager::createNamedLocation(NamedLocation* namedLocation) { AccountManager& accountManager = AccountManager::getInstance(); if (accountManager.isLoggedIn()) { @@ -55,185 +45,45 @@ void LocationManager::createNamedLocation(NamedLocation* namedLocation) { callbackParams.errorCallbackReceiver = this; callbackParams.errorCallbackMethod = "errorDataReceived"; - accountManager.authenticatedRequest(POST_PLACE_CREATE, QNetworkAccessManager::PostOperation, + accountManager.authenticatedRequest(POST_LOCATION_CREATE, QNetworkAccessManager::PostOperation, callbackParams, namedLocation->toJsonString().toUtf8()); } } -void LocationManager::goTo(QString destination) { - const QString USER_DESTINATION_TYPE = "user"; - const QString PLACE_DESTINATION_TYPE = "place"; - const QString OTHER_DESTINATION_TYPE = "coordinate_or_username"; +void LocationManager::errorDataReceived(QNetworkReply& errorReply) { - if (destination.startsWith("@")) { - // remove '@' and go to user - QString destinationUser = destination.remove(0, 1); - UserActivityLogger::getInstance().wentTo(USER_DESTINATION_TYPE, destinationUser); - goToUser(destinationUser); - return; - } - - if (destination.startsWith("#")) { - // remove '#' and go to named place - QString destinationPlace = destination.remove(0, 1); - UserActivityLogger::getInstance().wentTo(PLACE_DESTINATION_TYPE, destinationPlace); - goToPlace(destinationPlace); - return; - } - - // go to coordinate destination or to Username - if (!goToDestination(destination)) { - destination = QString(QUrl::toPercentEncoding(destination)); - UserActivityLogger::getInstance().wentTo(OTHER_DESTINATION_TYPE, destination); + if (errorReply.header(QNetworkRequest::ContentTypeHeader).toString().startsWith("application/json")) { + // we have some JSON error data we can parse for our error message + QJsonDocument responseJson = QJsonDocument::fromJson(errorReply.readAll()); - JSONCallbackParameters callbackParams; - callbackParams.jsonCallbackReceiver = this; - callbackParams.jsonCallbackMethod = "goToAddressFromResponse"; - callbackParams.errorCallbackReceiver = this; - callbackParams.errorCallbackMethod = "handleAddressLookupError"; + QJsonObject dataObject = responseJson.object()["data"].toObject(); - AccountManager::getInstance().authenticatedRequest(GET_ADDRESSES.arg(destination), - QNetworkAccessManager::GetOperation, - callbackParams); - } -} - -void LocationManager::goToAddressFromResponse(const QJsonObject& responseData) { - QJsonValue status = responseData["status"]; - - const QJsonObject& data = responseData["data"].toObject(); - const QJsonValue& userObject = data["user"]; - const QJsonValue& placeObject = data["place"]; - - if (!placeObject.isUndefined() && !userObject.isUndefined()) { - emit multipleDestinationsFound(userObject.toObject(), placeObject.toObject()); - } else if (placeObject.isUndefined()) { - Application::getInstance()->getAvatar()->goToLocationFromAddress(userObject.toObject()["address"].toObject()); - } else { - Application::getInstance()->getAvatar()->goToLocationFromAddress(placeObject.toObject()["address"].toObject()); - } -} - -void LocationManager::goToUser(QString userName) { - JSONCallbackParameters callbackParams; - callbackParams.jsonCallbackReceiver = Application::getInstance()->getAvatar(); - callbackParams.jsonCallbackMethod = "goToLocationFromResponse"; - callbackParams.errorCallbackReceiver = this; - callbackParams.errorCallbackMethod = "handleAddressLookupError"; - - userName = QString(QUrl::toPercentEncoding(userName)); - AccountManager::getInstance().authenticatedRequest(GET_USER_ADDRESS.arg(userName), - QNetworkAccessManager::GetOperation, - callbackParams); -} - -void LocationManager::goToPlace(QString placeName) { - JSONCallbackParameters callbackParams; - callbackParams.jsonCallbackReceiver = Application::getInstance()->getAvatar(); - callbackParams.jsonCallbackMethod = "goToLocationFromResponse"; - callbackParams.errorCallbackReceiver = this; - callbackParams.errorCallbackMethod = "handleAddressLookupError"; - - placeName = QString(QUrl::toPercentEncoding(placeName)); - AccountManager::getInstance().authenticatedRequest(GET_PLACE_ADDRESS.arg(placeName), - QNetworkAccessManager::GetOperation, - callbackParams); -} - -void LocationManager::goToOrientation(QString orientation) { - if (orientation.isEmpty()) { - return; - } - - QStringList orientationItems = orientation.remove(' ').split(QRegExp("_|,"), QString::SkipEmptyParts); - - const int NUMBER_OF_ORIENTATION_ITEMS = 4; - const int W_ITEM = 0; - const int X_ITEM = 1; - const int Y_ITEM = 2; - const int Z_ITEM = 3; - - if (orientationItems.size() == NUMBER_OF_ORIENTATION_ITEMS) { - - // replace last occurrence of '_' with decimal point - replaceLastOccurrence('-', '.', orientationItems[W_ITEM]); - replaceLastOccurrence('-', '.', orientationItems[X_ITEM]); - replaceLastOccurrence('-', '.', orientationItems[Y_ITEM]); - replaceLastOccurrence('-', '.', orientationItems[Z_ITEM]); - - double w = orientationItems[W_ITEM].toDouble(); - double x = orientationItems[X_ITEM].toDouble(); - double y = orientationItems[Y_ITEM].toDouble(); - double z = orientationItems[Z_ITEM].toDouble(); - - glm::quat newAvatarOrientation(w, x, y, z); - - MyAvatar* myAvatar = Application::getInstance()->getAvatar(); - glm::quat avatarOrientation = myAvatar->getOrientation(); - if (newAvatarOrientation != avatarOrientation) { - myAvatar->setOrientation(newAvatarOrientation); - emit myAvatar->transformChanged(); + qDebug() << dataObject; + + QString errorString = "There was a problem creating that location.\n"; + + // construct the error string from the returned attribute errors + foreach(const QString& key, dataObject.keys()) { + errorString += "\n\u2022 " + key + " - "; + + QJsonValue keyedErrorValue = dataObject[key]; + + if (keyedErrorValue.isArray()) { + foreach(const QJsonValue& attributeErrorValue, keyedErrorValue.toArray()) { + errorString += attributeErrorValue.toString() + ", "; + } + + // remove the trailing comma at end of error list + errorString.remove(errorString.length() - 2, 2); + } else if (keyedErrorValue.isString()) { + errorString += keyedErrorValue.toString(); + } } - } -} - -bool LocationManager::goToDestination(QString destination) { - - QStringList coordinateItems = destination.remove(' ').split(QRegExp("_|,"), QString::SkipEmptyParts); - - const int NUMBER_OF_COORDINATE_ITEMS = 3; - const int X_ITEM = 0; - const int Y_ITEM = 1; - const int Z_ITEM = 2; - if (coordinateItems.size() == NUMBER_OF_COORDINATE_ITEMS) { - - // replace last occurrence of '_' with decimal point - replaceLastOccurrence('-', '.', coordinateItems[X_ITEM]); - replaceLastOccurrence('-', '.', coordinateItems[Y_ITEM]); - replaceLastOccurrence('-', '.', coordinateItems[Z_ITEM]); - - double x = coordinateItems[X_ITEM].toDouble(); - double y = coordinateItems[Y_ITEM].toDouble(); - double z = coordinateItems[Z_ITEM].toDouble(); - - glm::vec3 newAvatarPos(x, y, z); - - MyAvatar* myAvatar = Application::getInstance()->getAvatar(); - glm::vec3 avatarPos = myAvatar->getPosition(); - if (newAvatarPos != avatarPos) { - // send a node kill request, indicating to other clients that they should play the "disappeared" effect - MyAvatar::sendKillAvatar(); - - qDebug("Going To Location: %f, %f, %f...", x, y, z); - myAvatar->slamPosition(newAvatarPos); - emit myAvatar->transformChanged(); - } - - return true; - } - - // no coordinates were parsed - return false; -} - -void LocationManager::handleAddressLookupError(QNetworkReply::NetworkError networkError, - const QString& errorString) { - QString messageBoxString; - - if (networkError == QNetworkReply::ContentNotFoundError) { - messageBoxString = "That address could not be found."; + + // emit our creationCompleted signal with the error + emit creationCompleted(errorString); + } else { - messageBoxString = errorString; - } - - QMessageBox::warning(Application::getInstance()->getWindow(), "", messageBoxString); -} - -void LocationManager::replaceLastOccurrence(const QChar search, const QChar replace, QString& string) { - int lastIndex; - lastIndex = string.lastIndexOf(search); - if (lastIndex > 0) { - lastIndex = string.lastIndexOf(search); - string.replace(lastIndex, 1, replace); + creationCompleted(UNKNOWN_ERROR_MESSAGE); } } diff --git a/interface/src/location/LocationManager.h b/interface/src/location/LocationManager.h index 30b4447ded..b6a662e323 100644 --- a/interface/src/location/LocationManager.h +++ b/interface/src/location/LocationManager.h @@ -13,8 +13,8 @@ #define hifi_LocationManager_h #include +#include -#include "AccountManager.h" #include "NamedLocation.h" class LocationManager : public QObject { @@ -29,29 +29,14 @@ public: SystemError }; - LocationManager(); void createNamedLocation(NamedLocation* namedLocation); - void goTo(QString destination); - void goToUser(QString userName); - void goToPlace(QString placeName); - void goToOrientation(QString orientation); - bool goToDestination(QString destination); - -public slots: - void handleAddressLookupError(QNetworkReply::NetworkError networkError, const QString& errorString); - -private: - void replaceLastOccurrence(const QChar search, const QChar replace, QString& string); - signals: - void creationCompleted(LocationManager::NamedLocationCreateResponse response); - void multipleDestinationsFound(const QJsonObject& userData, const QJsonObject& placeData); + void creationCompleted(const QString& errorMessage); private slots: void namedLocationDataReceived(const QJsonObject& data); - void errorDataReceived(QNetworkReply::NetworkError error, const QString& message); - void goToAddressFromResponse(const QJsonObject& jsonObject); + void errorDataReceived(QNetworkReply& errorReply); }; diff --git a/interface/src/location/NamedLocation.cpp b/interface/src/location/NamedLocation.cpp index ed7701a391..7785edfea1 100644 --- a/interface/src/location/NamedLocation.cpp +++ b/interface/src/location/NamedLocation.cpp @@ -9,19 +9,25 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include +#include + #include "NamedLocation.h" -const QString JSON_FORMAT = "{\"address\":{\"position\":\"%1,%2,%3\"," - "\"orientation\":\"%4,%5,%6,%7\",\"domain\":\"%8\"},\"name\":\"%9\"}"; +NamedLocation::NamedLocation(const QString& name, + const glm::vec3& position, const glm::quat& orientation, + const QUuid& domainID) : + _name(name), + _position(position), + _orientation(orientation), + _domainID(domainID) +{ + +} + +const QString JSON_FORMAT = "{\"location\":{\"path\":\"%1\",\"domain_id\":\"%2\",\"name\":\"%3\"}}"; QString NamedLocation::toJsonString() { - return JSON_FORMAT.arg(QString::number(_location.x), - QString::number(_location.y), - QString::number(_location.z), - QString::number(_orientation.w), - QString::number(_orientation.x), - QString::number(_orientation.y), - QString::number(_orientation.z), - _domain, - _locationName); + return JSON_FORMAT.arg(AddressManager::pathForPositionAndOrientation(_position, true, _orientation), + uuidStringWithoutCurlyBraces(_domainID), _name); } diff --git a/interface/src/location/NamedLocation.h b/interface/src/location/NamedLocation.h index ffbd157263..fca6852062 100644 --- a/interface/src/location/NamedLocation.h +++ b/interface/src/location/NamedLocation.h @@ -22,39 +22,33 @@ class NamedLocation : public QObject { Q_OBJECT public: - NamedLocation(QString locationName, glm::vec3 location, glm::quat orientation, QString domain) { - _locationName = locationName; - _location = location; - _orientation = orientation; - _domain = domain; - } + NamedLocation(const QString& name, const glm::vec3& position, const glm::quat& orientation, const QUuid& domainID); QString toJsonString(); - bool isEmpty() { return _locationName.isNull() || _locationName.isEmpty(); } + bool isEmpty() { return _name.isNull() || _name.isEmpty(); } - void setLocationName(QString locationName) { _locationName = locationName; } - QString locationName() { return _locationName; } + void setName(QString name) { _name = name; } + const QString& getName() const { return _name; } - void setLocation(glm::vec3 location) { _location = location; } - glm::vec3 location() { return _location; } + void setLocation(glm::vec3 position) { _position = position; } + const glm::vec3& getPosition() const { return _position; } - void setOrientation(glm::quat orentation) { _orientation = orentation; } - glm::quat orientation() { return _orientation; } + void setOrientation(const glm::quat& orentation) { _orientation = orentation; } + const glm::quat& getOrientation() const { return _orientation; } - void setDomain(QString domain) { _domain = domain; } - QString domain() { return _domain; } + void setDomainID(const QUuid& domainID) { _domainID = domainID; } + const QUuid& getDomainID() const { return _domainID; } signals: void dataReceived(bool locationExists); private: - - QString _locationName; + QString _name; QString _createdBy; - glm::vec3 _location; + glm::vec3 _position; glm::quat _orientation; - QString _domain; + QUuid _domainID; }; diff --git a/interface/src/renderer/TextureCache.cpp b/interface/src/renderer/TextureCache.cpp index bb07e83980..f22e1bf7a5 100644 --- a/interface/src/renderer/TextureCache.cpp +++ b/interface/src/renderer/TextureCache.cpp @@ -497,13 +497,9 @@ void NetworkTexture::setImage(const QImage& image, bool translucent, const QColo glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, image.width(), image.height(), 0, GL_RGB, GL_UNSIGNED_BYTE, image.constBits()); } - if (_type == SPLAT_TEXTURE) { - // generate mipmaps for splat textures - glGenerateMipmap(GL_TEXTURE_2D); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); - } else { - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - } + // generate mipmaps + glGenerateMipmap(GL_TEXTURE_2D); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); glBindTexture(GL_TEXTURE_2D, 0); } @@ -541,7 +537,8 @@ QSharedPointer DilatableNetworkTexture::getDilatedTexture(float dilatio glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, dilatedImage.width(), dilatedImage.height(), 0, GL_RGB, GL_UNSIGNED_BYTE, dilatedImage.constBits()); } - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glGenerateMipmap(GL_TEXTURE_2D); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); glBindTexture(GL_TEXTURE_2D, 0); } diff --git a/interface/src/scripting/LocationScriptingInterface.cpp b/interface/src/scripting/LocationScriptingInterface.cpp index 9e68778942..bead12117d 100644 --- a/interface/src/scripting/LocationScriptingInterface.cpp +++ b/interface/src/scripting/LocationScriptingInterface.cpp @@ -29,10 +29,9 @@ QString LocationScriptingInterface::getHref() { } QString LocationScriptingInterface::getPathname() { - const glm::vec3& position = Application::getInstance()->getAvatar()->getPosition(); - QString path; - path.sprintf("/%.4f,%.4f,%.4f", position.x, position.y, position.z); - return path; + MyAvatar* applicationAvatar = Application::getInstance()->getAvatar(); + return AddressManager::pathForPositionAndOrientation(applicationAvatar->getPosition(), + true, applicationAvatar->getOrientation()); } QString LocationScriptingInterface::getHostname() { @@ -40,7 +39,7 @@ QString LocationScriptingInterface::getHostname() { } void LocationScriptingInterface::assign(const QString& url) { - QMetaObject::invokeMethod(Menu::getInstance(), "goToURL", Q_ARG(const QString&, url)); + QMetaObject::invokeMethod(&AddressManager::getInstance(), "handleLookupString", Q_ARG(const QString&, url)); } QScriptValue LocationScriptingInterface::locationGetter(QScriptContext* context, QScriptEngine* engine) { diff --git a/interface/src/scripting/LocationScriptingInterface.h b/interface/src/scripting/LocationScriptingInterface.h index 20f63bceed..3d725776fd 100644 --- a/interface/src/scripting/LocationScriptingInterface.h +++ b/interface/src/scripting/LocationScriptingInterface.h @@ -18,6 +18,8 @@ #include #include +#include + #include "Application.h" class LocationScriptingInterface : public QObject { @@ -33,7 +35,7 @@ public: bool isConnected(); QString getHref(); - QString getProtocol() { return CUSTOM_URL_SCHEME; }; + QString getProtocol() { return HIFI_URL_SCHEME; }; QString getPathname(); QString getHostname(); diff --git a/interface/src/scripting/WindowScriptingInterface.cpp b/interface/src/scripting/WindowScriptingInterface.cpp index 031899689c..01a30b6d94 100644 --- a/interface/src/scripting/WindowScriptingInterface.cpp +++ b/interface/src/scripting/WindowScriptingInterface.cpp @@ -34,6 +34,26 @@ WindowScriptingInterface::WindowScriptingInterface() : { } +QScriptValue WindowScriptingInterface::hasFocus() { + return Application::getInstance()->getGLWidget()->hasFocus(); +} + +void WindowScriptingInterface::setCursorVisible(bool visible) { + Application::getInstance()->setCursorVisible(visible); +} + +void WindowScriptingInterface::setCursorPosition(int x, int y) { + QCursor::setPos(x, y); +} + +QScriptValue WindowScriptingInterface::getCursorPositionX() { + return QCursor::pos().x(); +} + +QScriptValue WindowScriptingInterface::getCursorPositionY() { + return QCursor::pos().y(); +} + QScriptValue WindowScriptingInterface::alert(const QString& message) { QScriptValue retVal; QMetaObject::invokeMethod(this, "showAlert", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QScriptValue, retVal), Q_ARG(const QString&, message)); @@ -497,3 +517,11 @@ int WindowScriptingInterface::getInnerWidth() { int WindowScriptingInterface::getInnerHeight() { return Application::getInstance()->getWindow()->geometry().height(); } + +int WindowScriptingInterface::getX() { + return Application::getInstance()->getWindow()->x(); +} + +int WindowScriptingInterface::getY() { + return Application::getInstance()->getWindow()->y(); +} diff --git a/interface/src/scripting/WindowScriptingInterface.h b/interface/src/scripting/WindowScriptingInterface.h index 84f6da287c..6a9fd868be 100644 --- a/interface/src/scripting/WindowScriptingInterface.h +++ b/interface/src/scripting/WindowScriptingInterface.h @@ -20,12 +20,21 @@ class WindowScriptingInterface : public QObject { Q_OBJECT Q_PROPERTY(int innerWidth READ getInnerWidth) Q_PROPERTY(int innerHeight READ getInnerHeight) + Q_PROPERTY(int x READ getX) + Q_PROPERTY(int y READ getY) public: static WindowScriptingInterface* getInstance(); int getInnerWidth(); int getInnerHeight(); + int getX(); + int getY(); public slots: + QScriptValue getCursorPositionX(); + QScriptValue getCursorPositionY(); + void setCursorPosition(int x, int y); + void setCursorVisible(bool visible); + QScriptValue hasFocus(); QScriptValue alert(const QString& message = ""); QScriptValue confirm(const QString& message = ""); QScriptValue form(const QString& title, QScriptValue array); diff --git a/interface/src/ui/ChatMessageArea.cpp b/interface/src/ui/ChatMessageArea.cpp index 1e16a8a2db..1dc38e9c94 100644 --- a/interface/src/ui/ChatMessageArea.cpp +++ b/interface/src/ui/ChatMessageArea.cpp @@ -19,8 +19,8 @@ ChatMessageArea::ChatMessageArea(bool useFixedHeight) : QTextBrowser(), _useFixe connect(document()->documentLayout(), &QAbstractTextDocumentLayout::documentSizeChanged, this, &ChatMessageArea::updateLayout); - connect(this, &QTextBrowser::anchorClicked, - Menu::getInstance(), &Menu::openUrl); + + connect(this, &QTextBrowser::anchorClicked, Application::getInstance(), &Application::openUrl); } void ChatMessageArea::setHtml(const QString& html) { diff --git a/interface/src/ui/ChatWindow.cpp b/interface/src/ui/ChatWindow.cpp index b9b5ed8e19..5add09a9a6 100644 --- a/interface/src/ui/ChatWindow.cpp +++ b/interface/src/ui/ChatWindow.cpp @@ -17,6 +17,9 @@ #include #include +#include +#include + #include "Application.h" #include "ChatMessageArea.h" #include "FlowLayout.h" @@ -28,7 +31,6 @@ #include "ChatWindow.h" - const int NUM_MESSAGES_TO_TIME_STAMP = 20; const QRegularExpression regexLinks("((?:(?:ftp)|(?:https?)|(?:hifi))://\\S+)"); @@ -169,7 +171,7 @@ bool ChatWindow::eventFilter(QObject* sender, QEvent* event) { } else if (event->type() == QEvent::MouseButtonRelease) { QVariant userVar = sender->property("user"); if (userVar.isValid()) { - Menu::getInstance()->goToUser("@" + userVar.toString()); + AddressManager::getInstance().goToUser(userVar.toString()); return true; } } diff --git a/interface/src/ui/OAuthWebViewHandler.cpp b/interface/src/ui/OAuthWebViewHandler.cpp index 8ec415584d..a1dbfe9e0b 100644 --- a/interface/src/ui/OAuthWebViewHandler.cpp +++ b/interface/src/ui/OAuthWebViewHandler.cpp @@ -11,6 +11,8 @@ #include +#include + #include "Application.h" #include "OAuthWebViewHandler.h" diff --git a/interface/src/ui/ScriptEditorWidget.cpp b/interface/src/ui/ScriptEditorWidget.cpp index f99c9d5ac4..1473e4a6a0 100644 --- a/interface/src/ui/ScriptEditorWidget.cpp +++ b/interface/src/ui/ScriptEditorWidget.cpp @@ -23,6 +23,8 @@ #include #include +#include + #include "Application.h" #include "ScriptHighlighting.h" diff --git a/interface/src/ui/UserLocationsDialog.cpp b/interface/src/ui/UserLocationsDialog.cpp index f72e66ce77..31f388d045 100644 --- a/interface/src/ui/UserLocationsDialog.cpp +++ b/interface/src/ui/UserLocationsDialog.cpp @@ -13,6 +13,8 @@ #include #include +#include + #include "Menu.h" #include "UserLocationsDialog.h" @@ -51,8 +53,8 @@ void UserLocationsDialog::updateEnabled() { } void UserLocationsDialog::goToModelIndex(const QModelIndex& index) { - QVariant location = _proxyModel.data(index.sibling(index.row(), UserLocationsModel::LocationColumn)); - Menu::getInstance()->goToURL(location.toString()); + QVariant address = _proxyModel.data(index.sibling(index.row(), UserLocationsModel::AddressColumn)); + AddressManager::getInstance().handleLookupString(address.toString()); } void UserLocationsDialog::deleteSelection() { diff --git a/interface/src/ui/overlays/BillboardOverlay.cpp b/interface/src/ui/overlays/BillboardOverlay.cpp index 7d85d54fef..f29fa6ed8d 100644 --- a/interface/src/ui/overlays/BillboardOverlay.cpp +++ b/interface/src/ui/overlays/BillboardOverlay.cpp @@ -9,7 +9,9 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include "../../Application.h" +#include + +#include "Application.h" #include "BillboardOverlay.h" diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp index 7fda9d74c9..1100371ac9 100644 --- a/libraries/networking/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -292,8 +292,7 @@ void AccountManager::passErrorToCallback(QNetworkReply* requestReply) { if (callbackParams.errorCallbackReceiver) { // invoke the right method on the callback receiver QMetaObject::invokeMethod(callbackParams.errorCallbackReceiver, qPrintable(callbackParams.errorCallbackMethod), - Q_ARG(QNetworkReply::NetworkError, requestReply->error()), - Q_ARG(const QString&, requestReply->errorString())); + Q_ARG(QNetworkReply&, *requestReply)); // remove the related reply-callback group from the map _pendingCallbackMap.remove(requestReply); diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp new file mode 100644 index 0000000000..f2e9ce64ab --- /dev/null +++ b/libraries/networking/src/AddressManager.cpp @@ -0,0 +1,244 @@ +// +// AddressManager.cpp +// libraries/networking/src +// +// Created by Stephen Birarda on 2014-09-10. +// 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 "AddressManager.h" + +AddressManager& AddressManager::getInstance() { + static AddressManager sharedInstance; + return sharedInstance; +} + +QString AddressManager::pathForPositionAndOrientation(const glm::vec3& position, bool hasOrientation, + const glm::quat& orientation) { + + QString pathString = "/" + createByteArray(position); + + if (hasOrientation) { + QString orientationString = createByteArray(glm::degrees(safeEulerAngles(orientation))); + pathString += "/" + orientationString; + } + + return pathString; +} + +const JSONCallbackParameters& AddressManager::apiCallbackParameters() { + static bool hasSetupParameters = false; + static JSONCallbackParameters callbackParams; + + if (!hasSetupParameters) { + callbackParams.jsonCallbackReceiver = this; + callbackParams.jsonCallbackMethod = "handleAPIResponse"; + callbackParams.errorCallbackReceiver = this; + callbackParams.errorCallbackMethod = "handleAPIError"; + } + + return callbackParams; +} + +bool AddressManager::handleUrl(const QUrl& lookupUrl) { + if (lookupUrl.scheme() == HIFI_URL_SCHEME) { + + // there are 4 possible lookup strings + + // 1. global place name (name of domain or place) - example: sanfrancisco + // 2. user name (prepended with @) - example: @philip + // 3. location string (posX,posY,posZ/eulerX,eulerY,eulerZ) + // 4. domain network address (IP or dns resolvable hostname) + + qDebug() << lookupUrl; + + if (lookupUrl.isRelative()) { + // if this is a relative path then handle it as a relative viewpoint + handleRelativeViewpoint(lookupUrl.path()); + } else { + // use our regex'ed helpers to figure out what we're supposed to do with this + if (!handleUsername(lookupUrl.authority())) { + // we're assuming this is either a network address or global place name + // check if it is a network address first + if (!handleNetworkAddress(lookupUrl.host())) { + // wasn't an address - lookup the place name + attemptPlaceNameLookup(lookupUrl.host()); + } + + // we may have a path that defines a relative viewpoint - if so we should jump to that now + handleRelativeViewpoint(lookupUrl.path()); + } + } + + return true; + } + + return false; +} + +void AddressManager::handleLookupString(const QString& lookupString) { + // we've verified that this is a valid hifi URL - hand it off to handleLookupString + QString sanitizedString = lookupString; + const QRegExp HIFI_SCHEME_REGEX = QRegExp(HIFI_URL_SCHEME + ":\\/{1,2}", Qt::CaseInsensitive); + sanitizedString = sanitizedString.remove(HIFI_SCHEME_REGEX); + + handleUrl(QUrl(HIFI_URL_SCHEME + "://" + sanitizedString)); +} + +void AddressManager::handleAPIResponse(const QJsonObject &jsonObject) { + QJsonObject dataObject = jsonObject["data"].toObject(); + + const QString ADDRESS_API_DOMAIN_KEY = "domain"; + const QString ADDRESS_API_ONLINE_KEY = "online"; + + if (!dataObject.contains(ADDRESS_API_ONLINE_KEY) + || dataObject[ADDRESS_API_ONLINE_KEY].toBool()) { + + if (dataObject.contains(ADDRESS_API_DOMAIN_KEY)) { + QJsonObject domainObject = dataObject[ADDRESS_API_DOMAIN_KEY].toObject(); + + const QString DOMAIN_NETWORK_ADDRESS_KEY = "network_address"; + QString domainHostname = domainObject[DOMAIN_NETWORK_ADDRESS_KEY].toString(); + + emit possibleDomainChangeRequired(domainHostname); + + // take the path that came back + const QString LOCATION_KEY = "location"; + const QString LOCATION_PATH_KEY = "path"; + QString returnedPath; + + if (domainObject.contains(LOCATION_PATH_KEY)) { + returnedPath = domainObject[LOCATION_PATH_KEY].toString(); + } else if (domainObject.contains(LOCATION_KEY)) { + returnedPath = domainObject[LOCATION_KEY].toObject()[LOCATION_PATH_KEY].toString(); + } + + if (!returnedPath.isEmpty()) { + // try to parse this returned path as a viewpoint, that's the only thing it could be for now + if (!handleRelativeViewpoint(returnedPath)) { + qDebug() << "Received a location path that was could not be handled as a viewpoint -" << returnedPath; + } + } + + } else { + qDebug() << "Received an address manager API response with no domain key. Cannot parse."; + qDebug() << jsonObject; + } + } else { + // we've been told that this result exists but is offline, emit our signal so the application can handle + emit lookupResultIsOffline(); + } +} + +void AddressManager::handleAPIError(QNetworkReply& errorReply) { + qDebug() << "AddressManager API error -" << errorReply.error() << "-" << errorReply.errorString(); +} + +const QString GET_PLACE = "/api/v1/places/%1"; + +void AddressManager::attemptPlaceNameLookup(const QString& lookupString) { + // assume this is a place name and see if we can get any info on it + QString placeName = QUrl::toPercentEncoding(lookupString); + AccountManager::getInstance().authenticatedRequest(GET_PLACE.arg(placeName), + QNetworkAccessManager::GetOperation, + apiCallbackParameters()); +} + +bool AddressManager::handleNetworkAddress(const QString& lookupString) { + const QString IP_ADDRESS_REGEX_STRING = "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}" + "([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(:\\d{1,5})?$"; + + const QString HOSTNAME_REGEX_STRING = "^((?:[A-Z0-9]|[A-Z0-9][A-Z0-9\\-]{0,61}[A-Z0-9])" + "(?:\\.(?:[A-Z0-9]|[A-Z0-9][A-Z0-9\\-]{0,61}[A-Z0-9]))+|localhost)(:{1}\\d{1,5})?$"; + + QRegExp hostnameRegex(HOSTNAME_REGEX_STRING, Qt::CaseInsensitive); + + if (hostnameRegex.indexIn(lookupString) != -1) { + emit possibleDomainChangeRequired(hostnameRegex.cap(0)); + return true; + } + + QRegExp ipAddressRegex(IP_ADDRESS_REGEX_STRING); + + if (ipAddressRegex.indexIn(lookupString) != -1) { + emit possibleDomainChangeRequired(ipAddressRegex.cap(0)); + return true; + } + + return false; +} + +bool AddressManager::handleRelativeViewpoint(const QString& lookupString) { + const QString FLOAT_REGEX_STRING = "([-+]?[0-9]*\\.?[0-9]+(?:[eE][-+]?[0-9]+)?)"; + const QString TRIPLE_FLOAT_REGEX_STRING = QString("\\/") + FLOAT_REGEX_STRING + "\\s*,\\s*" + + FLOAT_REGEX_STRING + "\\s*,\\s*" + FLOAT_REGEX_STRING + "\\s*(?:$|\\/)"; + + QRegExp tripleFloatRegex(TRIPLE_FLOAT_REGEX_STRING); + + if (tripleFloatRegex.indexIn(lookupString) != -1) { + // we have at least a position, so emit our signal to say we need to change position + glm::vec3 newPosition(tripleFloatRegex.cap(1).toFloat(), + tripleFloatRegex.cap(2).toFloat(), + tripleFloatRegex.cap(3).toFloat()); + + if (!isNaN(newPosition.x) && !isNaN(newPosition.y) && !isNaN(newPosition.z)) { + glm::vec3 newOrientation; + // we may also have an orientation + if (lookupString[tripleFloatRegex.matchedLength() - 1] == QChar('/') + && tripleFloatRegex.indexIn(lookupString, tripleFloatRegex.matchedLength() - 1) != -1) { + + glm::vec3 newOrientation(tripleFloatRegex.cap(1).toFloat(), + tripleFloatRegex.cap(2).toFloat(), + tripleFloatRegex.cap(3).toFloat()); + + if (!isNaN(newOrientation.x) && !isNaN(newOrientation.y) && !isNaN(newOrientation.z)) { + emit locationChangeRequired(newPosition, true, newOrientation); + return true; + } else { + qDebug() << "Orientation parsed from lookup string is invalid. Will not use for location change."; + } + } + + emit locationChangeRequired(newPosition, false, newOrientation); + + } else { + qDebug() << "Could not jump to position from lookup string because it has an invalid value."; + } + + return true; + } + + return false; +} + +const QString GET_USER_LOCATION = "/api/v1/users/%1/location"; + +bool AddressManager::handleUsername(const QString& lookupString) { + const QString USERNAME_REGEX_STRING = "^@(\\S+)"; + + QRegExp usernameRegex(USERNAME_REGEX_STRING); + + if (usernameRegex.indexIn(lookupString) != -1) { + goToUser(usernameRegex.cap(1)); + return true; + } + + return false; +} + +void AddressManager::goToUser(const QString& username) { + QString formattedUsername = QUrl::toPercentEncoding(username); + // this is a username - pull the captured name and lookup that user's location + AccountManager::getInstance().authenticatedRequest(GET_USER_LOCATION.arg(formattedUsername), + QNetworkAccessManager::GetOperation, + apiCallbackParameters()); +} diff --git a/libraries/networking/src/AddressManager.h b/libraries/networking/src/AddressManager.h new file mode 100644 index 0000000000..f27fb475c2 --- /dev/null +++ b/libraries/networking/src/AddressManager.h @@ -0,0 +1,53 @@ +// +// AddressManager.h +// libraries/networking/src +// +// Created by Stephen Birarda on 2014-09-10. +// 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_AddressManager_h +#define hifi_AddressManager_h + +#include + +#include +#include + +#include "AccountManager.h" + +static const QString HIFI_URL_SCHEME = "hifi"; + +class AddressManager : public QObject { + Q_OBJECT +public: + static AddressManager& getInstance(); + + static QString pathForPositionAndOrientation(const glm::vec3& position, bool hasOrientation = false, + const glm::quat& orientation = glm::quat()); + + void attemptPlaceNameLookup(const QString& lookupString); +public slots: + void handleLookupString(const QString& lookupString); + + void handleAPIResponse(const QJsonObject& jsonObject); + void handleAPIError(QNetworkReply& errorReply); + void goToUser(const QString& username); +signals: + void lookupResultIsOffline(); + void possibleDomainChangeRequired(const QString& newHostname); + void locationChangeRequired(const glm::vec3& newPosition, bool hasOrientationChange, const glm::vec3& newOrientation); +private: + const JSONCallbackParameters& apiCallbackParameters(); + + bool handleUrl(const QUrl& lookupUrl); + + bool handleNetworkAddress(const QString& lookupString); + bool handleRelativeViewpoint(const QString& pathSubsection); + bool handleUsername(const QString& lookupString); +}; + +#endif // hifi_AddressManager_h \ No newline at end of file diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index 91166129ad..9c0ae55d7e 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -73,6 +73,22 @@ void DomainHandler::setSockAddr(const HifiSockAddr& sockAddr, const QString& hos _hostname = hostname; } +void DomainHandler::setUUID(const QUuid& uuid) { + if (uuid != _uuid) { + _uuid = uuid; + qDebug() << "Domain uuid changed to" << uuidStringWithoutCurlyBraces(_uuid); + } +} + +QString DomainHandler::hostnameWithoutPort(const QString& hostname) { + int colonIndex = hostname.indexOf(':'); + return colonIndex > 0 ? hostname.left(colonIndex) : hostname; +} + +bool DomainHandler::isCurrentHostname(const QString& hostname) { + return hostnameWithoutPort(hostname) == _hostname; +} + void DomainHandler::setHostname(const QString& hostname) { if (hostname != _hostname) { diff --git a/libraries/networking/src/DomainHandler.h b/libraries/networking/src/DomainHandler.h index 91caddca22..bfdb5d7f38 100644 --- a/libraries/networking/src/DomainHandler.h +++ b/libraries/networking/src/DomainHandler.h @@ -37,10 +37,11 @@ public: void clearSettings(); const QUuid& getUUID() const { return _uuid; } - void setUUID(const QUuid& uuid) { _uuid = uuid; } + void setUUID(const QUuid& uuid); + static QString hostnameWithoutPort(const QString& hostname); + bool isCurrentHostname(const QString& hostname); const QString& getHostname() const { return _hostname; } - void setHostname(const QString& hostname); const QHostAddress& getIP() const { return _sockAddr.getAddress(); } void setIPToLocalhost() { _sockAddr.setAddress(QHostAddress(QHostAddress::LocalHost)); } @@ -63,6 +64,8 @@ public: void parseDTLSRequirementPacket(const QByteArray& dtlsRequirementPacket); void softReset(); +public slots: + void setHostname(const QString& hostname); private slots: void completedHostnameLookup(const QHostInfo& hostInfo); diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index d77ef321a8..79a399c621 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -397,7 +397,10 @@ int NodeList::processDomainServerList(const QByteArray& packet) { _numNoReplyDomainCheckIns = 0; // if this was the first domain-server list from this domain, we've now connected - _domainHandler.setIsConnected(true); + if (!_domainHandler.isConnected()) { + _domainHandler.setUUID(uuidFromPacketHeader(packet)); + _domainHandler.setIsConnected(true); + } int readNodes = 0; diff --git a/libraries/networking/src/UserActivityLogger.cpp b/libraries/networking/src/UserActivityLogger.cpp index e2d3434867..549f02ae3c 100644 --- a/libraries/networking/src/UserActivityLogger.cpp +++ b/libraries/networking/src/UserActivityLogger.cpp @@ -73,8 +73,8 @@ void UserActivityLogger::requestFinished(const QJsonObject& object) { // qDebug() << object; } -void UserActivityLogger::requestError(QNetworkReply::NetworkError error,const QString& string) { - qDebug() << error << ": " << string; +void UserActivityLogger::requestError(QNetworkReply& errorReply) { + qDebug() << errorReply.error() << "-" << errorReply.errorString(); } void UserActivityLogger::launch(QString applicationVersion) { diff --git a/libraries/networking/src/UserActivityLogger.h b/libraries/networking/src/UserActivityLogger.h index 7e8abe9fb2..1bd966d632 100644 --- a/libraries/networking/src/UserActivityLogger.h +++ b/libraries/networking/src/UserActivityLogger.h @@ -40,7 +40,7 @@ public slots: private slots: void requestFinished(const QJsonObject& object); - void requestError(QNetworkReply::NetworkError error,const QString& string); + void requestError(QNetworkReply& errorReply); private: UserActivityLogger(); diff --git a/libraries/script-engine/src/EventTypes.cpp b/libraries/script-engine/src/EventTypes.cpp index 0e6a27bc42..75b3eba1a3 100644 --- a/libraries/script-engine/src/EventTypes.cpp +++ b/libraries/script-engine/src/EventTypes.cpp @@ -253,7 +253,9 @@ void keyEventFromScriptValue(const QScriptValue& object, KeyEvent& event) { } else if (event.text.toUpper() == "CAPS LOCK") { event.key = Qt::Key_CapsLock; } else { - event.key = event.text.at(0).unicode(); + // Key values do not distinguish between uppercase and lowercase + // and use the uppercase key value. + event.key = event.text.toUpper().at(0).unicode(); } event.isValid = true; }