From 36c14de250289f7ad681017c9e60031a07357580 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Tue, 10 Mar 2015 13:11:39 -0700 Subject: [PATCH 01/38] Add rotation to facingAvatar billboard overlays --- interface/src/ui/overlays/BillboardOverlay.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/interface/src/ui/overlays/BillboardOverlay.cpp b/interface/src/ui/overlays/BillboardOverlay.cpp index 88c097575b..3e3e823737 100644 --- a/interface/src/ui/overlays/BillboardOverlay.cpp +++ b/interface/src/ui/overlays/BillboardOverlay.cpp @@ -58,6 +58,7 @@ void BillboardOverlay::render(RenderArgs* args) { // rotate about vertical to face the camera rotation = Application::getInstance()->getCamera()->getRotation(); rotation *= glm::angleAxis(glm::pi(), glm::vec3(0.0f, 1.0f, 0.0f)); + rotation *= getRotation(); } else { rotation = getRotation(); } From 5c9ec6bdcc727c6c6486696de66c3a70863d8740 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Thu, 12 Mar 2015 11:17:07 -0700 Subject: [PATCH 02/38] Add add/delete/rename/clear events to EntitiesScriptingInterface --- .../entities/src/EntityScriptingInterface.cpp | 19 +++++++++++++++++++ .../entities/src/EntityScriptingInterface.h | 7 ++++++- libraries/entities/src/EntityTree.cpp | 2 ++ libraries/entities/src/EntityTree.h | 1 + 4 files changed, 28 insertions(+), 1 deletion(-) diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index 5ef0db57ec..639798527a 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -35,6 +35,25 @@ bool EntityScriptingInterface::canAdjustLocks() { } +void EntityScriptingInterface::setEntityTree(EntityTree* modelTree) { + if (_entityTree) { + disconnect(_entityTree, &EntityTree::addingEntity, this, &EntityScriptingInterface::addingEntity); + disconnect(_entityTree, &EntityTree::deletingEntity, this, &EntityScriptingInterface::deletingEntity); + disconnect(_entityTree, &EntityTree::changingEntityID, this, &EntityScriptingInterface::changingEntityID); + disconnect(_entityTree, &EntityTree::clearingEntities, this, &EntityScriptingInterface::clearingEntities); + } + + _entityTree = modelTree; + + if (_entityTree) { + connect(_entityTree, &EntityTree::addingEntity, this, &EntityScriptingInterface::addingEntity); + connect(_entityTree, &EntityTree::deletingEntity, this, &EntityScriptingInterface::deletingEntity); + connect(_entityTree, &EntityTree::changingEntityID, this, &EntityScriptingInterface::changingEntityID); + connect(_entityTree, &EntityTree::clearingEntities, this, &EntityScriptingInterface::clearingEntities); + } +} + + EntityItemID EntityScriptingInterface::addEntity(const EntityItemProperties& properties) { // The application will keep track of creatorTokenID diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h index bac018f2ae..9300149a98 100644 --- a/libraries/entities/src/EntityScriptingInterface.h +++ b/libraries/entities/src/EntityScriptingInterface.h @@ -58,7 +58,7 @@ public: virtual NodeType_t getServerNodeType() const { return NodeType::EntityServer; } virtual OctreeEditPacketSender* createPacketSender() { return new EntityEditPacketSender(); } - void setEntityTree(EntityTree* modelTree) { _entityTree = modelTree; } + void setEntityTree(EntityTree* modelTree); EntityTree* getEntityTree(EntityTree*) { return _entityTree; } public slots: @@ -129,6 +129,11 @@ signals: void enterEntity(const EntityItemID& entityItemID); void leaveEntity(const EntityItemID& entityItemID); + void deletingEntity(const EntityItemID& entityID); + void addingEntity(const EntityItemID& entityID); + void changingEntityID(const EntityItemID& oldEntityID, const EntityItemID& newEntityID); + void clearingEntities(); + private: void queueEntityMessage(PacketType packetType, EntityItemID entityID, const EntityItemProperties& properties); diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index e952618c9f..e5a7fbee2f 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -37,6 +37,8 @@ EntityTreeElement* EntityTree::createNewElement(unsigned char * octalCode) { } void EntityTree::eraseAllOctreeElements(bool createNewRoot) { + emit clearingEntities(); + // this would be a good place to clean up our entities... if (_simulation) { _simulation->lock(); diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index 226bfa873a..5126682a99 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -163,6 +163,7 @@ signals: void addingEntity(const EntityItemID& entityID); void entityScriptChanging(const EntityItemID& entityItemID); void changingEntityID(const EntityItemID& oldEntityID, const EntityItemID& newEntityID); + void clearingEntities(); private: From d82598327b1fd2dfde849bc34854f260539b43ff Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Thu, 12 Mar 2015 11:18:32 -0700 Subject: [PATCH 03/38] Add light overlays --- examples/editEntities.js | 29 ++++- examples/libraries/lightOverlayManager.js | 123 ++++++++++++++++++++++ 2 files changed, 149 insertions(+), 3 deletions(-) create mode 100644 examples/libraries/lightOverlayManager.js diff --git a/examples/editEntities.js b/examples/editEntities.js index 689f73eed4..993e6cbb4d 100644 --- a/examples/editEntities.js +++ b/examples/editEntities.js @@ -29,12 +29,15 @@ Script.include([ "libraries/entityCameraTool.js", "libraries/gridTool.js", "libraries/entityList.js", + "libraries/lightOverlayManager.js", ]); var selectionDisplay = SelectionDisplay; var selectionManager = SelectionManager; var entityPropertyDialogBox = EntityPropertyDialogBox; +var lightOverlayManager = new LightOverlayManager(); + var cameraManager = new CameraManager(); var grid = Grid(); @@ -45,6 +48,7 @@ var entityListTool = EntityListTool(); selectionManager.addEventListener(function() { selectionDisplay.updateHandles(); + lightOverlayManager.updatePositions(); }); var windowDimensions = Controller.getViewportDimensions(); @@ -444,12 +448,31 @@ function rayPlaneIntersection(pickRay, point, normal) { function findClickedEntity(event) { var pickRay = Camera.computePickRay(event.x, event.y); - var foundIntersection = Entities.findRayIntersection(pickRay, true); // want precision picking + var entityResult = Entities.findRayIntersection(pickRay, true); // want precision picking + var lightResult = lightOverlayManager.findRayIntersection(pickRay); + lightResult.accurate = true; - if (!foundIntersection.accurate) { + var result; + + if (!entityResult.intersects && !lightResult.intersects) { + return null; + } else if (entityResult.intersects && !lightResult.intersects) { + result = entityResult; + } else if (!entityResult.intersects && lightResult.intersects) { + result = lightResult; + } else { + if (entityResult.distance < lightResult.distance) { + result = entityResult; + } else { + result = lightResult; + } + } + + if (!result.accurate) { return null; } - var foundEntity = foundIntersection.entityID; + + var foundEntity = result.entityID; if (!foundEntity.isKnownID) { var identify = Entities.identifyEntity(foundEntity); diff --git a/examples/libraries/lightOverlayManager.js b/examples/libraries/lightOverlayManager.js new file mode 100644 index 0000000000..1b90ee0a02 --- /dev/null +++ b/examples/libraries/lightOverlayManager.js @@ -0,0 +1,123 @@ +var POINT_LIGHT_URL = "http://s3.amazonaws.com/hifi-public/images/tools/point-light.svg"; +var SPOT_LIGHT_URL = "http://s3.amazonaws.com/hifi-public/images/tools/spot-light.svg"; + +LightOverlayManager = function() { + var self = this; + + // List of all created overlays + var allOverlays = []; + + // List of overlays not currently being used + var unusedOverlays = []; + + // Map from EntityItemID.id to overlay id + var entityOverlays = {}; + + // Map from EntityItemID.id to EntityItemID object + var entityIDs = {}; + + this.updatePositions = function(ids) { + for (var id in entityIDs) { + var entityID = entityIDs[id]; + var properties = Entities.getEntityProperties(entityID); + Overlays.editOverlay(entityOverlays[entityID.id], { + position: properties.position + }); + } + }; + + this.findRayIntersection = function(pickRay) { + var result = Overlays.findRayIntersection(pickRay); + var found = false; + + if (result.intersects) { + for (var id in entityOverlays) { + if (result.overlayID == entityOverlays[id]) { + result.entityID = entityIDs[id]; + found = true; + break; + } + } + + if (!found) { + result.intersects = false; + } + } + + return result; + }; + + // Allocate or get an unused overlay + function getOverlay() { + if (unusedOverlays.length == 0) { + var overlay = Overlays.addOverlay("billboard", { + }); + allOverlays.push(overlay); + } else { + var overlay = unusedOverlays.pop(); + }; + return overlay; + } + + function releaseOverlay(overlay) { + unusedOverlays.push(overlay); + Overlays.editOverlay(overlay, { visible: false }); + } + + function addEntity(entityID) { + var properties = Entities.getEntityProperties(entityID); + if (properties.type == "Light" && !(entityID.id in entityOverlays)) { + var overlay = getOverlay(); + entityOverlays[entityID.id] = overlay; + entityIDs[entityID.id] = entityID; + Overlays.editOverlay(overlay, { + position: properties.position, + url: properties.isSpotlight ? SPOT_LIGHT_URL : POINT_LIGHT_URL, + rotation: Quat.fromPitchYawRollDegrees(0, 0, 270), + visible: true, + alpha: 0.9, + color: { red: 255, green: 255, blue: 255 } + }); + } + } + + function deleteEntity(entityID) { + if (entityID.id in entityOverlays) { + releaseOverlay(entityOverlays[entityID.id]); + delete entityOverlays[entityID.id]; + } + } + + function changeEntityID(oldEntityID, newEntityID) { + entityOverlays[newEntityID.id] = entityOverlays[oldEntityID.id]; + entityIDs[newEntityID.id] = newEntityID; + + delete entityOverlays[oldEntityID.id]; + delete entityIDs[oldEntityID.id]; + } + + function clearEntities() { + for (var id in entityOverlays) { + releaseOverlay(entityOverlays[id]); + } + entityOverlays = {}; + entityIDs = {}; + } + + Entities.addingEntity.connect(addEntity); + Entities.changingEntityID.connect(changeEntityID); + Entities.deletingEntity.connect(deleteEntity); + Entities.clearingEntities.connect(clearEntities); + + // Add existing entities + var ids = Entities.findEntities(MyAvatar.position, 100); + for (var i = 0; i < ids.length; i++) { + addEntity(ids[i]); + } + + Script.scriptEnding.connect(function() { + for (var i = 0; i < allOverlays.length; i++) { + Overlays.deleteOverlay(allOverlays[i]); + } + }); +}; From ccbbba3def6d2c951936508dffb10a2afcd0fae6 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Thu, 12 Mar 2015 11:18:42 -0700 Subject: [PATCH 04/38] Update light default dimensions --- examples/editEntities.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/examples/editEntities.js b/examples/editEntities.js index 993e6cbb4d..cad086beb9 100644 --- a/examples/editEntities.js +++ b/examples/editEntities.js @@ -74,6 +74,8 @@ var DEFAULT_DIMENSIONS = { z: DEFAULT_DIMENSION }; +var DEFAULT_LIGHT_DIMENSIONS = Vec3.multiply(20, DEFAULT_DIMENSIONS); + var MENU_INSPECT_TOOL_ENABLED = "Inspect Tool"; var MENU_AUTO_FOCUS_ON_SELECT = "Auto Focus on Select"; var MENU_EASE_ON_FOCUS = "Ease Orientation on Focus"; @@ -333,8 +335,8 @@ var toolBar = (function () { if (position.x > 0 && position.y > 0 && position.z > 0) { placingEntityID = Entities.addEntity({ type: "Light", - position: grid.snapToSurface(grid.snapToGrid(position, false, DEFAULT_DIMENSIONS), DEFAULT_DIMENSIONS), - dimensions: DEFAULT_DIMENSIONS, + position: grid.snapToSurface(grid.snapToGrid(position, false, DEFAULT_LIGHT_DIMENSIONS), DEFAULT_LIGHT_DIMENSIONS), + dimensions: DEFAULT_LIGHT_DIMENSIONS, isSpotlight: false, diffuseColor: { red: 255, green: 255, blue: 255 }, ambientColor: { red: 255, green: 255, blue: 255 }, From c13ba4a31823e1046d33a5bc18927205ff288aa1 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Thu, 12 Mar 2015 11:38:29 -0700 Subject: [PATCH 05/38] Add toggling of light visibility --- examples/editEntities.js | 1 + examples/libraries/lightOverlayManager.js | 13 ++++++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/examples/editEntities.js b/examples/editEntities.js index cad086beb9..81b62ac0f0 100644 --- a/examples/editEntities.js +++ b/examples/editEntities.js @@ -205,6 +205,7 @@ var toolBar = (function () { } } toolBar.selectTool(activeButton, isActive); + lightOverlayManager.setVisible(isActive); }; var RESIZE_INTERVAL = 50; diff --git a/examples/libraries/lightOverlayManager.js b/examples/libraries/lightOverlayManager.js index 1b90ee0a02..9fe451ee13 100644 --- a/examples/libraries/lightOverlayManager.js +++ b/examples/libraries/lightOverlayManager.js @@ -4,6 +4,8 @@ var SPOT_LIGHT_URL = "http://s3.amazonaws.com/hifi-public/images/tools/spot-ligh LightOverlayManager = function() { var self = this; + var visible = false; + // List of all created overlays var allOverlays = []; @@ -47,6 +49,15 @@ LightOverlayManager = function() { return result; }; + this.setVisible = function(isVisible) { + if (visible != isVisible) { + visible = isVisible; + for (var id in entityOverlays) { + Overlays.editOverlay(entityOverlays[id], { visible: visible }); + } + } + }; + // Allocate or get an unused overlay function getOverlay() { if (unusedOverlays.length == 0) { @@ -74,7 +85,7 @@ LightOverlayManager = function() { position: properties.position, url: properties.isSpotlight ? SPOT_LIGHT_URL : POINT_LIGHT_URL, rotation: Quat.fromPitchYawRollDegrees(0, 0, 270), - visible: true, + visible: visible, alpha: 0.9, color: { red: 255, green: 255, blue: 255 } }); From 4335a41002cf33cab9985be4ac5c1ac0de8430c7 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Thu, 12 Mar 2015 11:38:51 -0700 Subject: [PATCH 06/38] Update editEntities to call setActive on init --- examples/editEntities.js | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/editEntities.js b/examples/editEntities.js index 81b62ac0f0..e787f71f0f 100644 --- a/examples/editEntities.js +++ b/examples/editEntities.js @@ -178,6 +178,7 @@ var toolBar = (function () { visible: true }); + that.setActive(false); } that.setActive = function(active) { From 2bedd1cfc7bee8b11b3fce9158906fa5476de4cf Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Thu, 12 Mar 2015 12:39:06 -0700 Subject: [PATCH 07/38] Move editEntities.js to edit.js --- examples/defaultScripts.js | 2 +- examples/{editEntities.js => edit.js} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename examples/{editEntities.js => edit.js} (100%) diff --git a/examples/defaultScripts.js b/examples/defaultScripts.js index 6b57bf18dd..05ffb0bd3f 100644 --- a/examples/defaultScripts.js +++ b/examples/defaultScripts.js @@ -9,7 +9,7 @@ // Script.load("progress.js"); -Script.load("editEntities.js"); +Script.load("edit.js"); Script.load("selectAudioDevice.js"); Script.load("controllers/hydra/hydraMove.js"); Script.load("headMove.js"); diff --git a/examples/editEntities.js b/examples/edit.js similarity index 100% rename from examples/editEntities.js rename to examples/edit.js From df8864f9b096da9448c480586a0edfa861ed0e0a Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 12 Mar 2015 15:04:51 -0700 Subject: [PATCH 08/38] Revert "make sure UserActivityLogger::close() waits for the pending log message to complete" This reverts commit 3bd3f5a6d22ca68d508c90a541015ab6c122c581. --- libraries/networking/src/AccountManager.cpp | 12 ------------ libraries/networking/src/AccountManager.h | 4 ---- libraries/networking/src/UserActivityLogger.cpp | 4 +--- 3 files changed, 1 insertion(+), 19 deletions(-) diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp index e71c80efc8..2a809f2a7c 100644 --- a/libraries/networking/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -19,7 +19,6 @@ #include #include #include -#include #include #include @@ -300,8 +299,6 @@ void AccountManager::processReply() { passErrorToCallback(requestReply); } delete requestReply; - - emit replyFinished(); } void AccountManager::passSuccessToCallback(QNetworkReply* requestReply) { @@ -342,15 +339,6 @@ void AccountManager::passErrorToCallback(QNetworkReply* requestReply) { } } -void AccountManager::waitForAllPendingReplies() { - while (_pendingCallbackMap.size() > 0) { - QEventLoop loop; - QObject::connect(this, &AccountManager::replyFinished, &loop, &QEventLoop::quit); - loop.exec(); - } -} - - void AccountManager::persistAccountToSettings() { if (_shouldPersistToSettingsFile) { // store this access token into the local settings diff --git a/libraries/networking/src/AccountManager.h b/libraries/networking/src/AccountManager.h index 22d070fbe6..2c9a441db1 100644 --- a/libraries/networking/src/AccountManager.h +++ b/libraries/networking/src/AccountManager.h @@ -72,8 +72,6 @@ public: void requestProfile(); DataServerAccountInfo& getAccountInfo() { return _accountInfo; } - - void waitForAllPendingReplies(); public slots: void requestAccessToken(const QString& login, const QString& password); @@ -95,8 +93,6 @@ signals: void loginFailed(); void logoutComplete(); void balanceChanged(qint64 newBalance); - void replyFinished(); - private slots: void processReply(); void handleKeypairGenerationError(); diff --git a/libraries/networking/src/UserActivityLogger.cpp b/libraries/networking/src/UserActivityLogger.cpp index f74ea99c1e..f2646369c1 100644 --- a/libraries/networking/src/UserActivityLogger.cpp +++ b/libraries/networking/src/UserActivityLogger.cpp @@ -61,7 +61,7 @@ void UserActivityLogger::logAction(QString action, QJsonObject details, JSONCall params.errorCallbackReceiver = this; params.errorCallbackMethod = "requestError"; } - + accountManager.authenticatedRequest(USER_ACTIVITY_URL, QNetworkAccessManager::PostOperation, params, @@ -89,8 +89,6 @@ void UserActivityLogger::launch(QString applicationVersion) { void UserActivityLogger::close() { const QString ACTION_NAME = "close"; logAction(ACTION_NAME, QJsonObject()); - - AccountManager::getInstance().waitForAllPendingReplies(); } void UserActivityLogger::changedDisplayName(QString displayName) { From 0b55a0f123dc7ebfec579bfd119659394bfef6e2 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 12 Mar 2015 15:06:44 -0700 Subject: [PATCH 09/38] remove the close event from UserActivityLogger --- interface/src/Application.cpp | 4 ---- libraries/networking/src/UserActivityLogger.cpp | 5 ----- libraries/networking/src/UserActivityLogger.h | 2 +- 3 files changed, 1 insertion(+), 10 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 72c17ed09b..3457000fdb 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -573,10 +573,6 @@ void Application::cleanupBeforeQuit() { _settingsThread.quit(); saveSettings(); _window->saveGeometry(); - - // TODO: now that this is in cleanupBeforeQuit do we really need it to stop and force - // an event loop to send the packet? - UserActivityLogger::getInstance().close(); // let the avatar mixer know we're out MyAvatar::sendKillAvatar(); diff --git a/libraries/networking/src/UserActivityLogger.cpp b/libraries/networking/src/UserActivityLogger.cpp index f2646369c1..64828708b2 100644 --- a/libraries/networking/src/UserActivityLogger.cpp +++ b/libraries/networking/src/UserActivityLogger.cpp @@ -86,11 +86,6 @@ void UserActivityLogger::launch(QString applicationVersion) { logAction(ACTION_NAME, actionDetails); } -void UserActivityLogger::close() { - const QString ACTION_NAME = "close"; - logAction(ACTION_NAME, QJsonObject()); -} - void UserActivityLogger::changedDisplayName(QString displayName) { const QString ACTION_NAME = "changed_display_name"; QJsonObject actionDetails; diff --git a/libraries/networking/src/UserActivityLogger.h b/libraries/networking/src/UserActivityLogger.h index 295ad5ee8d..2811be86a8 100644 --- a/libraries/networking/src/UserActivityLogger.h +++ b/libraries/networking/src/UserActivityLogger.h @@ -30,7 +30,7 @@ public slots: void logAction(QString action, QJsonObject details = QJsonObject(), JSONCallbackParameters params = JSONCallbackParameters()); void launch(QString applicationVersion); - void close(); + void changedDisplayName(QString displayName); void changedModel(QString typeOfModel, QString modelURL); void changedDomain(QString domainURL); From bc6ac9081fa42ed94fc89d28bc773f373bd28ff8 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 12 Mar 2015 15:08:05 -0700 Subject: [PATCH 10/38] remove the launch activity from UserActivityLogger --- interface/src/Application.cpp | 1 - libraries/networking/src/UserActivityLogger.cpp | 9 --------- libraries/networking/src/UserActivityLogger.h | 2 -- 3 files changed, 12 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 3457000fdb..2b990ec09b 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -407,7 +407,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : // set the account manager's root URL and trigger a login request if we don't have the access token accountManager.setAuthURL(DEFAULT_NODE_AUTH_URL); - UserActivityLogger::getInstance().launch(applicationVersion()); // once the event loop has started, check and signal for an access token QMetaObject::invokeMethod(&accountManager, "checkAndSignalForAccessToken", Qt::QueuedConnection); diff --git a/libraries/networking/src/UserActivityLogger.cpp b/libraries/networking/src/UserActivityLogger.cpp index 64828708b2..d21f524576 100644 --- a/libraries/networking/src/UserActivityLogger.cpp +++ b/libraries/networking/src/UserActivityLogger.cpp @@ -77,15 +77,6 @@ void UserActivityLogger::requestError(QNetworkReply& errorReply) { qDebug() << errorReply.error() << "-" << errorReply.errorString(); } -void UserActivityLogger::launch(QString applicationVersion) { - const QString ACTION_NAME = "launch"; - QJsonObject actionDetails; - QString VERSION_KEY = "version"; - actionDetails.insert(VERSION_KEY, applicationVersion); - - logAction(ACTION_NAME, actionDetails); -} - void UserActivityLogger::changedDisplayName(QString displayName) { const QString ACTION_NAME = "changed_display_name"; QJsonObject actionDetails; diff --git a/libraries/networking/src/UserActivityLogger.h b/libraries/networking/src/UserActivityLogger.h index 2811be86a8..edc17a6472 100644 --- a/libraries/networking/src/UserActivityLogger.h +++ b/libraries/networking/src/UserActivityLogger.h @@ -29,8 +29,6 @@ public slots: void disable(bool disable); void logAction(QString action, QJsonObject details = QJsonObject(), JSONCallbackParameters params = JSONCallbackParameters()); - void launch(QString applicationVersion); - void changedDisplayName(QString displayName); void changedModel(QString typeOfModel, QString modelURL); void changedDomain(QString domainURL); From 0fe843db019d644b4534e05f91c47e8d33d2d468 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 12 Mar 2015 15:53:05 -0700 Subject: [PATCH 11/38] Revert "remove the launch activity from UserActivityLogger" This reverts commit bc6ac9081fa42ed94fc89d28bc773f373bd28ff8. --- interface/src/Application.cpp | 1 + libraries/networking/src/UserActivityLogger.cpp | 9 +++++++++ libraries/networking/src/UserActivityLogger.h | 2 ++ 3 files changed, 12 insertions(+) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 2b990ec09b..3457000fdb 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -407,6 +407,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : // set the account manager's root URL and trigger a login request if we don't have the access token accountManager.setAuthURL(DEFAULT_NODE_AUTH_URL); + UserActivityLogger::getInstance().launch(applicationVersion()); // once the event loop has started, check and signal for an access token QMetaObject::invokeMethod(&accountManager, "checkAndSignalForAccessToken", Qt::QueuedConnection); diff --git a/libraries/networking/src/UserActivityLogger.cpp b/libraries/networking/src/UserActivityLogger.cpp index d21f524576..64828708b2 100644 --- a/libraries/networking/src/UserActivityLogger.cpp +++ b/libraries/networking/src/UserActivityLogger.cpp @@ -77,6 +77,15 @@ void UserActivityLogger::requestError(QNetworkReply& errorReply) { qDebug() << errorReply.error() << "-" << errorReply.errorString(); } +void UserActivityLogger::launch(QString applicationVersion) { + const QString ACTION_NAME = "launch"; + QJsonObject actionDetails; + QString VERSION_KEY = "version"; + actionDetails.insert(VERSION_KEY, applicationVersion); + + logAction(ACTION_NAME, actionDetails); +} + void UserActivityLogger::changedDisplayName(QString displayName) { const QString ACTION_NAME = "changed_display_name"; QJsonObject actionDetails; diff --git a/libraries/networking/src/UserActivityLogger.h b/libraries/networking/src/UserActivityLogger.h index edc17a6472..2811be86a8 100644 --- a/libraries/networking/src/UserActivityLogger.h +++ b/libraries/networking/src/UserActivityLogger.h @@ -29,6 +29,8 @@ public slots: void disable(bool disable); void logAction(QString action, QJsonObject details = QJsonObject(), JSONCallbackParameters params = JSONCallbackParameters()); + void launch(QString applicationVersion); + void changedDisplayName(QString displayName); void changedModel(QString typeOfModel, QString modelURL); void changedDomain(QString domainURL); From 165199c69d5ea0a013e9c169f8a0d8041cff8a26 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Thu, 12 Mar 2015 15:55:31 -0700 Subject: [PATCH 12/38] Update light overlay size --- examples/libraries/lightOverlayManager.js | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/libraries/lightOverlayManager.js b/examples/libraries/lightOverlayManager.js index 9fe451ee13..8032d77c49 100644 --- a/examples/libraries/lightOverlayManager.js +++ b/examples/libraries/lightOverlayManager.js @@ -87,6 +87,7 @@ LightOverlayManager = function() { rotation: Quat.fromPitchYawRollDegrees(0, 0, 270), visible: visible, alpha: 0.9, + scale: 0.5, color: { red: 255, green: 255, blue: 255 } }); } From a3c2df5c775dfc6cc1bc4b7669917212fbb018f5 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Thu, 12 Mar 2015 16:16:29 -0700 Subject: [PATCH 13/38] Add menu option to turn on/off showing lights in edit mode --- examples/editEntities.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/examples/editEntities.js b/examples/editEntities.js index fe91189404..72d4cfa0c1 100644 --- a/examples/editEntities.js +++ b/examples/editEntities.js @@ -79,10 +79,12 @@ var DEFAULT_LIGHT_DIMENSIONS = Vec3.multiply(20, DEFAULT_DIMENSIONS); var MENU_INSPECT_TOOL_ENABLED = "Inspect Tool"; var MENU_AUTO_FOCUS_ON_SELECT = "Auto Focus on Select"; var MENU_EASE_ON_FOCUS = "Ease Orientation on Focus"; +var MENU_SHOW_LIGHTS_IN_EDIT_MODE = "Show Lights in Edit Mode"; var SETTING_INSPECT_TOOL_ENABLED = "inspectToolEnabled"; var SETTING_AUTO_FOCUS_ON_SELECT = "autoFocusOnSelect"; var SETTING_EASE_ON_FOCUS = "cameraEaseOnFocus"; +var SETTING_SHOW_LIGHTS_IN_EDIT_MODE = "showLightsInEditMode"; var INSUFFICIENT_PERMISSIONS_ERROR_MSG = "You do not have the necessary permissions to edit on this domain." @@ -222,7 +224,7 @@ var toolBar = (function () { } } toolBar.selectTool(activeButton, isActive); - lightOverlayManager.setVisible(isActive); + lightOverlayManager.setVisible(isActive && Menu.isOptionChecked(MENU_SHOW_LIGHTS_IN_EDIT_MODE)); }; // Sets visibility of tool buttons, excluding the power button @@ -758,6 +760,8 @@ function setupModelMenus() { isCheckable: true, isChecked: Settings.getValue(SETTING_AUTO_FOCUS_ON_SELECT) == "true" }); Menu.addMenuItem({ menuName: "View", menuItemName: MENU_EASE_ON_FOCUS, afterItem: MENU_AUTO_FOCUS_ON_SELECT, isCheckable: true, isChecked: Settings.getValue(SETTING_EASE_ON_FOCUS) == "true" }); + Menu.addMenuItem({ menuName: "View", menuItemName: MENU_SHOW_LIGHTS_IN_EDIT_MODE, afterItem: MENU_EASE_ON_FOCUS, + isCheckable: true, isChecked: Settings.getValue(SETTING_SHOW_LIGHTS_IN_EDIT_MODE) == "true" }); Entities.setLightsArePickable(false); } @@ -784,11 +788,13 @@ function cleanupModelMenus() { Menu.removeMenuItem("View", MENU_INSPECT_TOOL_ENABLED); Menu.removeMenuItem("View", MENU_AUTO_FOCUS_ON_SELECT); Menu.removeMenuItem("View", MENU_EASE_ON_FOCUS); + Menu.removeMenuItem("View", MENU_SHOW_LIGHTS_IN_EDIT_MODE); } Script.scriptEnding.connect(function() { Settings.setValue(SETTING_AUTO_FOCUS_ON_SELECT, Menu.isOptionChecked(MENU_AUTO_FOCUS_ON_SELECT)); Settings.setValue(SETTING_EASE_ON_FOCUS, Menu.isOptionChecked(MENU_EASE_ON_FOCUS)); + Settings.setValue(SETTING_SHOW_LIGHTS_IN_EDIT_MODE, Menu.isOptionChecked(MENU_SHOW_LIGHTS_IN_EDIT_MODE)); progressDialog.cleanup(); toolBar.cleanup(); @@ -865,6 +871,8 @@ function handeMenuEvent(menuItem) { } } else if (menuItem == "Entity List...") { entityListTool.toggleVisible(); + } else if (menuItem == MENU_SHOW_LIGHTS_IN_EDIT_MODE) { + lightOverlayManager.setVisible(isActive && Menu.isOptionChecked(MENU_SHOW_LIGHTS_IN_EDIT_MODE)); } tooltip.show(false); } From 2fad4153a9a333a17a73c3bf94627ff6b9be4710 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 12 Mar 2015 16:34:27 -0700 Subject: [PATCH 14/38] remove unused Avatar::getBoundingRadius() --- interface/src/avatar/Avatar.cpp | 5 ----- interface/src/avatar/Avatar.h | 3 --- 2 files changed, 8 deletions(-) diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index a0fa21c674..75d77b780a 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -1026,11 +1026,6 @@ float Avatar::getHeadHeight() const { return DEFAULT_HEAD_HEIGHT; } -float Avatar::getBoundingRadius() const { - // TODO: also use head model when computing the avatar's bounding radius - return _skeletonModel.getBoundingRadius(); -} - float Avatar::getPelvisFloatingHeight() const { return -_skeletonModel.getBindExtents().minimum.y; } diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h index e9a21af98e..0cde800be0 100644 --- a/interface/src/avatar/Avatar.h +++ b/interface/src/avatar/Avatar.h @@ -133,9 +133,6 @@ public: virtual void applyCollision(const glm::vec3& contactPoint, const glm::vec3& penetration) { } - /// \return bounding radius of avatar - virtual float getBoundingRadius() const; - Q_INVOKABLE void setSkeletonOffset(const glm::vec3& offset); Q_INVOKABLE glm::vec3 getSkeletonOffset() { return _skeletonOffset; } virtual glm::vec3 getSkeletonPosition() const; From 1f3a267b16499a37d620c583ea7382db925b3670 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 12 Mar 2015 16:34:47 -0700 Subject: [PATCH 15/38] compute correct bounding shape dimensions --- interface/src/avatar/SkeletonModel.cpp | 62 +++++++++++++------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index d083116ecd..093c4b1668 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -659,57 +659,57 @@ void SkeletonModel::buildShapes() { void SkeletonModel::computeBoundingShape(const FBXGeometry& geometry) { // compute default joint transforms int numStates = _jointStates.size(); + assert(numStates == _shapes.size()); QVector transforms; transforms.fill(glm::mat4(), numStates); - // compute the default transforms - for (int i = 0; i < numStates; i++) { - JointState& state = _jointStates[i]; - const FBXJoint& joint = state.getFBXJoint(); - int parentIndex = joint.parentIndex; - if (parentIndex == -1) { - transforms[i] = _jointStates[i].getTransform(); - continue; - } - - glm::quat modifiedRotation = joint.preRotation * joint.rotation * joint.postRotation; - transforms[i] = transforms[parentIndex] * glm::translate(joint.translation) - * joint.preTransform * glm::mat4_cast(modifiedRotation) * joint.postTransform; - // TODO: Andrew to harvest transforms here to move shapes to correct positions so that - // bounding capsule calculations below are correct. - } - // compute bounding box that encloses all shapes Extents totalExtents; totalExtents.reset(); totalExtents.addPoint(glm::vec3(0.0f)); - for (int i = 0; i < _shapes.size(); i++) { + for (int i = 0; i < numStates; i++) { + // compute the default transform of this joint + JointState& state = _jointStates[i]; + const FBXJoint& joint = state.getFBXJoint(); + int parentIndex = joint.parentIndex; + if (parentIndex == -1) { + transforms[i] = _jointStates[i].getTransform(); + } else { + glm::quat modifiedRotation = joint.preRotation * joint.rotation * joint.postRotation; + transforms[i] = transforms[parentIndex] * glm::translate(joint.translation) + * joint.preTransform * glm::mat4_cast(modifiedRotation) * joint.postTransform; + } + Shape* shape = _shapes[i]; if (!shape) { continue; } + + // Each joint with a shape contributes to the totalExtents: a box + // that contains the sphere centered at the end of the joint with radius of the bone. + // TODO: skip hand and arm shapes for bounding box calculation - Extents shapeExtents; - shapeExtents.reset(); - glm::vec3 localPosition = shape->getTranslation(); + glm::vec3 jointPosition = extractTranslation(transforms[i]); + int type = shape->getType(); + float radius = 0.0f; if (type == CAPSULE_SHAPE) { // add the two furthest surface points of the capsule CapsuleShape* capsule = static_cast(shape); - glm::vec3 axis; - capsule->computeNormalizedAxis(axis); float radius = capsule->getRadius(); - float halfHeight = capsule->getHalfHeight(); - axis = halfHeight * axis + glm::vec3(radius); - - shapeExtents.addPoint(localPosition + axis); - shapeExtents.addPoint(localPosition - axis); + glm::vec3 axis(radius); + Extents shapeExtents; + shapeExtents.reset(); + shapeExtents.addPoint(jointPosition + axis); + shapeExtents.addPoint(jointPosition - axis); totalExtents.addExtents(shapeExtents); } else if (type == SPHERE_SHAPE) { float radius = shape->getBoundingRadius(); - glm::vec3 axis = glm::vec3(radius); - shapeExtents.addPoint(localPosition + axis); - shapeExtents.addPoint(localPosition - axis); + glm::vec3 axis(radius); + Extents shapeExtents; + shapeExtents.reset(); + shapeExtents.addPoint(jointPosition + axis); + shapeExtents.addPoint(jointPosition - axis); totalExtents.addExtents(shapeExtents); } } From 6bd857c4ea9ce6309dd91d6cdc3209940b7372fe Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 12 Mar 2015 16:35:12 -0700 Subject: [PATCH 16/38] update Avatar in PhysicsEngine on load new body --- interface/src/Application.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index a7a6c8c094..9979c54f11 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -4091,5 +4091,7 @@ void Application::checkSkeleton() { _myAvatar->setSkeletonModelURL(DEFAULT_BODY_MODEL_URL); _myAvatar->sendIdentityPacket(); + } else { + _physicsEngine.setAvatarData(_myAvatar); } } From d1b977ec9eea25a21dfd3fbc01dd4a766f160d1c Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 12 Mar 2015 16:36:08 -0700 Subject: [PATCH 17/38] remove warnings about unused variables and remind us to impelement unit test for tetrahedron mass props --- tests/physics/src/MeshInfoTests.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/physics/src/MeshInfoTests.cpp b/tests/physics/src/MeshInfoTests.cpp index fa841f3930..221ffa117c 100644 --- a/tests/physics/src/MeshInfoTests.cpp +++ b/tests/physics/src/MeshInfoTests.cpp @@ -104,15 +104,16 @@ void MeshInfoTests::testWithTetrahedronAsMesh(){ glm::vec3 p2(52.61236, 5.00000, -5.38580); glm::vec3 p3(2.00000, 5.00000, 3.00000); glm::vec3 centroid(15.92492, 0.782813, 3.72962); - float volume = 1873.233236f; - float inertia_a = 43520.33257f; - //actual should be 194711.28938f. But for some reason it becomes 194711.296875 during + /* TODO: actually test inertia/volume calculations here + //float volume = 1873.233236f; //runtime due to how floating points are stored. + float inertia_a = 43520.33257f; float inertia_b = 194711.289f; float inertia_c = 191168.76173f; float inertia_aa = 4417.66150f; float inertia_bb = -46343.16662f; float inertia_cc = 11996.20119f; + */ std::cout << std::setprecision(12); vector vertices = { p0, p1, p2, p3 }; vector triangles = { 0, 2, 1, 0, 3, 2, 0, 1, 3, 1, 2, 3 }; @@ -129,6 +130,7 @@ void MeshInfoTests::testWithTetrahedronAsMesh(){ p2 -= centroid; p3 -= centroid; } + void MeshInfoTests::testWithCube(){ glm::vec3 p0(1.0, -1.0, -1.0); glm::vec3 p1(1.0, -1.0, 1.0); @@ -232,4 +234,4 @@ void MeshInfoTests::runAllTests(){ testWithTetrahedronAsMesh(); testWithUnitCube(); testWithCube(); -} \ No newline at end of file +} From a1a12465da8e270961337f4c76bf86083cfc3147 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 12 Mar 2015 16:36:41 -0700 Subject: [PATCH 18/38] able to update avatar controller in PhysicsEngine --- libraries/avatars/src/AvatarData.cpp | 7 +- libraries/avatars/src/AvatarData.h | 5 +- libraries/physics/src/CharacterController.cpp | 706 ++++++++++++++++++ libraries/physics/src/CharacterController.h | 172 +++++ libraries/physics/src/PhysicsEngine.cpp | 106 ++- libraries/physics/src/PhysicsEngine.h | 16 +- 6 files changed, 971 insertions(+), 41 deletions(-) create mode 100644 libraries/physics/src/CharacterController.cpp create mode 100644 libraries/physics/src/CharacterController.h diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 3ab8f38fe6..6d7395754c 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -32,6 +32,9 @@ quint64 DEFAULT_FILTERED_LOG_EXPIRY = 2 * USECS_PER_SECOND; using namespace std; +const glm::vec3 DEFAULT_LOCAL_AABOX_CORNER(-0.5f); +const glm::vec3 DEFAULT_LOCAL_AABOX_SCALE(1.0f); + AvatarData::AvatarData() : _sessionUUID(), _position(0.0f), @@ -55,9 +58,9 @@ AvatarData::AvatarData() : _errorLogExpiry(0), _owningAvatarMixer(), _lastUpdateTimer(), - _velocity(0.0f) + _velocity(0.0f), + _localAABox(DEFAULT_LOCAL_AABOX_CORNER, DEFAULT_LOCAL_AABOX_SCALE) { - } AvatarData::~AvatarData() { diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 02b2c364c6..5490938e30 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -50,6 +50,7 @@ typedef unsigned long long quint64; #include +#include "AABox.h" #include "HandData.h" #include "HeadData.h" #include "Player.h" @@ -296,8 +297,7 @@ public: QElapsedTimer& getLastUpdateTimer() { return _lastUpdateTimer; } - virtual float getBoundingRadius() const { return 1.0f; } - + const AABox& getLocalAABox() const { return _localAABox; } const Referential* getReferential() const { return _referential; } void togglePhysicsEnabled() { _enablePhysics = !_enablePhysics; } @@ -410,6 +410,7 @@ private: QReadWriteLock _lock; bool _enablePhysics = false; + AABox _localAABox; }; Q_DECLARE_METATYPE(AvatarData*) diff --git a/libraries/physics/src/CharacterController.cpp b/libraries/physics/src/CharacterController.cpp new file mode 100644 index 0000000000..5173b368c1 --- /dev/null +++ b/libraries/physics/src/CharacterController.cpp @@ -0,0 +1,706 @@ +/* +Bullet Continuous Collision Detection and Physics Library +Copyright (c) 2003-2008 Erwin Coumans http://bulletphysics.com + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. + If you use this software in a product, an acknowledgment in the product documentation would be appreciated + but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ + + +//#include + +#include "BulletCollision/CollisionDispatch/btGhostObject.h" + +#include "CharacterController.h" + + +// static helper method +static btVector3 getNormalizedVector(const btVector3& v) { + // NOTE: check the length first, then normalize + // --> avoids assert when trying to normalize zero-length vectors + btScalar vLength = v.length(); + if (vLength < FLT_EPSILON) { + return btVector3(0.0f, 0.0f, 0.0f); + } + btVector3 n = v; + n /= vLength; + return n; +} + +///@todo Interact with dynamic objects, +///Ride kinematicly animated platforms properly +///More realistic (or maybe just a config option) falling +/// -> Should integrate falling velocity manually and use that in stepDown() +///Support jumping +///Support ducking + +/* This callback is unused, but we're keeping it around just in case we figure out how to use it. +class btKinematicClosestNotMeRayResultCallback : public btCollisionWorld::ClosestRayResultCallback +{ +public: +btKinematicClosestNotMeRayResultCallback(btCollisionObject* me) : btCollisionWorld::ClosestRayResultCallback(btVector3(0.0, 0.0, 0.0), btVector3(0.0, 0.0, 0.0)) +{ +m_me = me; +} + +virtual btScalar addSingleResult(btCollisionWorld::LocalRayResult& rayResult, bool normalInWorldSpace) +{ +if(rayResult.m_collisionObject == m_me) +return 1.0; + +return ClosestRayResultCallback::addSingleResult(rayResult, normalInWorldSpace); +} +protected: +btCollisionObject* m_me; +}; +*/ + +class btKinematicClosestNotMeConvexResultCallback : public btCollisionWorld::ClosestConvexResultCallback +{ + public: + btKinematicClosestNotMeConvexResultCallback(btCollisionObject* me, const btVector3& up, btScalar minSlopeDot) + : btCollisionWorld::ClosestConvexResultCallback(btVector3(0.0, 0.0, 0.0), btVector3(0.0, 0.0, 0.0)) + , m_me(me) + , m_up(up) + , m_minSlopeDot(minSlopeDot) + { + } + + virtual btScalar addSingleResult(btCollisionWorld::LocalConvexResult& convexResult, bool normalInWorldSpace) { + if (convexResult.m_hitCollisionObject == m_me) { + return btScalar(1.0); + } + + if (!convexResult.m_hitCollisionObject->hasContactResponse()) { + return btScalar(1.0); + } + + btVector3 hitNormalWorld; + if (normalInWorldSpace) { + hitNormalWorld = convexResult.m_hitNormalLocal; + } else { + ///need to transform normal into worldspace + hitNormalWorld = convexResult.m_hitCollisionObject->getWorldTransform().getBasis()*convexResult.m_hitNormalLocal; + } + + btScalar dotUp = m_up.dot(hitNormalWorld); + if (dotUp < m_minSlopeDot) { + return btScalar(1.0); + } + + return ClosestConvexResultCallback::addSingleResult(convexResult, normalInWorldSpace); + } + +protected: + btCollisionObject* m_me; + const btVector3 m_up; + btScalar m_minSlopeDot; +}; + +/* + * Returns the reflection direction of a ray going 'direction' hitting a surface with normal 'normal' + * + * from: http://www-cs-students.stanford.edu/~adityagp/final/node3.html + */ +btVector3 CharacterController::computeReflectionDirection(const btVector3& direction, const btVector3& normal) +{ + return direction - (btScalar(2.0) * direction.dot(normal)) * normal; +} + +/* + * Returns the portion of 'direction' that is parallel to 'normal' + */ +btVector3 CharacterController::parallelComponent(const btVector3& direction, const btVector3& normal) +{ + btScalar magnitude = direction.dot(normal); + return normal * magnitude; +} + +/* + * Returns the portion of 'direction' that is perpindicular to 'normal' + */ +btVector3 CharacterController::perpindicularComponent(const btVector3& direction, const btVector3& normal) +{ + return direction - parallelComponent(direction, normal); +} + +CharacterController::CharacterController( + btPairCachingGhostObject* ghostObject, + btConvexShape* convexShape, + btScalar stepHeight, + int upAxis) { + m_upAxis = upAxis; + m_addedMargin = 0.02; + m_walkDirection.setValue(0,0,0); + m_useGhostObjectSweepTest = true; + m_ghostObject = ghostObject; + m_stepHeight = stepHeight; + m_turnAngle = btScalar(0.0); + m_convexShape = convexShape; + m_useWalkDirection = true; // use walk direction by default, legacy behavior + m_velocityTimeInterval = 0.0; + m_verticalVelocity = 0.0; + m_verticalOffset = 0.0; + m_gravity = 9.8 * 3 ; // 3G acceleration. + m_fallSpeed = 55.0; // Terminal velocity of a sky diver in m/s. + m_jumpSpeed = 10.0; // ? + m_wasOnGround = false; + m_wasJumping = false; + m_interpolateUp = true; + setMaxSlope(btRadians(45.0)); + m_currentStepOffset = 0; + + // internal state data members + full_drop = false; + bounce_fix = false; +} + +CharacterController::~CharacterController() { +} + +btPairCachingGhostObject* CharacterController::getGhostObject() { + return m_ghostObject; +} + +bool CharacterController::recoverFromPenetration(btCollisionWorld* collisionWorld) { + // Here we must refresh the overlapping paircache as the penetrating movement itself or the + // previous recovery iteration might have used setWorldTransform and pushed us into an object + // that is not in the previous cache contents from the last timestep, as will happen if we + // are pushed into a new AABB overlap. Unhandled this means the next convex sweep gets stuck. + // + // Do this by calling the broadphase's setAabb with the moved AABB, this will update the broadphase + // paircache and the ghostobject's internal paircache at the same time. /BW + + btVector3 minAabb, maxAabb; + m_convexShape->getAabb(m_ghostObject->getWorldTransform(), minAabb, maxAabb); + collisionWorld->getBroadphase()->setAabb(m_ghostObject->getBroadphaseHandle(), + minAabb, + maxAabb, + collisionWorld->getDispatcher()); + + bool penetration = false; + + collisionWorld->getDispatcher()->dispatchAllCollisionPairs(m_ghostObject->getOverlappingPairCache(), collisionWorld->getDispatchInfo(), collisionWorld->getDispatcher()); + + m_currentPosition = m_ghostObject->getWorldTransform().getOrigin(); + + btScalar maxPen = btScalar(0.0); + for (int i = 0; i < m_ghostObject->getOverlappingPairCache()->getNumOverlappingPairs(); i++) { + m_manifoldArray.resize(0); + + btBroadphasePair* collisionPair = &m_ghostObject->getOverlappingPairCache()->getOverlappingPairArray()[i]; + + btCollisionObject* obj0 = static_cast(collisionPair->m_pProxy0->m_clientObject); + btCollisionObject* obj1 = static_cast(collisionPair->m_pProxy1->m_clientObject); + + if ((obj0 && !obj0->hasContactResponse()) || (obj1 && !obj1->hasContactResponse())) { + continue; + } + + if (collisionPair->m_algorithm) { + collisionPair->m_algorithm->getAllContactManifolds(m_manifoldArray); + } + + for (int j=0;jgetBody0() == m_ghostObject ? btScalar(-1.0) : btScalar(1.0); + for (int p=0;pgetNumContacts();p++) { + const btManifoldPoint&pt = manifold->getContactPoint(p); + + btScalar dist = pt.getDistance(); + + if (dist < 0.0) { + if (dist < maxPen) { + maxPen = dist; + m_touchingNormal = pt.m_normalWorldOnB * directionSign;//?? + + } + m_currentPosition += pt.m_normalWorldOnB * directionSign * dist * btScalar(0.2); + penetration = true; + } else { + //printf("touching %f\n", dist); + } + } + + //manifold->clearManifold(); + } + } + btTransform newTrans = m_ghostObject->getWorldTransform(); + newTrans.setOrigin(m_currentPosition); + m_ghostObject->setWorldTransform(newTrans); + //printf("m_touchingNormal = %f,%f,%f\n", m_touchingNormal[0], m_touchingNormal[1], m_touchingNormal[2]); + return penetration; +} + +void CharacterController::stepUp( btCollisionWorld* world) { + // phase 1: up + btTransform start, end; + m_targetPosition = m_currentPosition + getUpAxisDirections()[m_upAxis] * (m_stepHeight + (m_verticalOffset > 0.f?m_verticalOffset:0.f)); + + start.setIdentity(); + end.setIdentity(); + + /* FIXME: Handle penetration properly */ + start.setOrigin(m_currentPosition + getUpAxisDirections()[m_upAxis] * (m_convexShape->getMargin() + m_addedMargin)); + end.setOrigin(m_targetPosition); + + btKinematicClosestNotMeConvexResultCallback callback(m_ghostObject, -getUpAxisDirections()[m_upAxis], btScalar(0.7071)); + callback.m_collisionFilterGroup = getGhostObject()->getBroadphaseHandle()->m_collisionFilterGroup; + callback.m_collisionFilterMask = getGhostObject()->getBroadphaseHandle()->m_collisionFilterMask; + + if (m_useGhostObjectSweepTest) { + m_ghostObject->convexSweepTest(m_convexShape, start, end, callback, world->getDispatchInfo().m_allowedCcdPenetration); + } + else { + world->convexSweepTest(m_convexShape, start, end, callback); + } + + if (callback.hasHit()) { + // Only modify the position if the hit was a slope and not a wall or ceiling. + if (callback.m_hitNormalWorld.dot(getUpAxisDirections()[m_upAxis]) > 0.0) { + // we moved up only a fraction of the step height + m_currentStepOffset = m_stepHeight * callback.m_closestHitFraction; + if (m_interpolateUp == true) { + m_currentPosition.setInterpolate3 (m_currentPosition, m_targetPosition, callback.m_closestHitFraction); + } else { + m_currentPosition = m_targetPosition; + } + } + m_verticalVelocity = 0.0; + m_verticalOffset = 0.0; + } else { + m_currentStepOffset = m_stepHeight; + m_currentPosition = m_targetPosition; + } +} + +void CharacterController::updateTargetPositionBasedOnCollision(const btVector3& hitNormal, btScalar tangentMag, btScalar normalMag) { + btVector3 movementDirection = m_targetPosition - m_currentPosition; + btScalar movementLength = movementDirection.length(); + if (movementLength>SIMD_EPSILON) { + movementDirection.normalize(); + + btVector3 reflectDir = computeReflectionDirection(movementDirection, hitNormal); + reflectDir.normalize(); + + btVector3 parallelDir, perpindicularDir; + + parallelDir = parallelComponent(reflectDir, hitNormal); + perpindicularDir = perpindicularComponent(reflectDir, hitNormal); + + m_targetPosition = m_currentPosition; + //if (tangentMag != 0.0) { + if (0) { + btVector3 parComponent = parallelDir * btScalar(tangentMag*movementLength); + //printf("parComponent=%f,%f,%f\n", parComponent[0], parComponent[1], parComponent[2]); + m_targetPosition += parComponent; + } + + if (normalMag != 0.0) { + btVector3 perpComponent = perpindicularDir * btScalar(normalMag*movementLength); + //printf("perpComponent=%f,%f,%f\n", perpComponent[0], perpComponent[1], perpComponent[2]); + m_targetPosition += perpComponent; + } + } else { + //printf("movementLength don't normalize a zero vector\n"); + } +} + +void CharacterController::stepForwardAndStrafe( btCollisionWorld* collisionWorld, const btVector3& walkMove) { + //printf("m_normalizedDirection=%f,%f,%f\n", + // m_normalizedDirection[0], m_normalizedDirection[1], m_normalizedDirection[2]); + // phase 2: forward and strafe + btTransform start, end; + m_targetPosition = m_currentPosition + walkMove; + + start.setIdentity(); + end.setIdentity(); + + btScalar fraction = 1.0; + btScalar distance2 = (m_currentPosition-m_targetPosition).length2(); + //printf("distance2=%f\n", distance2); + + if (m_touchingContact) { + if (m_normalizedDirection.dot(m_touchingNormal) > btScalar(0.0)) { + //interferes with step movement + //updateTargetPositionBasedOnCollision(m_touchingNormal); + } + } + + int maxIter = 10; + + while (fraction > btScalar(0.01) && maxIter-- > 0) { + start.setOrigin(m_currentPosition); + end.setOrigin(m_targetPosition); + btVector3 sweepDirNegative(m_currentPosition - m_targetPosition); + + btKinematicClosestNotMeConvexResultCallback callback(m_ghostObject, sweepDirNegative, btScalar(0.0)); + callback.m_collisionFilterGroup = getGhostObject()->getBroadphaseHandle()->m_collisionFilterGroup; + callback.m_collisionFilterMask = getGhostObject()->getBroadphaseHandle()->m_collisionFilterMask; + + + btScalar margin = m_convexShape->getMargin(); + m_convexShape->setMargin(margin + m_addedMargin); + + + if (m_useGhostObjectSweepTest) { + m_ghostObject->convexSweepTest(m_convexShape, start, end, callback, collisionWorld->getDispatchInfo().m_allowedCcdPenetration); + } else { + collisionWorld->convexSweepTest(m_convexShape, start, end, callback, collisionWorld->getDispatchInfo().m_allowedCcdPenetration); + } + + m_convexShape->setMargin(margin); + + + fraction -= callback.m_closestHitFraction; + + if (callback.hasHit()) { + // we moved only a fraction + //btScalar hitDistance; + //hitDistance = (callback.m_hitPointWorld - m_currentPosition).length(); + + //m_currentPosition.setInterpolate3 (m_currentPosition, m_targetPosition, callback.m_closestHitFraction); + + updateTargetPositionBasedOnCollision(callback.m_hitNormalWorld); + btVector3 currentDir = m_targetPosition - m_currentPosition; + distance2 = currentDir.length2(); + if (distance2 > SIMD_EPSILON) { + currentDir.normalize(); + /* See Quake2: "If velocity is against original velocity, stop ead to avoid tiny oscilations in sloping corners." */ + if (currentDir.dot(m_normalizedDirection) <= btScalar(0.0)) { + break; + } + } else { + //printf("currentDir: don't normalize a zero vector\n"); + break; + } + } else { + // we moved whole way + m_currentPosition = m_targetPosition; + } + + //if (callback.m_closestHitFraction == 0.f) { + // break; + //} + + } +} + +void CharacterController::stepDown( btCollisionWorld* collisionWorld, btScalar dt) { + btTransform start, end, end_double; + bool runonce = false; + + // phase 3: down + /*btScalar additionalDownStep = (m_wasOnGround && !onGround()) ? m_stepHeight : 0.0; + btVector3 step_drop = getUpAxisDirections()[m_upAxis] * (m_currentStepOffset + additionalDownStep); + btScalar downVelocity = (additionalDownStep == 0.0 && m_verticalVelocity<0.0?-m_verticalVelocity:0.0) * dt; + btVector3 gravity_drop = getUpAxisDirections()[m_upAxis] * downVelocity; + m_targetPosition -= (step_drop + gravity_drop);*/ + + btVector3 orig_position = m_targetPosition; + + btScalar downVelocity = (m_verticalVelocity<0.f?-m_verticalVelocity:0.f) * dt; + + if (downVelocity > 0.0 && downVelocity > m_fallSpeed && (m_wasOnGround || !m_wasJumping)) { + downVelocity = m_fallSpeed; + } + + btVector3 step_drop = getUpAxisDirections()[m_upAxis] * (m_currentStepOffset + downVelocity); + m_targetPosition -= step_drop; + + btKinematicClosestNotMeConvexResultCallback callback(m_ghostObject, getUpAxisDirections()[m_upAxis], m_maxSlopeCosine); + callback.m_collisionFilterGroup = getGhostObject()->getBroadphaseHandle()->m_collisionFilterGroup; + callback.m_collisionFilterMask = getGhostObject()->getBroadphaseHandle()->m_collisionFilterMask; + + btKinematicClosestNotMeConvexResultCallback callback2 (m_ghostObject, getUpAxisDirections()[m_upAxis], m_maxSlopeCosine); + callback2.m_collisionFilterGroup = getGhostObject()->getBroadphaseHandle()->m_collisionFilterGroup; + callback2.m_collisionFilterMask = getGhostObject()->getBroadphaseHandle()->m_collisionFilterMask; + + while (1) { + start.setIdentity(); + end.setIdentity(); + + end_double.setIdentity(); + + start.setOrigin(m_currentPosition); + end.setOrigin(m_targetPosition); + + //set double test for 2x the step drop, to check for a large drop vs small drop + end_double.setOrigin(m_targetPosition - step_drop); + + if (m_useGhostObjectSweepTest) { + m_ghostObject->convexSweepTest(m_convexShape, start, end, callback, collisionWorld->getDispatchInfo().m_allowedCcdPenetration); + + if (!callback.hasHit()) { + //test a double fall height, to see if the character should interpolate it's fall (full) or not (partial) + m_ghostObject->convexSweepTest(m_convexShape, start, end_double, callback2, collisionWorld->getDispatchInfo().m_allowedCcdPenetration); + } + } else { + collisionWorld->convexSweepTest(m_convexShape, start, end, callback, collisionWorld->getDispatchInfo().m_allowedCcdPenetration); + + if (!callback.hasHit()) { + //test a double fall height, to see if the character should interpolate it's fall (large) or not (small) + collisionWorld->convexSweepTest(m_convexShape, start, end_double, callback2, collisionWorld->getDispatchInfo().m_allowedCcdPenetration); + } + } + + btScalar downVelocity2 = (m_verticalVelocity<0.f?-m_verticalVelocity:0.f) * dt; + bool has_hit = false; + if(bounce_fix == true) { + has_hit = callback.hasHit() || callback2.hasHit(); + } else { + has_hit = callback2.hasHit(); + } + + if(downVelocity2 > 0.0 && downVelocity2 < m_stepHeight && has_hit == true && runonce == false + && (m_wasOnGround || !m_wasJumping)) { + //redo the velocity calculation when falling a small amount, for fast stairs motion + //for larger falls, use the smoother/slower interpolated movement by not touching the target position + + m_targetPosition = orig_position; + downVelocity = m_stepHeight; + + btVector3 step_drop = getUpAxisDirections()[m_upAxis] * (m_currentStepOffset + downVelocity); + m_targetPosition -= step_drop; + runonce = true; + continue; //re-run previous tests + } + break; + } + + if (callback.hasHit() || runonce == true) { + // we dropped a fraction of the height -> hit floor + + btScalar fraction = (m_currentPosition.getY() - callback.m_hitPointWorld.getY()) / 2; + + //printf("hitpoint: %g - pos %g\n", callback.m_hitPointWorld.getY(), m_currentPosition.getY()); + + if (bounce_fix == true) { + if (full_drop == true) { + m_currentPosition.setInterpolate3 (m_currentPosition, m_targetPosition, callback.m_closestHitFraction); + } else { + //due to errors in the closestHitFraction variable when used with large polygons, calculate the hit fraction manually + m_currentPosition.setInterpolate3 (m_currentPosition, m_targetPosition, fraction); + } + } + else + m_currentPosition.setInterpolate3 (m_currentPosition, m_targetPosition, callback.m_closestHitFraction); + + full_drop = false; + + m_verticalVelocity = 0.0; + m_verticalOffset = 0.0; + m_wasJumping = false; + } else { + // we dropped the full height + + full_drop = true; + + if (bounce_fix == true) { + downVelocity = (m_verticalVelocity<0.f?-m_verticalVelocity:0.f) * dt; + if (downVelocity > m_fallSpeed && (m_wasOnGround || !m_wasJumping)) { + m_targetPosition += step_drop; //undo previous target change + downVelocity = m_fallSpeed; + step_drop = getUpAxisDirections()[m_upAxis] * (m_currentStepOffset + downVelocity); + m_targetPosition -= step_drop; + } + } + //printf("full drop - %g, %g\n", m_currentPosition.getY(), m_targetPosition.getY()); + + m_currentPosition = m_targetPosition; + } +} + + + +void CharacterController::setWalkDirection(const btVector3& walkDirection) { + m_useWalkDirection = true; + m_walkDirection = walkDirection; + m_normalizedDirection = getNormalizedVector(m_walkDirection); +} + + + +void CharacterController::setVelocityForTimeInterval(const btVector3& velocity, btScalar timeInterval) { + //printf("setVelocity!\n"); + //printf(" interval: %f\n", timeInterval); + //printf(" velocity: (%f, %f, %f)\n", velocity.x(), velocity.y(), velocity.z()); + + m_useWalkDirection = false; + m_walkDirection = velocity; + m_normalizedDirection = getNormalizedVector(m_walkDirection); + m_velocityTimeInterval += timeInterval; +} + +void CharacterController::reset( btCollisionWorld* collisionWorld ) { + m_verticalVelocity = 0.0; + m_verticalOffset = 0.0; + m_wasOnGround = false; + m_wasJumping = false; + m_walkDirection.setValue(0,0,0); + m_velocityTimeInterval = 0.0; + + //clear pair cache + btHashedOverlappingPairCache *cache = m_ghostObject->getOverlappingPairCache(); + while (cache->getOverlappingPairArray().size() > 0) { + cache->removeOverlappingPair(cache->getOverlappingPairArray()[0].m_pProxy0, cache->getOverlappingPairArray()[0].m_pProxy1, collisionWorld->getDispatcher()); + } +} + +void CharacterController::warp(const btVector3& origin) { + btTransform xform; + xform.setIdentity(); + xform.setOrigin(origin); + m_ghostObject->setWorldTransform(xform); +} + + +void CharacterController::preStep( btCollisionWorld* collisionWorld) { + int numPenetrationLoops = 0; + m_touchingContact = false; + while (recoverFromPenetration(collisionWorld)) { + numPenetrationLoops++; + m_touchingContact = true; + if (numPenetrationLoops > 4) { + //printf("character could not recover from penetration = %d\n", numPenetrationLoops); + break; + } + } + + m_currentPosition = m_ghostObject->getWorldTransform().getOrigin(); + m_targetPosition = m_currentPosition; + //printf("m_targetPosition=%f,%f,%f\n", m_targetPosition[0], m_targetPosition[1], m_targetPosition[2]); +} + +void CharacterController::playerStep( btCollisionWorld* collisionWorld, btScalar dt) { + //printf("playerStep(): "); + //printf(" dt = %f", dt); + + // quick check... + if (!m_useWalkDirection && m_velocityTimeInterval <= 0.0) { + //printf("\n"); + return; // no motion + } + + m_wasOnGround = onGround(); + + // Update fall velocity. + m_verticalVelocity -= m_gravity * dt; + if (m_verticalVelocity > 0.0 && m_verticalVelocity > m_jumpSpeed) { + m_verticalVelocity = m_jumpSpeed; + } + if (m_verticalVelocity < 0.0 && btFabs(m_verticalVelocity) > btFabs(m_fallSpeed)) { + m_verticalVelocity = -btFabs(m_fallSpeed); + } + m_verticalOffset = m_verticalVelocity * dt; + + + btTransform xform; + xform = m_ghostObject->getWorldTransform(); + + //printf("walkDirection(%f,%f,%f)\n", walkDirection[0], walkDirection[1], walkDirection[2]); + //printf("walkSpeed=%f\n", walkSpeed); + + stepUp (collisionWorld); + if (m_useWalkDirection) { + stepForwardAndStrafe(collisionWorld, m_walkDirection); + } else { + //printf(" time: %f", m_velocityTimeInterval); + // still have some time left for moving! + btScalar dtMoving = + (dt < m_velocityTimeInterval) ? dt : m_velocityTimeInterval; + m_velocityTimeInterval -= dt; + + // how far will we move while we are moving? + btVector3 move = m_walkDirection * dtMoving; + + //printf(" dtMoving: %f", dtMoving); + + // okay, step + stepForwardAndStrafe(collisionWorld, move); + } + stepDown(collisionWorld, dt); + + //printf("\n"); + + xform.setOrigin(m_currentPosition); + m_ghostObject->setWorldTransform(xform); +} + +void CharacterController::setFallSpeed(btScalar fallSpeed) { + m_fallSpeed = fallSpeed; +} + +void CharacterController::setJumpSpeed(btScalar jumpSpeed) { + m_jumpSpeed = jumpSpeed; +} + +void CharacterController::setMaxJumpHeight(btScalar maxJumpHeight) { + m_maxJumpHeight = maxJumpHeight; +} + +bool CharacterController::canJump() const { + return onGround(); +} + +void CharacterController::jump() { + if (!canJump()) { + return; + } + + m_verticalVelocity = m_jumpSpeed; + m_wasJumping = true; + +#if 0 + currently no jumping. + btTransform xform; + m_rigidBody->getMotionState()->getWorldTransform(xform); + btVector3 up = xform.getBasis()[1]; + up.normalize(); + btScalar magnitude = (btScalar(1.0)/m_rigidBody->getInvMass()) * btScalar(8.0); + m_rigidBody->applyCentralImpulse (up * magnitude); +#endif +} + +void CharacterController::setGravity(btScalar gravity) { + m_gravity = gravity; +} + +btScalar CharacterController::getGravity() const { + return m_gravity; +} + +void CharacterController::setMaxSlope(btScalar slopeRadians) { + m_maxSlopeRadians = slopeRadians; + m_maxSlopeCosine = btCos(slopeRadians); +} + +btScalar CharacterController::getMaxSlope() const { + return m_maxSlopeRadians; +} + +bool CharacterController::onGround() const { + return m_verticalVelocity == 0.0 && m_verticalOffset == 0.0; +} + +btVector3* CharacterController::getUpAxisDirections() { + static btVector3 sUpAxisDirection[3] = { btVector3(1.0f, 0.0f, 0.0f), btVector3(0.0f, 1.0f, 0.0f), btVector3(0.0f, 0.0f, 1.0f) }; + + return sUpAxisDirection; +} + +void CharacterController::debugDraw(btIDebugDraw* debugDrawer) { +} + +void CharacterController::setUpInterpolate(bool value) { + m_interpolateUp = value; +} diff --git a/libraries/physics/src/CharacterController.h b/libraries/physics/src/CharacterController.h new file mode 100644 index 0000000000..301253b2bd --- /dev/null +++ b/libraries/physics/src/CharacterController.h @@ -0,0 +1,172 @@ +/* +Bullet Continuous Collision Detection and Physics Library +Copyright (c) 2003-2008 Erwin Coumans http://bulletphysics.com + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. + If you use this software in a product, an acknowledgment in the product documentation would be appreciated but + is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ + + +#ifndef hifi_CharacterController_h +#define hifi_CharacterController_h + +#include +#include +#include + + +class btConvexShape; +class btCollisionWorld; +class btCollisionDispatcher; +class btPairCachingGhostObject; + +///CharacterController is a custom version of btKinematicCharacterController + +///btKinematicCharacterController is an object that supports a sliding motion in a world. +///It uses a ghost object and convex sweep test to test for upcoming collisions. This is combined with discrete collision detection to recover from penetrations. +///Interaction between btKinematicCharacterController and dynamic rigid bodies needs to be explicity implemented by the user. + +ATTRIBUTE_ALIGNED16(class) CharacterController : public btCharacterControllerInterface +{ +protected: + + btScalar m_halfHeight; + + btPairCachingGhostObject* m_ghostObject; + btConvexShape* m_convexShape;//is also in m_ghostObject, but it needs to be convex, so we store it here to avoid upcast + + btScalar m_verticalVelocity; + btScalar m_verticalOffset; + btScalar m_fallSpeed; + btScalar m_jumpSpeed; + btScalar m_maxJumpHeight; + btScalar m_maxSlopeRadians; // Slope angle that is set (used for returning the exact value) + btScalar m_maxSlopeCosine; // Cosine equivalent of m_maxSlopeRadians (calculated once when set, for optimization) + btScalar m_gravity; + + btScalar m_turnAngle; + + btScalar m_stepHeight; + + btScalar m_addedMargin;//@todo: remove this and fix the code + + ///this is the desired walk direction, set by the user + btVector3 m_walkDirection; + btVector3 m_normalizedDirection; + + //some internal variables + btVector3 m_currentPosition; + btScalar m_currentStepOffset; + btVector3 m_targetPosition; + + ///keep track of the contact manifolds + btManifoldArray m_manifoldArray; + + bool m_touchingContact; + btVector3 m_touchingNormal; + + bool m_wasOnGround; + bool m_wasJumping; + bool m_useGhostObjectSweepTest; + bool m_useWalkDirection; + btScalar m_velocityTimeInterval; + int m_upAxis; + + static btVector3* getUpAxisDirections(); + bool m_interpolateUp; + bool full_drop; + bool bounce_fix; + + btVector3 computeReflectionDirection(const btVector3& direction, const btVector3& normal); + btVector3 parallelComponent(const btVector3& direction, const btVector3& normal); + btVector3 perpindicularComponent(const btVector3& direction, const btVector3& normal); + + bool recoverFromPenetration(btCollisionWorld* collisionWorld); + void stepUp(btCollisionWorld* collisionWorld); + void updateTargetPositionBasedOnCollision(const btVector3& hit_normal, btScalar tangentMag = btScalar(0.0), btScalar normalMag = btScalar(1.0)); + void stepForwardAndStrafe(btCollisionWorld* collisionWorld, const btVector3& walkMove); + void stepDown(btCollisionWorld* collisionWorld, btScalar dt); +public: + + BT_DECLARE_ALIGNED_ALLOCATOR(); + + CharacterController( + btPairCachingGhostObject* ghostObject, + btConvexShape* convexShape, + btScalar stepHeight, + int upAxis = 1); + ~CharacterController(); + + + ///btActionInterface interface + virtual void updateAction(btCollisionWorld* collisionWorld, btScalar deltaTime) { + preStep(collisionWorld); + playerStep(collisionWorld, deltaTime); + } + + ///btActionInterface interface + void debugDraw(btIDebugDraw* debugDrawer); + + void setUpAxis(int axis) { + if (axis < 0) + axis = 0; + if (axis > 2) + axis = 2; + m_upAxis = axis; + } + + /// This should probably be called setPositionIncrementPerSimulatorStep. + /// This is neither a direction nor a velocity, but the amount to + /// increment the position each simulation iteration, regardless + /// of dt. + /// This call will reset any velocity set by setVelocityForTimeInterval(). + virtual void setWalkDirection(const btVector3& walkDirection); + + /// Caller provides a velocity with which the character should move for + /// the given time period. After the time period, velocity is reset + /// to zero. + /// This call will reset any walk direction set by setWalkDirection(). + /// Negative time intervals will result in no motion. + virtual void setVelocityForTimeInterval(const btVector3& velocity, + btScalar timeInterval); + + void reset(btCollisionWorld* collisionWorld ); + void warp(const btVector3& origin); + + void preStep(btCollisionWorld* collisionWorld); + void playerStep(btCollisionWorld* collisionWorld, btScalar dt); + + void setFallSpeed(btScalar fallSpeed); + void setJumpSpeed(btScalar jumpSpeed); + void setMaxJumpHeight(btScalar maxJumpHeight); + bool canJump() const; + + void jump(); + + void setGravity(btScalar gravity); + btScalar getGravity() const; + + /// The max slope determines the maximum angle that the controller can walk up. + /// The slope angle is measured in radians. + void setMaxSlope(btScalar slopeRadians); + btScalar getMaxSlope() const; + + btPairCachingGhostObject* getGhostObject(); + void setUseGhostSweepTest(bool useGhostObjectSweepTest) { + m_useGhostObjectSweepTest = useGhostObjectSweepTest; + } + + bool onGround() const; + void setUpInterpolate(bool value); +}; + +#endif // hifi_CharacterController_h diff --git a/libraries/physics/src/PhysicsEngine.cpp b/libraries/physics/src/PhysicsEngine.cpp index 634c3707a8..a46095ee0c 100644 --- a/libraries/physics/src/PhysicsEngine.cpp +++ b/libraries/physics/src/PhysicsEngine.cpp @@ -9,11 +9,12 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include + #include "PhysicsEngine.h" #include "ShapeInfoUtil.h" #include "PhysicsHelpers.h" #include "ThreadSafeDynamicsWorld.h" -#include "AvatarData.h" static uint32_t _numSubsteps; @@ -23,10 +24,12 @@ uint32_t PhysicsEngine::getNumSubsteps() { } PhysicsEngine::PhysicsEngine(const glm::vec3& offset) - : _originOffset(offset) { + : _originOffset(offset), + _avatarShapeLocalOffset(0.0f) { } PhysicsEngine::~PhysicsEngine() { + // TODO: delete engine components... if we ever plan to create more than one instance } // begin EntitySimulation overrides @@ -260,6 +263,9 @@ void PhysicsEngine::init(EntityEditPacketSender* packetSender) { _constraintSolver = new btSequentialImpulseConstraintSolver; _dynamicsWorld = new ThreadSafeDynamicsWorld(_collisionDispatcher, _broadphaseFilter, _constraintSolver, _collisionConfig); + _ghostPairCallback = new btGhostPairCallback(); + _dynamicsWorld->getPairCache()->setInternalGhostPairCallback(_ghostPairCallback); + // default gravity of the world is zero, so each object must specify its own gravity // TODO: set up gravity zones _dynamicsWorld->setGravity(btVector3(0.0f, 0.0f, 0.0f)); @@ -271,6 +277,9 @@ void PhysicsEngine::init(EntityEditPacketSender* packetSender) { } void PhysicsEngine::stepSimulation() { + // expect the engine to have an avatar (and hence: a character controller) + assert(_avatarData); + lock(); // NOTE: the grand order of operations is: // (1) relay incoming changes @@ -288,17 +297,13 @@ void PhysicsEngine::stepSimulation() { float timeStep = btMin(dt, MAX_TIMESTEP); if (_avatarData->isPhysicsEnabled()) { - _avatarGhostObject->setWorldTransform(btTransform(glmToBullet(_avatarData->getOrientation()), - glmToBullet(_avatarData->getPosition()))); - // WORKAROUND: there is a bug in the debug Bullet-2.82 libs where a zero length walk velocity will trigger - // an assert when the getNormalizedVector() helper function in btKinematicCharacterController.cpp tries to - // first normalize a vector before checking its length. Here we workaround the problem by checking the - // length first. NOTE: the character's velocity is reset to zero after each step, so when we DON'T set - // the velocity for this time interval it is the same thing as setting its velocity to zero. + // update character controller + glm::quat rotation = _avatarData->getOrientation(); + glm::vec3 position = _avatarData->getPosition() + rotation * _avatarShapeLocalOffset; + _avatarGhostObject->setWorldTransform(btTransform(glmToBullet(rotation), glmToBullet(position))); + btVector3 walkVelocity = glmToBullet(_avatarData->getVelocity()); - if (walkVelocity.length2() > FLT_EPSILON * FLT_EPSILON) { - _characterController->setVelocityForTimeInterval(walkVelocity, timeStep); - } + _characterController->setVelocityForTimeInterval(walkVelocity, timeStep); } // This is step (2). @@ -323,8 +328,10 @@ void PhysicsEngine::stepSimulation() { if (_avatarData->isPhysicsEnabled()) { const btTransform& avatarTransform = _avatarGhostObject->getWorldTransform(); - _avatarData->setOrientation(bulletToGLM(avatarTransform.getRotation())); - _avatarData->setPosition(bulletToGLM(avatarTransform.getOrigin())); + glm::quat rotation = bulletToGLM(avatarTransform.getRotation()); + glm::vec3 offset = rotation * _avatarShapeLocalOffset; + _avatarData->setOrientation(rotation); + _avatarData->setPosition(bulletToGLM(avatarTransform.getOrigin()) - offset); } unlock(); @@ -610,28 +617,69 @@ bool PhysicsEngine::updateObjectHard(btRigidBody* body, ObjectMotionState* motio void PhysicsEngine::setAvatarData(AvatarData *avatarData) { - _avatarData = avatarData; + assert(avatarData); // don't pass NULL argument + + // compute capsule dimensions + AABox box = avatarData->getLocalAABox(); + const glm::vec3& diagonal = box.getScale(); + float radius = 0.5f * sqrtf(0.5f * (diagonal.x * diagonal.x + diagonal.z * diagonal.z)); + float halfHeight = 0.5f * diagonal.y; + glm::vec3 offset = box.getCorner() + 0.5f * diagonal; + + if (!_avatarData) { + // _avatarData is being initialized + _avatarData = avatarData; + } else { + // _avatarData is being updated + assert(_avatarData == avatarData); + + // get old dimensions from shape + btCapsuleShape* capsule = static_cast(_avatarGhostObject->getCollisionShape()); + btScalar oldRadius = capsule->getRadius(); + btScalar oldHalfHeight = capsule->getHalfHeight(); + + // compare dimensions (and offset) + float radiusDelta = glm::abs(radius - oldRadius); + float heightDelta = glm::abs(halfHeight - oldHalfHeight); + if (radiusDelta < FLT_EPSILON && heightDelta < FLT_EPSILON) { + // shape hasn't changed --> nothing to do + float offsetDelta = glm::distance(offset, _avatarShapeLocalOffset); + if (offsetDelta > FLT_EPSILON) { + // if only the offset changes then we can update it --> no need to rebuild shape + _avatarShapeLocalOffset = offset; + } + return; + } + + // delete old controller and friends + _dynamicsWorld->removeCollisionObject(_avatarGhostObject); + _dynamicsWorld->removeAction(_characterController); + delete _characterController; + _characterController = NULL; + delete _avatarGhostObject; + _avatarGhostObject = NULL; + delete capsule; + } + + // set offset + _avatarShapeLocalOffset = offset; + + // build ghost, shape, and controller _avatarGhostObject = new btPairCachingGhostObject(); _avatarGhostObject->setWorldTransform(btTransform(glmToBullet(_avatarData->getOrientation()), - glmToBullet(_avatarData->getPosition()))); + glmToBullet(_avatarData->getPosition()))); + // ?TODO: use ShapeManager to get avatar's shape? + btCapsuleShape* capsule = new btCapsuleShape(radius, 2.0f * halfHeight); - // XXX these values should be computed from the character model. - btScalar characterRadius = 0.3f; - btScalar characterHeight = 1.75 - 2.0f * characterRadius; - btScalar stepHeight = btScalar(0.35); - - btConvexShape* capsule = new btCapsuleShape(characterRadius, characterHeight); _avatarGhostObject->setCollisionShape(capsule); _avatarGhostObject->setCollisionFlags(btCollisionObject::CF_CHARACTER_OBJECT); - _characterController = new btKinematicCharacterController(_avatarGhostObject, capsule, stepHeight); + const float MIN_STEP_HEIGHT = 0.35f; + btScalar stepHeight = glm::max(MIN_STEP_HEIGHT, 0.6f * halfHeight); + _characterController = new CharacterController(_avatarGhostObject, capsule, stepHeight); _dynamicsWorld->addCollisionObject(_avatarGhostObject, btBroadphaseProxy::CharacterFilter, - btBroadphaseProxy::StaticFilter | btBroadphaseProxy::DefaultFilter); + btBroadphaseProxy::StaticFilter | btBroadphaseProxy::DefaultFilter); _dynamicsWorld->addAction(_characterController); - _characterController->reset (_dynamicsWorld); - // _characterController->warp (btVector3(10.210001,-2.0306311,16.576973)); - - btGhostPairCallback* ghostPairCallback = new btGhostPairCallback(); - _dynamicsWorld->getPairCache()->setInternalGhostPairCallback(ghostPairCallback); + _characterController->reset(_dynamicsWorld); } diff --git a/libraries/physics/src/PhysicsEngine.h b/libraries/physics/src/PhysicsEngine.h index 649aec2755..acf1617b16 100644 --- a/libraries/physics/src/PhysicsEngine.h +++ b/libraries/physics/src/PhysicsEngine.h @@ -16,21 +16,19 @@ #include #include -#include #include -#include -#include -#include +//#include +#include #include #include #include "BulletUtil.h" +#include "CharacterController.h" #include "ContactInfo.h" #include "EntityMotionState.h" #include "ShapeManager.h" #include "ThreadSafeDynamicsWorld.h" -#include "AvatarData.h" const float HALF_SIMULATION_EXTENT = 512.0f; // meters @@ -106,6 +104,7 @@ private: btBroadphaseInterface* _broadphaseFilter = NULL; btSequentialImpulseConstraintSolver* _constraintSolver = NULL; ThreadSafeDynamicsWorld* _dynamicsWorld = NULL; + btGhostPairCallback* _ghostPairCallback = NULL; ShapeManager _shapeManager; glm::vec3 _originOffset; @@ -123,9 +122,10 @@ private: uint32_t _lastNumSubstepsAtUpdateInternal = 0; /// character collisions - btCharacterControllerInterface* _characterController = 0; - class btPairCachingGhostObject* _avatarGhostObject = 0; - AvatarData *_avatarData = 0; + CharacterController* _characterController = NULL; + class btPairCachingGhostObject* _avatarGhostObject = NULL; + AvatarData* _avatarData = NULL; + glm::vec3 _avatarShapeLocalOffset; }; #endif // hifi_PhysicsEngine_h From 645fc7dbc9c9594cd4c45c18985446c482c29090 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Fri, 13 Mar 2015 09:39:06 -0700 Subject: [PATCH 19/38] remove unused variable --- interface/src/avatar/SkeletonModel.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index 5dad5a0a67..4fdebd5f6f 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -692,7 +692,6 @@ void SkeletonModel::computeBoundingShape(const FBXGeometry& geometry) { glm::vec3 jointPosition = extractTranslation(transforms[i]); int type = shape->getType(); - float radius = 0.0f; if (type == CAPSULE_SHAPE) { // add the two furthest surface points of the capsule CapsuleShape* capsule = static_cast(shape); From 924ebe1e54d0ba2a67f9f8062bc1669d037a8b24 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Fri, 13 Mar 2015 09:39:47 -0700 Subject: [PATCH 20/38] propagate skeleton's bounding box into AvatarData --- interface/src/Application.cpp | 1 + interface/src/avatar/MyAvatar.cpp | 11 +++++++++++ interface/src/avatar/MyAvatar.h | 1 + libraries/avatars/src/AvatarData.h | 3 ++- 4 files changed, 15 insertions(+), 1 deletion(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 437ef3374c..9b2bec079f 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -4094,6 +4094,7 @@ void Application::checkSkeleton() { _myAvatar->setSkeletonModelURL(DEFAULT_BODY_MODEL_URL); _myAvatar->sendIdentityPacket(); } else { + _myAvatar->updateLocalAABox(); _physicsEngine.setAvatarData(_myAvatar); } } diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index d9c9ff3ad1..bdb0877cda 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -954,6 +954,17 @@ glm::vec3 MyAvatar::getSkeletonPosition() const { return Avatar::getPosition(); } +void MyAvatar::updateLocalAABox() { + const CapsuleShape& capsule = _skeletonModel.getBoundingShape(); + float radius = capsule.getRadius(); + float height = 2.0f * (capsule.getHalfHeight() + radius); + glm::vec3 offset = _skeletonModel.getBoundingShapeOffset(); + glm::vec3 corner(-radius, -0.5f * height, -radius); + corner += offset; + glm::vec3 scale(2.0f * radius, height, 2.0f * radius); + _localAABox.setBox(corner, scale); +} + QString MyAvatar::getScriptedMotorFrame() const { QString frame = "avatar"; if (_scriptedMotorFrame == SCRIPTED_MOTOR_CAMERA_FRAME) { diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 7376acfdcb..08c0228f1e 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -121,6 +121,7 @@ public: virtual void setAttachmentData(const QVector& attachmentData); virtual glm::vec3 getSkeletonPosition() const; + void updateLocalAABox(); void clearJointAnimationPriorities(); diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 5490938e30..28123124a0 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -403,6 +403,8 @@ protected: glm::vec3 _velocity; + AABox _localAABox; + private: // privatize the copy constructor and assignment operator so they cannot be called AvatarData(const AvatarData&); @@ -410,7 +412,6 @@ private: QReadWriteLock _lock; bool _enablePhysics = false; - AABox _localAABox; }; Q_DECLARE_METATYPE(AvatarData*) From 99e2d799c525d72bb344178dfa327608be4c225d Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Fri, 13 Mar 2015 09:40:32 -0700 Subject: [PATCH 21/38] correct math for capsule halfHeight --- libraries/physics/src/PhysicsEngine.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/libraries/physics/src/PhysicsEngine.cpp b/libraries/physics/src/PhysicsEngine.cpp index a46095ee0c..3e2fabfd89 100644 --- a/libraries/physics/src/PhysicsEngine.cpp +++ b/libraries/physics/src/PhysicsEngine.cpp @@ -623,7 +623,11 @@ void PhysicsEngine::setAvatarData(AvatarData *avatarData) { AABox box = avatarData->getLocalAABox(); const glm::vec3& diagonal = box.getScale(); float radius = 0.5f * sqrtf(0.5f * (diagonal.x * diagonal.x + diagonal.z * diagonal.z)); - float halfHeight = 0.5f * diagonal.y; + float halfHeight = 0.5f * diagonal.y - radius; + float MIN_HALF_HEIGHT = 0.1f; + if (halfHeight < MIN_HALF_HEIGHT) { + halfHeight = MIN_HALF_HEIGHT; + } glm::vec3 offset = box.getCorner() + 0.5f * diagonal; if (!_avatarData) { @@ -675,7 +679,7 @@ void PhysicsEngine::setAvatarData(AvatarData *avatarData) { _avatarGhostObject->setCollisionFlags(btCollisionObject::CF_CHARACTER_OBJECT); const float MIN_STEP_HEIGHT = 0.35f; - btScalar stepHeight = glm::max(MIN_STEP_HEIGHT, 0.6f * halfHeight); + btScalar stepHeight = glm::max(MIN_STEP_HEIGHT, radius + 0.5f * halfHeight); _characterController = new CharacterController(_avatarGhostObject, capsule, stepHeight); _dynamicsWorld->addCollisionObject(_avatarGhostObject, btBroadphaseProxy::CharacterFilter, From 2a84d9a6ab2944ad184778decf7d2077235088b8 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Fri, 13 Mar 2015 10:06:49 -0700 Subject: [PATCH 22/38] Fix issue with .svo's not loading when the url has parameters --- interface/src/Application.cpp | 7 ++++--- interface/src/GLCanvas.cpp | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 72c17ed09b..25077e0ac1 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -889,7 +889,7 @@ bool Application::event(QEvent* event) { if (!url.isEmpty()) { if (url.scheme() == HIFI_URL_SCHEME) { DependencyManager::get()->handleLookupString(fileEvent->url().toString()); - } else if (url.url().toLower().endsWith(SVO_EXTENSION)) { + } else if (url.path().toLower().endsWith(SVO_EXTENSION)) { emit svoImportRequested(url.url()); } } @@ -1455,10 +1455,11 @@ void Application::dropEvent(QDropEvent *event) { QString snapshotPath; const QMimeData *mimeData = event->mimeData(); foreach (QUrl url, mimeData->urls()) { - if (url.url().toLower().endsWith(SNAPSHOT_EXTENSION)) { + auto lower = url.path().toLower(); + if (lower.endsWith(SNAPSHOT_EXTENSION)) { snapshotPath = url.toLocalFile(); break; - } else if (url.url().toLower().endsWith(SVO_EXTENSION)) { + } else if (lower.endsWith(SVO_EXTENSION)) { emit svoImportRequested(url.url()); event->acceptProposedAction(); return; diff --git a/interface/src/GLCanvas.cpp b/interface/src/GLCanvas.cpp index 4ece8f0857..4587fca0f4 100644 --- a/interface/src/GLCanvas.cpp +++ b/interface/src/GLCanvas.cpp @@ -170,7 +170,7 @@ void GLCanvas::wheelEvent(QWheelEvent* event) { void GLCanvas::dragEnterEvent(QDragEnterEvent* event) { const QMimeData *mimeData = event->mimeData(); foreach (QUrl url, mimeData->urls()) { - auto lower = url.url().toLower(); + auto lower = url.path().toLower(); if (lower.endsWith(SNAPSHOT_EXTENSION) || lower.endsWith(SVO_EXTENSION)) { event->acceptProposedAction(); break; From 504dceda6b4e75f259393dc755a383d21fca5e6c Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Fri, 13 Mar 2015 10:07:12 -0700 Subject: [PATCH 23/38] Update edit.js icons --- examples/edit.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/edit.js b/examples/edit.js index c236336266..e7c8c897ac 100644 --- a/examples/edit.js +++ b/examples/edit.js @@ -126,7 +126,7 @@ var toolBar = (function () { // Hide active button for now - this may come back, so not deleting yet. activeButton = toolBar.addTool({ - imageURL: toolIconUrl + "models-tool.svg", + imageURL: toolIconUrl + "edit-status.svg", subImage: { x: 0, y: Tool.IMAGE_WIDTH, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT }, width: toolWidth, height: toolHeight, @@ -135,7 +135,7 @@ var toolBar = (function () { }, true, false); newModelButton = toolBar.addTool({ - imageURL: toolIconUrl + "add-model-tool.svg", + imageURL: toolIconUrl + "upload.svg", subImage: { x: 0, y: Tool.IMAGE_WIDTH, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT }, width: toolWidth, height: toolHeight, @@ -144,7 +144,7 @@ var toolBar = (function () { }); browseModelsButton = toolBar.addTool({ - imageURL: toolIconUrl + "list-icon.svg", + imageURL: toolIconUrl + "marketplace.svg", width: toolWidth, height: toolHeight, alpha: 0.9, From e94bac1971f3fc5e84324de22578b60f8fc0c0ca Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Fri, 13 Mar 2015 10:07:31 -0700 Subject: [PATCH 24/38] Fix subimage indexing in toolBars.js --- examples/libraries/toolBars.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/libraries/toolBars.js b/examples/libraries/toolBars.js index 951b6704ec..670a69dec7 100644 --- a/examples/libraries/toolBars.js +++ b/examples/libraries/toolBars.js @@ -94,7 +94,7 @@ Tool = function(properties, selectable, selected) { // selectable and selected a } selected = doSelect; - properties.subImage.y = (selected ? 2 : 1) * properties.subImage.height; + properties.subImage.y = (selected ? 0 : 1) * properties.subImage.height; Overlays.editOverlay(this.overlay(), { subImage: properties.subImage }); } this.toggle = function() { @@ -102,7 +102,7 @@ Tool = function(properties, selectable, selected) { // selectable and selected a return; } selected = !selected; - properties.subImage.y = (selected ? 2 : 1) * properties.subImage.height; + properties.subImage.y = (selected ? 0 : 1) * properties.subImage.height; Overlays.editOverlay(this.overlay(), { subImage: properties.subImage }); return selected; From 691b078efc2e91378dfe97ceec7b748c570ddfe3 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Fri, 13 Mar 2015 18:33:07 +0100 Subject: [PATCH 25/38] Fix the timeout crash --- libraries/networking/src/ResourceCache.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/libraries/networking/src/ResourceCache.cpp b/libraries/networking/src/ResourceCache.cpp index 658f32aba1..4596ac5146 100644 --- a/libraries/networking/src/ResourceCache.cpp +++ b/libraries/networking/src/ResourceCache.cpp @@ -224,8 +224,12 @@ void Resource::refresh() { } if (_reply) { ResourceCache::requestCompleted(this); - delete _reply; + _reply->disconnect(this); + _replyTimer->disconnect(this); + _reply->deleteLater(); _reply = nullptr; + _replyTimer->deleteLater(); + _replyTimer = nullptr; } init(); _request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork); @@ -296,9 +300,9 @@ void Resource::handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal) { return; } _reply->disconnect(this); + _replyTimer->disconnect(this); QNetworkReply* reply = _reply; _reply = nullptr; - _replyTimer->disconnect(this); _replyTimer->deleteLater(); _replyTimer = nullptr; ResourceCache::requestCompleted(this); @@ -369,9 +373,9 @@ void Resource::makeRequest() { void Resource::handleReplyError(QNetworkReply::NetworkError error, QDebug debug) { _reply->disconnect(this); + _replyTimer->disconnect(this); _reply->deleteLater(); _reply = nullptr; - _replyTimer->disconnect(this); _replyTimer->deleteLater(); _replyTimer = nullptr; ResourceCache::requestCompleted(this); From 905de8b03c384dcb426728d8f92fc479e2ba084c Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Fri, 13 Mar 2015 11:46:15 -0700 Subject: [PATCH 26/38] Adjust marketplace window size --- examples/edit.js | 8 ++++++-- examples/libraries/entityList.js | 2 +- examples/libraries/gridTool.js | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/examples/edit.js b/examples/edit.js index c236336266..7630ca1b14 100644 --- a/examples/edit.js +++ b/examples/edit.js @@ -110,6 +110,10 @@ var importingSVOOverlay = Overlays.addOverlay("text", { visible: false, }); +var MARKETPLACE_URL = "https://metaverse.highfidelity.io/marketplace"; +var marketplaceWindow = new WebWindow('Marketplace', MARKETPLACE_URL, 900, 700, false); +marketplaceWindow.setVisible(false); + var toolBar = (function () { var that = {}, toolBar, @@ -311,7 +315,7 @@ var toolBar = (function () { return true; } if (browseModelsButton === toolBar.clicked(clickedOverlay)) { - browseModelsButtonDown = true; + marketplaceWindow.setVisible(true); return true; } @@ -989,7 +993,7 @@ PropertiesTool = function(opts) { var that = {}; var url = Script.resolvePath('html/entityProperties.html'); - var webView = new WebWindow('Entity Properties', url, 200, 280); + var webView = new WebWindow('Entity Properties', url, 200, 280, true); var visible = false; diff --git a/examples/libraries/entityList.js b/examples/libraries/entityList.js index 942edf18b6..d0b8ddac7f 100644 --- a/examples/libraries/entityList.js +++ b/examples/libraries/entityList.js @@ -2,7 +2,7 @@ EntityListTool = function(opts) { var that = {}; var url = Script.resolvePath('html/entityList.html'); - var webView = new WebWindow('Entities', url, 200, 280); + var webView = new WebWindow('Entities', url, 200, 280, true); var visible = false; diff --git a/examples/libraries/gridTool.js b/examples/libraries/gridTool.js index 6e16186abc..ed4e999be8 100644 --- a/examples/libraries/gridTool.js +++ b/examples/libraries/gridTool.js @@ -229,7 +229,7 @@ GridTool = function(opts) { var listeners = []; var url = Script.resolvePath('html/gridControls.html'); - var webView = new WebWindow('Grid', url, 200, 280); + var webView = new WebWindow('Grid', url, 200, 280, true); horizontalGrid.addListener(function(data) { webView.eventBridge.emitScriptEvent(JSON.stringify(data)); From 93cdb3a293afc164a55726d58760c5d9669543bb Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Fri, 13 Mar 2015 11:51:40 -0700 Subject: [PATCH 27/38] Add option for non-tool window WebWindows --- interface/src/scripting/WebWindowClass.cpp | 42 +++++++++++++------ interface/src/scripting/WebWindowClass.h | 5 ++- .../scripting/WindowScriptingInterface.cpp | 4 +- .../src/scripting/WindowScriptingInterface.h | 2 +- 4 files changed, 35 insertions(+), 18 deletions(-) diff --git a/interface/src/scripting/WebWindowClass.cpp b/interface/src/scripting/WebWindowClass.cpp index 2e0f88c776..36f0139a6d 100644 --- a/interface/src/scripting/WebWindowClass.cpp +++ b/interface/src/scripting/WebWindowClass.cpp @@ -18,6 +18,7 @@ #include #include "Application.h" +#include "MainWindow.h" #include "WindowScriptingInterface.h" #include "WebWindowClass.h" @@ -33,26 +34,40 @@ void ScriptEventBridge::emitScriptEvent(const QString& data) { } -WebWindowClass::WebWindowClass(const QString& title, const QString& url, int width, int height) +WebWindowClass::WebWindowClass(const QString& title, const QString& url, int width, int height, bool isToolWindow) : QObject(NULL), - _eventBridge(new ScriptEventBridge(this)) { + _eventBridge(new ScriptEventBridge(this)), + _isToolWindow(isToolWindow) { - ToolWindow* toolWindow = Application::getInstance()->getToolWindow(); + if (_isToolWindow) { + ToolWindow* toolWindow = Application::getInstance()->getToolWindow(); - _dockWidget = new QDockWidget(title, toolWindow); - _dockWidget->setFeatures(QDockWidget::DockWidgetMovable); + auto dockWidget = new QDockWidget(title, toolWindow); + dockWidget->setFeatures(QDockWidget::DockWidgetMovable); - _webView = new QWebView(_dockWidget); + _webView = new QWebView(dockWidget); + addEventBridgeToWindowObject(); + + dockWidget->setWidget(_webView); + + toolWindow->addDockWidget(Qt::RightDockWidgetArea, dockWidget); + + _windowWidget = dockWidget; + } else { + _windowWidget = new QWidget(Application::getInstance()->getWindow(), Qt::Window); + _windowWidget->setMinimumSize(width, height); + + _webView = new QWebView(_windowWidget); + addEventBridgeToWindowObject(); + } + + _webView->setPage(new InterfaceWebPage()); _webView->setUrl(url); - addEventBridgeToWindowObject(); - _dockWidget->setWidget(_webView); - - toolWindow->addDockWidget(Qt::RightDockWidgetArea, _dockWidget); + connect(this, &WebWindowClass::destroyed, _windowWidget, &QWidget::deleteLater); connect(_webView->page()->mainFrame(), &QWebFrame::javaScriptWindowObjectCleared, this, &WebWindowClass::addEventBridgeToWindowObject); - connect(this, &WebWindowClass::destroyed, _dockWidget, &QWidget::deleteLater); } WebWindowClass::~WebWindowClass() { @@ -67,7 +82,7 @@ void WebWindowClass::setVisible(bool visible) { QMetaObject::invokeMethod( Application::getInstance()->getToolWindow(), "setVisible", Qt::BlockingQueuedConnection, Q_ARG(bool, visible)); } - QMetaObject::invokeMethod(_dockWidget, "setVisible", Qt::BlockingQueuedConnection, Q_ARG(bool, visible)); + QMetaObject::invokeMethod(_windowWidget, "setVisible", Qt::BlockingQueuedConnection, Q_ARG(bool, visible)); } QScriptValue WebWindowClass::constructor(QScriptContext* context, QScriptEngine* engine) { @@ -78,7 +93,8 @@ QScriptValue WebWindowClass::constructor(QScriptContext* context, QScriptEngine* Q_ARG(const QString&, file), Q_ARG(QString, context->argument(1).toString()), Q_ARG(int, context->argument(2).toInteger()), - Q_ARG(int, context->argument(3).toInteger())); + Q_ARG(int, context->argument(3).toInteger()), + Q_ARG(bool, context->argument(4).toBool())); connect(engine, &QScriptEngine::destroyed, retVal, &WebWindowClass::deleteLater); diff --git a/interface/src/scripting/WebWindowClass.h b/interface/src/scripting/WebWindowClass.h index 0fa88804f2..c923fdd748 100644 --- a/interface/src/scripting/WebWindowClass.h +++ b/interface/src/scripting/WebWindowClass.h @@ -35,7 +35,7 @@ class WebWindowClass : public QObject { Q_OBJECT Q_PROPERTY(QObject* eventBridge READ getEventBridge) public: - WebWindowClass(const QString& title, const QString& url, int width, int height); + WebWindowClass(const QString& title, const QString& url, int width, int height, bool isToolWindow = false); ~WebWindowClass(); static QScriptValue constructor(QScriptContext* context, QScriptEngine* engine); @@ -46,9 +46,10 @@ public slots: void addEventBridgeToWindowObject(); private: - QDockWidget* _dockWidget; + QWidget* _windowWidget; QWebView* _webView; ScriptEventBridge* _eventBridge; + bool _isToolWindow; }; #endif diff --git a/interface/src/scripting/WindowScriptingInterface.cpp b/interface/src/scripting/WindowScriptingInterface.cpp index 52de31df3c..4a6afe4dbe 100644 --- a/interface/src/scripting/WindowScriptingInterface.cpp +++ b/interface/src/scripting/WindowScriptingInterface.cpp @@ -35,8 +35,8 @@ WindowScriptingInterface::WindowScriptingInterface() : connect(Application::getInstance(), &Application::svoImportRequested, this, &WindowScriptingInterface::svoImportRequested); } -WebWindowClass* WindowScriptingInterface::doCreateWebWindow(const QString& title, const QString& url, int width, int height) { - return new WebWindowClass(title, url, width, height); +WebWindowClass* WindowScriptingInterface::doCreateWebWindow(const QString& title, const QString& url, int width, int height, bool isToolWindow) { + return new WebWindowClass(title, url, width, height, isToolWindow); } QScriptValue WindowScriptingInterface::hasFocus() { diff --git a/interface/src/scripting/WindowScriptingInterface.h b/interface/src/scripting/WindowScriptingInterface.h index 34942366eb..e3af898267 100644 --- a/interface/src/scripting/WindowScriptingInterface.h +++ b/interface/src/scripting/WindowScriptingInterface.h @@ -83,7 +83,7 @@ private slots: void nonBlockingFormAccepted() { _nonBlockingFormActive = false; _formResult = QDialog::Accepted; emit nonBlockingFormClosed(); } void nonBlockingFormRejected() { _nonBlockingFormActive = false; _formResult = QDialog::Rejected; emit nonBlockingFormClosed(); } - WebWindowClass* doCreateWebWindow(const QString& title, const QString& url, int width, int height); + WebWindowClass* doCreateWebWindow(const QString& title, const QString& url, int width, int height, bool isToolWindow); private: QString jsRegExp2QtRegExp(QString string); From 7ef1964a526b27ed41d136c5427a4b9160c83b2b Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Fri, 13 Mar 2015 11:52:02 -0700 Subject: [PATCH 28/38] Add user agent for WebWindow --- interface/src/scripting/WebWindowClass.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/interface/src/scripting/WebWindowClass.cpp b/interface/src/scripting/WebWindowClass.cpp index 36f0139a6d..6f44497117 100644 --- a/interface/src/scripting/WebWindowClass.cpp +++ b/interface/src/scripting/WebWindowClass.cpp @@ -34,6 +34,16 @@ void ScriptEventBridge::emitScriptEvent(const QString& data) { } +class InterfaceWebPage : public QWebPage { +public: + InterfaceWebPage(QWidget* parent = nullptr) : QWebPage(parent) { } +protected: + virtual QString userAgentForUrl(const QUrl & url) const { + return "HighFidelityInterface/1.0"; + } +}; + + WebWindowClass::WebWindowClass(const QString& title, const QString& url, int width, int height, bool isToolWindow) : QObject(NULL), _eventBridge(new ScriptEventBridge(this)), From 405301f8617608c5a0376220186c96e7dc17d8eb Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Fri, 13 Mar 2015 20:24:52 +0100 Subject: [PATCH 29/38] Don't refresh if no lastModified tag in http request --- libraries/networking/src/ResourceCache.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/libraries/networking/src/ResourceCache.cpp b/libraries/networking/src/ResourceCache.cpp index 4596ac5146..dfb0d6c150 100644 --- a/libraries/networking/src/ResourceCache.cpp +++ b/libraries/networking/src/ResourceCache.cpp @@ -332,6 +332,10 @@ void Resource::maybeRefresh() { // We don't need to update, return return; } + } else if (!variant.isValid() || !variant.canConvert() || + !variant.value().isValid() || variant.value().isNull()) { + qDebug() << "Cannot determine when" << _url.fileName() << "was modified last, cached version might be outdated"; + return; } qDebug() << "Loaded" << _url.fileName() << "from the disk cache but the network version is newer, refreshing."; refresh(); @@ -355,10 +359,19 @@ void Resource::makeRequest() { } else { if (Q_LIKELY(NetworkAccessManager::getInstance().cache())) { QNetworkCacheMetaData metaData = NetworkAccessManager::getInstance().cache()->metaData(_url); + bool needUpdate = false; if (metaData.expirationDate().isNull() || metaData.expirationDate() <= QDateTime::currentDateTime()) { // If the expiration date is NULL or in the past, // put one far enough away that it won't be an issue. metaData.setExpirationDate(QDateTime::currentDateTime().addYears(100)); + needUpdate = true; + } + if (metaData.lastModified().isNull()) { + // If the lastModified date is NULL, set it to now. + metaData.setLastModified(QDateTime::currentDateTime()); + needUpdate = true; + } + if (needUpdate) { NetworkAccessManager::getInstance().cache()->updateMetaData(metaData); } } From dc3453a878ee719c0a00008e5417342abd27ad1c Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Fri, 13 Mar 2015 14:10:46 -0700 Subject: [PATCH 30/38] Add Window.raiseMainWindow() --- examples/edit.js | 2 ++ interface/src/scripting/WindowScriptingInterface.cpp | 9 +++++++-- interface/src/scripting/WindowScriptingInterface.h | 1 + 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/examples/edit.js b/examples/edit.js index 9ce8561ab0..0402b8d42b 100644 --- a/examples/edit.js +++ b/examples/edit.js @@ -865,6 +865,8 @@ function importSVO(importURL) { if (isActive) { selectionManager.setSelections(pastedEntityIDs); } + + Window.raiseMainWindow(); } else { Window.alert("There was an error importing the entity file."); } diff --git a/interface/src/scripting/WindowScriptingInterface.cpp b/interface/src/scripting/WindowScriptingInterface.cpp index 4a6afe4dbe..7f4b5ddf45 100644 --- a/interface/src/scripting/WindowScriptingInterface.cpp +++ b/interface/src/scripting/WindowScriptingInterface.cpp @@ -44,8 +44,13 @@ QScriptValue WindowScriptingInterface::hasFocus() { } void WindowScriptingInterface::setFocus() { - Application::getInstance()->getWindow()->activateWindow(); - Application::getInstance()->getWindow()->setFocus(); + auto window = Application::getInstance()->getWindow(); + window->activateWindow(); + window->setFocus(); +} + +void WindowScriptingInterface::raiseMainWindow() { + Application::getInstance()->getWindow()->raise(); } void WindowScriptingInterface::setCursorVisible(bool visible) { diff --git a/interface/src/scripting/WindowScriptingInterface.h b/interface/src/scripting/WindowScriptingInterface.h index e3af898267..6a812f14e3 100644 --- a/interface/src/scripting/WindowScriptingInterface.h +++ b/interface/src/scripting/WindowScriptingInterface.h @@ -43,6 +43,7 @@ public slots: void setCursorVisible(bool visible); QScriptValue hasFocus(); void setFocus(); + void raiseMainWindow(); QScriptValue alert(const QString& message = ""); QScriptValue confirm(const QString& message = ""); QScriptValue form(const QString& title, QScriptValue array); From 53b5c7e9718ac1206bdd50b0974e3ab6707af614 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Fri, 13 Mar 2015 14:11:01 -0700 Subject: [PATCH 31/38] Update WebWindow to raise when being set to visible --- interface/src/scripting/WebWindowClass.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/interface/src/scripting/WebWindowClass.cpp b/interface/src/scripting/WebWindowClass.cpp index 6f44497117..292018a42c 100644 --- a/interface/src/scripting/WebWindowClass.cpp +++ b/interface/src/scripting/WebWindowClass.cpp @@ -89,8 +89,12 @@ void WebWindowClass::addEventBridgeToWindowObject() { void WebWindowClass::setVisible(bool visible) { if (visible) { - QMetaObject::invokeMethod( - Application::getInstance()->getToolWindow(), "setVisible", Qt::BlockingQueuedConnection, Q_ARG(bool, visible)); + if (_isToolWindow) { + QMetaObject::invokeMethod( + Application::getInstance()->getToolWindow(), "setVisible", Qt::BlockingQueuedConnection, Q_ARG(bool, visible)); + } else { + QMetaObject::invokeMethod(_windowWidget, "raise", Qt::BlockingQueuedConnection); + } } QMetaObject::invokeMethod(_windowWidget, "setVisible", Qt::BlockingQueuedConnection, Q_ARG(bool, visible)); } From e4c6d49c4d0f2d2f28b438994f91c8658e54307c Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Fri, 13 Mar 2015 16:36:18 -0700 Subject: [PATCH 32/38] ShapeManager can release shape by pointer --- libraries/physics/src/ShapeManager.cpp | 66 ++++++++++++++++++------- libraries/physics/src/ShapeManager.h | 11 +++-- tests/physics/src/ShapeManagerTests.cpp | 38 ++++++++++++-- 3 files changed, 90 insertions(+), 25 deletions(-) diff --git a/libraries/physics/src/ShapeManager.cpp b/libraries/physics/src/ShapeManager.cpp index 02228bf7aa..513fbfa7a5 100644 --- a/libraries/physics/src/ShapeManager.cpp +++ b/libraries/physics/src/ShapeManager.cpp @@ -21,7 +21,7 @@ ShapeManager::~ShapeManager() { int numShapes = _shapeMap.size(); for (int i = 0; i < numShapes; ++i) { ShapeReference* shapeRef = _shapeMap.getAtIndex(i); - delete shapeRef->_shape; + delete shapeRef->shape; } _shapeMap.clear(); } @@ -40,26 +40,27 @@ btCollisionShape* ShapeManager::getShape(const ShapeInfo& info) { DoubleHashKey key = info.getHash(); ShapeReference* shapeRef = _shapeMap.find(key); if (shapeRef) { - shapeRef->_refCount++; - return shapeRef->_shape; + shapeRef->refCount++; + return shapeRef->shape; } btCollisionShape* shape = ShapeInfoUtil::createShapeFromInfo(info); if (shape) { ShapeReference newRef; - newRef._refCount = 1; - newRef._shape = shape; + newRef.refCount = 1; + newRef.shape = shape; + newRef.key = key; _shapeMap.insert(key, newRef); } return shape; } -bool ShapeManager::releaseShape(const ShapeInfo& info) { - DoubleHashKey key = info.getHash(); +// private helper method +bool ShapeManager::releaseShape(const DoubleHashKey& key) { ShapeReference* shapeRef = _shapeMap.find(key); if (shapeRef) { - if (shapeRef->_refCount > 0) { - shapeRef->_refCount--; - if (shapeRef->_refCount == 0) { + if (shapeRef->refCount > 0) { + shapeRef->refCount--; + if (shapeRef->refCount == 0) { _pendingGarbage.push_back(key); const int MAX_GARBAGE_CAPACITY = 127; if (_pendingGarbage.size() > MAX_GARBAGE_CAPACITY) { @@ -78,10 +79,19 @@ bool ShapeManager::releaseShape(const ShapeInfo& info) { return false; } +bool ShapeManager::releaseShape(const ShapeInfo& info) { + return releaseShape(info.getHash()); +} + bool ShapeManager::releaseShape(const btCollisionShape* shape) { - ShapeInfo info; - ShapeInfoUtil::collectInfoFromShape(shape, info); - return releaseShape(info); + int numShapes = _shapeMap.size(); + for (int i = 0; i < numShapes; ++i) { + ShapeReference* shapeRef = _shapeMap.getAtIndex(i); + if (shape == shapeRef->shape) { + return releaseShape(shapeRef->key); + } + } + return false; } void ShapeManager::collectGarbage() { @@ -89,8 +99,8 @@ void ShapeManager::collectGarbage() { for (int i = 0; i < numShapes; ++i) { DoubleHashKey& key = _pendingGarbage[i]; ShapeReference* shapeRef = _shapeMap.find(key); - if (shapeRef && shapeRef->_refCount == 0) { - delete shapeRef->_shape; + if (shapeRef && shapeRef->refCount == 0) { + delete shapeRef->shape; _shapeMap.remove(key); } } @@ -101,7 +111,29 @@ int ShapeManager::getNumReferences(const ShapeInfo& info) const { DoubleHashKey key = info.getHash(); const ShapeReference* shapeRef = _shapeMap.find(key); if (shapeRef) { - return shapeRef->_refCount; + return shapeRef->refCount; } - return -1; + return 0; +} + +int ShapeManager::getNumReferences(const btCollisionShape* shape) const { + int numShapes = _shapeMap.size(); + for (int i = 0; i < numShapes; ++i) { + const ShapeReference* shapeRef = _shapeMap.getAtIndex(i); + if (shape == shapeRef->shape) { + return shapeRef->refCount; + } + } + return 0; +} + +bool ShapeManager::hasShape(const btCollisionShape* shape) const { + int numShapes = _shapeMap.size(); + for (int i = 0; i < numShapes; ++i) { + const ShapeReference* shapeRef = _shapeMap.getAtIndex(i); + if (shape == shapeRef->shape) { + return true; + } + } + return false; } diff --git a/libraries/physics/src/ShapeManager.h b/libraries/physics/src/ShapeManager.h index 3fe80c7b61..e04fa1280c 100644 --- a/libraries/physics/src/ShapeManager.h +++ b/libraries/physics/src/ShapeManager.h @@ -38,12 +38,17 @@ public: // validation methods int getNumShapes() const { return _shapeMap.size(); } int getNumReferences(const ShapeInfo& info) const; + int getNumReferences(const btCollisionShape* shape) const; + bool hasShape(const btCollisionShape* shape) const; private: + bool releaseShape(const DoubleHashKey& key); + struct ShapeReference { - int _refCount; - btCollisionShape* _shape; - ShapeReference() : _refCount(0), _shape(NULL) {} + int refCount; + btCollisionShape* shape; + DoubleHashKey key; + ShapeReference() : refCount(0), shape(NULL) {} }; btHashMap _shapeMap; diff --git a/tests/physics/src/ShapeManagerTests.cpp b/tests/physics/src/ShapeManagerTests.cpp index 6dfae15e78..d86f296d0e 100644 --- a/tests/physics/src/ShapeManagerTests.cpp +++ b/tests/physics/src/ShapeManagerTests.cpp @@ -21,10 +21,8 @@ void ShapeManagerTests::testShapeAccounting() { ShapeInfo info; info.setBox(glm::vec3(1.0f, 1.0f, 1.0f)); - // NOTE: ShapeManager returns -1 as refcount when the shape is unknown, - // which is distinct from "known but with zero references" int numReferences = shapeManager.getNumReferences(info); - if (numReferences != -1) { + if (numReferences != 0) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: expected ignorant ShapeManager after initialization" << std::endl; } @@ -104,8 +102,7 @@ void ShapeManagerTests::testShapeAccounting() { if (shapeManager.getNumShapes() != 0) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: expected zero shapes after release" << std::endl; } - numReferences = shapeManager.getNumReferences(info); - if (numReferences != -1) { + if (shapeManager.hasShape(shape)) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: expected ignorant ShapeManager after garbage collection" << std::endl; } @@ -122,33 +119,64 @@ void ShapeManagerTests::testShapeAccounting() { void ShapeManagerTests::addManyShapes() { ShapeManager shapeManager; + QVector shapes; + int numSizes = 100; float startSize = 1.0f; float endSize = 99.0f; float deltaSize = (endSize - startSize) / (float)numSizes; ShapeInfo info; for (int i = 0; i < numSizes; ++i) { + // make a sphere float s = startSize + (float)i * deltaSize; glm::vec3 scale(s, 1.23f + s, s - 0.573f); info.setBox(0.5f * scale); btCollisionShape* shape = shapeManager.getShape(info); + shapes.push_back(shape); if (!shape) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: i = " << i << " null box shape for scale = " << scale << std::endl; } + + // make a box float radius = 0.5f * s; info.setSphere(radius); shape = shapeManager.getShape(info); + shapes.push_back(shape); if (!shape) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: i = " << i << " null sphere shape for radius = " << radius << std::endl; } } + + // verify shape count int numShapes = shapeManager.getNumShapes(); if (numShapes != 2 * numSizes) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: expected numShapes = " << numSizes << " but found numShapes = " << numShapes << std::endl; } + + // release each shape by pointer + for (int i = 0; i < numShapes; ++i) { + btCollisionShape* shape = shapes[i]; + bool success = shapeManager.releaseShape(shape); + if (!success) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: failed to release shape index " << i << std::endl; + break; + } + } + + // verify zero references + for (int i = 0; i < numShapes; ++i) { + btCollisionShape* shape = shapes[i]; + int numReferences = shapeManager.getNumReferences(shape); + if (numReferences != 0) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: expected zero references for shape " << i + << " but refCount = " << numReferences << std::endl; + } + } } void ShapeManagerTests::addBoxShape() { From 1193d86d7b9ce233a5ee9b9e287e48ff5531950a Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Fri, 13 Mar 2015 16:39:25 -0700 Subject: [PATCH 33/38] Update webwindow to use datawebview --- interface/src/scripting/WebWindowClass.cpp | 16 +++------------- interface/src/ui/DataWebPage.cpp | 6 +++++- interface/src/ui/DataWebPage.h | 5 ++++- 3 files changed, 12 insertions(+), 15 deletions(-) diff --git a/interface/src/scripting/WebWindowClass.cpp b/interface/src/scripting/WebWindowClass.cpp index 292018a42c..00859b0c02 100644 --- a/interface/src/scripting/WebWindowClass.cpp +++ b/interface/src/scripting/WebWindowClass.cpp @@ -18,9 +18,10 @@ #include #include "Application.h" +#include "ui/DataWebPage.h" #include "MainWindow.h" -#include "WindowScriptingInterface.h" #include "WebWindowClass.h" +#include "WindowScriptingInterface.h" ScriptEventBridge::ScriptEventBridge(QObject* parent) : QObject(parent) { } @@ -33,17 +34,6 @@ void ScriptEventBridge::emitScriptEvent(const QString& data) { emit scriptEventReceived(data); } - -class InterfaceWebPage : public QWebPage { -public: - InterfaceWebPage(QWidget* parent = nullptr) : QWebPage(parent) { } -protected: - virtual QString userAgentForUrl(const QUrl & url) const { - return "HighFidelityInterface/1.0"; - } -}; - - WebWindowClass::WebWindowClass(const QString& title, const QString& url, int width, int height, bool isToolWindow) : QObject(NULL), _eventBridge(new ScriptEventBridge(this)), @@ -71,7 +61,7 @@ WebWindowClass::WebWindowClass(const QString& title, const QString& url, int wid addEventBridgeToWindowObject(); } - _webView->setPage(new InterfaceWebPage()); + _webView->setPage(new DataWebPage()); _webView->setUrl(url); diff --git a/interface/src/ui/DataWebPage.cpp b/interface/src/ui/DataWebPage.cpp index 299ef86c51..b7aa6351a8 100644 --- a/interface/src/ui/DataWebPage.cpp +++ b/interface/src/ui/DataWebPage.cpp @@ -40,4 +40,8 @@ bool DataWebPage::acceptNavigationRequest(QWebFrame* frame, const QNetworkReques Qt::AutoConnection, Q_ARG(const QString&, request.url().toString())); return false; } -} \ No newline at end of file +} + +QString DataWebPage::userAgentForUrl(const QUrl & url) const { + return INTERFACE_USER_AGENT; +} diff --git a/interface/src/ui/DataWebPage.h b/interface/src/ui/DataWebPage.h index 6d89077a33..03c6781d05 100644 --- a/interface/src/ui/DataWebPage.h +++ b/interface/src/ui/DataWebPage.h @@ -14,12 +14,15 @@ #include +const QString INTERFACE_USER_AGENT = "HighFidelityInterface/1.0"; + class DataWebPage : public QWebPage { public: DataWebPage(QObject* parent = 0); protected: void javaScriptConsoleMessage(const QString & message, int lineNumber, const QString & sourceID); bool acceptNavigationRequest(QWebFrame* frame, const QNetworkRequest& request, QWebPage::NavigationType type); + virtual QString userAgentForUrl(const QUrl & url) const; }; -#endif // hifi_DataWebPage_h \ No newline at end of file +#endif // hifi_DataWebPage_h From 9d5c0cf4e3d19c368050f224092fcb60040fcd8a Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Fri, 13 Mar 2015 17:25:24 -0700 Subject: [PATCH 34/38] Add layout to WebWindowClass to that it maximizes the webview --- interface/src/scripting/WebWindowClass.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/interface/src/scripting/WebWindowClass.cpp b/interface/src/scripting/WebWindowClass.cpp index 00859b0c02..ae0e327cb1 100644 --- a/interface/src/scripting/WebWindowClass.cpp +++ b/interface/src/scripting/WebWindowClass.cpp @@ -57,7 +57,14 @@ WebWindowClass::WebWindowClass(const QString& title, const QString& url, int wid _windowWidget = new QWidget(Application::getInstance()->getWindow(), Qt::Window); _windowWidget->setMinimumSize(width, height); + auto layout = new QVBoxLayout(_windowWidget); + layout->setContentsMargins(0, 0, 0, 0); + _windowWidget->setLayout(layout); + _webView = new QWebView(_windowWidget); + + layout->addWidget(_webView); + addEventBridgeToWindowObject(); } From f44ab9da8f46f98665c24a16cf3ded511b51d574 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Fri, 13 Mar 2015 17:29:01 -0700 Subject: [PATCH 35/38] Update OAuthNetworkAccessManager to only send auth to metaverse --- libraries/networking/src/OAuthNetworkAccessManager.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libraries/networking/src/OAuthNetworkAccessManager.cpp b/libraries/networking/src/OAuthNetworkAccessManager.cpp index 1a52dc4ce7..89a73d5984 100644 --- a/libraries/networking/src/OAuthNetworkAccessManager.cpp +++ b/libraries/networking/src/OAuthNetworkAccessManager.cpp @@ -14,6 +14,7 @@ #include #include "AccountManager.h" +#include "LimitedNodeList.h" #include "SharedUtil.h" #include "OAuthNetworkAccessManager.h" @@ -32,7 +33,7 @@ QNetworkReply* OAuthNetworkAccessManager::createRequest(QNetworkAccessManager::O QIODevice* outgoingData) { AccountManager& accountManager = AccountManager::getInstance(); - if (accountManager.hasValidAccessToken()) { + if (accountManager.hasValidAccessToken() && req.url().host() == DEFAULT_NODE_AUTH_URL.host()) { QNetworkRequest authenticatedRequest(req); authenticatedRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); authenticatedRequest.setRawHeader(ACCESS_TOKEN_AUTHORIZATION_HEADER, From aca509d5cce707ec75416b9b502f5ab33a59ccc6 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Fri, 13 Mar 2015 17:35:06 -0700 Subject: [PATCH 36/38] Add Application::improtSVOFromURL --- interface/src/Application.cpp | 4 ++++ interface/src/Application.h | 2 ++ 2 files changed, 6 insertions(+) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 25077e0ac1..36835405f7 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -877,6 +877,10 @@ void Application::controlledBroadcastToNodes(const QByteArray& packet, const Nod } } +void Application::importSVOFromURL(QUrl url) { + emit svoImportRequested(url.url()); +} + bool Application::event(QEvent* event) { // handle custom URL diff --git a/interface/src/Application.h b/interface/src/Application.h index 248aaa0f6a..b013692393 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -216,6 +216,8 @@ public: float getFieldOfView() { return _fieldOfView.get(); } void setFieldOfView(float fov) { _fieldOfView.set(fov); } + void importSVOFromURL(QUrl url); + NodeToOctreeSceneStats* getOcteeSceneStats() { return &_octreeServerSceneStats; } void lockOctreeSceneStats() { _octreeSceneStatsLock.lockForRead(); } void unlockOctreeSceneStats() { _octreeSceneStatsLock.unlock(); } From 47e63719ee5129a198572f90d8d8bc33c5bc62c3 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Fri, 13 Mar 2015 17:35:33 -0700 Subject: [PATCH 37/38] Handle svo request in DataWebPage as import requests --- interface/src/ui/DataWebPage.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/interface/src/ui/DataWebPage.cpp b/interface/src/ui/DataWebPage.cpp index b7aa6351a8..7172d87d36 100644 --- a/interface/src/ui/DataWebPage.cpp +++ b/interface/src/ui/DataWebPage.cpp @@ -31,8 +31,12 @@ void DataWebPage::javaScriptConsoleMessage(const QString& message, int lineNumbe } bool DataWebPage::acceptNavigationRequest(QWebFrame* frame, const QNetworkRequest& request, QWebPage::NavigationType type) { - + if (!request.url().toString().startsWith(HIFI_URL_SCHEME)) { + if (request.url().path().toLower().endsWith(SVO_EXTENSION)) { + Application::getInstance()->importSVOFromURL(request.url()); + return false; + } return true; } else { // this is a hifi URL - have the AddressManager handle it From 235b5d77ae72000cc25360a05a05c8a74001f4f1 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Fri, 13 Mar 2015 17:38:36 -0700 Subject: [PATCH 38/38] Add missing include to DataWebPage --- interface/src/ui/DataWebPage.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/interface/src/ui/DataWebPage.cpp b/interface/src/ui/DataWebPage.cpp index 7172d87d36..de97ab94a6 100644 --- a/interface/src/ui/DataWebPage.cpp +++ b/interface/src/ui/DataWebPage.cpp @@ -11,6 +11,7 @@ #include +#include "Application.h" #include #include @@ -21,7 +22,7 @@ DataWebPage::DataWebPage(QObject* parent) : { // use an OAuthNetworkAccessManager instead of regular QNetworkAccessManager so our requests are authed setNetworkAccessManager(OAuthNetworkAccessManager::getInstance()); - + // give the page an empty stylesheet settings()->setUserStyleSheetUrl(QUrl()); }