From 14f56310f605cfe67abc1b43bd7b6a6856527f21 Mon Sep 17 00:00:00 2001 From: John Grosen Date: Thu, 12 Jun 2014 23:28:43 -0700 Subject: [PATCH 01/37] Changed AccountManager to only rely on a proper OAuth response. This involved making another request after the initial OAuth authorization. Because of this now-two-step process, some methods and signals were renamed to make their purpose more clear. Additionally, a _hasProfile member variable was added to DataServerAccountInfo in order to allow for knowledge of whether the profile elements had been fetched when being deserialized from disk. Error handling for the whole process is still nonexistant. --- interface/src/Menu.cpp | 9 +- interface/src/XmppClient.cpp | 2 +- libraries/networking/src/AccountManager.cpp | 175 +++++++++++------- libraries/networking/src/AccountManager.h | 35 ++-- .../networking/src/DataServerAccountInfo.cpp | 31 +++- .../networking/src/DataServerAccountInfo.h | 17 +- 6 files changed, 172 insertions(+), 97 deletions(-) diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 3015a638f6..0b49c08f51 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -127,7 +127,7 @@ Menu::Menu() : toggleLoginMenuItem(); // connect to the appropriate slots of the AccountManager so that we can change the Login/Logout menu item - connect(&accountManager, &AccountManager::accessTokenChanged, this, &Menu::toggleLoginMenuItem); + connect(&accountManager, &AccountManager::profileChanged, this, &Menu::toggleLoginMenuItem); connect(&accountManager, &AccountManager::logoutComplete, this, &Menu::toggleLoginMenuItem); addDisabledActionAndSeparator(fileMenu, "Scripts"); @@ -326,7 +326,7 @@ Menu::Menu() : shadowGroup->addAction(addCheckableActionToQMenuAndActionHash(shadowMenu, "None", 0, true)); shadowGroup->addAction(addCheckableActionToQMenuAndActionHash(shadowMenu, MenuOption::SimpleShadows, 0, false)); shadowGroup->addAction(addCheckableActionToQMenuAndActionHash(shadowMenu, MenuOption::CascadedShadows, 0, false)); - + addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Metavoxels, 0, true); addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::BuckyBalls, 0, false); addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Particles, 0, true); @@ -407,7 +407,7 @@ Menu::Menu() : addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::DisableNackPackets, 0, false); addDisabledActionAndSeparator(developerMenu, "Testing"); - + QMenu* timingMenu = developerMenu->addMenu("Timing and Statistics Tools"); QMenu* perfTimerMenu = timingMenu->addMenu("Performance Timer"); addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::DisplayTimingDetails, 0, true); @@ -462,7 +462,7 @@ Menu::Menu() : false, appInstance->getAudio(), SLOT(toggleToneInjection())); - addCheckableActionToQMenuAndActionHash(audioDebugMenu, MenuOption::AudioScope, + addCheckableActionToQMenuAndActionHash(audioDebugMenu, MenuOption::AudioScope, Qt::CTRL | Qt::Key_P, false, appInstance->getAudio(), SLOT(toggleScope())); @@ -1775,4 +1775,3 @@ QString Menu::getSnapshotsLocation() const { } return _snapshotsLocation; } - diff --git a/interface/src/XmppClient.cpp b/interface/src/XmppClient.cpp index 666906681c..ef9db55620 100644 --- a/interface/src/XmppClient.cpp +++ b/interface/src/XmppClient.cpp @@ -23,7 +23,7 @@ XmppClient::XmppClient() : _xmppMUCManager() { AccountManager& accountManager = AccountManager::getInstance(); - connect(&accountManager, SIGNAL(accessTokenChanged()), this, SLOT(connectToServer())); + connect(&accountManager, SIGNAL(profileChanged()), this, SLOT(connectToServer())); connect(&accountManager, SIGNAL(logoutComplete()), this, SLOT(disconnectFromServer())); } diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp index b4aedbcb7c..918261a953 100644 --- a/libraries/networking/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -55,13 +55,13 @@ AccountManager::AccountManager() : { qRegisterMetaType("OAuthAccessToken"); qRegisterMetaTypeStreamOperators("OAuthAccessToken"); - + qRegisterMetaType("DataServerAccountInfo"); qRegisterMetaTypeStreamOperators("DataServerAccountInfo"); - + qRegisterMetaType("QNetworkAccessManager::Operation"); qRegisterMetaType("JSONCallbackParameters"); - + connect(&_accountInfo, &DataServerAccountInfo::balanceChanged, this, &AccountManager::accountInfoBalanceChanged); } @@ -70,18 +70,18 @@ const QString DOUBLE_SLASH_SUBSTITUTE = "slashslash"; void AccountManager::logout() { // a logout means we want to delete the DataServerAccountInfo we currently have for this URL, in-memory and in file _accountInfo = DataServerAccountInfo(); - + emit balanceChanged(0); connect(&_accountInfo, &DataServerAccountInfo::balanceChanged, this, &AccountManager::accountInfoBalanceChanged); - + QSettings settings; settings.beginGroup(ACCOUNTS_GROUP); - + QString keyURLString(_authURL.toString().replace("//", DOUBLE_SLASH_SUBSTITUTE)); settings.remove(keyURLString); - + qDebug() << "Removed account info for" << _authURL << "from in-memory accounts and .ini file"; - + emit logoutComplete(); // the username has changed to blank emit usernameChanged(QString()); @@ -93,7 +93,7 @@ void AccountManager::updateBalance() { JSONCallbackParameters callbackParameters; callbackParameters.jsonCallbackReceiver = &_accountInfo; callbackParameters.jsonCallbackMethod = "setBalanceFromJSON"; - + authenticatedRequest("/api/v1/wallets/mine", QNetworkAccessManager::GetOperation, callbackParameters); } } @@ -105,28 +105,33 @@ void AccountManager::accountInfoBalanceChanged(qint64 newBalance) { void AccountManager::setAuthURL(const QUrl& authURL) { if (_authURL != authURL) { _authURL = authURL; - + qDebug() << "URL for node authentication has been changed to" << qPrintable(_authURL.toString()); qDebug() << "Re-setting authentication flow."; - + // check if there are existing access tokens to load from settings QSettings settings; settings.beginGroup(ACCOUNTS_GROUP); - + foreach(const QString& key, settings.allKeys()) { // take a key copy to perform the double slash replacement QString keyCopy(key); QUrl keyURL(keyCopy.replace("slashslash", "//")); - + if (keyURL == _authURL) { // pull out the stored access token and store it in memory _accountInfo = settings.value(key).value(); qDebug() << "Found a data-server access token for" << qPrintable(keyURL.toString()); - - emit accessTokenChanged(); + + // profile info isn't guaranteed to be saved too + if (_accountInfo.hasProfile()) { + emit profileChanged(); + } else { + requestProfile(); + } } } - + // tell listeners that the auth endpoint has changed emit authEndpointChanged(); } @@ -147,36 +152,36 @@ void AccountManager::authenticatedRequest(const QString& path, QNetworkAccessMan void AccountManager::invokedRequest(const QString& path, QNetworkAccessManager::Operation operation, const JSONCallbackParameters& callbackParams, const QByteArray& dataByteArray, QHttpMultiPart* dataMultiPart) { - + if (!_networkAccessManager) { _networkAccessManager = new QNetworkAccessManager(this); } - + if (hasValidAccessToken()) { QNetworkRequest authenticatedRequest; - + QUrl requestURL = _authURL; - + if (path.startsWith("/")) { requestURL.setPath(path); } else { requestURL.setPath("/" + path); } - + requestURL.setQuery("access_token=" + _accountInfo.getAccessToken().token); - + authenticatedRequest.setUrl(requestURL); - + if (VERBOSE_HTTP_REQUEST_DEBUGGING) { qDebug() << "Making an authenticated request to" << qPrintable(requestURL.toString()); - + if (!dataByteArray.isEmpty()) { qDebug() << "The POST/PUT body -" << QString(dataByteArray); } } - + QNetworkReply* networkReply = NULL; - + switch (operation) { case QNetworkAccessManager::GetOperation: networkReply = _networkAccessManager->get(authenticatedRequest); @@ -198,24 +203,24 @@ void AccountManager::invokedRequest(const QString& path, QNetworkAccessManager:: networkReply = _networkAccessManager->put(authenticatedRequest, dataByteArray); } } - + break; default: // other methods not yet handled break; } - + if (networkReply) { if (!callbackParams.isEmpty()) { // if we have information for a callback, insert the callbackParams into our local map _pendingCallbackMap.insert(networkReply, callbackParams); - + if (callbackParams.updateReciever && !callbackParams.updateSlot.isEmpty()) { callbackParams.updateReciever->connect(networkReply, SIGNAL(uploadProgress(qint64, qint64)), callbackParams.updateSlot.toStdString().c_str()); } } - + // if we ended up firing of a request, hook up to it now connect(networkReply, SIGNAL(finished()), SLOT(processReply())); } @@ -224,7 +229,7 @@ void AccountManager::invokedRequest(const QString& path, QNetworkAccessManager:: void AccountManager::processReply() { QNetworkReply* requestReply = reinterpret_cast(sender()); - + if (requestReply->error() == QNetworkReply::NoError) { passSuccessToCallback(requestReply); } else { @@ -235,17 +240,17 @@ void AccountManager::processReply() { void AccountManager::passSuccessToCallback(QNetworkReply* requestReply) { QJsonDocument jsonResponse = QJsonDocument::fromJson(requestReply->readAll()); - + JSONCallbackParameters callbackParams = _pendingCallbackMap.value(requestReply); - + if (callbackParams.jsonCallbackReceiver) { // invoke the right method on the callback receiver QMetaObject::invokeMethod(callbackParams.jsonCallbackReceiver, qPrintable(callbackParams.jsonCallbackMethod), Q_ARG(const QJsonObject&, jsonResponse.object())); - + // remove the related reply-callback group from the map _pendingCallbackMap.remove(requestReply); - + } else { if (VERBOSE_HTTP_REQUEST_DEBUGGING) { qDebug() << "Received JSON response from data-server that has no matching callback."; @@ -256,13 +261,13 @@ void AccountManager::passSuccessToCallback(QNetworkReply* requestReply) { void AccountManager::passErrorToCallback(QNetworkReply* requestReply) { JSONCallbackParameters callbackParams = _pendingCallbackMap.value(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())); - + // remove the related reply-callback group from the map _pendingCallbackMap.remove(requestReply); } else { @@ -274,12 +279,12 @@ void AccountManager::passErrorToCallback(QNetworkReply* requestReply) { } bool AccountManager::hasValidAccessToken() { - + if (_accountInfo.getAccessToken().token.isEmpty() || _accountInfo.getAccessToken().isExpired()) { if (VERBOSE_HTTP_REQUEST_DEBUGGING) { qDebug() << "An access token is required for requests to" << qPrintable(_authURL.toString()); } - + return false; } else { return true; @@ -288,12 +293,12 @@ bool AccountManager::hasValidAccessToken() { bool AccountManager::checkAndSignalForAccessToken() { bool hasToken = hasValidAccessToken(); - + if (!hasToken) { // emit a signal so somebody can call back to us and request an access token given a username and password emit authRequired(); } - + return hasToken; } @@ -304,36 +309,36 @@ void AccountManager::requestAccessToken(const QString& login, const QString& pas } QNetworkRequest request; - + QUrl grantURL = _authURL; grantURL.setPath("/oauth/token"); - + const QString ACCOUNT_MANAGER_REQUESTED_SCOPE = "owner"; - + QByteArray postData; postData.append("grant_type=password&"); postData.append("username=" + login + "&"); postData.append("password=" + QUrl::toPercentEncoding(password) + "&"); postData.append("scope=" + ACCOUNT_MANAGER_REQUESTED_SCOPE); - + request.setUrl(grantURL); request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); - + QNetworkReply* requestReply = _networkAccessManager->post(request, postData); - connect(requestReply, &QNetworkReply::finished, this, &AccountManager::requestFinished); - connect(requestReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(requestError(QNetworkReply::NetworkError))); + connect(requestReply, &QNetworkReply::finished, this, &AccountManager::requestAccessTokenFinished); + connect(requestReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(requestAccessTokenError(QNetworkReply::NetworkError))); } -void AccountManager::requestFinished() { +void AccountManager::requestAccessTokenFinished() { QNetworkReply* requestReply = reinterpret_cast(sender()); - + QJsonDocument jsonResponse = QJsonDocument::fromJson(requestReply->readAll()); const QJsonObject& rootObject = jsonResponse.object(); - + if (!rootObject.contains("error")) { // construct an OAuthAccessToken from the json object - + if (!rootObject.contains("access_token") || !rootObject.contains("expires_in") || !rootObject.contains("token_type")) { // TODO: error handling - malformed token response @@ -342,23 +347,21 @@ void AccountManager::requestFinished() { // clear the path from the response URL so we have the right root URL for this access token QUrl rootURL = requestReply->url(); rootURL.setPath(""); - + qDebug() << "Storing an account with access-token for" << qPrintable(rootURL.toString()); - - _accountInfo = DataServerAccountInfo(rootObject); - + + _accountInfo = DataServerAccountInfo(); + _accountInfo.setAccessTokenFromJSON(rootObject); + emit loginComplete(rootURL); - // the username has changed to whatever came back - emit usernameChanged(_accountInfo.getUsername()); - - // we have found or requested an access token - emit accessTokenChanged(); - + // store this access token into the local settings QSettings localSettings; localSettings.beginGroup(ACCOUNTS_GROUP); localSettings.setValue(rootURL.toString().replace("//", DOUBLE_SLASH_SUBSTITUTE), QVariant::fromValue(_accountInfo)); + + requestProfile(); } } else { // TODO: error handling @@ -367,7 +370,53 @@ void AccountManager::requestFinished() { } } -void AccountManager::requestError(QNetworkReply::NetworkError error) { +void AccountManager::requestAccessTokenError(QNetworkReply::NetworkError error) { // TODO: error handling qDebug() << "AccountManager requestError - " << error; } + +void AccountManager::requestProfile() { + if (!_networkAccessManager) { + _networkAccessManager = new QNetworkAccessManager(this); + } + + QUrl profileURL = _authURL; + profileURL.setPath("/api/v1/users/profile"); + profileURL.setQuery("access_token=" + _accountInfo.getAccessToken().token); + + QNetworkReply* profileReply = _networkAccessManager->get(QNetworkRequest(profileURL)); + connect(profileReply, &QNetworkReply::finished, this, &AccountManager::requestProfileFinished); + connect(profileReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(requestProfileError(QNetworkReply::NetworkError))); +} + +void AccountManager::requestProfileFinished() { + QNetworkReply* profileReply = reinterpret_cast(sender()); + + QJsonDocument jsonResponse = QJsonDocument::fromJson(profileReply->readAll()); + const QJsonObject& rootObject = jsonResponse.object(); + + if (rootObject.contains("status") && rootObject["status"].toString() == "success") { + _accountInfo.setProfileInfoFromJSON(rootObject); + + emit profileChanged(); + + // the username has changed to whatever came back + emit usernameChanged(_accountInfo.getUsername()); + + // store the whole profile into the local settings + QUrl rootURL = profileReply->url(); + rootURL.setPath(""); + QSettings localSettings; + localSettings.beginGroup(ACCOUNTS_GROUP); + localSettings.setValue(rootURL.toString().replace("//", DOUBLE_SLASH_SUBSTITUTE), + QVariant::fromValue(_accountInfo)); + } else { + // TODO: error handling + qDebug() << "Error in response for profile"; + } +} + +void AccountManager::requestProfileError(QNetworkReply::NetworkError error) { + // TODO: error handling + qDebug() << "AccountManager requestProfileError - " << error; +} diff --git a/libraries/networking/src/AccountManager.h b/libraries/networking/src/AccountManager.h index 628b084ea8..c18836ca54 100644 --- a/libraries/networking/src/AccountManager.h +++ b/libraries/networking/src/AccountManager.h @@ -23,9 +23,9 @@ class JSONCallbackParameters { public: JSONCallbackParameters(); - + bool isEmpty() const { return !jsonCallbackReceiver && !errorCallbackReceiver; } - + QObject* jsonCallbackReceiver; QString jsonCallbackMethod; QObject* errorCallbackReceiver; @@ -38,30 +38,33 @@ class AccountManager : public QObject { Q_OBJECT public: static AccountManager& getInstance(); - + void authenticatedRequest(const QString& path, QNetworkAccessManager::Operation operation = QNetworkAccessManager::GetOperation, const JSONCallbackParameters& callbackParams = JSONCallbackParameters(), const QByteArray& dataByteArray = QByteArray(), QHttpMultiPart* dataMultiPart = NULL); - + const QUrl& getAuthURL() const { return _authURL; } void setAuthURL(const QUrl& authURL); bool hasAuthEndpoint() { return !_authURL.isEmpty(); } - + bool isLoggedIn() { return !_authURL.isEmpty() && hasValidAccessToken(); } bool hasValidAccessToken(); Q_INVOKABLE bool checkAndSignalForAccessToken(); - + void requestAccessToken(const QString& login, const QString& password); - + void requestProfile(); + const DataServerAccountInfo& getAccountInfo() const { return _accountInfo; } - + void destroy() { delete _networkAccessManager; } - + public slots: - void requestFinished(); - void requestError(QNetworkReply::NetworkError error); + void requestAccessTokenFinished(); + void requestProfileFinished(); + void requestAccessTokenError(QNetworkReply::NetworkError error); + void requestProfileError(QNetworkReply::NetworkError error); void logout(); void updateBalance(); void accountInfoBalanceChanged(qint64 newBalance); @@ -69,7 +72,7 @@ signals: void authRequired(); void authEndpointChanged(); void usernameChanged(const QString& username); - void accessTokenChanged(); + void profileChanged(); void loginComplete(const QUrl& authURL); void loginFailed(); void logoutComplete(); @@ -80,19 +83,19 @@ private: AccountManager(); AccountManager(AccountManager const& other); // not implemented void operator=(AccountManager const& other); // not implemented - + void passSuccessToCallback(QNetworkReply* reply); void passErrorToCallback(QNetworkReply* reply); - + Q_INVOKABLE void invokedRequest(const QString& path, QNetworkAccessManager::Operation operation, const JSONCallbackParameters& callbackParams, const QByteArray& dataByteArray, QHttpMultiPart* dataMultiPart); - + QUrl _authURL; QNetworkAccessManager* _networkAccessManager; QMap _pendingCallbackMap; - + DataServerAccountInfo _accountInfo; }; diff --git a/libraries/networking/src/DataServerAccountInfo.cpp b/libraries/networking/src/DataServerAccountInfo.cpp index 809f083e35..7f3b40ab82 100644 --- a/libraries/networking/src/DataServerAccountInfo.cpp +++ b/libraries/networking/src/DataServerAccountInfo.cpp @@ -19,11 +19,13 @@ DataServerAccountInfo::DataServerAccountInfo() : _xmppPassword(), _discourseApiKey(), _balance(0), - _hasBalance(false) + _hasBalance(false), + _hasProfile(false) { - + } +/* DataServerAccountInfo::DataServerAccountInfo(const QJsonObject& jsonObject) : _accessToken(jsonObject), _username(), @@ -36,6 +38,7 @@ DataServerAccountInfo::DataServerAccountInfo(const QJsonObject& jsonObject) : setXMPPPassword(userJSONObject["xmpp_password"].toString()); setDiscourseApiKey(userJSONObject["discourse_api_key"].toString()); } +*/ DataServerAccountInfo::DataServerAccountInfo(const DataServerAccountInfo& otherInfo) { _accessToken = otherInfo._accessToken; @@ -44,6 +47,7 @@ DataServerAccountInfo::DataServerAccountInfo(const DataServerAccountInfo& otherI _discourseApiKey = otherInfo._discourseApiKey; _balance = otherInfo._balance; _hasBalance = otherInfo._hasBalance; + _hasProfile = otherInfo._hasProfile; } DataServerAccountInfo& DataServerAccountInfo::operator=(const DataServerAccountInfo& otherInfo) { @@ -54,19 +58,24 @@ DataServerAccountInfo& DataServerAccountInfo::operator=(const DataServerAccountI void DataServerAccountInfo::swap(DataServerAccountInfo& otherInfo) { using std::swap; - + swap(_accessToken, otherInfo._accessToken); swap(_username, otherInfo._username); swap(_xmppPassword, otherInfo._xmppPassword); swap(_discourseApiKey, otherInfo._discourseApiKey); swap(_balance, otherInfo._balance); swap(_hasBalance, otherInfo._hasBalance); + swap(_hasProfile, otherInfo._hasProfile); +} + +void DataServerAccountInfo::setAccessTokenFromJSON(const QJsonObject& jsonObject) { + _accessToken = OAuthAccessToken(jsonObject); } void DataServerAccountInfo::setUsername(const QString& username) { if (_username != username) { _username = username; - + qDebug() << "Username changed to" << username; } } @@ -87,7 +96,7 @@ void DataServerAccountInfo::setBalance(qint64 balance) { if (!_hasBalance || _balance != balance) { _balance = balance; _hasBalance = true; - + emit balanceChanged(_balance); } } @@ -99,12 +108,20 @@ void DataServerAccountInfo::setBalanceFromJSON(const QJsonObject& jsonObject) { } } +void DataServerAccountInfo::setProfileInfoFromJSON(const QJsonObject& jsonObject) { + QJsonObject user = jsonObject["data"].toObject()["user"].toObject(); + setUsername(user["username"].toString()); + setXMPPPassword(user["xmpp_password"].toString()); + setDiscourseApiKey(user["discourse_api_key"].toString()); + setHasProfile(true); +} + QDataStream& operator<<(QDataStream &out, const DataServerAccountInfo& info) { - out << info._accessToken << info._username << info._xmppPassword << info._discourseApiKey; + out << info._accessToken << info._username << info._xmppPassword << info._discourseApiKey << info._hasProfile; return out; } QDataStream& operator>>(QDataStream &in, DataServerAccountInfo& info) { - in >> info._accessToken >> info._username >> info._xmppPassword >> info._discourseApiKey; + in >> info._accessToken >> info._username >> info._xmppPassword >> info._discourseApiKey >> info._hasProfile; return in; } diff --git a/libraries/networking/src/DataServerAccountInfo.h b/libraries/networking/src/DataServerAccountInfo.h index e0209326f9..27999aba2f 100644 --- a/libraries/networking/src/DataServerAccountInfo.h +++ b/libraries/networking/src/DataServerAccountInfo.h @@ -22,12 +22,13 @@ class DataServerAccountInfo : public QObject { Q_OBJECT public: DataServerAccountInfo(); - DataServerAccountInfo(const QJsonObject& jsonObject); + // DataServerAccountInfo(const QJsonObject& jsonObject); DataServerAccountInfo(const DataServerAccountInfo& otherInfo); DataServerAccountInfo& operator=(const DataServerAccountInfo& otherInfo); - + const OAuthAccessToken& getAccessToken() const { return _accessToken; } - + void setAccessTokenFromJSON(const QJsonObject& jsonObject); + const QString& getUsername() const { return _username; } void setUsername(const QString& username); @@ -36,26 +37,32 @@ public: const QString& getDiscourseApiKey() const { return _discourseApiKey; } void setDiscourseApiKey(const QString& discourseApiKey); - + qint64 getBalance() const { return _balance; } void setBalance(qint64 balance); bool hasBalance() const { return _hasBalance; } void setHasBalance(bool hasBalance) { _hasBalance = hasBalance; } Q_INVOKABLE void setBalanceFromJSON(const QJsonObject& jsonObject); + bool hasProfile() const { return _hasProfile; } + void setHasProfile(bool hasProfile) { _hasProfile = hasProfile; } + + void setProfileInfoFromJSON(const QJsonObject& jsonObject); + friend QDataStream& operator<<(QDataStream &out, const DataServerAccountInfo& info); friend QDataStream& operator>>(QDataStream &in, DataServerAccountInfo& info); signals: qint64 balanceChanged(qint64 newBalance); private: void swap(DataServerAccountInfo& otherInfo); - + OAuthAccessToken _accessToken; QString _username; QString _xmppPassword; QString _discourseApiKey; qint64 _balance; bool _hasBalance; + bool _hasProfile; }; #endif // hifi_DataServerAccountInfo_h From e4a0275f57fdc2455bdc4880e37a5e828dcd21ce Mon Sep 17 00:00:00 2001 From: barnold1953 Date: Fri, 13 Jun 2014 10:53:11 -0700 Subject: [PATCH 02/37] Exposed sixense and mouse options and oculus UI angle to preferences --- interface/src/Menu.cpp | 6 +- interface/src/Menu.h | 10 +- interface/src/devices/OculusManager.h | 2 + interface/src/devices/SixenseManager.cpp | 4 +- interface/src/devices/SixenseManager.h | 3 + interface/src/ui/ApplicationOverlay.cpp | 8 +- interface/src/ui/ApplicationOverlay.h | 5 +- interface/src/ui/PreferencesDialog.cpp | 13 + interface/ui/preferencesDialog.ui | 331 ++++++++++++++++++++++- 9 files changed, 367 insertions(+), 15 deletions(-) diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 3015a638f6..644b08f03d 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -45,6 +45,7 @@ #include "ui/ModelsBrowser.h" #include "ui/LoginDialog.h" #include "ui/NodeBounds.h" +#include "devices/OculusManager.h" Menu* Menu::_instance = NULL; @@ -83,6 +84,7 @@ Menu::Menu() : _audioJitterBufferSamples(0), _bandwidthDialog(NULL), _fieldOfView(DEFAULT_FIELD_OF_VIEW_DEGREES), + _realWorldFieldOfView(DEFAULT_REAL_WORLD_FIELD_OF_VIEW_DEGREES), _faceshiftEyeDeflection(DEFAULT_FACESHIFT_EYE_DEFLECTION), _frustumDrawMode(FRUSTUM_DRAW_MODE_ALL), _viewFrustumOffset(DEFAULT_FRUSTUM_OFFSET), @@ -91,6 +93,9 @@ Menu::Menu() : _lodToolsDialog(NULL), _maxVoxels(DEFAULT_MAX_VOXELS_PER_SYSTEM), _voxelSizeScale(DEFAULT_OCTREE_SIZE_SCALE), + _oculusUIAngularSize(DEFAULT_OCULUS_UI_ANGULAR_SIZE), + _sixenseReticleMoveSpeed(DEFAULT_SIXENSE_RETICLE_MOVE_SPEED), + _invertSixenseButtons(DEFAULT_INVERT_SIXENSE_MOUSE_BUTTONS), _automaticAvatarLOD(true), _avatarLODDecreaseFPS(DEFAULT_ADJUST_AVATAR_LOD_DOWN_FPS), _avatarLODIncreaseFPS(ADJUST_LOD_UP_FPS), @@ -387,7 +392,6 @@ Menu::Menu() : QMenu* sixenseOptionsMenu = developerMenu->addMenu("Sixense Options"); addCheckableActionToQMenuAndActionHash(sixenseOptionsMenu, MenuOption::SixenseMouseInput, 0, true); - addCheckableActionToQMenuAndActionHash(sixenseOptionsMenu, MenuOption::SixenseInvertInputButtons, 0, false); QMenu* handOptionsMenu = developerMenu->addMenu("Hand Options"); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 41f0b41498..4d2174a448 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -90,6 +90,12 @@ public: void setFieldOfView(float fieldOfView) { _fieldOfView = fieldOfView; } float getRealWorldFieldOfView() const { return _realWorldFieldOfView; } void setRealWorldFieldOfView(float realWorldFieldOfView) { _realWorldFieldOfView = realWorldFieldOfView; } + float getOculusUIAngularSize() const { return _oculusUIAngularSize; } + void setOculusUIAngularSize(float oculusUIAngularSize) { _oculusUIAngularSize = oculusUIAngularSize; } + float getSixenseReticleMoveSpeed() const { return _sixenseReticleMoveSpeed; } + void setSixenseReticleMoveSpeed(float sixenseReticleMoveSpeed) { _sixenseReticleMoveSpeed = sixenseReticleMoveSpeed; } + bool getInvertSixenseButtons() const { return _invertSixenseButtons; } + void setInvertSixenseButtons(bool invertSixenseButtons) { _invertSixenseButtons = invertSixenseButtons; } float getFaceshiftEyeDeflection() const { return _faceshiftEyeDeflection; } void setFaceshiftEyeDeflection(float faceshiftEyeDeflection) { _faceshiftEyeDeflection = faceshiftEyeDeflection; } @@ -255,6 +261,9 @@ private: LodToolsDialog* _lodToolsDialog; int _maxVoxels; float _voxelSizeScale; + float _oculusUIAngularSize; + float _sixenseReticleMoveSpeed; + bool _invertSixenseButtons; bool _automaticAvatarLOD; float _avatarLODDecreaseFPS; float _avatarLODIncreaseFPS; @@ -400,7 +409,6 @@ namespace MenuOption { const QString SettingsExport = "Export Settings"; const QString SettingsImport = "Import Settings"; const QString SimpleShadows = "Simple"; - const QString SixenseInvertInputButtons = "Invert Sixense Mouse Input Buttons"; const QString SixenseMouseInput = "Enable Sixense Mouse Input"; const QString ShowBordersVoxelNodes = "Show Voxel Nodes"; const QString ShowBordersModelNodes = "Show Model Nodes"; diff --git a/interface/src/devices/OculusManager.h b/interface/src/devices/OculusManager.h index caff38a6b0..21b9d67f4d 100644 --- a/interface/src/devices/OculusManager.h +++ b/interface/src/devices/OculusManager.h @@ -20,6 +20,8 @@ #include "renderer/ProgramObject.h" +const float DEFAULT_OCULUS_UI_ANGULAR_SIZE = 72.0f; + class Camera; /// Handles interaction with the Oculus Rift. diff --git a/interface/src/devices/SixenseManager.cpp b/interface/src/devices/SixenseManager.cpp index fa902be46f..5257a777e0 100644 --- a/interface/src/devices/SixenseManager.cpp +++ b/interface/src/devices/SixenseManager.cpp @@ -339,7 +339,7 @@ void SixenseManager::emulateMouse(PalmData* palm, int index) { Qt::MouseButton bumperButton; Qt::MouseButton triggerButton; - if (Menu::getInstance()->isOptionChecked(MenuOption::SixenseInvertInputButtons)) { + if (Menu::getInstance()->getInvertSixenseButtons()) { bumperButton = Qt::LeftButton; triggerButton = Qt::RightButton; } else { @@ -351,7 +351,7 @@ void SixenseManager::emulateMouse(PalmData* palm, int index) { float xAngle = (atan2(direction.z, direction.x) + M_PI_2) + 0.5f; float yAngle = 1.0f - ((atan2(direction.z, direction.y) + M_PI_2) + 0.5f); - float cursorRange = widget->width(); + float cursorRange = widget->width() * (1.0f - Menu::getInstance()->getSixenseReticleMoveSpeed() * 0.01f) * 2.0f; pos.setX(cursorRange * xAngle); pos.setY(cursorRange * yAngle); diff --git a/interface/src/devices/SixenseManager.h b/interface/src/devices/SixenseManager.h index f3c5633f90..71511cd06b 100644 --- a/interface/src/devices/SixenseManager.h +++ b/interface/src/devices/SixenseManager.h @@ -30,6 +30,9 @@ const unsigned int BUTTON_FWD = 1U << 7; // Event type that represents moving the controller const unsigned int CONTROLLER_MOVE_EVENT = 1500U; +const float DEFAULT_SIXENSE_RETICLE_MOVE_SPEED = 1.0f; +const bool DEFAULT_INVERT_SIXENSE_MOUSE_BUTTONS = true; + /// Handles interaction with the Sixense SDK (e.g., Razer Hydra). class SixenseManager : public QObject { Q_OBJECT diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index 95ac803e37..a45e42dc0b 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -33,7 +33,7 @@ ApplicationOverlay::ApplicationOverlay() : _framebufferObject(NULL), _oculusAngle(65.0f * RADIANS_PER_DEGREE), _distance(0.5f), - _textureFov(PI / 2.5f), + _textureFov(DEFAULT_OCULUS_UI_ANGULAR_SIZE * RADIANS_PER_DEGREE), _uiType(HEMISPHERE) { } @@ -50,6 +50,8 @@ const float WHITE_TEXT[] = { 0.93f, 0.93f, 0.93f }; void ApplicationOverlay::renderOverlay(bool renderToTexture) { PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "ApplicationOverlay::displayOverlay()"); + _textureFov = Menu::getInstance()->getOculusUIAngularSize() * RADIANS_PER_DEGREE; + Application* application = Application::getInstance(); Overlays& overlays = application->getOverlays(); @@ -154,8 +156,6 @@ void ApplicationOverlay::displayOverlayTextureOculus(Camera& whichCamera) { MyAvatar* myAvatar = application->getAvatar(); const glm::vec3& viewMatrixTranslation = application->getViewMatrixTranslation(); - - // Get vertical FoV of the displayed overlay texture const float halfVerticalAngle = _oculusAngle / 2.0f; const float overlayAspectRatio = glWidget->width() / (float)glWidget->height(); @@ -338,7 +338,7 @@ void ApplicationOverlay::renderControllerPointer() { float xAngle = (atan2(direction.z, direction.x) + M_PI_2) + 0.5f; float yAngle = 1.0f - ((atan2(direction.z, direction.y) + M_PI_2) + 0.5f); - float cursorRange = glWidget->width(); + float cursorRange = glWidget->width() * (1.0f - Menu::getInstance()->getSixenseReticleMoveSpeed() * 0.01f) * 2.0f; int mouseX = cursorRange * xAngle; int mouseY = cursorRange * yAngle; diff --git a/interface/src/ui/ApplicationOverlay.h b/interface/src/ui/ApplicationOverlay.h index 03a323cd5d..b6066099fa 100644 --- a/interface/src/ui/ApplicationOverlay.h +++ b/interface/src/ui/ApplicationOverlay.h @@ -31,10 +31,7 @@ public: // Getters QOpenGLFramebufferObject* getFramebufferObject(); - float getOculusAngle() const { return _oculusAngle; } - - // Setters - void setOculusAngle(float oculusAngle) { _oculusAngle = oculusAngle; } + void setUIType(UIType uiType) { _uiType = uiType; } private: diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index d5c6079d4b..5e6c6984eb 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -137,6 +137,13 @@ void PreferencesDialog::loadPreferences() { ui.maxVoxelsSpin->setValue(menuInstance->getMaxVoxels()); ui.maxVoxelsPPSSpin->setValue(menuInstance->getMaxVoxelPacketsPerSecond()); + + ui.oculusUIAngularSizeSpin->setValue(menuInstance->getOculusUIAngularSize()); + + ui.sixenseReticleMoveSpeedSpin->setValue(menuInstance->getSixenseReticleMoveSpeed()); + + ui.invertSixenseButtonsCheckBox->setChecked(menuInstance->getInvertSixenseButtons()); + } void PreferencesDialog::savePreferences() { @@ -189,6 +196,12 @@ void PreferencesDialog::savePreferences() { (float)ui.faceshiftEyeDeflectionSider->maximum()); Menu::getInstance()->setMaxVoxelPacketsPerSecond(ui.maxVoxelsPPSSpin->value()); + Menu::getInstance()->setOculusUIAngularSize(ui.oculusUIAngularSizeSpin->value()); + + Menu::getInstance()->setSixenseReticleMoveSpeed(ui.sixenseReticleMoveSpeedSpin->value()); + + Menu::getInstance()->setInvertSixenseButtons(ui.invertSixenseButtonsCheckBox->isChecked()); + Menu::getInstance()->setAudioJitterBufferSamples(ui.audioJitterSpin->value()); Application::getInstance()->getAudio()->setJitterBufferSamples(ui.audioJitterSpin->value()); diff --git a/interface/ui/preferencesDialog.ui b/interface/ui/preferencesDialog.ui index a1c2073ab6..f00d7c4788 100644 --- a/interface/ui/preferencesDialog.ui +++ b/interface/ui/preferencesDialog.ui @@ -154,9 +154,9 @@ color: #0e7077 0 - -204 - 494 - 1091 + -1002 + 477 + 1386 @@ -1605,6 +1605,331 @@ padding: 10px;margin-top:10px + + + + + 0 + 0 + + + + + 0 + 40 + + + + + Arial + 20 + 50 + false + + + + color: #0e7077 + + + Oculus Rift + + + Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft + + + + + + + 0 + + + 10 + + + 0 + + + 10 + + + + + + Arial + + + + color: rgb(51, 51, 51) + + + User Interface Angular Size + + + 15 + + + maxVoxelsSpin + + + + + + + + Arial + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + 125 + 36 + + + + + Arial + + + + 30 + + + 160 + + + 1 + + + 72 + + + + + + + + + + 0 + 0 + + + + + 0 + 40 + + + + + Arial + 20 + 50 + false + + + + color: #0e7077 + + + Sixense Controllers + + + Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft + + + + + + + 0 + + + 10 + + + 0 + + + 10 + + + + + + Arial + + + + color: rgb(51, 51, 51) + + + Invert Mouse Buttons + + + 15 + + + maxVoxelsSpin + + + + + + + + Arial + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + 32 + 0 + + + + + 0 + 0 + + + + + + + + 32 + 32 + + + + + + + + + + 0 + + + 10 + + + 0 + + + 10 + + + + + + Arial + + + + color: rgb(51, 51, 51) + + + Reticle Movement Speed + + + 15 + + + maxVoxelsSpin + + + + + + + + Arial + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + 125 + 36 + + + + + Arial + + + + 100 + + + 1 + + + 50 + + + + + From 4a1307fa382ff92a25877f21474c57c42a55825d Mon Sep 17 00:00:00 2001 From: barnold1953 Date: Fri, 13 Jun 2014 10:54:50 -0700 Subject: [PATCH 03/37] Killed two warnings --- interface/src/ui/ApplicationOverlay.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index a45e42dc0b..f6afc15223 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -316,7 +316,6 @@ void ApplicationOverlay::renderControllerPointer() { MyAvatar* myAvatar = application->getAvatar(); const HandData* handData = Application::getInstance()->getAvatar()->getHandData(); - int numberOfPalms = handData->getNumPalms(); for (unsigned int palmIndex = 2; palmIndex < 4; palmIndex++) { const PalmData* palmData = NULL; @@ -431,8 +430,6 @@ void ApplicationOverlay::renderMagnifier(int mouseX, int mouseY) magnifyHeight = widgetHeight - mouseY; } - const float halfMagnifyHeight = magnifyHeight / 2.0f; - float newWidth = magnifyWidth * magnification; float newHeight = magnifyHeight * magnification; From 3af4e32c813260b4edf55c60d3294cc032f9d58d Mon Sep 17 00:00:00 2001 From: John Grosen Date: Fri, 13 Jun 2014 11:08:38 -0700 Subject: [PATCH 04/37] Now determines hasProfile from presence of username --- libraries/networking/src/DataServerAccountInfo.cpp | 14 +++++++------- libraries/networking/src/DataServerAccountInfo.h | 4 +--- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/libraries/networking/src/DataServerAccountInfo.cpp b/libraries/networking/src/DataServerAccountInfo.cpp index 7f3b40ab82..99d92d8738 100644 --- a/libraries/networking/src/DataServerAccountInfo.cpp +++ b/libraries/networking/src/DataServerAccountInfo.cpp @@ -19,8 +19,7 @@ DataServerAccountInfo::DataServerAccountInfo() : _xmppPassword(), _discourseApiKey(), _balance(0), - _hasBalance(false), - _hasProfile(false) + _hasBalance(false) { } @@ -47,7 +46,6 @@ DataServerAccountInfo::DataServerAccountInfo(const DataServerAccountInfo& otherI _discourseApiKey = otherInfo._discourseApiKey; _balance = otherInfo._balance; _hasBalance = otherInfo._hasBalance; - _hasProfile = otherInfo._hasProfile; } DataServerAccountInfo& DataServerAccountInfo::operator=(const DataServerAccountInfo& otherInfo) { @@ -65,7 +63,6 @@ void DataServerAccountInfo::swap(DataServerAccountInfo& otherInfo) { swap(_discourseApiKey, otherInfo._discourseApiKey); swap(_balance, otherInfo._balance); swap(_hasBalance, otherInfo._hasBalance); - swap(_hasProfile, otherInfo._hasProfile); } void DataServerAccountInfo::setAccessTokenFromJSON(const QJsonObject& jsonObject) { @@ -108,20 +105,23 @@ void DataServerAccountInfo::setBalanceFromJSON(const QJsonObject& jsonObject) { } } +bool DataServerAccountInfo::hasProfile() const { + return _username.length() > 0; +} + void DataServerAccountInfo::setProfileInfoFromJSON(const QJsonObject& jsonObject) { QJsonObject user = jsonObject["data"].toObject()["user"].toObject(); setUsername(user["username"].toString()); setXMPPPassword(user["xmpp_password"].toString()); setDiscourseApiKey(user["discourse_api_key"].toString()); - setHasProfile(true); } QDataStream& operator<<(QDataStream &out, const DataServerAccountInfo& info) { - out << info._accessToken << info._username << info._xmppPassword << info._discourseApiKey << info._hasProfile; + out << info._accessToken << info._username << info._xmppPassword << info._discourseApiKey; return out; } QDataStream& operator>>(QDataStream &in, DataServerAccountInfo& info) { - in >> info._accessToken >> info._username >> info._xmppPassword >> info._discourseApiKey >> info._hasProfile; + in >> info._accessToken >> info._username >> info._xmppPassword >> info._discourseApiKey; return in; } diff --git a/libraries/networking/src/DataServerAccountInfo.h b/libraries/networking/src/DataServerAccountInfo.h index 27999aba2f..8ea7972392 100644 --- a/libraries/networking/src/DataServerAccountInfo.h +++ b/libraries/networking/src/DataServerAccountInfo.h @@ -44,8 +44,7 @@ public: void setHasBalance(bool hasBalance) { _hasBalance = hasBalance; } Q_INVOKABLE void setBalanceFromJSON(const QJsonObject& jsonObject); - bool hasProfile() const { return _hasProfile; } - void setHasProfile(bool hasProfile) { _hasProfile = hasProfile; } + bool hasProfile() const; void setProfileInfoFromJSON(const QJsonObject& jsonObject); @@ -62,7 +61,6 @@ private: QString _discourseApiKey; qint64 _balance; bool _hasBalance; - bool _hasProfile; }; #endif // hifi_DataServerAccountInfo_h From 422db14812a20a63cb7c3a987733832253c7e883 Mon Sep 17 00:00:00 2001 From: barnold1953 Date: Fri, 13 Jun 2014 11:34:57 -0700 Subject: [PATCH 05/37] Fixed some improper logic and used better defaults --- interface/src/devices/SixenseManager.cpp | 17 +++++++++++------ interface/src/devices/SixenseManager.h | 5 +++-- interface/src/ui/ApplicationOverlay.cpp | 13 ++++++------- 3 files changed, 20 insertions(+), 15 deletions(-) diff --git a/interface/src/devices/SixenseManager.cpp b/interface/src/devices/SixenseManager.cpp index 5257a777e0..ed55dc4c66 100644 --- a/interface/src/devices/SixenseManager.cpp +++ b/interface/src/devices/SixenseManager.cpp @@ -185,6 +185,11 @@ void SixenseManager::update(float deltaTime) { #endif // HAVE_SIXENSE } +float SixenseManager::getCursorPixelRangeMultiplier() const { + //scales (0,100) to (0.4,2.0) + return ((Menu::getInstance()->getSixenseReticleMoveSpeed()) * 0.008f + 0.2f) * 2.0f; +} + #ifdef HAVE_SIXENSE // the calibration sequence is: @@ -347,14 +352,14 @@ void SixenseManager::emulateMouse(PalmData* palm, int index) { triggerButton = Qt::LeftButton; } - // Get the angles, scaled between 0-1 - float xAngle = (atan2(direction.z, direction.x) + M_PI_2) + 0.5f; - float yAngle = 1.0f - ((atan2(direction.z, direction.y) + M_PI_2) + 0.5f); + // Get the angles, scaled between (-0.5,0.5) + float xAngle = (atan2(direction.z, direction.x) + M_PI_2); + float yAngle = 0.5f - ((atan2(direction.z, direction.y) + M_PI_2)); - float cursorRange = widget->width() * (1.0f - Menu::getInstance()->getSixenseReticleMoveSpeed() * 0.01f) * 2.0f; + float cursorRange = widget->width() * getCursorPixelRangeMultiplier(); - pos.setX(cursorRange * xAngle); - pos.setY(cursorRange * yAngle); + pos.setX(widget->width() / 2.0f + cursorRange * xAngle); + pos.setY(widget->height() / 2.0f + cursorRange * yAngle); //If we are off screen then we should stop processing, and if a trigger or bumper is pressed, //we should unpress them. diff --git a/interface/src/devices/SixenseManager.h b/interface/src/devices/SixenseManager.h index 71511cd06b..41e061a25b 100644 --- a/interface/src/devices/SixenseManager.h +++ b/interface/src/devices/SixenseManager.h @@ -30,8 +30,8 @@ const unsigned int BUTTON_FWD = 1U << 7; // Event type that represents moving the controller const unsigned int CONTROLLER_MOVE_EVENT = 1500U; -const float DEFAULT_SIXENSE_RETICLE_MOVE_SPEED = 1.0f; -const bool DEFAULT_INVERT_SIXENSE_MOUSE_BUTTONS = true; +const float DEFAULT_SIXENSE_RETICLE_MOVE_SPEED = 37.5f; +const bool DEFAULT_INVERT_SIXENSE_MOUSE_BUTTONS = false; /// Handles interaction with the Sixense SDK (e.g., Razer Hydra). class SixenseManager : public QObject { @@ -42,6 +42,7 @@ public: ~SixenseManager(); void update(float deltaTime); + float getCursorPixelRangeMultiplier() const; public slots: diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index f6afc15223..8ba0b42910 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -333,14 +333,14 @@ void ApplicationOverlay::renderControllerPointer() { // Get directon relative to avatar orientation glm::vec3 direction = glm::inverse(myAvatar->getOrientation()) * palmData->getFingerDirection(); - // Get the angles, scaled between 0-1 - float xAngle = (atan2(direction.z, direction.x) + M_PI_2) + 0.5f; - float yAngle = 1.0f - ((atan2(direction.z, direction.y) + M_PI_2) + 0.5f); + // Get the angles, scaled between (-0.5,0.5) + float xAngle = (atan2(direction.z, direction.x) + M_PI_2) ; + float yAngle = 0.5f - ((atan2(direction.z, direction.y) + M_PI_2)); - float cursorRange = glWidget->width() * (1.0f - Menu::getInstance()->getSixenseReticleMoveSpeed() * 0.01f) * 2.0f; + float cursorRange = glWidget->width() * application->getSixenseManager()->getCursorPixelRangeMultiplier(); - int mouseX = cursorRange * xAngle; - int mouseY = cursorRange * yAngle; + int mouseX = glWidget->width() / 2.0f + cursorRange * xAngle; + int mouseY = glWidget->height() / 2.0f + cursorRange * yAngle; //If the cursor is out of the screen then don't render it if (mouseX < 0 || mouseX >= glWidget->width() || mouseY < 0 || mouseY >= glWidget->height()) { @@ -409,7 +409,6 @@ void ApplicationOverlay::renderMagnifier(int mouseX, int mouseY) const float horizontalAngle = halfVerticalAngle * 2.0f * overlayAspectRatio; const float halfHorizontalAngle = horizontalAngle / 2; - float magnifyWidth = 80.0f; float magnifyHeight = 60.0f; From 3747a5eeae79bee2b14b6bd43db226ac37316808 Mon Sep 17 00:00:00 2001 From: barnold1953 Date: Fri, 13 Jun 2014 13:38:28 -0700 Subject: [PATCH 06/37] update hemisphere VBO on ui size change --- interface/src/ui/ApplicationOverlay.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index 8ba0b42910..e228e39976 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -692,8 +692,11 @@ void ApplicationOverlay::renderTexturedHemisphere() { static VerticesIndices vbo(0, 0); int vertices = slices * (stacks - 1) + 1; int indices = slices * 2 * 3 * (stacks - 2) + slices * 3; - //We only generate the VBO once - if (vbo.first == 0) { + + static float oldTextureFOV = _textureFov; + //We only generate the VBO when the _textureFov changes + if (vbo.first == 0 || oldTextureFOV != _textureFov) { + oldTextureFOV = _textureFov; TextureVertex* vertexData = new TextureVertex[vertices]; TextureVertex* vertex = vertexData; for (int i = 0; i < stacks - 1; i++) { @@ -718,7 +721,9 @@ void ApplicationOverlay::renderTexturedHemisphere() { vertex->uv.y = 0.5f; vertex++; - glGenBuffers(1, &vbo.first); + if (vbo.first == 0){ + glGenBuffers(1, &vbo.first); + } glBindBuffer(GL_ARRAY_BUFFER, vbo.first); const int BYTES_PER_VERTEX = sizeof(TextureVertex); glBufferData(GL_ARRAY_BUFFER, vertices * BYTES_PER_VERTEX, vertexData, GL_STATIC_DRAW); From 230af7f5a7820ddf632c28bf2258f279440fd537 Mon Sep 17 00:00:00 2001 From: barnold1953 Date: Fri, 13 Jun 2014 13:49:35 -0700 Subject: [PATCH 07/37] Stopped clamping the magnification --- interface/src/ui/ApplicationOverlay.cpp | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index e228e39976..830a3f843b 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -415,20 +415,6 @@ void ApplicationOverlay::renderMagnifier(int mouseX, int mouseY) mouseX -= magnifyWidth / 2; mouseY -= magnifyHeight / 2; - //clamp the magnification - if (mouseX < 0) { - magnifyWidth += mouseX; - mouseX = 0; - } else if (mouseX + magnifyWidth > widgetWidth) { - magnifyWidth = widgetWidth - mouseX; - } - if (mouseY < 0) { - magnifyHeight += mouseY; - mouseY = 0; - } else if (mouseY + magnifyHeight > widgetHeight) { - magnifyHeight = widgetHeight - mouseY; - } - float newWidth = magnifyWidth * magnification; float newHeight = magnifyHeight * magnification; From 1a3de1ef28d343146b9c2312466d4ad7f5586e91 Mon Sep 17 00:00:00 2001 From: John Grosen Date: Fri, 13 Jun 2014 15:01:20 -0700 Subject: [PATCH 08/37] Totally removed DataServerAccountInfo's JSON constructor --- .../networking/src/DataServerAccountInfo.cpp | 15 --------------- libraries/networking/src/DataServerAccountInfo.h | 1 - 2 files changed, 16 deletions(-) diff --git a/libraries/networking/src/DataServerAccountInfo.cpp b/libraries/networking/src/DataServerAccountInfo.cpp index 99d92d8738..c60a17e0d8 100644 --- a/libraries/networking/src/DataServerAccountInfo.cpp +++ b/libraries/networking/src/DataServerAccountInfo.cpp @@ -24,21 +24,6 @@ DataServerAccountInfo::DataServerAccountInfo() : } -/* -DataServerAccountInfo::DataServerAccountInfo(const QJsonObject& jsonObject) : - _accessToken(jsonObject), - _username(), - _xmppPassword(), - _balance(0), - _hasBalance(false) -{ - QJsonObject userJSONObject = jsonObject["user"].toObject(); - setUsername(userJSONObject["username"].toString()); - setXMPPPassword(userJSONObject["xmpp_password"].toString()); - setDiscourseApiKey(userJSONObject["discourse_api_key"].toString()); -} -*/ - DataServerAccountInfo::DataServerAccountInfo(const DataServerAccountInfo& otherInfo) { _accessToken = otherInfo._accessToken; _username = otherInfo._username; diff --git a/libraries/networking/src/DataServerAccountInfo.h b/libraries/networking/src/DataServerAccountInfo.h index 8ea7972392..5800942376 100644 --- a/libraries/networking/src/DataServerAccountInfo.h +++ b/libraries/networking/src/DataServerAccountInfo.h @@ -22,7 +22,6 @@ class DataServerAccountInfo : public QObject { Q_OBJECT public: DataServerAccountInfo(); - // DataServerAccountInfo(const QJsonObject& jsonObject); DataServerAccountInfo(const DataServerAccountInfo& otherInfo); DataServerAccountInfo& operator=(const DataServerAccountInfo& otherInfo); From c593117ac5bccd39d09625298d54c596846bf411 Mon Sep 17 00:00:00 2001 From: barnold1953 Date: Fri, 13 Jun 2014 15:03:09 -0700 Subject: [PATCH 09/37] New reticle for sixense and oculus mouse movement --- .../resources/images/sixense-reticle.png | Bin 0 -> 26350 bytes interface/src/ui/ApplicationOverlay.cpp | 62 ++++++++++-------- interface/src/ui/ApplicationOverlay.h | 2 + 3 files changed, 35 insertions(+), 29 deletions(-) create mode 100644 interface/resources/images/sixense-reticle.png diff --git a/interface/resources/images/sixense-reticle.png b/interface/resources/images/sixense-reticle.png new file mode 100644 index 0000000000000000000000000000000000000000..b168b25106b99693112c3b2f3a351b4a49733325 GIT binary patch literal 26350 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 int mouseY = application->getMouseY(); + + //lazily load crosshair texture + if (_crosshairTexture == 0) { + _crosshairTexture = Application::getInstance()->getGLWidget()->bindTexture(QImage(Application::resourcesPath() + "images/sixense-reticle.png")); + } + glEnable(GL_TEXTURE_2D); + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, _crosshairTexture); + + + + if (OculusManager::isConnected() && application->getLastMouseMoveType() == QEvent::MouseMove) { const float pointerWidth = 10; const float pointerHeight = 10; - const float crossPad = 4; _numMagnifiers = 1; _mouseX[0] = application->getMouseX(); @@ -289,25 +304,22 @@ void ApplicationOverlay::renderPointers() { glBegin(GL_QUADS); - glColor3f(1, 0, 0); + glColor3f(0.0f, 198.0f / 255.0f, 244.0f / 255.0f); //Horizontal crosshair - glVertex2i(mouseX, mouseY - crossPad); - glVertex2i(mouseX + pointerWidth, mouseY - crossPad); - glVertex2i(mouseX + pointerWidth, mouseY - pointerHeight + crossPad); - glVertex2i(mouseX, mouseY - pointerHeight + crossPad); - - //Vertical crosshair - glVertex2i(mouseX + crossPad, mouseY); - glVertex2i(mouseX + pointerWidth - crossPad, mouseY); - glVertex2i(mouseX + pointerWidth - crossPad, mouseY - pointerHeight); - glVertex2i(mouseX + crossPad, mouseY - pointerHeight); + glTexCoord2d(0.0f, 0.0f); glVertex2i(mouseX, mouseY); + glTexCoord2d(1.0f, 0.0f); glVertex2i(mouseX + pointerWidth, mouseY); + glTexCoord2d(1.0f, 1.0f); glVertex2i(mouseX + pointerWidth, mouseY - pointerHeight); + glTexCoord2d(0.0f, 1.0f); glVertex2i(mouseX, mouseY - pointerHeight); glEnd(); } else if (application->getLastMouseMoveType() == CONTROLLER_MOVE_EVENT && Menu::getInstance()->isOptionChecked(MenuOption::SixenseMouseInput)) { //only render controller pointer if we aren't already rendering a mouse pointer renderControllerPointer(); } + + glDisable(GL_TEXTURE_2D); + } void ApplicationOverlay::renderControllerPointer() { @@ -349,13 +361,12 @@ void ApplicationOverlay::renderControllerPointer() { float pointerWidth = 40; float pointerHeight = 40; - float crossPad = 16; + //if we have the oculus, we should make the cursor smaller since it will be //magnified if (OculusManager::isConnected()) { - pointerWidth /= 4; - pointerHeight /= 4; - crossPad /= 4; + pointerWidth /= 2; + pointerHeight /= 2; _mouseX[_numMagnifiers] = mouseX; _mouseY[_numMagnifiers] = mouseY; @@ -368,19 +379,12 @@ void ApplicationOverlay::renderControllerPointer() { glBegin(GL_QUADS); - glColor3f(0.0f, 0.0f, 1.0f); + glColor3f(0.0f, 198.0f/255.0f, 244.0f/255.0f); - //Horizontal crosshair - glVertex2i(mouseX, mouseY - crossPad); - glVertex2i(mouseX + pointerWidth, mouseY - crossPad); - glVertex2i(mouseX + pointerWidth, mouseY - pointerHeight + crossPad); - glVertex2i(mouseX, mouseY - pointerHeight + crossPad); - - //Vertical crosshair - glVertex2i(mouseX + crossPad, mouseY); - glVertex2i(mouseX + pointerWidth - crossPad, mouseY); - glVertex2i(mouseX + pointerWidth - crossPad, mouseY - pointerHeight); - glVertex2i(mouseX + crossPad, mouseY - pointerHeight); + glTexCoord2d(0.0f, 0.0f); glVertex2i(mouseX, mouseY); + glTexCoord2d(1.0f, 0.0f); glVertex2i(mouseX + pointerWidth, mouseY); + glTexCoord2d(1.0f, 1.0f); glVertex2i(mouseX + pointerWidth, mouseY - pointerHeight); + glTexCoord2d(0.0f, 1.0f); glVertex2i(mouseX, mouseY - pointerHeight); glEnd(); } diff --git a/interface/src/ui/ApplicationOverlay.h b/interface/src/ui/ApplicationOverlay.h index b6066099fa..dc1d3d641f 100644 --- a/interface/src/ui/ApplicationOverlay.h +++ b/interface/src/ui/ApplicationOverlay.h @@ -59,6 +59,8 @@ private: int _mouseX[2]; int _mouseY[2]; int _numMagnifiers; + + GLuint _crosshairTexture; }; #endif // hifi_ApplicationOverlay_h \ No newline at end of file From cb1669653d771c510667caef7ee899d4640a148f Mon Sep 17 00:00:00 2001 From: barnold1953 Date: Fri, 13 Jun 2014 15:05:23 -0700 Subject: [PATCH 10/37] Mouse reticle bigger in oculus --- interface/src/ui/ApplicationOverlay.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index 2b98360b02..9222cb092d 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -292,8 +292,8 @@ void ApplicationOverlay::renderPointers() { if (OculusManager::isConnected() && application->getLastMouseMoveType() == QEvent::MouseMove) { - const float pointerWidth = 10; - const float pointerHeight = 10; + const float pointerWidth = 20; + const float pointerHeight = 20; _numMagnifiers = 1; _mouseX[0] = application->getMouseX(); From 587c0e5a9d1ca50b661b8ee310b8186a895d082c Mon Sep 17 00:00:00 2001 From: barnold1953 Date: Fri, 13 Jun 2014 16:01:59 -0700 Subject: [PATCH 11/37] Render Oculus pointers separate from texture for better quality --- interface/src/ui/ApplicationOverlay.cpp | 108 +++++++++++++++++++----- interface/src/ui/ApplicationOverlay.h | 3 +- 2 files changed, 88 insertions(+), 23 deletions(-) diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index 9222cb092d..fe6baadf66 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -37,8 +37,6 @@ ApplicationOverlay::ApplicationOverlay() : _uiType(HEMISPHERE), _crosshairTexture(0) { - - } ApplicationOverlay::~ApplicationOverlay() { @@ -259,6 +257,8 @@ void ApplicationOverlay::displayOverlayTextureOculus(Camera& whichCamera) { glEnd(); } + renderControllerPointersOculus(); + glPopMatrix(); glDepthMask(GL_TRUE); @@ -289,8 +289,6 @@ void ApplicationOverlay::renderPointers() { glBindTexture(GL_TEXTURE_2D, _crosshairTexture); - - if (OculusManager::isConnected() && application->getLastMouseMoveType() == QEvent::MouseMove) { const float pointerWidth = 20; const float pointerHeight = 20; @@ -299,30 +297,36 @@ void ApplicationOverlay::renderPointers() { _mouseX[0] = application->getMouseX(); _mouseY[0] = application->getMouseY(); - mouseX -= pointerWidth / 2.0f; - mouseY += pointerHeight / 2.0f; + //If we are in oculus, render reticle later - glBegin(GL_QUADS); - glColor3f(0.0f, 198.0f / 255.0f, 244.0f / 255.0f); + if (!OculusManager::isConnected()) { - //Horizontal crosshair - glTexCoord2d(0.0f, 0.0f); glVertex2i(mouseX, mouseY); - glTexCoord2d(1.0f, 0.0f); glVertex2i(mouseX + pointerWidth, mouseY); - glTexCoord2d(1.0f, 1.0f); glVertex2i(mouseX + pointerWidth, mouseY - pointerHeight); - glTexCoord2d(0.0f, 1.0f); glVertex2i(mouseX, mouseY - pointerHeight); + mouseX -= pointerWidth / 2.0f; + mouseY += pointerHeight / 2.0f; - glEnd(); + glBegin(GL_QUADS); + + glColor3f(0.0f, 198.0f / 255.0f, 244.0f / 255.0f); + + //Horizontal crosshair + glTexCoord2d(0.0f, 0.0f); glVertex2i(mouseX, mouseY); + glTexCoord2d(1.0f, 0.0f); glVertex2i(mouseX + pointerWidth, mouseY); + glTexCoord2d(1.0f, 1.0f); glVertex2i(mouseX + pointerWidth, mouseY - pointerHeight); + glTexCoord2d(0.0f, 1.0f); glVertex2i(mouseX, mouseY - pointerHeight); + + glEnd(); + } } else if (application->getLastMouseMoveType() == CONTROLLER_MOVE_EVENT && Menu::getInstance()->isOptionChecked(MenuOption::SixenseMouseInput)) { //only render controller pointer if we aren't already rendering a mouse pointer - renderControllerPointer(); + renderControllerPointers(); } - + glBindTexture(GL_TEXTURE_2D, 0); glDisable(GL_TEXTURE_2D); } -void ApplicationOverlay::renderControllerPointer() { +void ApplicationOverlay::renderControllerPointers() { Application* application = Application::getInstance(); QGLWidget* glWidget = application->getGLWidget(); MyAvatar* myAvatar = application->getAvatar(); @@ -365,13 +369,12 @@ void ApplicationOverlay::renderControllerPointer() { //if we have the oculus, we should make the cursor smaller since it will be //magnified if (OculusManager::isConnected()) { - pointerWidth /= 2; - pointerHeight /= 2; _mouseX[_numMagnifiers] = mouseX; _mouseY[_numMagnifiers] = mouseY; _numMagnifiers++; - + //If oculus is enabled, we draw the crosshairs later + continue; } mouseX -= pointerWidth / 2.0f; @@ -390,13 +393,74 @@ void ApplicationOverlay::renderControllerPointer() { } } +void ApplicationOverlay::renderControllerPointersOculus() { + Application* application = Application::getInstance(); + QGLWidget* glWidget = application->getGLWidget(); + + const int widgetWidth = glWidget->width(); + const int widgetHeight = glWidget->height(); + + const float reticleSize = 50.0f; + + glBindTexture(GL_TEXTURE_2D, _crosshairTexture); + glDisable(GL_DEPTH_TEST); + + for (int i = 0; i < _numMagnifiers; i++) { + + float mouseX = (float)_mouseX[i]; + float mouseY = (float)_mouseY[i]; + mouseX -= reticleSize / 2; + mouseY += reticleSize / 2; + + // Get position on hemisphere using angle + if (_uiType == HEMISPHERE) { + + //Get new UV coordinates from our magnification window + float newULeft = mouseX / widgetWidth; + float newURight = (mouseX + reticleSize) / widgetWidth; + float newVBottom = 1.0 - mouseY / widgetHeight; + float newVTop = 1.0 - (mouseY - reticleSize) / widgetHeight; + + // Project our position onto the hemisphere using the UV coordinates + float lX = sin((newULeft - 0.5f) * _textureFov); + float rX = sin((newURight - 0.5f) * _textureFov); + float bY = sin((newVBottom - 0.5f) * _textureFov); + float tY = sin((newVTop - 0.5f) * _textureFov); + + float dist; + //Bottom Left + dist = sqrt(lX * lX + bY * bY); + float blZ = sqrt(1.0f - dist * dist); + //Top Left + dist = sqrt(lX * lX + tY * tY); + float tlZ = sqrt(1.0f - dist * dist); + //Bottom Right + dist = sqrt(rX * rX + bY * bY); + float brZ = sqrt(1.0f - dist * dist); + //Top Right + dist = sqrt(rX * rX + tY * tY); + float trZ = sqrt(1.0f - dist * dist); + + glBegin(GL_QUADS); + + glColor3f(0.0f, 198.0f / 255.0f, 244.0f / 255.0f); + + glTexCoord2f(0.0f, 0.0f); glVertex3f(lX, tY, -tlZ); + glTexCoord2f(1.0f, 0.0f); glVertex3f(rX, tY, -trZ); + glTexCoord2f(1.0f, 1.0f); glVertex3f(rX, bY, -brZ); + glTexCoord2f(0.0f, 1.0f); glVertex3f(lX, bY, -blZ); + + glEnd(); + } + } + glEnable(GL_DEPTH_TEST); +} + //Renders a small magnification of the currently bound texture at the coordinates void ApplicationOverlay::renderMagnifier(int mouseX, int mouseY) { Application* application = Application::getInstance(); QGLWidget* glWidget = application->getGLWidget(); - MyAvatar* myAvatar = application->getAvatar(); - const glm::vec3& viewMatrixTranslation = application->getViewMatrixTranslation(); float leftX, rightX, leftZ, rightZ, topZ, bottomZ; diff --git a/interface/src/ui/ApplicationOverlay.h b/interface/src/ui/ApplicationOverlay.h index dc1d3d641f..71f78d606d 100644 --- a/interface/src/ui/ApplicationOverlay.h +++ b/interface/src/ui/ApplicationOverlay.h @@ -44,7 +44,8 @@ private: typedef QPair VerticesIndices; void renderPointers(); - void renderControllerPointer(); + void renderControllerPointers(); + void renderControllerPointersOculus(); void renderMagnifier(int mouseX, int mouseY); void renderAudioMeter(); void renderStatsAndLogs(); From 53d4cc795a62b767f27c975267ba37de596afe7f Mon Sep 17 00:00:00 2001 From: barnold1953 Date: Fri, 13 Jun 2014 16:09:45 -0700 Subject: [PATCH 12/37] Removed unused UI types that clutter code --- interface/src/ui/ApplicationOverlay.cpp | 272 ++++++++---------------- interface/src/ui/ApplicationOverlay.h | 7 +- 2 files changed, 85 insertions(+), 194 deletions(-) diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index fe6baadf66..2ef5665bee 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -34,7 +34,6 @@ ApplicationOverlay::ApplicationOverlay() : _oculusAngle(65.0f * RADIANS_PER_DEGREE), _distance(0.5f), _textureFov(DEFAULT_OCULUS_UI_ANGULAR_SIZE * RADIANS_PER_DEGREE), - _uiType(HEMISPHERE), _crosshairTexture(0) { } @@ -157,25 +156,6 @@ void ApplicationOverlay::displayOverlayTextureOculus(Camera& whichCamera) { MyAvatar* myAvatar = application->getAvatar(); const glm::vec3& viewMatrixTranslation = application->getViewMatrixTranslation(); - // Get vertical FoV of the displayed overlay texture - const float halfVerticalAngle = _oculusAngle / 2.0f; - const float overlayAspectRatio = glWidget->width() / (float)glWidget->height(); - const float halfOverlayHeight = _distance * tan(halfVerticalAngle); - const float overlayHeight = halfOverlayHeight * 2.0f; - - // The more vertices, the better the curve - const int numHorizontalVertices = 20; - const int numVerticalVertices = 20; - // U texture coordinate width at each quad - const float quadTexWidth = 1.0f / (numHorizontalVertices - 1); - const float quadTexHeight = 1.0f / (numVerticalVertices - 1); - - // Get horizontal angle and angle increment from vertical angle and aspect ratio - const float horizontalAngle = halfVerticalAngle * 2.0f * overlayAspectRatio; - const float angleIncrement = horizontalAngle / (numHorizontalVertices - 1); - const float halfHorizontalAngle = horizontalAngle / 2; - - const float verticalAngleIncrement = _oculusAngle / (numVerticalVertices - 1); glActiveTexture(GL_TEXTURE0); @@ -211,8 +191,6 @@ void ApplicationOverlay::displayOverlayTextureOculus(Camera& whichCamera) { glEnable(GL_ALPHA_TEST); glAlphaFunc(GL_GREATER, 0.01f); - float leftX, rightX, leftZ, rightZ, topZ, bottomZ; - //Draw the magnifiers for (int i = 0; i < _numMagnifiers; i++) { renderMagnifier(_mouseX[i], _mouseY[i]); @@ -221,42 +199,8 @@ void ApplicationOverlay::displayOverlayTextureOculus(Camera& whichCamera) { glDepthMask(GL_FALSE); glDisable(GL_ALPHA_TEST); - //TODO: Remove immediate mode in favor of VBO - if (_uiType == HEMISPHERE) { - renderTexturedHemisphere(); - } else{ - glBegin(GL_QUADS); - // Place the vertices in a semicircle curve around the camera - for (int i = 0; i < numHorizontalVertices - 1; i++) { - for (int j = 0; j < numVerticalVertices - 1; j++) { - - // Calculate the X and Z coordinates from the angles and radius from camera - leftX = sin(angleIncrement * i - halfHorizontalAngle) * _distance; - rightX = sin(angleIncrement * (i + 1) - halfHorizontalAngle) * _distance; - leftZ = -cos(angleIncrement * i - halfHorizontalAngle) * _distance; - rightZ = -cos(angleIncrement * (i + 1) - halfHorizontalAngle) * _distance; - if (_uiType == 2) { - topZ = -cos((verticalAngleIncrement * (j + 1) - halfVerticalAngle) * overlayAspectRatio) * _distance; - bottomZ = -cos((verticalAngleIncrement * j - halfVerticalAngle) * overlayAspectRatio) * _distance; - } else { - topZ = -99999; - bottomZ = -99999; - } - - glTexCoord2f(quadTexWidth * i, (j + 1) * quadTexHeight); - glVertex3f(leftX, (j + 1) * quadTexHeight * overlayHeight - halfOverlayHeight, max(topZ, leftZ)); - glTexCoord2f(quadTexWidth * (i + 1), (j + 1) * quadTexHeight); - glVertex3f(rightX, (j + 1) * quadTexHeight * overlayHeight - halfOverlayHeight, max(topZ, rightZ)); - glTexCoord2f(quadTexWidth * (i + 1), j * quadTexHeight); - glVertex3f(rightX, j * quadTexHeight * overlayHeight - halfOverlayHeight, max(bottomZ, rightZ)); - glTexCoord2f(quadTexWidth * i, j * quadTexHeight); - glVertex3f(leftX, j * quadTexHeight * overlayHeight - halfOverlayHeight, max(bottomZ, leftZ)); - } - } - - glEnd(); - } - + renderTexturedHemisphere(); + renderControllerPointersOculus(); glPopMatrix(); @@ -411,106 +355,12 @@ void ApplicationOverlay::renderControllerPointersOculus() { float mouseY = (float)_mouseY[i]; mouseX -= reticleSize / 2; mouseY += reticleSize / 2; - - // Get position on hemisphere using angle - if (_uiType == HEMISPHERE) { - - //Get new UV coordinates from our magnification window - float newULeft = mouseX / widgetWidth; - float newURight = (mouseX + reticleSize) / widgetWidth; - float newVBottom = 1.0 - mouseY / widgetHeight; - float newVTop = 1.0 - (mouseY - reticleSize) / widgetHeight; - - // Project our position onto the hemisphere using the UV coordinates - float lX = sin((newULeft - 0.5f) * _textureFov); - float rX = sin((newURight - 0.5f) * _textureFov); - float bY = sin((newVBottom - 0.5f) * _textureFov); - float tY = sin((newVTop - 0.5f) * _textureFov); - - float dist; - //Bottom Left - dist = sqrt(lX * lX + bY * bY); - float blZ = sqrt(1.0f - dist * dist); - //Top Left - dist = sqrt(lX * lX + tY * tY); - float tlZ = sqrt(1.0f - dist * dist); - //Bottom Right - dist = sqrt(rX * rX + bY * bY); - float brZ = sqrt(1.0f - dist * dist); - //Top Right - dist = sqrt(rX * rX + tY * tY); - float trZ = sqrt(1.0f - dist * dist); - - glBegin(GL_QUADS); - - glColor3f(0.0f, 198.0f / 255.0f, 244.0f / 255.0f); - - glTexCoord2f(0.0f, 0.0f); glVertex3f(lX, tY, -tlZ); - glTexCoord2f(1.0f, 0.0f); glVertex3f(rX, tY, -trZ); - glTexCoord2f(1.0f, 1.0f); glVertex3f(rX, bY, -brZ); - glTexCoord2f(0.0f, 1.0f); glVertex3f(lX, bY, -blZ); - - glEnd(); - } - } - glEnable(GL_DEPTH_TEST); -} - -//Renders a small magnification of the currently bound texture at the coordinates -void ApplicationOverlay::renderMagnifier(int mouseX, int mouseY) -{ - Application* application = Application::getInstance(); - QGLWidget* glWidget = application->getGLWidget(); - - float leftX, rightX, leftZ, rightZ, topZ, bottomZ; - - const int widgetWidth = glWidget->width(); - const int widgetHeight = glWidget->height(); - const float magnification = 4.0f; - - // Get vertical FoV of the displayed overlay texture - const float halfVerticalAngle = _oculusAngle / 2.0f; - const float overlayAspectRatio = glWidget->width() / (float)glWidget->height(); - const float halfOverlayHeight = _distance * tan(halfVerticalAngle); - - // Get horizontal angle and angle increment from vertical angle and aspect ratio - const float horizontalAngle = halfVerticalAngle * 2.0f * overlayAspectRatio; - const float halfHorizontalAngle = horizontalAngle / 2; - - float magnifyWidth = 80.0f; - float magnifyHeight = 60.0f; - - mouseX -= magnifyWidth / 2; - mouseY -= magnifyHeight / 2; - - float newWidth = magnifyWidth * magnification; - float newHeight = magnifyHeight * magnification; - - // Magnification Texture Coordinates - float magnifyULeft = mouseX / (float)widgetWidth; - float magnifyURight = (mouseX + magnifyWidth) / (float)widgetWidth; - float magnifyVBottom = 1.0f - mouseY / (float)widgetHeight; - float magnifyVTop = 1.0f - (mouseY + magnifyHeight) / (float)widgetHeight; - - // Coordinates of magnification overlay - float newMouseX = (mouseX + magnifyWidth / 2) - newWidth / 2.0f; - float newMouseY = (mouseY + magnifyHeight / 2) + newHeight / 2.0f; - - // Get angle on the UI - float leftAngle = (newMouseX / (float)widgetWidth) * horizontalAngle - halfHorizontalAngle; - float rightAngle = ((newMouseX + newWidth) / (float)widgetWidth) * horizontalAngle - halfHorizontalAngle; - - float bottomAngle = (newMouseY / (float)widgetHeight) * _oculusAngle - halfVerticalAngle; - float topAngle = ((newMouseY - newHeight) / (float)widgetHeight) * _oculusAngle - halfVerticalAngle; - - // Get position on hemisphere using angle - if (_uiType == HEMISPHERE) { - + //Get new UV coordinates from our magnification window - float newULeft = newMouseX / widgetWidth; - float newURight = (newMouseX + newWidth) / widgetWidth; - float newVBottom = 1.0 - newMouseY / widgetHeight; - float newVTop = 1.0 - (newMouseY - newHeight) / widgetHeight; + float newULeft = mouseX / widgetWidth; + float newURight = (mouseX + reticleSize) / widgetWidth; + float newVBottom = 1.0 - mouseY / widgetHeight; + float newVTop = 1.0 - (mouseY - reticleSize) / widgetHeight; // Project our position onto the hemisphere using the UV coordinates float lX = sin((newULeft - 0.5f) * _textureFov); @@ -534,40 +384,86 @@ void ApplicationOverlay::renderMagnifier(int mouseX, int mouseY) glBegin(GL_QUADS); - glTexCoord2f(magnifyULeft, magnifyVBottom); glVertex3f(lX, tY, -tlZ); - glTexCoord2f(magnifyURight, magnifyVBottom); glVertex3f(rX, tY, -trZ); - glTexCoord2f(magnifyURight, magnifyVTop); glVertex3f(rX, bY, -brZ); - glTexCoord2f(magnifyULeft, magnifyVTop); glVertex3f(lX, bY, -blZ); - - glEnd(); - - } else { - leftX = sin(leftAngle) * _distance; - rightX = sin(rightAngle) * _distance; - leftZ = -cos(leftAngle) * _distance; - rightZ = -cos(rightAngle) * _distance; - if (_uiType == CURVED_SEMICIRCLE) { - topZ = -cos(topAngle * overlayAspectRatio) * _distance; - bottomZ = -cos(bottomAngle * overlayAspectRatio) * _distance; - } else { - // Dont want to use topZ or bottomZ for SEMICIRCLE - topZ = -99999; - bottomZ = -99999; - } - - float bottomY = (1.0 - newMouseY / (float)widgetHeight) * halfOverlayHeight * 2.0f - halfOverlayHeight; - float topY = bottomY + (newHeight / widgetHeight) * halfOverlayHeight * 2; - - //TODO: Remove immediate mode in favor of VBO - glBegin(GL_QUADS); - - glTexCoord2f(magnifyULeft, magnifyVBottom); glVertex3f(leftX, topY, max(topZ, leftZ)); - glTexCoord2f(magnifyURight, magnifyVBottom); glVertex3f(rightX, topY, max(topZ, rightZ)); - glTexCoord2f(magnifyURight, magnifyVTop); glVertex3f(rightX, bottomY, max(bottomZ, rightZ)); - glTexCoord2f(magnifyULeft, magnifyVTop); glVertex3f(leftX, bottomY, max(bottomZ, leftZ)); + glColor3f(0.0f, 198.0f / 255.0f, 244.0f / 255.0f); + + glTexCoord2f(0.0f, 0.0f); glVertex3f(lX, tY, -tlZ); + glTexCoord2f(1.0f, 0.0f); glVertex3f(rX, tY, -trZ); + glTexCoord2f(1.0f, 1.0f); glVertex3f(rX, bY, -brZ); + glTexCoord2f(0.0f, 1.0f); glVertex3f(lX, bY, -blZ); glEnd(); + } + glEnable(GL_DEPTH_TEST); +} + +//Renders a small magnification of the currently bound texture at the coordinates +void ApplicationOverlay::renderMagnifier(int mouseX, int mouseY) +{ + Application* application = Application::getInstance(); + QGLWidget* glWidget = application->getGLWidget(); + + + const int widgetWidth = glWidget->width(); + const int widgetHeight = glWidget->height(); + const float magnification = 4.0f; + + float magnifyWidth = 80.0f; + float magnifyHeight = 60.0f; + + mouseX -= magnifyWidth / 2; + mouseY -= magnifyHeight / 2; + + float newWidth = magnifyWidth * magnification; + float newHeight = magnifyHeight * magnification; + + // Magnification Texture Coordinates + float magnifyULeft = mouseX / (float)widgetWidth; + float magnifyURight = (mouseX + magnifyWidth) / (float)widgetWidth; + float magnifyVBottom = 1.0f - mouseY / (float)widgetHeight; + float magnifyVTop = 1.0f - (mouseY + magnifyHeight) / (float)widgetHeight; + + // Coordinates of magnification overlay + float newMouseX = (mouseX + magnifyWidth / 2) - newWidth / 2.0f; + float newMouseY = (mouseY + magnifyHeight / 2) + newHeight / 2.0f; + + // Get position on hemisphere using angle + + //Get new UV coordinates from our magnification window + float newULeft = newMouseX / widgetWidth; + float newURight = (newMouseX + newWidth) / widgetWidth; + float newVBottom = 1.0 - newMouseY / widgetHeight; + float newVTop = 1.0 - (newMouseY - newHeight) / widgetHeight; + + // Project our position onto the hemisphere using the UV coordinates + float lX = sin((newULeft - 0.5f) * _textureFov); + float rX = sin((newURight - 0.5f) * _textureFov); + float bY = sin((newVBottom - 0.5f) * _textureFov); + float tY = sin((newVTop - 0.5f) * _textureFov); + + float dist; + //Bottom Left + dist = sqrt(lX * lX + bY * bY); + float blZ = sqrt(1.0f - dist * dist); + //Top Left + dist = sqrt(lX * lX + tY * tY); + float tlZ = sqrt(1.0f - dist * dist); + //Bottom Right + dist = sqrt(rX * rX + bY * bY); + float brZ = sqrt(1.0f - dist * dist); + //Top Right + dist = sqrt(rX * rX + tY * tY); + float trZ = sqrt(1.0f - dist * dist); + + glBegin(GL_QUADS); + + glTexCoord2f(magnifyULeft, magnifyVBottom); glVertex3f(lX, tY, -tlZ); + glTexCoord2f(magnifyURight, magnifyVBottom); glVertex3f(rX, tY, -trZ); + glTexCoord2f(magnifyURight, magnifyVTop); glVertex3f(rX, bY, -brZ); + glTexCoord2f(magnifyULeft, magnifyVTop); glVertex3f(lX, bY, -blZ); + + glEnd(); + } void ApplicationOverlay::renderAudioMeter() { diff --git a/interface/src/ui/ApplicationOverlay.h b/interface/src/ui/ApplicationOverlay.h index 71f78d606d..53a0125dae 100644 --- a/interface/src/ui/ApplicationOverlay.h +++ b/interface/src/ui/ApplicationOverlay.h @@ -19,8 +19,6 @@ class QOpenGLFramebufferObject; class ApplicationOverlay { public: - enum UIType { HEMISPHERE, SEMICIRCLE, CURVED_SEMICIRCLE }; - ApplicationOverlay(); ~ApplicationOverlay(); @@ -31,9 +29,7 @@ public: // Getters QOpenGLFramebufferObject* getFramebufferObject(); - - void setUIType(UIType uiType) { _uiType = uiType; } - + private: // Interleaved vertex data struct TextureVertex { @@ -56,7 +52,6 @@ private: float _oculusAngle; float _distance; float _textureFov; - UIType _uiType; int _mouseX[2]; int _mouseY[2]; int _numMagnifiers; From bef625d237a14dd12bf36c79d61518ab919bc515 Mon Sep 17 00:00:00 2001 From: barnold1953 Date: Fri, 13 Jun 2014 16:13:43 -0700 Subject: [PATCH 13/37] Removed pointless Code --- interface/src/ui/ApplicationOverlay.cpp | 25 +------------------------ 1 file changed, 1 insertion(+), 24 deletions(-) diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index 2ef5665bee..13630290fd 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -234,33 +234,10 @@ void ApplicationOverlay::renderPointers() { if (OculusManager::isConnected() && application->getLastMouseMoveType() == QEvent::MouseMove) { - const float pointerWidth = 20; - const float pointerHeight = 20; - + //If we are in oculus, render reticle later _numMagnifiers = 1; _mouseX[0] = application->getMouseX(); _mouseY[0] = application->getMouseY(); - - //If we are in oculus, render reticle later - - - if (!OculusManager::isConnected()) { - - mouseX -= pointerWidth / 2.0f; - mouseY += pointerHeight / 2.0f; - - glBegin(GL_QUADS); - - glColor3f(0.0f, 198.0f / 255.0f, 244.0f / 255.0f); - - //Horizontal crosshair - glTexCoord2d(0.0f, 0.0f); glVertex2i(mouseX, mouseY); - glTexCoord2d(1.0f, 0.0f); glVertex2i(mouseX + pointerWidth, mouseY); - glTexCoord2d(1.0f, 1.0f); glVertex2i(mouseX + pointerWidth, mouseY - pointerHeight); - glTexCoord2d(0.0f, 1.0f); glVertex2i(mouseX, mouseY - pointerHeight); - - glEnd(); - } } else if (application->getLastMouseMoveType() == CONTROLLER_MOVE_EVENT && Menu::getInstance()->isOptionChecked(MenuOption::SixenseMouseInput)) { //only render controller pointer if we aren't already rendering a mouse pointer renderControllerPointers(); From 3fbde70f96694979d62a5ae22ae0f8bf17f147f1 Mon Sep 17 00:00:00 2001 From: Kai Ludwig Date: Sun, 15 Jun 2014 21:05:23 +0200 Subject: [PATCH 14/37] Added functions to test usleep, msleep and sleep for accuracy. --- libraries/shared/src/SharedUtil.cpp | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/libraries/shared/src/SharedUtil.cpp b/libraries/shared/src/SharedUtil.cpp index f29e8e3345..06a5ab8137 100644 --- a/libraries/shared/src/SharedUtil.cpp +++ b/libraries/shared/src/SharedUtil.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include "OctalCode.h" #include "SharedUtil.h" @@ -415,13 +416,16 @@ void printVoxelCode(unsigned char* voxelCode) { #ifdef _WIN32 void usleep(int waitTime) { - __int64 time1 = 0, time2 = 0, sysFreq = 0; - - QueryPerformanceCounter((LARGE_INTEGER *)&time1); - QueryPerformanceFrequency((LARGE_INTEGER *)&sysFreq); - do { - QueryPerformanceCounter((LARGE_INTEGER *)&time2); - } while( (time2 - time1) < waitTime); + quint64 compTime = waitTime + usecTimestampNow(); + quint64 compTimeSleep = compTime - 2000; + while (true) { + if (usecTimestampNow() < compTimeSleep) { + QThread::msleep(1); + } + if (usecTimestampNow() >= compTime) { + break; + } + } } #endif From 05900420a56ee37087f88a72fa84ee955850a5ae Mon Sep 17 00:00:00 2001 From: Kai Ludwig Date: Sun, 15 Jun 2014 21:07:03 +0200 Subject: [PATCH 15/37] Replaced windows usleep version with a non busy waiting high accuracy version. This fixes the high CPU load for the windows servers too! --- interface/src/Util.cpp | 40 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/interface/src/Util.cpp b/interface/src/Util.cpp index 07ca65b286..5ce1435bd6 100644 --- a/interface/src/Util.cpp +++ b/interface/src/Util.cpp @@ -21,6 +21,8 @@ #include +#include + #include "InterfaceConfig.h" #include "ui/TextRenderer.h" #include "VoxelConstants.h" @@ -409,8 +411,44 @@ void runTimingTests() { float NSEC_TO_USEC = 1.0f / 1000.0f; elapsedUsecs = (float)startTime.nsecsElapsed() * NSEC_TO_USEC; - qDebug("QElapsedTimer::nsecElapsed() usecs: %f", elapsedUsecs / (float) numTests); + qDebug("QElapsedTimer::nsecElapsed() usecs: %f", elapsedUsecs); + // Test sleep functions for accuracy + startTime.start(); + QThread::msleep(1); + elapsedUsecs = (float)startTime.nsecsElapsed() * NSEC_TO_USEC; + qDebug("QThread::msleep(1) ms: %f", elapsedUsecs / 1000.0f); + + startTime.start(); + QThread::sleep(1); + elapsedUsecs = (float)startTime.nsecsElapsed() * NSEC_TO_USEC; + qDebug("QThread::sleep(1) ms: %f", elapsedUsecs / 1000.0f); + + startTime.start(); + usleep(1); + elapsedUsecs = (float)startTime.nsecsElapsed() * NSEC_TO_USEC; + qDebug("usleep(1) ms: %f", elapsedUsecs / 1000.0f); + + startTime.start(); + usleep(10); + elapsedUsecs = (float)startTime.nsecsElapsed() * NSEC_TO_USEC; + qDebug("usleep(10) ms: %f", elapsedUsecs / 1000.0f); + + startTime.start(); + usleep(100); + elapsedUsecs = (float)startTime.nsecsElapsed() * NSEC_TO_USEC; + qDebug("usleep(100) ms: %f", elapsedUsecs / 1000.0f); + + startTime.start(); + usleep(1000); + elapsedUsecs = (float)startTime.nsecsElapsed() * NSEC_TO_USEC; + qDebug("usleep(1000) ms: %f", elapsedUsecs / 1000.0f); + + startTime.start(); + usleep(15000); + elapsedUsecs = (float)startTime.nsecsElapsed() * NSEC_TO_USEC; + qDebug("usleep(15000) ms: %f", elapsedUsecs / 1000.0f); + // Random number generation startTime.start(); for (int i = 0; i < numTests; i++) { From 8c700d43f34ef26bc389ad2f2374b9e57b2c1289 Mon Sep 17 00:00:00 2001 From: Kai Ludwig Date: Sun, 15 Jun 2014 21:08:14 +0200 Subject: [PATCH 16/37] Modified CALLBACK_ACCELERATOR_RATIO for windows because some systems need larger input buffer to work fine. --- interface/src/Audio.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/Audio.cpp b/interface/src/Audio.cpp index 2cbe3a062f..271bcd5279 100644 --- a/interface/src/Audio.cpp +++ b/interface/src/Audio.cpp @@ -1419,7 +1419,7 @@ bool Audio::switchOutputToAudioDevice(const QAudioDeviceInfo& outputDeviceInfo) // proportional to the accelerator ratio. #ifdef Q_OS_WIN -const float Audio::CALLBACK_ACCELERATOR_RATIO = 0.4f; +const float Audio::CALLBACK_ACCELERATOR_RATIO = 0.1f; #endif #ifdef Q_OS_MAC From 94e58e9559bff84fd60233acdf3aaa3f9b4d314f Mon Sep 17 00:00:00 2001 From: barnold1953 Date: Mon, 16 Jun 2014 10:04:56 -0700 Subject: [PATCH 17/37] Fixed coding standard and made getCursorPixelRangeMultiplier more clear --- interface/src/devices/SixenseManager.cpp | 10 ++++++++-- interface/src/ui/ApplicationOverlay.cpp | 8 +++++--- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/interface/src/devices/SixenseManager.cpp b/interface/src/devices/SixenseManager.cpp index ed55dc4c66..43dadaef1c 100644 --- a/interface/src/devices/SixenseManager.cpp +++ b/interface/src/devices/SixenseManager.cpp @@ -185,9 +185,14 @@ void SixenseManager::update(float deltaTime) { #endif // HAVE_SIXENSE } +const float MAXIMUM_PIXEL_RANGE_MULT = 2.0f; +const float MINIMUM_PIXEL_RANGE_MULT = 0.4f; +const float RANGE_MULT = (MAXIMUM_PIXEL_RANGE_MULT - MINIMUM_PIXEL_RANGE_MULT) * 0.01; + +//Returns a multiplier to be applied to the cursor range for the controllers float SixenseManager::getCursorPixelRangeMultiplier() const { - //scales (0,100) to (0.4,2.0) - return ((Menu::getInstance()->getSixenseReticleMoveSpeed()) * 0.008f + 0.2f) * 2.0f; + //scales (0,100) to (MINIMUM_PIXEL_RANGE_MULT, MAXIMUM_PIXEL_RANGE_MULT) + return Menu::getInstance()->getSixenseReticleMoveSpeed() * RANGE_MULT + MINIMUM_PIXEL_RANGE_MULT; } #ifdef HAVE_SIXENSE @@ -356,6 +361,7 @@ void SixenseManager::emulateMouse(PalmData* palm, int index) { float xAngle = (atan2(direction.z, direction.x) + M_PI_2); float yAngle = 0.5f - ((atan2(direction.z, direction.y) + M_PI_2)); + // Get the pixel range over which the xAngle and yAngle are scaled float cursorRange = widget->width() * getCursorPixelRangeMultiplier(); pos.setX(widget->width() / 2.0f + cursorRange * xAngle); diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index 13630290fd..aedc81facc 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -46,6 +46,8 @@ ApplicationOverlay::~ApplicationOverlay() { const float WHITE_TEXT[] = { 0.93f, 0.93f, 0.93f }; +const float RETICLE_COLOR[] = { 0.0f, 198.0f / 255.0f, 244.0f / 255.0f }; + // Renders the overlays either to a texture or to the screen void ApplicationOverlay::renderOverlay(bool renderToTexture) { PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "ApplicationOverlay::displayOverlay()"); @@ -222,7 +224,6 @@ void ApplicationOverlay::renderPointers() { int mouseX = application->getMouseX(); int mouseY = application->getMouseY(); - //lazily load crosshair texture if (_crosshairTexture == 0) { _crosshairTexture = Application::getInstance()->getGLWidget()->bindTexture(QImage(Application::resourcesPath() + "images/sixense-reticle.png")); @@ -274,6 +275,7 @@ void ApplicationOverlay::renderControllerPointers() { float xAngle = (atan2(direction.z, direction.x) + M_PI_2) ; float yAngle = 0.5f - ((atan2(direction.z, direction.y) + M_PI_2)); + // Get the pixel range over which the xAngle and yAngle are scaled float cursorRange = glWidget->width() * application->getSixenseManager()->getCursorPixelRangeMultiplier(); int mouseX = glWidget->width() / 2.0f + cursorRange * xAngle; @@ -303,7 +305,7 @@ void ApplicationOverlay::renderControllerPointers() { glBegin(GL_QUADS); - glColor3f(0.0f, 198.0f/255.0f, 244.0f/255.0f); + glColor3f(RETICLE_COLOR[0], RETICLE_COLOR[1], RETICLE_COLOR[2]); glTexCoord2d(0.0f, 0.0f); glVertex2i(mouseX, mouseY); glTexCoord2d(1.0f, 0.0f); glVertex2i(mouseX + pointerWidth, mouseY); @@ -361,7 +363,7 @@ void ApplicationOverlay::renderControllerPointersOculus() { glBegin(GL_QUADS); - glColor3f(0.0f, 198.0f / 255.0f, 244.0f / 255.0f); + glColor3f(RETICLE_COLOR[0], RETICLE_COLOR[1], RETICLE_COLOR[2]); glTexCoord2f(0.0f, 0.0f); glVertex3f(lX, tY, -tlZ); glTexCoord2f(1.0f, 0.0f); glVertex3f(rX, tY, -trZ); From 031fe323ea5e98b5d37167da3c983ce7960d45b8 Mon Sep 17 00:00:00 2001 From: barnold1953 Date: Mon, 16 Jun 2014 10:12:09 -0700 Subject: [PATCH 18/37] Got rid of some verbosity --- interface/src/devices/SixenseManager.cpp | 13 +++++++------ interface/src/devices/SixenseManager.h | 2 +- interface/src/ui/ApplicationOverlay.cpp | 2 +- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/interface/src/devices/SixenseManager.cpp b/interface/src/devices/SixenseManager.cpp index 43dadaef1c..2c62a58800 100644 --- a/interface/src/devices/SixenseManager.cpp +++ b/interface/src/devices/SixenseManager.cpp @@ -185,14 +185,15 @@ void SixenseManager::update(float deltaTime) { #endif // HAVE_SIXENSE } -const float MAXIMUM_PIXEL_RANGE_MULT = 2.0f; -const float MINIMUM_PIXEL_RANGE_MULT = 0.4f; -const float RANGE_MULT = (MAXIMUM_PIXEL_RANGE_MULT - MINIMUM_PIXEL_RANGE_MULT) * 0.01; +//Constants for getCursorPixelRangeMultiplier() +const float MIN_PIXEL_RANGE_MULT = 0.4f; +const float MAX_PIXEL_RANGE_MULT = 2.0f; +const float RANGE_MULT = (MAX_PIXEL_RANGE_MULT - MIN_PIXEL_RANGE_MULT) * 0.01; //Returns a multiplier to be applied to the cursor range for the controllers -float SixenseManager::getCursorPixelRangeMultiplier() const { +float SixenseManager::getCursorPixelRangeMult() const { //scales (0,100) to (MINIMUM_PIXEL_RANGE_MULT, MAXIMUM_PIXEL_RANGE_MULT) - return Menu::getInstance()->getSixenseReticleMoveSpeed() * RANGE_MULT + MINIMUM_PIXEL_RANGE_MULT; + return Menu::getInstance()->getSixenseReticleMoveSpeed() * RANGE_MULT + MIN_PIXEL_RANGE_MULT; } #ifdef HAVE_SIXENSE @@ -362,7 +363,7 @@ void SixenseManager::emulateMouse(PalmData* palm, int index) { float yAngle = 0.5f - ((atan2(direction.z, direction.y) + M_PI_2)); // Get the pixel range over which the xAngle and yAngle are scaled - float cursorRange = widget->width() * getCursorPixelRangeMultiplier(); + float cursorRange = widget->width() * getCursorPixelRangeMult(); pos.setX(widget->width() / 2.0f + cursorRange * xAngle); pos.setY(widget->height() / 2.0f + cursorRange * yAngle); diff --git a/interface/src/devices/SixenseManager.h b/interface/src/devices/SixenseManager.h index 41e061a25b..8803c2c006 100644 --- a/interface/src/devices/SixenseManager.h +++ b/interface/src/devices/SixenseManager.h @@ -42,7 +42,7 @@ public: ~SixenseManager(); void update(float deltaTime); - float getCursorPixelRangeMultiplier() const; + float getCursorPixelRangeMult() const; public slots: diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index aedc81facc..471ff20d66 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -276,7 +276,7 @@ void ApplicationOverlay::renderControllerPointers() { float yAngle = 0.5f - ((atan2(direction.z, direction.y) + M_PI_2)); // Get the pixel range over which the xAngle and yAngle are scaled - float cursorRange = glWidget->width() * application->getSixenseManager()->getCursorPixelRangeMultiplier(); + float cursorRange = glWidget->width() * application->getSixenseManager()->getCursorPixelRangeMult(); int mouseX = glWidget->width() / 2.0f + cursorRange * xAngle; int mouseY = glWidget->height() / 2.0f + cursorRange * yAngle; From af3b2a9d0f6c4f8e332e5a766915d53d309afad0 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Mon, 16 Jun 2014 10:43:13 -0700 Subject: [PATCH 19/37] Update LocationManager::goTo to use new addresses API The new API address is now used for goTo when # or @ isn't present Fixed the multiple locations popup to show when multiple locations are found --- interface/src/Menu.cpp | 8 ++- interface/src/avatar/MyAvatar.cpp | 64 +++++++++++----------- interface/src/avatar/MyAvatar.h | 1 + interface/src/location/LocationManager.cpp | 57 ++++++------------- interface/src/location/LocationManager.h | 7 +-- 5 files changed, 58 insertions(+), 79 deletions(-) diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 0c6a3efa24..46b2bfc806 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -1002,7 +1002,9 @@ bool Menu::goToDestination(QString destination) { } void Menu::goTo(QString destination) { - LocationManager::getInstance().goTo(destination); + LocationManager* manager = &LocationManager::getInstance(); + manager->goTo(destination); + connect(manager, &LocationManager::multipleDestinationsFound, getInstance(), &Menu::multipleDestinationsDecision); } void Menu::goTo() { @@ -1091,9 +1093,9 @@ void Menu::multipleDestinationsDecision(const QJsonObject& userData, const QJson int userResponse = msgBox.exec(); if (userResponse == QMessageBox::Ok) { - Application::getInstance()->getAvatar()->goToLocationFromResponse(userData); + Application::getInstance()->getAvatar()->goToLocationFromAddress(userData["address"].toObject()); } else if (userResponse == QMessageBox::Open) { - Application::getInstance()->getAvatar()->goToLocationFromResponse(userData); + Application::getInstance()->getAvatar()->goToLocationFromAddress(placeData["address"].toObject()); } LocationManager* manager = reinterpret_cast(sender()); diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index a14152aa04..54ed641d72 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1626,44 +1626,46 @@ void MyAvatar::resetSize() { } void MyAvatar::goToLocationFromResponse(const QJsonObject& jsonObject) { - if (jsonObject["status"].toString() == "success") { - - // send a node kill request, indicating to other clients that they should play the "disappeared" effect - sendKillAvatar(); - QJsonObject locationObject = jsonObject["data"].toObject()["address"].toObject(); - 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; - setPosition(newPosition); - emit transformChanged(); + goToLocationFromAddress(locationObject); } else { QMessageBox::warning(Application::getInstance()->getWindow(), "", "That user or location could not be found."); } } +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; + setPosition(newPosition); + emit transformChanged(); +} + void MyAvatar::updateMotionBehaviorsFromMenu() { Menu* menu = Menu::getInstance(); if (menu->isOptionChecked(MenuOption::ObeyEnvironmentalGravity)) { diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index d99102c356..2fbc488feb 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -129,6 +129,7 @@ public slots: void resetSize(); void goToLocationFromResponse(const QJsonObject& jsonObject); + void goToLocationFromAddress(const QJsonObject& jsonObject); // Set/Get update the thrust that will move the avatar around void addThrust(glm::vec3 newThrust) { _thrust += newThrust; }; diff --git a/interface/src/location/LocationManager.cpp b/interface/src/location/LocationManager.cpp index f80c331df4..f1fd5e71f1 100644 --- a/interface/src/location/LocationManager.cpp +++ b/interface/src/location/LocationManager.cpp @@ -16,10 +16,11 @@ const QString GET_USER_ADDRESS = "/api/v1/users/%1/address"; const QString GET_PLACE_ADDRESS = "/api/v1/places/%1/address"; +const QString GET_ADDRESSES = "/api/v1/addresses/%1"; const QString POST_PLACE_CREATE = "/api/v1/places/"; -LocationManager::LocationManager() : _userData(), _placeData() { +LocationManager::LocationManager() { }; @@ -74,59 +75,37 @@ void LocationManager::goTo(QString destination) { // go to coordinate destination or to Username if (!goToDestination(destination)) { - // reset data on local variables - _userData = QJsonObject(); - _placeData = QJsonObject(); - destination = QString(QUrl::toPercentEncoding(destination)); JSONCallbackParameters callbackParams; callbackParams.jsonCallbackReceiver = this; - callbackParams.jsonCallbackMethod = "goToUserFromResponse"; - AccountManager::getInstance().authenticatedRequest(GET_USER_ADDRESS.arg(destination), - QNetworkAccessManager::GetOperation, - callbackParams); - - callbackParams.jsonCallbackMethod = "goToLocationFromResponse"; - AccountManager::getInstance().authenticatedRequest(GET_PLACE_ADDRESS.arg(destination), + callbackParams.jsonCallbackMethod = "goToAddressFromResponse"; + AccountManager::getInstance().authenticatedRequest(GET_ADDRESSES.arg(destination), QNetworkAccessManager::GetOperation, callbackParams); } } -void LocationManager::goToUserFromResponse(const QJsonObject& jsonObject) { - _userData = jsonObject; - checkForMultipleDestinations(); -} +void LocationManager::goToAddressFromResponse(const QJsonObject& responseData) { + QJsonValue status = responseData["status"]; + qDebug() << responseData; + if (!status.isUndefined() && status.toString() == "success") { + const QJsonObject& data = responseData["data"].toObject(); + const QJsonValue& userObject = data["user"]; + const QJsonValue& placeObject = data["place"]; -void LocationManager::goToLocationFromResponse(const QJsonObject& jsonObject) { - _placeData = jsonObject; - checkForMultipleDestinations(); -} - -void LocationManager::checkForMultipleDestinations() { - if (!_userData.isEmpty() && !_placeData.isEmpty()) { - if (_userData.contains("status") && _userData["status"].toString() == "success" && - _placeData.contains("status") && _placeData["status"].toString() == "success") { - emit multipleDestinationsFound(_userData, _placeData); - return; + 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()); } - - if (_userData.contains("status") && _userData["status"].toString() == "success") { - Application::getInstance()->getAvatar()->goToLocationFromResponse(_userData); - return; - } - - if (_placeData.contains("status") && _placeData["status"].toString() == "success") { - Application::getInstance()->getAvatar()->goToLocationFromResponse(_placeData); - return; - } - + } else { QMessageBox::warning(Application::getInstance()->getWindow(), "", "That user or location could not be found."); } } void LocationManager::goToUser(QString userName) { - JSONCallbackParameters callbackParams; callbackParams.jsonCallbackReceiver = Application::getInstance()->getAvatar(); callbackParams.jsonCallbackMethod = "goToLocationFromResponse"; diff --git a/interface/src/location/LocationManager.h b/interface/src/location/LocationManager.h index ac66b3d08b..b781f3f54e 100644 --- a/interface/src/location/LocationManager.h +++ b/interface/src/location/LocationManager.h @@ -39,11 +39,7 @@ public: bool goToDestination(QString destination); private: - QJsonObject _userData; - QJsonObject _placeData; - void replaceLastOccurrence(const QChar search, const QChar replace, QString& string); - void checkForMultipleDestinations(); signals: void creationCompleted(LocationManager::NamedLocationCreateResponse response); @@ -52,8 +48,7 @@ signals: private slots: void namedLocationDataReceived(const QJsonObject& data); void errorDataReceived(QNetworkReply::NetworkError error, const QString& message); - void goToLocationFromResponse(const QJsonObject& jsonObject); - void goToUserFromResponse(const QJsonObject& jsonObject); + void goToAddressFromResponse(const QJsonObject& jsonObject); }; From d5aa12db7d9bb99d634f9e08796a2141baca57b2 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Mon, 16 Jun 2014 10:55:05 -0700 Subject: [PATCH 20/37] Added models support to inspect.js --- examples/inspect.js | 49 ++++++++++++++++++++++++++++++--------------- 1 file changed, 33 insertions(+), 16 deletions(-) diff --git a/examples/inspect.js b/examples/inspect.js index 28db1e7735..af63957ec3 100644 --- a/examples/inspect.js +++ b/examples/inspect.js @@ -177,30 +177,45 @@ function keyReleaseEvent(event) { function mousePressEvent(event) { if (alt && !isActive) { - isActive = true; mouseLastX = event.x; mouseLastY = event.y; // Compute trajectories related values var pickRay = Camera.computePickRay(mouseLastX, mouseLastY); - var intersection = Voxels.findRayIntersection(pickRay); + var voxelIntersection = Voxels.findRayIntersection(pickRay); + var modelIntersection = Models.findRayIntersection(pickRay); position = Camera.getPosition(); - avatarTarget = MyAvatar.getTargetAvatarPosition(); - voxelTarget = intersection.intersection; - if (Vec3.length(Vec3.subtract(avatarTarget, position)) < Vec3.length(Vec3.subtract(voxelTarget, position))) { - if (avatarTarget.x != 0 || avatarTarget.y != 0 || avatarTarget.z != 0) { - center = avatarTarget; - } else { - center = voxelTarget; - } - } else { - if (voxelTarget.x != 0 || voxelTarget.y != 0 || voxelTarget.z != 0) { - center = voxelTarget; - } else { - center = avatarTarget; - } + var avatarTarget = MyAvatar.getTargetAvatarPosition(); + var voxelTarget = voxelIntersection.intersection; + + + var distance = -1; + var string; + + if (modelIntersection.intersects && modelIntersection.accurate) { + distance = modelIntersection.distance; + center = modelIntersection.modelProperties.position; + string = "Inspecting model"; + } + + if ((distance == -1 || Vec3.length(Vec3.subtract(avatarTarget, position)) < distance) && + (avatarTarget.x != 0 || avatarTarget.y != 0 || avatarTarget.z != 0)) { + distance = Vec3.length(Vec3.subtract(avatarTarget, position)); + center = avatarTarget; + string = "Inspecting avatar"; + } + + if ((distance == -1 || Vec3.length(Vec3.subtract(voxelTarget, position)) < distance) && + (voxelTarget.x != 0 || voxelTarget.y != 0 || voxelTarget.z != 0)) { + distance = Vec3.length(Vec3.subtract(voxelTarget, position)); + center = voxelTarget; + string = "Inspecting voxel"; + } + + if (distance == -1) { + return; } vector = Vec3.subtract(position, center); @@ -209,6 +224,8 @@ function mousePressEvent(event) { altitude = Math.asin(vector.y / Vec3.length(vector)); Camera.keepLookingAt(center); + print(string); + isActive = true; } } From b3b3a72946202da452d1846879fa5f271047c899 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Mon, 16 Jun 2014 10:55:34 -0700 Subject: [PATCH 21/37] Disable editModels.js mouse events when ALT is pressed. (no conflict with inspect.js) --- examples/editModels.js | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/examples/editModels.js b/examples/editModels.js index 93a34b9a3a..53633d3c0c 100644 --- a/examples/editModels.js +++ b/examples/editModels.js @@ -691,6 +691,10 @@ function rayPlaneIntersection(pickRay, point, normal) { } function mousePressEvent(event) { + if (altIsPressed) { + return; + } + mouseLastPosition = { x: event.x, y: event.y }; modelSelected = false; var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y}); @@ -790,6 +794,10 @@ var oldModifier = 0; var modifier = 0; var wasShifted = false; function mouseMoveEvent(event) { + if (altIsPressed) { + return; + } + var pickRay = Camera.computePickRay(event.x, event.y); if (!modelSelected) { @@ -894,6 +902,10 @@ function mouseMoveEvent(event) { } function mouseReleaseEvent(event) { + if (altIsPressed) { + return; + } + modelSelected = false; glowedModelID.id = -1; @@ -962,4 +974,16 @@ Menu.menuItemEvent.connect(function(menuItem){ } }); +// handling of inspect.js concurrence +altIsPressed = false; +Controller.keyPressEvent.connect(function(event) { + if (event.text == "ALT") { + altIsPressed = true; + } +}); +Controller.keyReleaseEvent.connect(function(event) { + if (event.text == "ALT") { + altIsPressed = false; + } +}); From 978b7521dba30fb90e5eb39a2a5075dcd9fcd1ff Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Mon, 16 Jun 2014 11:30:47 -0700 Subject: [PATCH 22/37] Move connect call for multiple destinations to constructor --- interface/src/Menu.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 46b2bfc806..8f023e607f 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -165,6 +165,8 @@ Menu::Menu() : Qt::Key_At, this, SLOT(goTo())); + connect(&LocationManager::getInstance(), &LocationManager::multipleDestinationsFound, + this, &Menu::multipleDestinationsDecision); addDisabledActionAndSeparator(fileMenu, "Upload Avatar Model"); addActionToQMenuAndActionHash(fileMenu, MenuOption::UploadHead, 0, Application::getInstance(), SLOT(uploadHead())); @@ -1002,9 +1004,7 @@ bool Menu::goToDestination(QString destination) { } void Menu::goTo(QString destination) { - LocationManager* manager = &LocationManager::getInstance(); - manager->goTo(destination); - connect(manager, &LocationManager::multipleDestinationsFound, getInstance(), &Menu::multipleDestinationsDecision); + LocationManager::getInstance().goTo(destination); } void Menu::goTo() { From 9faf9d7749d4f50d4a03d6b44cbb43314ad86b36 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Mon, 16 Jun 2014 11:43:04 -0700 Subject: [PATCH 23/37] Remove extra connect and disconnect for multipleDestinationsDecision --- interface/src/Menu.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index f2f68a3594..f4fb8a363f 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -1074,9 +1074,7 @@ bool Menu::goToURL(QString location) { } void Menu::goToUser(const QString& user) { - LocationManager* manager = &LocationManager::getInstance(); - manager->goTo(user); - connect(manager, &LocationManager::multipleDestinationsFound, this, &Menu::multipleDestinationsDecision); + LocationManager::getInstance().goTo(user); } /// Open a url, shortcutting any "hifi" scheme URLs to the local application. @@ -1104,7 +1102,6 @@ void Menu::multipleDestinationsDecision(const QJsonObject& userData, const QJson } LocationManager* manager = reinterpret_cast(sender()); - disconnect(manager, &LocationManager::multipleDestinationsFound, this, &Menu::multipleDestinationsDecision); } void Menu::muteEnvironment() { From d08b63b2476244721ae1d601ad47d681bae28ea3 Mon Sep 17 00:00:00 2001 From: Kai Ludwig Date: Mon, 16 Jun 2014 23:30:28 +0200 Subject: [PATCH 24/37] changed 2000 into a constant defined inline --- libraries/shared/src/SharedUtil.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libraries/shared/src/SharedUtil.cpp b/libraries/shared/src/SharedUtil.cpp index 06a5ab8137..e4d2e1c835 100644 --- a/libraries/shared/src/SharedUtil.cpp +++ b/libraries/shared/src/SharedUtil.cpp @@ -416,8 +416,9 @@ void printVoxelCode(unsigned char* voxelCode) { #ifdef _WIN32 void usleep(int waitTime) { + const quint64 BUSY_LOOP_USECS = 2000; quint64 compTime = waitTime + usecTimestampNow(); - quint64 compTimeSleep = compTime - 2000; + quint64 compTimeSleep = compTime - BUSY_LOOP_USECS; while (true) { if (usecTimestampNow() < compTimeSleep) { QThread::msleep(1); From 5843425db90a5269ba47d46db00854de2e54f630 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Tue, 17 Jun 2014 09:18:58 -0700 Subject: [PATCH 25/37] Fix scripts being loaded on start even if they don't exist Application::loadScript should not store a reference to a script until it has been successfully loaded. Previously if a script didn't exist it would be "loaded" and show up in the running scripts window, but wouldn't have been successfully loaded, and wouldn't be running anything at all. --- interface/src/Application.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 6b44503af4..bc6841e626 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -3523,12 +3523,13 @@ ScriptEngine* Application::loadScript(const QString& scriptName, bool loadScript } else { // start the script on a new thread... scriptEngine = new ScriptEngine(scriptUrl, &_controllerScriptingInterface); - _scriptEnginesHash.insert(scriptURLString, scriptEngine); if (!scriptEngine->hasScript()) { qDebug() << "Application::loadScript(), script failed to load..."; return NULL; } + + _scriptEnginesHash.insert(scriptURLString, scriptEngine); _runningScriptsWidget->setRunningScripts(getRunningScripts()); } From f367e1840d20e9c758c5b41a9e01d65da6b22cbd Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Tue, 17 Jun 2014 10:37:35 -0700 Subject: [PATCH 26/37] Added a detached mode by default in inpect.js --- examples/inspect.js | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/examples/inspect.js b/examples/inspect.js index af63957ec3..b292d5f609 100644 --- a/examples/inspect.js +++ b/examples/inspect.js @@ -34,6 +34,7 @@ var noMode = 0; var orbitMode = 1; var radialMode = 2; var panningMode = 3; +var detachedMode = 4; var mode = noMode; @@ -48,6 +49,9 @@ var radius = 0.0; var azimuth = 0.0; var altitude = 0.0; +var avatarPosition; +var avatarOrientation; + function handleRadialMode(dx, dy) { azimuth += dx / AZIMUTH_RATE; @@ -108,7 +112,7 @@ function restoreCameraState() { } function handleModes() { - var newMode = noMode; + var newMode = (mode == noMode) ? noMode : detachedMode; if (alt) { if (control) { if (shift) { @@ -121,6 +125,22 @@ function handleModes() { } } + // if entering detachMode + if (newMode == detachedMode && mode != detachedMode) { + avatarPosition = MyAvatar.position; + avatarOrientation = MyAvatar.orientation; + } + // if leaving detachMode + if (mode == detachedMode && newMode == detachedMode && + (avatarPosition.x != MyAvatar.position.x || + avatarPosition.y != MyAvatar.position.y || + avatarPosition.z != MyAvatar.position.z || + avatarOrientation.x != MyAvatar.orientation.x || + avatarOrientation.y != MyAvatar.orientation.y || + avatarOrientation.z != MyAvatar.orientation.z || + avatarOrientation.w != MyAvatar.orientation.w)) { + newMode = noMode; + } // if leaving noMode if (mode == noMode && newMode != noMode) { saveCameraState(); @@ -252,6 +272,10 @@ function mouseMoveEvent(event) { } } +function update() { + handleModes(); +} + function scriptEnding() { if (mode != noMode) { restoreCameraState(); @@ -265,4 +289,5 @@ Controller.mousePressEvent.connect(mousePressEvent); Controller.mouseReleaseEvent.connect(mouseReleaseEvent); Controller.mouseMoveEvent.connect(mouseMoveEvent); +Script.update.connect(update); Script.scriptEnding.connect(scriptEnding); \ No newline at end of file From e75340f8ceb2b966e5ae7c1b8acf346ea6490d3a Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 17 Jun 2014 10:48:29 -0700 Subject: [PATCH 27/37] Add PlaneShape::getNormal() method. --- libraries/shared/src/PlaneShape.cpp | 4 ++++ libraries/shared/src/PlaneShape.h | 1 + 2 files changed, 5 insertions(+) diff --git a/libraries/shared/src/PlaneShape.cpp b/libraries/shared/src/PlaneShape.cpp index a8b4468c93..0617feb863 100644 --- a/libraries/shared/src/PlaneShape.cpp +++ b/libraries/shared/src/PlaneShape.cpp @@ -30,6 +30,10 @@ PlaneShape::PlaneShape(const glm::vec4& coefficients) : } } +glm::vec3 PlaneShape::getNormal() const { + return _rotation * UNROTATED_NORMAL; +} + glm::vec4 PlaneShape::getCoefficients() const { glm::vec3 normal = _rotation * UNROTATED_NORMAL; return glm::vec4(normal.x, normal.y, normal.z, -glm::dot(normal, _position)); diff --git a/libraries/shared/src/PlaneShape.h b/libraries/shared/src/PlaneShape.h index 524d53ec73..24a3f1a2bc 100644 --- a/libraries/shared/src/PlaneShape.h +++ b/libraries/shared/src/PlaneShape.h @@ -18,6 +18,7 @@ class PlaneShape : public Shape { public: PlaneShape(const glm::vec4& coefficients = glm::vec4(0.0f, 1.0f, 0.0f, 0.0f)); + glm::vec3 getNormal() const; glm::vec4 getCoefficients() const; }; From ab3d582d79ca08001ef87cdf486ef3d28e63b5e5 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 17 Jun 2014 10:49:14 -0700 Subject: [PATCH 28/37] Add ray intersection tests against most shapes. --- libraries/shared/src/ShapeCollider.cpp | 81 ++++++++++++++++++++++++ libraries/shared/src/ShapeCollider.h | 88 +++++++++++++++----------- 2 files changed, 132 insertions(+), 37 deletions(-) diff --git a/libraries/shared/src/ShapeCollider.cpp b/libraries/shared/src/ShapeCollider.cpp index 7c29fbae00..24d7fdb01a 100644 --- a/libraries/shared/src/ShapeCollider.cpp +++ b/libraries/shared/src/ShapeCollider.cpp @@ -765,5 +765,86 @@ bool capsuleAACube(const CapsuleShape* capsuleA, const glm::vec3& cubeCenter, fl return sphereAACube(nearestApproach, capsuleA->getRadius(), cubeCenter, cubeSide, collisions); } +bool findRayIntersectionWithShapes(const QVector shapes, const glm::vec3& rayStart, const glm::vec3& rayDirection, float& minDistance) { + float hitDistance = FLT_MAX; + int numShapes = shapes.size(); + for (int i = 0; i < numShapes; ++i) { + Shape* shape = shapes.at(i); + if (shape) { + float distance; + if (findRayIntersectionWithShape(shape, rayStart, rayDirection, distance)) { + if (distance < hitDistance) { + hitDistance = distance; + } + } + } + } + if (hitDistance < FLT_MAX) { + minDistance = hitDistance; + } + return false; +} + +bool findRayIntersectionWithShape(const Shape* shape, const glm::vec3& rayStart, const glm::vec3& rayDirection, float& distance) { + // NOTE: rayDirection is assumed to be normalized + int typeA = shape->getType(); + if (typeA == Shape::SPHERE_SHAPE) { + const SphereShape* sphere = static_cast(shape); + glm::vec3 sphereCenter = sphere->getPosition(); + float r2 = sphere->getRadius() * sphere->getRadius(); // r2 = radius^2 + + // compute closest approach (CA) + float a = glm::dot(sphere->getPosition() - rayStart, rayDirection); // a = distance from ray-start to CA + float b2 = glm::distance2(sphereCenter, rayStart + a * rayDirection); // b2 = squared distance from sphere-center to CA + if (b2 > r2) { + // ray does not hit sphere + return false; + } + float c = sqrtf(r2 - b2); // c = distance from CA to sphere surface along rayDirection + float d2 = glm::distance2(rayStart, sphereCenter); // d2 = squared distance from sphere-center to ray-start + if (a < 0.0f) { + // ray points away from sphere-center + if (d2 > r2) { + // ray starts outside sphere + return false; + } + // ray starts inside sphere + distance = c + a; + } else if (d2 > r2) { + // ray starts outside sphere + distance = a - c; + } else { + // ray starts inside sphere + distance = a + c; + } + return true; + } else if (typeA == Shape::CAPSULE_SHAPE) { + const CapsuleShape* capsule = static_cast(shape); + float radius = capsule->getRadius(); + glm::vec3 capsuleStart, capsuleEnd; + capsule->getStartPoint(capsuleStart); + capsule->getEndPoint(capsuleEnd); + // NOTE: findRayCapsuleIntersection returns 'true' with distance = 0 when rayStart is inside capsule. + // TODO: implement the raycast to return inside surface intersection for the internal rayStart. + return findRayCapsuleIntersection(rayStart, rayDirection, capsuleStart, capsuleEnd, radius, distance); + } else if (typeA == Shape::PLANE_SHAPE) { + const PlaneShape* plane = static_cast(shape); + glm::vec3 n = plane->getNormal(); + glm::vec3 P = plane->getPosition(); + float denominator = glm::dot(n, rayDirection); + if (fabsf(denominator) < EPSILON) { + // line is parallel to plane + return glm::dot(P - rayStart, n) < EPSILON; + } else { + float d = glm::dot(P - rayStart, n) / denominator; + if (d > 0.0f) { + // ray points toward plane + distance = d; + return true; + } + } + } + return false; +} } // namespace ShapeCollider diff --git a/libraries/shared/src/ShapeCollider.h b/libraries/shared/src/ShapeCollider.h index 9e83e31571..308a8cf10b 100644 --- a/libraries/shared/src/ShapeCollider.h +++ b/libraries/shared/src/ShapeCollider.h @@ -21,8 +21,8 @@ namespace ShapeCollider { - /// \param shapeA pointer to first shape - /// \param shapeB pointer to second shape + /// \param shapeA pointer to first shape (cannot be NULL) + /// \param shapeB pointer to second shape (cannot be NULL) /// \param collisions[out] collision details /// \return true if shapes collide bool collideShapes(const Shape* shapeA, const Shape* shapeB, CollisionList& collisions); @@ -33,123 +33,137 @@ namespace ShapeCollider { /// \return true if any shapes collide bool collideShapesCoarse(const QVector& shapesA, const QVector& shapesB, CollisionInfo& collision); - /// \param shapeA a pointer to a shape + /// \param shapeA a pointer to a shape (cannot be NULL) /// \param cubeCenter center of cube /// \param cubeSide lenght of side of cube /// \param collisions[out] average collision details /// \return true if shapeA collides with axis aligned cube bool collideShapeWithAACube(const Shape* shapeA, const glm::vec3& cubeCenter, float cubeSide, CollisionList& collisions); - /// \param sphereA pointer to first shape - /// \param sphereB pointer to second shape + /// \param sphereA pointer to first shape (cannot be NULL) + /// \param sphereB pointer to second shape (cannot be NULL) /// \param[out] collisions where to append collision details /// \return true if shapes collide bool sphereSphere(const SphereShape* sphereA, const SphereShape* sphereB, CollisionList& collisions); - /// \param sphereA pointer to first shape - /// \param capsuleB pointer to second shape + /// \param sphereA pointer to first shape (cannot be NULL) + /// \param capsuleB pointer to second shape (cannot be NULL) /// \param[out] collisions where to append collision details /// \return true if shapes collide bool sphereCapsule(const SphereShape* sphereA, const CapsuleShape* capsuleB, CollisionList& collisions); - /// \param sphereA pointer to first shape - /// \param planeB pointer to second shape + /// \param sphereA pointer to first shape (cannot be NULL) + /// \param planeB pointer to second shape (cannot be NULL) /// \param[out] collisions where to append collision details /// \return true if shapes collide bool spherePlane(const SphereShape* sphereA, const PlaneShape* planeB, CollisionList& collisions); - /// \param capsuleA pointer to first shape - /// \param sphereB pointer to second shape + /// \param capsuleA pointer to first shape (cannot be NULL) + /// \param sphereB pointer to second shape (cannot be NULL) /// \param[out] collisions where to append collision details /// \return true if shapes collide bool capsuleSphere(const CapsuleShape* capsuleA, const SphereShape* sphereB, CollisionList& collisions); - /// \param capsuleA pointer to first shape - /// \param capsuleB pointer to second shape + /// \param capsuleA pointer to first shape (cannot be NULL) + /// \param capsuleB pointer to second shape (cannot be NULL) /// \param[out] collisions where to append collision details /// \return true if shapes collide bool capsuleCapsule(const CapsuleShape* capsuleA, const CapsuleShape* capsuleB, CollisionList& collisions); - /// \param capsuleA pointer to first shape - /// \param planeB pointer to second shape + /// \param capsuleA pointer to first shape (cannot be NULL) + /// \param planeB pointer to second shape (cannot be NULL) /// \param[out] collisions where to append collision details /// \return true if shapes collide bool capsulePlane(const CapsuleShape* capsuleA, const PlaneShape* planeB, CollisionList& collisions); - /// \param planeA pointer to first shape - /// \param sphereB pointer to second shape + /// \param planeA pointer to first shape (cannot be NULL) + /// \param sphereB pointer to second shape (cannot be NULL) /// \param[out] collisions where to append collision details /// \return true if shapes collide bool planeSphere(const PlaneShape* planeA, const SphereShape* sphereB, CollisionList& collisions); - /// \param planeA pointer to first shape - /// \param capsuleB pointer to second shape + /// \param planeA pointer to first shape (cannot be NULL) + /// \param capsuleB pointer to second shape (cannot be NULL) /// \param[out] collisions where to append collision details /// \return true if shapes collide bool planeCapsule(const PlaneShape* planeA, const CapsuleShape* capsuleB, CollisionList& collisions); - /// \param planeA pointer to first shape - /// \param planeB pointer to second shape + /// \param planeA pointer to first shape (cannot be NULL) + /// \param planeB pointer to second shape (cannot be NULL) /// \param[out] collisions where to append collision details /// \return true if shapes collide bool planePlane(const PlaneShape* planeA, const PlaneShape* planeB, CollisionList& collisions); - /// \param sphereA pointer to first shape - /// \param listB pointer to second shape + /// \param sphereA pointer to first shape (cannot be NULL) + /// \param listB pointer to second shape (cannot be NULL) /// \param[out] collisions where to append collision details /// \return true if shapes collide bool sphereList(const SphereShape* sphereA, const ListShape* listB, CollisionList& collisions); - /// \param capuleA pointer to first shape - /// \param listB pointer to second shape + /// \param capuleA pointer to first shape (cannot be NULL) + /// \param listB pointer to second shape (cannot be NULL) /// \param[out] collisions where to append collision details /// \return true if shapes collide bool capsuleList(const CapsuleShape* capsuleA, const ListShape* listB, CollisionList& collisions); - /// \param planeA pointer to first shape - /// \param listB pointer to second shape + /// \param planeA pointer to first shape (cannot be NULL) + /// \param listB pointer to second shape (cannot be NULL) /// \param[out] collisions where to append collision details /// \return true if shapes collide bool planeList(const PlaneShape* planeA, const ListShape* listB, CollisionList& collisions); - /// \param listA pointer to first shape - /// \param sphereB pointer to second shape + /// \param listA pointer to first shape (cannot be NULL) + /// \param sphereB pointer to second shape (cannot be NULL) /// \param[out] collisions where to append collision details /// \return true if shapes collide bool listSphere(const ListShape* listA, const SphereShape* sphereB, CollisionList& collisions); - /// \param listA pointer to first shape - /// \param capsuleB pointer to second shape + /// \param listA pointer to first shape (cannot be NULL) + /// \param capsuleB pointer to second shape (cannot be NULL) /// \param[out] collisions where to append collision details /// \return true if shapes collide bool listCapsule(const ListShape* listA, const CapsuleShape* capsuleB, CollisionList& collisions); - /// \param listA pointer to first shape - /// \param planeB pointer to second shape + /// \param listA pointer to first shape (cannot be NULL) + /// \param planeB pointer to second shape (cannot be NULL) /// \param[out] collisions where to append collision details /// \return true if shapes collide bool listPlane(const ListShape* listA, const PlaneShape* planeB, CollisionList& collisions); - /// \param listA pointer to first shape - /// \param capsuleB pointer to second shape + /// \param listA pointer to first shape (cannot be NULL) + /// \param capsuleB pointer to second shape (cannot be NULL) /// \param[out] collisions where to append collision details /// \return true if shapes collide bool listList(const ListShape* listA, const ListShape* listB, CollisionList& collisions); - /// \param sphereA pointer to sphere + /// \param sphereA pointer to sphere (cannot be NULL) /// \param cubeCenter center of cube /// \param cubeSide lenght of side of cube /// \param[out] collisions where to append collision details /// \return true if sphereA collides with axis aligned cube bool sphereAACube(const SphereShape* sphereA, const glm::vec3& cubeCenter, float cubeSide, CollisionList& collisions); - /// \param capsuleA pointer to capsule + /// \param capsuleA pointer to capsule (cannot be NULL) /// \param cubeCenter center of cube /// \param cubeSide lenght of side of cube /// \param[out] collisions where to append collision details /// \return true if capsuleA collides with axis aligned cube bool capsuleAACube(const CapsuleShape* capsuleA, const glm::vec3& cubeCenter, float cubeSide, CollisionList& collisions); + /// \param shapes list of pointers to shapes (shape pointers may be NULL) + /// \param startPoint beginning of ray + /// \param direction direction of ray + /// \param minDistance[out] shortest distance to intersection of ray with a shapes + /// \return true if ray hits any shape in shapes + bool findRayIntersectionWithShapes(const QVector shapes, const glm::vec3& startPoint, const glm::vec3& direction, float& minDistance); + + /// \param shapeA pointer to shape (cannot be NULL) + /// \param startPoint beginning of ray + /// \param direction direction of ray + /// \param distance[out] distance to intersection of shape and ray + /// \return true if ray hits shapeA + bool findRayIntersectionWithShape(const Shape* shapeA, const glm::vec3& startPoint, const glm::vec3& direction, float& distance); + } // namespace ShapeCollider #endif // hifi_ShapeCollider_h From 1e200c3b9ce4d366611d79eed656b4309406c833 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Tue, 17 Jun 2014 19:49:35 +0200 Subject: [PATCH 29/37] The balance display didn't get over ~21.1 because the parsed json object int32 is maximal 2147483647, fixed this by parsing it from json to double. --- libraries/networking/src/DataServerAccountInfo.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/networking/src/DataServerAccountInfo.cpp b/libraries/networking/src/DataServerAccountInfo.cpp index c60a17e0d8..507c085d26 100644 --- a/libraries/networking/src/DataServerAccountInfo.cpp +++ b/libraries/networking/src/DataServerAccountInfo.cpp @@ -85,7 +85,7 @@ void DataServerAccountInfo::setBalance(qint64 balance) { void DataServerAccountInfo::setBalanceFromJSON(const QJsonObject& jsonObject) { if (jsonObject["status"].toString() == "success") { - qint64 balanceInSatoshis = jsonObject["data"].toObject()["wallet"].toObject()["balance"].toInt(); + qint64 balanceInSatoshis = jsonObject["data"].toObject()["wallet"].toObject()["balance"].toDouble(); setBalance(balanceInSatoshis); } } From fa6aed3e01a9d004cca5de4fa12c7538f37c35e9 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 17 Jun 2014 10:49:53 -0700 Subject: [PATCH 30/37] Add unit tests for ray intersections with shapes. --- tests/physics/src/ShapeColliderTests.cpp | 408 +++++++++++++++++++++++ tests/physics/src/ShapeColliderTests.h | 8 + 2 files changed, 416 insertions(+) diff --git a/tests/physics/src/ShapeColliderTests.cpp b/tests/physics/src/ShapeColliderTests.cpp index 7b3d956065..3387ba6aba 100644 --- a/tests/physics/src/ShapeColliderTests.cpp +++ b/tests/physics/src/ShapeColliderTests.cpp @@ -11,6 +11,7 @@ //#include #include +#include #include #include @@ -897,6 +898,405 @@ void ShapeColliderTests::sphereMissesAACube() { } } +void ShapeColliderTests::rayHitsSphere() { + float startDistance = 3.0f; + glm::vec3 rayStart(-startDistance, 0.0f, 0.0f); + glm::vec3 rayDirection(1.0f, 0.0f, 0.0f); + + float radius = 1.0f; + glm::vec3 center(0.0f); + + SphereShape sphere(radius, center); + + // very simple ray along xAxis + { + float distance = FLT_MAX; + if (!ShapeCollider::findRayIntersectionWithShape(&sphere, rayStart, rayDirection, distance)) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should intersect sphere" << std::endl; + } + + float expectedDistance = startDistance - radius; + float relativeError = fabsf(distance - expectedDistance) / startDistance; + if (relativeError > EPSILON) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray sphere intersection distance error = " << relativeError << std::endl; + } + } + + // ray along a diagonal axis + { + rayStart = glm::vec3(startDistance, startDistance, 0.0f); + rayDirection = - glm::normalize(rayStart); + + float distance = FLT_MAX; + if (!ShapeCollider::findRayIntersectionWithShape(&sphere, rayStart, rayDirection, distance)) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should intersect sphere" << std::endl; + } + + float expectedDistance = SQUARE_ROOT_OF_2 * startDistance - radius; + float relativeError = fabsf(distance - expectedDistance) / startDistance; + if (relativeError > EPSILON) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray sphere intersection distance error = " << relativeError << std::endl; + } + } + + // rotated and displaced ray and sphere + { + startDistance = 7.41f; + radius = 3.917f; + + glm::vec3 axis = glm::normalize(glm::vec3(1.0f, 2.0f, 3.0f)); + glm::quat rotation = glm::angleAxis(0.987654321f, axis); + glm::vec3 translation(35.7f, 2.46f, -1.97f); + + glm::vec3 unrotatedRayDirection(-1.0f, 0.0f, 0.0f); + glm::vec3 untransformedRayStart(startDistance, 0.0f, 0.0f); + + rayStart = rotation * (untransformedRayStart + translation); + rayDirection = rotation * unrotatedRayDirection; + + sphere.setRadius(radius); + sphere.setPosition(rotation * translation); + + float distance = FLT_MAX; + if (!ShapeCollider::findRayIntersectionWithShape(&sphere, rayStart, rayDirection, distance)) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should intersect sphere" << std::endl; + } + + float expectedDistance = startDistance - radius; + float relativeError = fabsf(distance - expectedDistance) / startDistance; + if (relativeError > EPSILON) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray sphere intersection distance error = " << relativeError << std::endl; + } + } +} + +void ShapeColliderTests::rayBarelyHitsSphere() { + float radius = 1.0f; + glm::vec3 center(0.0f); + float delta = 2.0f * EPSILON; + + float startDistance = 3.0f; + glm::vec3 rayStart(-startDistance, radius - delta, 0.0f); + glm::vec3 rayDirection(1.0f, 0.0f, 0.0f); + + SphereShape sphere(radius, center); + + // very simple ray along xAxis + float distance = FLT_MAX; + if (!ShapeCollider::findRayIntersectionWithShape(&sphere, rayStart, rayDirection, distance)) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should just barely hit sphere" << std::endl; + } + + // translate and rotate the whole system... + glm::vec3 axis = glm::normalize(glm::vec3(1.0f, 2.0f, 3.0f)); + glm::quat rotation = glm::angleAxis(0.987654321f, axis); + glm::vec3 translation(35.7f, 2.46f, -1.97f); + + rayStart = rotation * (rayStart + translation); + rayDirection = rotation * rayDirection; + sphere.setPosition(rotation * translation); + + // ...and test again + distance = FLT_MAX; + if (!ShapeCollider::findRayIntersectionWithShape(&sphere, rayStart, rayDirection, distance)) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should just barely hit sphere" << std::endl; + } +} + + +void ShapeColliderTests::rayBarelyMissesSphere() { + // same as the barely-hits case, but this time we move the ray away from sphere + float radius = 1.0f; + glm::vec3 center(0.0f); + float delta = 2.0f * EPSILON; + + float startDistance = 3.0f; + glm::vec3 rayStart(-startDistance, radius + delta, 0.0f); + glm::vec3 rayDirection(1.0f, 0.0f, 0.0f); + + SphereShape sphere(radius, center); + + // very simple ray along xAxis + float distance = FLT_MAX; + if (ShapeCollider::findRayIntersectionWithShape(&sphere, rayStart, rayDirection, distance)) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should just barely miss sphere" << std::endl; + } + if (distance != FLT_MAX) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: distance should be unchanged after intersection miss" << std::endl; + } + + // translate and rotate the whole system... + glm::vec3 axis = glm::normalize(glm::vec3(1.0f, 2.0f, 3.0f)); + glm::quat rotation = glm::angleAxis(0.987654321f, axis); + glm::vec3 translation(35.7f, 2.46f, -1.97f); + + rayStart = rotation * (rayStart + translation); + rayDirection = rotation * rayDirection; + sphere.setPosition(rotation * translation); + + // ...and test again + distance = FLT_MAX; + if (ShapeCollider::findRayIntersectionWithShape(&sphere, rayStart, rayDirection, distance)) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should just barely miss sphere" << std::endl; + } + if (distance != FLT_MAX) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: distance should be unchanged after intersection miss" << std::endl; + } +} + +void ShapeColliderTests::rayHitsCapsule() { + float startDistance = 3.0f; + float radius = 1.0f; + float halfHeight = 2.0f; + glm::vec3 center(0.0f); + CapsuleShape capsule(radius, halfHeight); + + { // simple test along xAxis + // toward capsule center + glm::vec3 rayStart(startDistance, 0.0f, 0.0f); + glm::vec3 rayDirection(-1.0f, 0.0f, 0.0f); + float distance = FLT_MAX; + if (!ShapeCollider::findRayIntersectionWithShape(&capsule, rayStart, rayDirection, distance)) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should hit capsule" << std::endl; + } + float expectedDistance = startDistance - radius; + float relativeError = fabsf(distance - expectedDistance) / startDistance; + if (relativeError > EPSILON) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray capsule intersection distance error = " << relativeError << std::endl; + } + + // toward top of cylindrical wall + rayStart.y = halfHeight; + distance = FLT_MAX; + if (!ShapeCollider::findRayIntersectionWithShape(&capsule, rayStart, rayDirection, distance)) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should hit capsule" << std::endl; + } + relativeError = fabsf(distance - expectedDistance) / startDistance; + if (relativeError > EPSILON) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray capsule intersection distance error = " << relativeError << std::endl; + } + + // toward top cap + float delta = 2.0f * EPSILON; + rayStart.y = halfHeight + delta; + distance = FLT_MAX; + if (!ShapeCollider::findRayIntersectionWithShape(&capsule, rayStart, rayDirection, distance)) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should hit capsule" << std::endl; + } + relativeError = fabsf(distance - expectedDistance) / startDistance; + if (relativeError > EPSILON) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray capsule intersection distance error = " << relativeError << std::endl; + } + + const float EDGE_CASE_SLOP_FACTOR = 20.0f; + + // toward tip of top cap + rayStart.y = halfHeight + radius - delta; + distance = FLT_MAX; + if (!ShapeCollider::findRayIntersectionWithShape(&capsule, rayStart, rayDirection, distance)) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should hit capsule" << std::endl; + } + expectedDistance = startDistance - radius * sqrtf(2.0f * delta); // using small angle approximation of cosine + relativeError = fabsf(distance - expectedDistance) / startDistance; + // for edge cases we allow a LOT of error + if (relativeError > EDGE_CASE_SLOP_FACTOR * EPSILON) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray capsule intersection distance error = " << relativeError << std::endl; + } + + // toward tip of bottom cap + rayStart.y = - halfHeight - radius + delta; + distance = FLT_MAX; + if (!ShapeCollider::findRayIntersectionWithShape(&capsule, rayStart, rayDirection, distance)) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should hit capsule" << std::endl; + } + expectedDistance = startDistance - radius * sqrtf(2.0f * delta); // using small angle approximation of cosine + relativeError = fabsf(distance - expectedDistance) / startDistance; + // for edge cases we allow a LOT of error + if (relativeError > EDGE_CASE_SLOP_FACTOR * EPSILON) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray capsule intersection distance error = " << relativeError << std::endl; + } + + // toward edge of capsule cylindrical face + rayStart.y = 0.0f; + rayStart.z = radius - delta; + distance = FLT_MAX; + if (!ShapeCollider::findRayIntersectionWithShape(&capsule, rayStart, rayDirection, distance)) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should hit capsule" << std::endl; + } + expectedDistance = startDistance - radius * sqrtf(2.0f * delta); // using small angle approximation of cosine + relativeError = fabsf(distance - expectedDistance) / startDistance; + // for edge cases we allow a LOT of error + if (relativeError > EDGE_CASE_SLOP_FACTOR * EPSILON) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray capsule intersection distance error = " << relativeError << std::endl; + } + } + // TODO: test at steep angles near cylinder/cap junction +} + +void ShapeColliderTests::rayMissesCapsule() { + // same as edge case hit tests, but shifted in the opposite direction + float startDistance = 3.0f; + float radius = 1.0f; + float halfHeight = 2.0f; + glm::vec3 center(0.0f); + CapsuleShape capsule(radius, halfHeight); + + { // simple test along xAxis + // toward capsule center + glm::vec3 rayStart(startDistance, 0.0f, 0.0f); + glm::vec3 rayDirection(-1.0f, 0.0f, 0.0f); + float delta = 2.0f * EPSILON; + + // over top cap + rayStart.y = halfHeight + radius + delta; + float distance = FLT_MAX; + if (ShapeCollider::findRayIntersectionWithShape(&capsule, rayStart, rayDirection, distance)) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should miss capsule" << std::endl; + } + if (distance != FLT_MAX) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: distance should be unchanged after intersection miss" << std::endl; + } + + // below bottom cap + rayStart.y = - halfHeight - radius - delta; + distance = FLT_MAX; + if (ShapeCollider::findRayIntersectionWithShape(&capsule, rayStart, rayDirection, distance)) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should miss capsule" << std::endl; + } + if (distance != FLT_MAX) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: distance should be unchanged after intersection miss" << std::endl; + } + + // past edge of capsule cylindrical face + rayStart.y = 0.0f; + rayStart.z = radius + delta; + distance = FLT_MAX; + if (ShapeCollider::findRayIntersectionWithShape(&capsule, rayStart, rayDirection, distance)) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should miss capsule" << std::endl; + } + if (distance != FLT_MAX) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: distance should be unchanged after intersection miss" << std::endl; + } + } + // TODO: test at steep angles near edge +} + +void ShapeColliderTests::rayHitsPlane() { + // make a simple plane + float planeDistanceFromOrigin = 3.579; + glm::vec3 planePosition(0.0f, planeDistanceFromOrigin, 0.0f); + PlaneShape plane; + plane.setPosition(planePosition); + + // make a simple ray + float startDistance = 1.234f; + glm::vec3 rayStart(-startDistance, 0.0f, 0.0f); + glm::vec3 rayDirection = glm::normalize(glm::vec3(1.0f, 1.0f, 1.0f)); + + float distance = FLT_MAX; + if (!ShapeCollider::findRayIntersectionWithShape(&plane, rayStart, rayDirection, distance)) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should hit plane" << std::endl; + } + + float expectedDistance = SQUARE_ROOT_OF_3 * planeDistanceFromOrigin; + float relativeError = fabsf(distance - expectedDistance) / planeDistanceFromOrigin; + if (relativeError > EPSILON) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray plane intersection distance error = " << relativeError << std::endl; + } + + // rotate the whole system and try again + float angle = 37.8f; + glm::vec3 axis = glm::normalize( glm::vec3(-7.0f, 2.8f, 9.3f) ); + glm::quat rotation = glm::angleAxis(angle, axis); + + plane.setPosition(rotation * planePosition); + plane.setRotation(rotation); + rayStart = rotation * rayStart; + rayDirection = rotation * rayDirection; + + distance = FLT_MAX; + if (!ShapeCollider::findRayIntersectionWithShape(&plane, rayStart, rayDirection, distance)) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should hit plane" << std::endl; + } + + expectedDistance = SQUARE_ROOT_OF_3 * planeDistanceFromOrigin; + relativeError = fabsf(distance - expectedDistance) / planeDistanceFromOrigin; + if (relativeError > EPSILON) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray plane intersection distance error = " << relativeError << std::endl; + } +} + +void ShapeColliderTests::rayMissesPlane() { + // make a simple plane + float planeDistanceFromOrigin = 3.579; + glm::vec3 planePosition(0.0f, planeDistanceFromOrigin, 0.0f); + PlaneShape plane; + plane.setPosition(planePosition); + + { // parallel rays should miss + float startDistance = 1.234f; + glm::vec3 rayStart(-startDistance, 0.0f, 0.0f); + glm::vec3 rayDirection = glm::normalize(glm::vec3(-1.0f, 0.0f, -1.0f)); + + float distance = FLT_MAX; + if (ShapeCollider::findRayIntersectionWithShape(&plane, rayStart, rayDirection, distance)) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should miss plane" << std::endl; + } + if (distance != FLT_MAX) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: distance should be unchanged after intersection miss" << std::endl; + } + + // rotate the whole system and try again + float angle = 37.8f; + glm::vec3 axis = glm::normalize( glm::vec3(-7.0f, 2.8f, 9.3f) ); + glm::quat rotation = glm::angleAxis(angle, axis); + + plane.setPosition(rotation * planePosition); + plane.setRotation(rotation); + rayStart = rotation * rayStart; + rayDirection = rotation * rayDirection; + + distance = FLT_MAX; + if (ShapeCollider::findRayIntersectionWithShape(&plane, rayStart, rayDirection, distance)) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should miss plane" << std::endl; + } + if (distance != FLT_MAX) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: distance should be unchanged after intersection miss" << std::endl; + } + } + + { // make a simple ray that points away from plane + float startDistance = 1.234f; + glm::vec3 rayStart(-startDistance, 0.0f, 0.0f); + glm::vec3 rayDirection = glm::normalize(glm::vec3(-1.0f, -1.0f, -1.0f)); + + float distance = FLT_MAX; + if (ShapeCollider::findRayIntersectionWithShape(&plane, rayStart, rayDirection, distance)) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should miss plane" << std::endl; + } + if (distance != FLT_MAX) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: distance should be unchanged after intersection miss" << std::endl; + } + + // rotate the whole system and try again + float angle = 37.8f; + glm::vec3 axis = glm::normalize( glm::vec3(-7.0f, 2.8f, 9.3f) ); + glm::quat rotation = glm::angleAxis(angle, axis); + + plane.setPosition(rotation * planePosition); + plane.setRotation(rotation); + rayStart = rotation * rayStart; + rayDirection = rotation * rayDirection; + + distance = FLT_MAX; + if (ShapeCollider::findRayIntersectionWithShape(&plane, rayStart, rayDirection, distance)) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should miss plane" << std::endl; + } + if (distance != FLT_MAX) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: distance should be unchanged after intersection miss" << std::endl; + } + } +} void ShapeColliderTests::runAllTests() { sphereMissesSphere(); @@ -911,4 +1311,12 @@ void ShapeColliderTests::runAllTests() { sphereTouchesAACubeFaces(); sphereTouchesAACubeEdges(); sphereMissesAACube(); + + rayHitsSphere(); + rayBarelyHitsSphere(); + rayBarelyMissesSphere(); + rayHitsCapsule(); + rayMissesCapsule(); + rayHitsPlane(); + rayMissesPlane(); } diff --git a/tests/physics/src/ShapeColliderTests.h b/tests/physics/src/ShapeColliderTests.h index b51c48a61e..fd9f1f9706 100644 --- a/tests/physics/src/ShapeColliderTests.h +++ b/tests/physics/src/ShapeColliderTests.h @@ -27,6 +27,14 @@ namespace ShapeColliderTests { void sphereTouchesAACubeEdges(); void sphereMissesAACube(); + void rayHitsSphere(); + void rayBarelyHitsSphere(); + void rayBarelyMissesSphere(); + void rayHitsCapsule(); + void rayMissesCapsule(); + void rayHitsPlane(); + void rayMissesPlane(); + void runAllTests(); } From b37b9fb09745f6468904c22df80ae97b14bb3f76 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 17 Jun 2014 11:59:26 -0700 Subject: [PATCH 31/37] remove warnings about unused variables --- interface/src/Menu.cpp | 2 -- interface/src/ui/ApplicationOverlay.cpp | 3 --- 2 files changed, 5 deletions(-) diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 0dccf6256b..5c8c2e97aa 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -1104,8 +1104,6 @@ void Menu::multipleDestinationsDecision(const QJsonObject& userData, const QJson } else if (userResponse == QMessageBox::Open) { Application::getInstance()->getAvatar()->goToLocationFromAddress(placeData["address"].toObject()); } - - LocationManager* manager = reinterpret_cast(sender()); } void Menu::muteEnvironment() { diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index 471ff20d66..af4648244f 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -154,7 +154,6 @@ void ApplicationOverlay::displayOverlayTextureOculus(Camera& whichCamera) { Application* application = Application::getInstance(); - QGLWidget* glWidget = application->getGLWidget(); MyAvatar* myAvatar = application->getAvatar(); const glm::vec3& viewMatrixTranslation = application->getViewMatrixTranslation(); @@ -221,8 +220,6 @@ void ApplicationOverlay::renderPointers() { Application* application = Application::getInstance(); // Render a crosshair over the mouse when in Oculus _numMagnifiers = 0; - int mouseX = application->getMouseX(); - int mouseY = application->getMouseY(); //lazily load crosshair texture if (_crosshairTexture == 0) { From 3f3632564254fc5c1a164cfe04eb02a74aed739d Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 17 Jun 2014 11:59:37 -0700 Subject: [PATCH 32/37] remove warnings about signed/unsigned comparison --- assignment-client/src/audio/AudioMixer.cpp | 2 +- assignment-client/src/audio/AudioMixerClientData.cpp | 10 +++++----- domain-server/src/DomainServer.cpp | 2 +- libraries/audio/src/Sound.cpp | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index b3909660e2..d96fce450a 100644 --- a/assignment-client/src/audio/AudioMixer.cpp +++ b/assignment-client/src/audio/AudioMixer.cpp @@ -335,7 +335,7 @@ void AudioMixer::prepareMixForListeningNode(Node* node) { AudioMixerClientData* otherNodeClientData = (AudioMixerClientData*) otherNode->getLinkedData(); // enumerate the ARBs attached to the otherNode and add all that should be added to mix - for (unsigned int i = 0; i < otherNodeClientData->getRingBuffers().size(); i++) { + for (int i = 0; i < otherNodeClientData->getRingBuffers().size(); i++) { PositionalAudioRingBuffer* otherNodeBuffer = otherNodeClientData->getRingBuffers()[i]; if ((*otherNode != *node diff --git a/assignment-client/src/audio/AudioMixerClientData.cpp b/assignment-client/src/audio/AudioMixerClientData.cpp index 7fb2a7dcab..9494e927a9 100644 --- a/assignment-client/src/audio/AudioMixerClientData.cpp +++ b/assignment-client/src/audio/AudioMixerClientData.cpp @@ -25,14 +25,14 @@ AudioMixerClientData::AudioMixerClientData() : } AudioMixerClientData::~AudioMixerClientData() { - for (unsigned int i = 0; i < _ringBuffers.size(); i++) { + for (int i = 0; i < _ringBuffers.size(); i++) { // delete this attached PositionalAudioRingBuffer delete _ringBuffers[i]; } } AvatarAudioRingBuffer* AudioMixerClientData::getAvatarAudioRingBuffer() const { - for (unsigned int i = 0; i < _ringBuffers.size(); i++) { + for (int i = 0; i < _ringBuffers.size(); i++) { if (_ringBuffers[i]->getType() == PositionalAudioRingBuffer::Microphone) { return (AvatarAudioRingBuffer*) _ringBuffers[i]; } @@ -79,7 +79,7 @@ int AudioMixerClientData::parseData(const QByteArray& packet) { InjectedAudioRingBuffer* matchingInjectedRingBuffer = NULL; - for (unsigned int i = 0; i < _ringBuffers.size(); i++) { + for (int i = 0; i < _ringBuffers.size(); i++) { if (_ringBuffers[i]->getType() == PositionalAudioRingBuffer::Injector && ((InjectedAudioRingBuffer*) _ringBuffers[i])->getStreamIdentifier() == streamIdentifier) { matchingInjectedRingBuffer = (InjectedAudioRingBuffer*) _ringBuffers[i]; @@ -99,7 +99,7 @@ int AudioMixerClientData::parseData(const QByteArray& packet) { } void AudioMixerClientData::checkBuffersBeforeFrameSend(int jitterBufferLengthSamples) { - for (unsigned int i = 0; i < _ringBuffers.size(); i++) { + for (int i = 0; i < _ringBuffers.size(); i++) { if (_ringBuffers[i]->shouldBeAddedToMix(jitterBufferLengthSamples)) { // this is a ring buffer that is ready to go // set its flag so we know to push its buffer when all is said and done @@ -113,7 +113,7 @@ void AudioMixerClientData::checkBuffersBeforeFrameSend(int jitterBufferLengthSam } void AudioMixerClientData::pushBuffersAfterFrameSend() { - for (unsigned int i = 0; i < _ringBuffers.size(); i++) { + for (int i = 0; i < _ringBuffers.size(); i++) { // this was a used buffer, push the output pointer forwards PositionalAudioRingBuffer* audioBuffer = _ringBuffers[i]; diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index f7185063a9..d55a9b52ca 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -659,7 +659,7 @@ void DomainServer::readAvailableDatagrams() { wasNoisyTimerStarted = true; } - const quint64 NOISY_MESSAGE_INTERVAL_MSECS = 5 * 1000; + const qint64 NOISY_MESSAGE_INTERVAL_MSECS = 5 * 1000; if (requestAssignment.getType() != Assignment::AgentType || noisyMessageTimer.elapsed() > NOISY_MESSAGE_INTERVAL_MSECS) { diff --git a/libraries/audio/src/Sound.cpp b/libraries/audio/src/Sound.cpp index 9bc78f2303..7ef3afdf29 100644 --- a/libraries/audio/src/Sound.cpp +++ b/libraries/audio/src/Sound.cpp @@ -258,7 +258,7 @@ void Sound::interpretAsWav(const QByteArray& inputAudioByteArray, QByteArray& ou // Now pull out the data quint32 outputAudioByteArraySize = qFromLittleEndian(dataHeader.descriptor.size); outputAudioByteArray.resize(outputAudioByteArraySize); - if (waveStream.readRawData(outputAudioByteArray.data(), outputAudioByteArraySize) != outputAudioByteArraySize) { + if (waveStream.readRawData(outputAudioByteArray.data(), outputAudioByteArraySize) != (int)outputAudioByteArraySize) { qDebug() << "Error reading WAV file"; } From 9a3f8508cfaf3e7662a131f3d647a97b67ea5de2 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Tue, 17 Jun 2014 12:12:53 -0700 Subject: [PATCH 33/37] add support for inside out ray intersection on AACube and AABox --- libraries/octree/src/AABox.cpp | 37 ++++++++++++++++++++++++++++++++ libraries/octree/src/AACube.cpp | 38 +++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+) diff --git a/libraries/octree/src/AABox.cpp b/libraries/octree/src/AABox.cpp index 7aa4d76134..409b362b24 100644 --- a/libraries/octree/src/AABox.cpp +++ b/libraries/octree/src/AABox.cpp @@ -180,6 +180,18 @@ static bool findIntersection(float origin, float direction, float corner, float return false; } +// finds the intersection between a ray and the inside facing plane on one axis +static bool findInsideOutIntersection(float origin, float direction, float corner, float size, float& distance) { + if (direction > EPSILON) { + distance = -1.0f * (origin - (corner + size)) / direction; + return true; + } else if (direction < -EPSILON) { + distance = -1.0f * (origin - corner) / direction; + return true; + } + return false; +} + bool AABox::expandedIntersectsSegment(const glm::vec3& start, const glm::vec3& end, float expansion) const { // handle the trivial cases where the expanded box contains the start or end if (expandedContains(start, expansion) || expandedContains(end, expansion)) { @@ -207,9 +219,34 @@ bool AABox::expandedIntersectsSegment(const glm::vec3& start, const glm::vec3& e bool AABox::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face) const { // handle the trivial case where the box contains the origin if (contains(origin)) { + // We still want to calculate the distance from the origin to the inside out plane + float axisDistance; + if ((findInsideOutIntersection(origin.x, direction.x, _corner.x, _scale.x, axisDistance) && axisDistance >= 0 && + isWithin(origin.y + axisDistance*direction.y, _corner.y, _scale.y) && + isWithin(origin.z + axisDistance*direction.z, _corner.z, _scale.z))) { + distance = axisDistance; + face = direction.x > 0 ? MIN_X_FACE : MAX_X_FACE; + return true; + } + if ((findInsideOutIntersection(origin.y, direction.y, _corner.y, _scale.y, axisDistance) && axisDistance >= 0 && + isWithin(origin.x + axisDistance*direction.x, _corner.x, _scale.x) && + isWithin(origin.z + axisDistance*direction.z, _corner.z, _scale.z))) { + distance = axisDistance; + face = direction.y > 0 ? MIN_Y_FACE : MAX_Y_FACE; + return true; + } + if ((findInsideOutIntersection(origin.z, direction.z, _corner.z, _scale.z, axisDistance) && axisDistance >= 0 && + isWithin(origin.y + axisDistance*direction.y, _corner.y, _scale.y) && + isWithin(origin.x + axisDistance*direction.x, _corner.x, _scale.x))) { + distance = axisDistance; + face = direction.z > 0 ? MIN_Z_FACE : MAX_Z_FACE; + return true; + } + // This case is unexpected, but mimics the previous behavior for inside out intersections distance = 0; return true; } + // check each axis float axisDistance; if ((findIntersection(origin.x, direction.x, _corner.x, _scale.x, axisDistance) && axisDistance >= 0 && diff --git a/libraries/octree/src/AACube.cpp b/libraries/octree/src/AACube.cpp index 443d725a38..5a8839db4e 100644 --- a/libraries/octree/src/AACube.cpp +++ b/libraries/octree/src/AACube.cpp @@ -169,6 +169,18 @@ static bool findIntersection(float origin, float direction, float corner, float return false; } +// finds the intersection between a ray and the inside facing plane on one axis +static bool findInsideOutIntersection(float origin, float direction, float corner, float size, float& distance) { + if (direction > EPSILON) { + distance = -1.0f * (origin - (corner + size)) / direction; + return true; + } else if (direction < -EPSILON) { + distance = -1.0f * (origin - corner) / direction; + return true; + } + return false; +} + bool AACube::expandedIntersectsSegment(const glm::vec3& start, const glm::vec3& end, float expansion) const { // handle the trivial cases where the expanded box contains the start or end if (expandedContains(start, expansion) || expandedContains(end, expansion)) { @@ -196,9 +208,35 @@ bool AACube::expandedIntersectsSegment(const glm::vec3& start, const glm::vec3& bool AACube::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face) const { // handle the trivial case where the box contains the origin if (contains(origin)) { + + // We still want to calculate the distance from the origin to the inside out plane + float axisDistance; + if ((findInsideOutIntersection(origin.x, direction.x, _corner.x, _scale, axisDistance) && axisDistance >= 0 && + isWithin(origin.y + axisDistance*direction.y, _corner.y, _scale) && + isWithin(origin.z + axisDistance*direction.z, _corner.z, _scale))) { + distance = axisDistance; + face = direction.x > 0 ? MIN_X_FACE : MAX_X_FACE; + return true; + } + if ((findInsideOutIntersection(origin.y, direction.y, _corner.y, _scale, axisDistance) && axisDistance >= 0 && + isWithin(origin.x + axisDistance*direction.x, _corner.x, _scale) && + isWithin(origin.z + axisDistance*direction.z, _corner.z, _scale))) { + distance = axisDistance; + face = direction.y > 0 ? MIN_Y_FACE : MAX_Y_FACE; + return true; + } + if ((findInsideOutIntersection(origin.z, direction.z, _corner.z, _scale, axisDistance) && axisDistance >= 0 && + isWithin(origin.y + axisDistance*direction.y, _corner.y, _scale) && + isWithin(origin.x + axisDistance*direction.x, _corner.x, _scale))) { + distance = axisDistance; + face = direction.z > 0 ? MIN_Z_FACE : MAX_Z_FACE; + return true; + } + // This case is unexpected, but mimics the previous behavior for inside out intersections distance = 0; return true; } + // check each axis float axisDistance; if ((findIntersection(origin.x, direction.x, _corner.x, _scale, axisDistance) && axisDistance >= 0 && From f18864bd72019f7b69a8d13b8aaa2d505237f721 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 17 Jun 2014 12:36:36 -0700 Subject: [PATCH 34/37] Moved findRayIntersection() to the Shape classes --- libraries/shared/src/CapsuleShape.cpp | 10 ++++ libraries/shared/src/CapsuleShape.h | 2 + libraries/shared/src/ListShape.h | 3 ++ libraries/shared/src/PlaneShape.cpp | 17 +++++++ libraries/shared/src/PlaneShape.h | 2 + libraries/shared/src/Shape.h | 2 + libraries/shared/src/ShapeCollider.cpp | 64 +----------------------- libraries/shared/src/ShapeCollider.h | 7 --- libraries/shared/src/SphereShape.h | 2 + tests/physics/src/ShapeColliderTests.cpp | 44 ++++++++-------- 10 files changed, 61 insertions(+), 92 deletions(-) diff --git a/libraries/shared/src/CapsuleShape.cpp b/libraries/shared/src/CapsuleShape.cpp index 8e887107dc..5416ff92a6 100644 --- a/libraries/shared/src/CapsuleShape.cpp +++ b/libraries/shared/src/CapsuleShape.cpp @@ -13,6 +13,8 @@ #include #include "CapsuleShape.h" + +#include "GeometryUtil.h" #include "SharedUtil.h" @@ -84,3 +86,11 @@ void CapsuleShape::setEndPoints(const glm::vec3& startPoint, const glm::vec3& en updateBoundingRadius(); } +bool CapsuleShape::findRayIntersection(const glm::vec3& rayStart, const glm::vec3& rayDirection, float& distance) const { + glm::vec3 capsuleStart, capsuleEnd; + getStartPoint(capsuleStart); + getEndPoint(capsuleEnd); + // NOTE: findRayCapsuleIntersection returns 'true' with distance = 0 when rayStart is inside capsule. + // TODO: implement the raycast to return inside surface intersection for the internal rayStart. + return findRayCapsuleIntersection(rayStart, rayDirection, capsuleStart, capsuleEnd, _radius, distance); +} diff --git a/libraries/shared/src/CapsuleShape.h b/libraries/shared/src/CapsuleShape.h index 756ae18911..fdd6c3eda6 100644 --- a/libraries/shared/src/CapsuleShape.h +++ b/libraries/shared/src/CapsuleShape.h @@ -39,6 +39,8 @@ public: void setRadiusAndHalfHeight(float radius, float height); void setEndPoints(const glm::vec3& startPoint, const glm::vec3& endPoint); + bool findRayIntersection(const glm::vec3& rayStart, const glm::vec3& rayDirection, float& distance) const; + protected: void updateBoundingRadius() { _boundingRadius = _radius + _halfHeight; } diff --git a/libraries/shared/src/ListShape.h b/libraries/shared/src/ListShape.h index 7ba2410a23..17e7d7b2b6 100644 --- a/libraries/shared/src/ListShape.h +++ b/libraries/shared/src/ListShape.h @@ -55,6 +55,9 @@ public: void setShapes(QVector& shapes); + // TODO: either implement this or remove ListShape altogether + bool findRayIntersection(const glm::vec3& rayStart, const glm::vec3& rayDirection, float& distance) const { return false; } + protected: void clear(); void computeBoundingRadius(); diff --git a/libraries/shared/src/PlaneShape.cpp b/libraries/shared/src/PlaneShape.cpp index 0617feb863..e9563c6d8b 100644 --- a/libraries/shared/src/PlaneShape.cpp +++ b/libraries/shared/src/PlaneShape.cpp @@ -38,3 +38,20 @@ glm::vec4 PlaneShape::getCoefficients() const { glm::vec3 normal = _rotation * UNROTATED_NORMAL; return glm::vec4(normal.x, normal.y, normal.z, -glm::dot(normal, _position)); } + +bool PlaneShape::findRayIntersection(const glm::vec3& rayStart, const glm::vec3& rayDirection, float& distance) const { + glm::vec3 n = getNormal(); + float denominator = glm::dot(n, rayDirection); + if (fabsf(denominator) < EPSILON) { + // line is parallel to plane + return glm::dot(_position - rayStart, n) < EPSILON; + } else { + float d = glm::dot(_position - rayStart, n) / denominator; + if (d > 0.0f) { + // ray points toward plane + distance = d; + return true; + } + } + return false; +} diff --git a/libraries/shared/src/PlaneShape.h b/libraries/shared/src/PlaneShape.h index 24a3f1a2bc..b8a93324b7 100644 --- a/libraries/shared/src/PlaneShape.h +++ b/libraries/shared/src/PlaneShape.h @@ -20,6 +20,8 @@ public: glm::vec3 getNormal() const; glm::vec4 getCoefficients() const; + + bool findRayIntersection(const glm::vec3& rayStart, const glm::vec3& rayDirection, float& distance) const; }; #endif // hifi_PlaneShape_h diff --git a/libraries/shared/src/Shape.h b/libraries/shared/src/Shape.h index 87b84ea73b..3926f6cd07 100644 --- a/libraries/shared/src/Shape.h +++ b/libraries/shared/src/Shape.h @@ -38,6 +38,8 @@ public: virtual void setPosition(const glm::vec3& position) { _position = position; } virtual void setRotation(const glm::quat& rotation) { _rotation = rotation; } + virtual bool findRayIntersection(const glm::vec3& rayStart, const glm::vec3& rayDirection, float& distance) const = 0; + protected: // these ctors are protected (used by derived classes only) Shape(Type type) : _type(type), _boundingRadius(0.f), _position(0.f), _rotation() {} diff --git a/libraries/shared/src/ShapeCollider.cpp b/libraries/shared/src/ShapeCollider.cpp index 24d7fdb01a..bbedeb401d 100644 --- a/libraries/shared/src/ShapeCollider.cpp +++ b/libraries/shared/src/ShapeCollider.cpp @@ -772,7 +772,7 @@ bool findRayIntersectionWithShapes(const QVector shapes, const glm::vec3 Shape* shape = shapes.at(i); if (shape) { float distance; - if (findRayIntersectionWithShape(shape, rayStart, rayDirection, distance)) { + if (shape->findRayIntersection(rayStart, rayDirection, distance)) { if (distance < hitDistance) { hitDistance = distance; } @@ -785,66 +785,4 @@ bool findRayIntersectionWithShapes(const QVector shapes, const glm::vec3 return false; } -bool findRayIntersectionWithShape(const Shape* shape, const glm::vec3& rayStart, const glm::vec3& rayDirection, float& distance) { - // NOTE: rayDirection is assumed to be normalized - int typeA = shape->getType(); - if (typeA == Shape::SPHERE_SHAPE) { - const SphereShape* sphere = static_cast(shape); - glm::vec3 sphereCenter = sphere->getPosition(); - float r2 = sphere->getRadius() * sphere->getRadius(); // r2 = radius^2 - - // compute closest approach (CA) - float a = glm::dot(sphere->getPosition() - rayStart, rayDirection); // a = distance from ray-start to CA - float b2 = glm::distance2(sphereCenter, rayStart + a * rayDirection); // b2 = squared distance from sphere-center to CA - if (b2 > r2) { - // ray does not hit sphere - return false; - } - float c = sqrtf(r2 - b2); // c = distance from CA to sphere surface along rayDirection - float d2 = glm::distance2(rayStart, sphereCenter); // d2 = squared distance from sphere-center to ray-start - if (a < 0.0f) { - // ray points away from sphere-center - if (d2 > r2) { - // ray starts outside sphere - return false; - } - // ray starts inside sphere - distance = c + a; - } else if (d2 > r2) { - // ray starts outside sphere - distance = a - c; - } else { - // ray starts inside sphere - distance = a + c; - } - return true; - } else if (typeA == Shape::CAPSULE_SHAPE) { - const CapsuleShape* capsule = static_cast(shape); - float radius = capsule->getRadius(); - glm::vec3 capsuleStart, capsuleEnd; - capsule->getStartPoint(capsuleStart); - capsule->getEndPoint(capsuleEnd); - // NOTE: findRayCapsuleIntersection returns 'true' with distance = 0 when rayStart is inside capsule. - // TODO: implement the raycast to return inside surface intersection for the internal rayStart. - return findRayCapsuleIntersection(rayStart, rayDirection, capsuleStart, capsuleEnd, radius, distance); - } else if (typeA == Shape::PLANE_SHAPE) { - const PlaneShape* plane = static_cast(shape); - glm::vec3 n = plane->getNormal(); - glm::vec3 P = plane->getPosition(); - float denominator = glm::dot(n, rayDirection); - if (fabsf(denominator) < EPSILON) { - // line is parallel to plane - return glm::dot(P - rayStart, n) < EPSILON; - } else { - float d = glm::dot(P - rayStart, n) / denominator; - if (d > 0.0f) { - // ray points toward plane - distance = d; - return true; - } - } - } - return false; -} - } // namespace ShapeCollider diff --git a/libraries/shared/src/ShapeCollider.h b/libraries/shared/src/ShapeCollider.h index 308a8cf10b..8261aceaf3 100644 --- a/libraries/shared/src/ShapeCollider.h +++ b/libraries/shared/src/ShapeCollider.h @@ -157,13 +157,6 @@ namespace ShapeCollider { /// \return true if ray hits any shape in shapes bool findRayIntersectionWithShapes(const QVector shapes, const glm::vec3& startPoint, const glm::vec3& direction, float& minDistance); - /// \param shapeA pointer to shape (cannot be NULL) - /// \param startPoint beginning of ray - /// \param direction direction of ray - /// \param distance[out] distance to intersection of shape and ray - /// \return true if ray hits shapeA - bool findRayIntersectionWithShape(const Shape* shapeA, const glm::vec3& startPoint, const glm::vec3& direction, float& distance); - } // namespace ShapeCollider #endif // hifi_ShapeCollider_h diff --git a/libraries/shared/src/SphereShape.h b/libraries/shared/src/SphereShape.h index 62783ab340..e87b8acab1 100644 --- a/libraries/shared/src/SphereShape.h +++ b/libraries/shared/src/SphereShape.h @@ -29,6 +29,8 @@ public: float getRadius() const { return _boundingRadius; } void setRadius(float radius) { _boundingRadius = radius; } + + bool findRayIntersection(const glm::vec3& rayStart, const glm::vec3& rayDirection, float& distance) const; }; #endif // hifi_SphereShape_h diff --git a/tests/physics/src/ShapeColliderTests.cpp b/tests/physics/src/ShapeColliderTests.cpp index 3387ba6aba..608e012998 100644 --- a/tests/physics/src/ShapeColliderTests.cpp +++ b/tests/physics/src/ShapeColliderTests.cpp @@ -911,7 +911,7 @@ void ShapeColliderTests::rayHitsSphere() { // very simple ray along xAxis { float distance = FLT_MAX; - if (!ShapeCollider::findRayIntersectionWithShape(&sphere, rayStart, rayDirection, distance)) { + if (!sphere.findRayIntersection(rayStart, rayDirection, distance)) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should intersect sphere" << std::endl; } @@ -928,7 +928,7 @@ void ShapeColliderTests::rayHitsSphere() { rayDirection = - glm::normalize(rayStart); float distance = FLT_MAX; - if (!ShapeCollider::findRayIntersectionWithShape(&sphere, rayStart, rayDirection, distance)) { + if (!sphere.findRayIntersection(rayStart, rayDirection, distance)) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should intersect sphere" << std::endl; } @@ -958,7 +958,7 @@ void ShapeColliderTests::rayHitsSphere() { sphere.setPosition(rotation * translation); float distance = FLT_MAX; - if (!ShapeCollider::findRayIntersectionWithShape(&sphere, rayStart, rayDirection, distance)) { + if (!sphere.findRayIntersection(rayStart, rayDirection, distance)) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should intersect sphere" << std::endl; } @@ -983,7 +983,7 @@ void ShapeColliderTests::rayBarelyHitsSphere() { // very simple ray along xAxis float distance = FLT_MAX; - if (!ShapeCollider::findRayIntersectionWithShape(&sphere, rayStart, rayDirection, distance)) { + if (!sphere.findRayIntersection(rayStart, rayDirection, distance)) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should just barely hit sphere" << std::endl; } @@ -998,7 +998,7 @@ void ShapeColliderTests::rayBarelyHitsSphere() { // ...and test again distance = FLT_MAX; - if (!ShapeCollider::findRayIntersectionWithShape(&sphere, rayStart, rayDirection, distance)) { + if (!sphere.findRayIntersection(rayStart, rayDirection, distance)) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should just barely hit sphere" << std::endl; } } @@ -1018,7 +1018,7 @@ void ShapeColliderTests::rayBarelyMissesSphere() { // very simple ray along xAxis float distance = FLT_MAX; - if (ShapeCollider::findRayIntersectionWithShape(&sphere, rayStart, rayDirection, distance)) { + if (sphere.findRayIntersection(rayStart, rayDirection, distance)) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should just barely miss sphere" << std::endl; } if (distance != FLT_MAX) { @@ -1036,7 +1036,7 @@ void ShapeColliderTests::rayBarelyMissesSphere() { // ...and test again distance = FLT_MAX; - if (ShapeCollider::findRayIntersectionWithShape(&sphere, rayStart, rayDirection, distance)) { + if (sphere.findRayIntersection(rayStart, rayDirection, distance)) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should just barely miss sphere" << std::endl; } if (distance != FLT_MAX) { @@ -1056,7 +1056,7 @@ void ShapeColliderTests::rayHitsCapsule() { glm::vec3 rayStart(startDistance, 0.0f, 0.0f); glm::vec3 rayDirection(-1.0f, 0.0f, 0.0f); float distance = FLT_MAX; - if (!ShapeCollider::findRayIntersectionWithShape(&capsule, rayStart, rayDirection, distance)) { + if (!capsule.findRayIntersection(rayStart, rayDirection, distance)) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should hit capsule" << std::endl; } float expectedDistance = startDistance - radius; @@ -1068,7 +1068,7 @@ void ShapeColliderTests::rayHitsCapsule() { // toward top of cylindrical wall rayStart.y = halfHeight; distance = FLT_MAX; - if (!ShapeCollider::findRayIntersectionWithShape(&capsule, rayStart, rayDirection, distance)) { + if (!capsule.findRayIntersection(rayStart, rayDirection, distance)) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should hit capsule" << std::endl; } relativeError = fabsf(distance - expectedDistance) / startDistance; @@ -1080,7 +1080,7 @@ void ShapeColliderTests::rayHitsCapsule() { float delta = 2.0f * EPSILON; rayStart.y = halfHeight + delta; distance = FLT_MAX; - if (!ShapeCollider::findRayIntersectionWithShape(&capsule, rayStart, rayDirection, distance)) { + if (!capsule.findRayIntersection(rayStart, rayDirection, distance)) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should hit capsule" << std::endl; } relativeError = fabsf(distance - expectedDistance) / startDistance; @@ -1093,7 +1093,7 @@ void ShapeColliderTests::rayHitsCapsule() { // toward tip of top cap rayStart.y = halfHeight + radius - delta; distance = FLT_MAX; - if (!ShapeCollider::findRayIntersectionWithShape(&capsule, rayStart, rayDirection, distance)) { + if (!capsule.findRayIntersection(rayStart, rayDirection, distance)) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should hit capsule" << std::endl; } expectedDistance = startDistance - radius * sqrtf(2.0f * delta); // using small angle approximation of cosine @@ -1106,7 +1106,7 @@ void ShapeColliderTests::rayHitsCapsule() { // toward tip of bottom cap rayStart.y = - halfHeight - radius + delta; distance = FLT_MAX; - if (!ShapeCollider::findRayIntersectionWithShape(&capsule, rayStart, rayDirection, distance)) { + if (!capsule.findRayIntersection(rayStart, rayDirection, distance)) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should hit capsule" << std::endl; } expectedDistance = startDistance - radius * sqrtf(2.0f * delta); // using small angle approximation of cosine @@ -1120,7 +1120,7 @@ void ShapeColliderTests::rayHitsCapsule() { rayStart.y = 0.0f; rayStart.z = radius - delta; distance = FLT_MAX; - if (!ShapeCollider::findRayIntersectionWithShape(&capsule, rayStart, rayDirection, distance)) { + if (!capsule.findRayIntersection(rayStart, rayDirection, distance)) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should hit capsule" << std::endl; } expectedDistance = startDistance - radius * sqrtf(2.0f * delta); // using small angle approximation of cosine @@ -1150,7 +1150,7 @@ void ShapeColliderTests::rayMissesCapsule() { // over top cap rayStart.y = halfHeight + radius + delta; float distance = FLT_MAX; - if (ShapeCollider::findRayIntersectionWithShape(&capsule, rayStart, rayDirection, distance)) { + if (capsule.findRayIntersection(rayStart, rayDirection, distance)) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should miss capsule" << std::endl; } if (distance != FLT_MAX) { @@ -1160,7 +1160,7 @@ void ShapeColliderTests::rayMissesCapsule() { // below bottom cap rayStart.y = - halfHeight - radius - delta; distance = FLT_MAX; - if (ShapeCollider::findRayIntersectionWithShape(&capsule, rayStart, rayDirection, distance)) { + if (capsule.findRayIntersection(rayStart, rayDirection, distance)) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should miss capsule" << std::endl; } if (distance != FLT_MAX) { @@ -1171,7 +1171,7 @@ void ShapeColliderTests::rayMissesCapsule() { rayStart.y = 0.0f; rayStart.z = radius + delta; distance = FLT_MAX; - if (ShapeCollider::findRayIntersectionWithShape(&capsule, rayStart, rayDirection, distance)) { + if (capsule.findRayIntersection(rayStart, rayDirection, distance)) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should miss capsule" << std::endl; } if (distance != FLT_MAX) { @@ -1194,7 +1194,7 @@ void ShapeColliderTests::rayHitsPlane() { glm::vec3 rayDirection = glm::normalize(glm::vec3(1.0f, 1.0f, 1.0f)); float distance = FLT_MAX; - if (!ShapeCollider::findRayIntersectionWithShape(&plane, rayStart, rayDirection, distance)) { + if (!plane.findRayIntersection(rayStart, rayDirection, distance)) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should hit plane" << std::endl; } @@ -1215,7 +1215,7 @@ void ShapeColliderTests::rayHitsPlane() { rayDirection = rotation * rayDirection; distance = FLT_MAX; - if (!ShapeCollider::findRayIntersectionWithShape(&plane, rayStart, rayDirection, distance)) { + if (!plane.findRayIntersection(rayStart, rayDirection, distance)) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should hit plane" << std::endl; } @@ -1239,7 +1239,7 @@ void ShapeColliderTests::rayMissesPlane() { glm::vec3 rayDirection = glm::normalize(glm::vec3(-1.0f, 0.0f, -1.0f)); float distance = FLT_MAX; - if (ShapeCollider::findRayIntersectionWithShape(&plane, rayStart, rayDirection, distance)) { + if (plane.findRayIntersection(rayStart, rayDirection, distance)) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should miss plane" << std::endl; } if (distance != FLT_MAX) { @@ -1257,7 +1257,7 @@ void ShapeColliderTests::rayMissesPlane() { rayDirection = rotation * rayDirection; distance = FLT_MAX; - if (ShapeCollider::findRayIntersectionWithShape(&plane, rayStart, rayDirection, distance)) { + if (plane.findRayIntersection(rayStart, rayDirection, distance)) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should miss plane" << std::endl; } if (distance != FLT_MAX) { @@ -1271,7 +1271,7 @@ void ShapeColliderTests::rayMissesPlane() { glm::vec3 rayDirection = glm::normalize(glm::vec3(-1.0f, -1.0f, -1.0f)); float distance = FLT_MAX; - if (ShapeCollider::findRayIntersectionWithShape(&plane, rayStart, rayDirection, distance)) { + if (plane.findRayIntersection(rayStart, rayDirection, distance)) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should miss plane" << std::endl; } if (distance != FLT_MAX) { @@ -1289,7 +1289,7 @@ void ShapeColliderTests::rayMissesPlane() { rayDirection = rotation * rayDirection; distance = FLT_MAX; - if (ShapeCollider::findRayIntersectionWithShape(&plane, rayStart, rayDirection, distance)) { + if (plane.findRayIntersection(rayStart, rayDirection, distance)) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should miss plane" << std::endl; } if (distance != FLT_MAX) { From b6cecf3cfd4ef651f1fcc8120dae7880265b37e7 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 17 Jun 2014 12:50:49 -0700 Subject: [PATCH 35/37] Add SphereShape.cpp to project --- libraries/shared/src/SphereShape.cpp | 44 ++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 libraries/shared/src/SphereShape.cpp diff --git a/libraries/shared/src/SphereShape.cpp b/libraries/shared/src/SphereShape.cpp new file mode 100644 index 0000000000..49137fac43 --- /dev/null +++ b/libraries/shared/src/SphereShape.cpp @@ -0,0 +1,44 @@ +// +// SphereShape.cpp +// libraries/shared/src +// +// Created by Andrew Meadows on 2014.06.17 +// 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 "SphereShape.h" + +bool SphereShape::findRayIntersection(const glm::vec3& rayStart, const glm::vec3& rayDirection, float& distance) const { + float r2 = _boundingRadius * _boundingRadius; + + // compute closest approach (CA) + float a = glm::dot(_position - rayStart, rayDirection); // a = distance from ray-start to CA + float b2 = glm::distance2(_position, rayStart + a * rayDirection); // b2 = squared distance from sphere-center to CA + if (b2 > r2) { + // ray does not hit sphere + return false; + } + float c = sqrtf(r2 - b2); // c = distance from CA to sphere surface along rayDirection + float d2 = glm::distance2(rayStart, _position); // d2 = squared distance from sphere-center to ray-start + if (a < 0.0f) { + // ray points away from sphere-center + if (d2 > r2) { + // ray starts outside sphere + return false; + } + // ray starts inside sphere + distance = c + a; + } else if (d2 > r2) { + // ray starts outside sphere + distance = a - c; + } else { + // ray starts inside sphere + distance = a + c; + } + return true; +} From 4413049302d1a308bbb4870c4d8dff163e18402b Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Tue, 17 Jun 2014 13:05:35 -0700 Subject: [PATCH 36/37] fixed a bug in inside out ray casting returning the wrong face --- libraries/octree/src/AABox.cpp | 6 +- libraries/octree/src/AACube.cpp | 6 +- tests/octree/src/AABoxCubeTests.cpp | 100 ++++++++++++++++++++++++++++ tests/octree/src/AABoxCubeTests.h | 20 ++++++ tests/octree/src/OctreeTests.cpp | 2 +- tests/octree/src/OctreeTests.h | 2 +- tests/octree/src/main.cpp | 2 + 7 files changed, 130 insertions(+), 8 deletions(-) create mode 100644 tests/octree/src/AABoxCubeTests.cpp create mode 100644 tests/octree/src/AABoxCubeTests.h diff --git a/libraries/octree/src/AABox.cpp b/libraries/octree/src/AABox.cpp index 409b362b24..60f26a5533 100644 --- a/libraries/octree/src/AABox.cpp +++ b/libraries/octree/src/AABox.cpp @@ -225,21 +225,21 @@ bool AABox::findRayIntersection(const glm::vec3& origin, const glm::vec3& direct isWithin(origin.y + axisDistance*direction.y, _corner.y, _scale.y) && isWithin(origin.z + axisDistance*direction.z, _corner.z, _scale.z))) { distance = axisDistance; - face = direction.x > 0 ? MIN_X_FACE : MAX_X_FACE; + face = direction.x > 0 ? MAX_X_FACE : MIN_X_FACE; return true; } if ((findInsideOutIntersection(origin.y, direction.y, _corner.y, _scale.y, axisDistance) && axisDistance >= 0 && isWithin(origin.x + axisDistance*direction.x, _corner.x, _scale.x) && isWithin(origin.z + axisDistance*direction.z, _corner.z, _scale.z))) { distance = axisDistance; - face = direction.y > 0 ? MIN_Y_FACE : MAX_Y_FACE; + face = direction.y > 0 ? MAX_Y_FACE : MIN_Y_FACE; return true; } if ((findInsideOutIntersection(origin.z, direction.z, _corner.z, _scale.z, axisDistance) && axisDistance >= 0 && isWithin(origin.y + axisDistance*direction.y, _corner.y, _scale.y) && isWithin(origin.x + axisDistance*direction.x, _corner.x, _scale.x))) { distance = axisDistance; - face = direction.z > 0 ? MIN_Z_FACE : MAX_Z_FACE; + face = direction.z > 0 ? MAX_Z_FACE : MIN_Z_FACE; return true; } // This case is unexpected, but mimics the previous behavior for inside out intersections diff --git a/libraries/octree/src/AACube.cpp b/libraries/octree/src/AACube.cpp index 5a8839db4e..e359eac9e9 100644 --- a/libraries/octree/src/AACube.cpp +++ b/libraries/octree/src/AACube.cpp @@ -215,21 +215,21 @@ bool AACube::findRayIntersection(const glm::vec3& origin, const glm::vec3& direc isWithin(origin.y + axisDistance*direction.y, _corner.y, _scale) && isWithin(origin.z + axisDistance*direction.z, _corner.z, _scale))) { distance = axisDistance; - face = direction.x > 0 ? MIN_X_FACE : MAX_X_FACE; + face = direction.x > 0 ? MAX_X_FACE : MIN_X_FACE; return true; } if ((findInsideOutIntersection(origin.y, direction.y, _corner.y, _scale, axisDistance) && axisDistance >= 0 && isWithin(origin.x + axisDistance*direction.x, _corner.x, _scale) && isWithin(origin.z + axisDistance*direction.z, _corner.z, _scale))) { distance = axisDistance; - face = direction.y > 0 ? MIN_Y_FACE : MAX_Y_FACE; + face = direction.y > 0 ? MAX_Y_FACE : MIN_Y_FACE; return true; } if ((findInsideOutIntersection(origin.z, direction.z, _corner.z, _scale, axisDistance) && axisDistance >= 0 && isWithin(origin.y + axisDistance*direction.y, _corner.y, _scale) && isWithin(origin.x + axisDistance*direction.x, _corner.x, _scale))) { distance = axisDistance; - face = direction.z > 0 ? MIN_Z_FACE : MAX_Z_FACE; + face = direction.z > 0 ? MAX_Z_FACE : MIN_Z_FACE; return true; } // This case is unexpected, but mimics the previous behavior for inside out intersections diff --git a/tests/octree/src/AABoxCubeTests.cpp b/tests/octree/src/AABoxCubeTests.cpp new file mode 100644 index 0000000000..85787e279b --- /dev/null +++ b/tests/octree/src/AABoxCubeTests.cpp @@ -0,0 +1,100 @@ +// +// AABoxCubeTests.h +// tests/octree/src +// +// Created by Brad Hefta-Gaub on 06/04/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 +// + +#include + +#include +#include + +#include "AABoxCubeTests.h" + +void AABoxCubeTests::AABoxCubeTests() { + qDebug() << "******************************************************************************************"; + qDebug() << "AABoxCubeTests::AABoxCubeTests()"; + + { + qDebug() << "Test 1: AABox.findRayIntersection() inside out MIN_X_FACE"; + + glm::vec3 corner(0.0f, 0.0f, 0.0f); + float size = 1.0f; + + AABox box(corner, size); + glm::vec3 origin(0.5f, 0.5f, 0.5f); + glm::vec3 direction(-1.0f, 0.0f, 0.0f); + float distance; + BoxFace face; + + bool intersects = box.findRayIntersection(origin, direction, distance, face); + + if (intersects && distance == 0.5f && face == MIN_X_FACE) { + qDebug() << "Test 1: PASSED"; + } else { + qDebug() << "intersects=" << intersects << "expected=" << true; + qDebug() << "distance=" << distance << "expected=" << 0.5f; + qDebug() << "face=" << face << "expected=" << MIN_X_FACE; + + } + } + + { + qDebug() << "Test 2: AABox.findRayIntersection() inside out MAX_X_FACE"; + + glm::vec3 corner(0.0f, 0.0f, 0.0f); + float size = 1.0f; + + AABox box(corner, size); + glm::vec3 origin(0.5f, 0.5f, 0.5f); + glm::vec3 direction(1.0f, 0.0f, 0.0f); + float distance; + BoxFace face; + + bool intersects = box.findRayIntersection(origin, direction, distance, face); + + if (intersects && distance == 0.5f && face == MAX_X_FACE) { + qDebug() << "Test 2: PASSED"; + } else { + qDebug() << "intersects=" << intersects << "expected=" << true; + qDebug() << "distance=" << distance << "expected=" << 0.5f; + qDebug() << "face=" << face << "expected=" << MAX_X_FACE; + + } + } + + { + qDebug() << "Test 3: AABox.findRayIntersection() outside in"; + + glm::vec3 corner(0.5f, 0.0f, 0.0f); + float size = 0.5f; + + AABox box(corner, size); + glm::vec3 origin(0.25f, 0.25f, 0.25f); + glm::vec3 direction(1.0f, 0.0f, 0.0f); + float distance; + BoxFace face; + + bool intersects = box.findRayIntersection(origin, direction, distance, face); + + if (intersects && distance == 0.25f && face == MIN_X_FACE) { + qDebug() << "Test 3: PASSED"; + } else { + qDebug() << "intersects=" << intersects << "expected=" << true; + qDebug() << "distance=" << distance << "expected=" << 0.5f; + qDebug() << "face=" << face << "expected=" << MIN_X_FACE; + + } + } + + qDebug() << "******************************************************************************************"; +} + +void AABoxCubeTests::runAllTests() { + AABoxCubeTests(); +} diff --git a/tests/octree/src/AABoxCubeTests.h b/tests/octree/src/AABoxCubeTests.h new file mode 100644 index 0000000000..8d1ece51f8 --- /dev/null +++ b/tests/octree/src/AABoxCubeTests.h @@ -0,0 +1,20 @@ +// +// AABoxCubeTests.h +// tests/octree/src +// +// Created by Brad Hefta-Gaub on 06/04/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 +// + +#ifndef hifi_AABoxCubeTests_h +#define hifi_AABoxCubeTests_h + +namespace AABoxCubeTests { + void AABoxCubeTests(); + void runAllTests(); +} + +#endif // hifi_AABoxCubeTests_h diff --git a/tests/octree/src/OctreeTests.cpp b/tests/octree/src/OctreeTests.cpp index ddc3f2c74d..70ef97f225 100644 --- a/tests/octree/src/OctreeTests.cpp +++ b/tests/octree/src/OctreeTests.cpp @@ -1,6 +1,6 @@ // // OctreeTests.h -// tests/physics/src +// tests/octree/src // // Created by Brad Hefta-Gaub on 06/04/2014. // Copyright 2014 High Fidelity, Inc. diff --git a/tests/octree/src/OctreeTests.h b/tests/octree/src/OctreeTests.h index 53b0d9fb83..5eae6c6158 100644 --- a/tests/octree/src/OctreeTests.h +++ b/tests/octree/src/OctreeTests.h @@ -1,6 +1,6 @@ // // OctreeTests.h -// tests/physics/src +// tests/octree/src // // Created by Brad Hefta-Gaub on 06/04/2014. // Copyright 2014 High Fidelity, Inc. diff --git a/tests/octree/src/main.cpp b/tests/octree/src/main.cpp index ec3dc19e01..de7b3926ae 100644 --- a/tests/octree/src/main.cpp +++ b/tests/octree/src/main.cpp @@ -9,8 +9,10 @@ // #include "OctreeTests.h" +#include "AABoxCubeTests.h" int main(int argc, char** argv) { OctreeTests::runAllTests(); + AABoxCubeTests::runAllTests(); return 0; } From 89fbeb0b6d42e9a7c61b524c8080ad4f70161722 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Tue, 17 Jun 2014 13:09:48 -0700 Subject: [PATCH 37/37] Fix invalid remote scripts being loaded --- libraries/script-engine/src/ScriptEngine.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index a4aae61248..b0cce114a9 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -147,7 +147,12 @@ ScriptEngine::ScriptEngine(const QUrl& scriptURL, QEventLoop loop; QObject::connect(reply, SIGNAL(finished()), &loop, SLOT(quit())); loop.exec(); - _scriptContents = reply->readAll(); + if (reply->error() == QNetworkReply::NoError && reply->attribute(QNetworkRequest::HttpStatusCodeAttribute) == 200) { + _scriptContents = reply->readAll(); + } else { + qDebug() << "ERROR Loading file:" << url.toString(); + emit errorMessage("ERROR Loading file:" + url.toString()); + } } } }