diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 57e133c599..0918b85a63 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -814,8 +814,9 @@ void DomainServer::requestUserPublicKey(const QString& username) { qDebug() << "Requesting public key for user" << username; - AccountManager::getInstance().unauthenticatedRequest(USER_PUBLIC_KEY_PATH.arg(username), - QNetworkAccessManager::GetOperation, callbackParams); + AccountManager::getInstance().sendRequest(USER_PUBLIC_KEY_PATH.arg(username), + AccountManagerAuth::None, + QNetworkAccessManager::GetOperation, callbackParams); } QUrl DomainServer::oauthRedirectURL() { @@ -1116,8 +1117,10 @@ void DomainServer::sendPendingTransactionsToServer() { transactionCallbackParams.jsonCallbackMethod = "transactionJSONCallback"; while (i != _pendingAssignmentCredits.end()) { - accountManager.authenticatedRequest("api/v1/transactions", QNetworkAccessManager::PostOperation, - transactionCallbackParams, i.value()->postJson().toJson()); + accountManager.sendRequest("api/v1/transactions", + AccountManagerAuth::Required, + QNetworkAccessManager::PostOperation, + transactionCallbackParams, i.value()->postJson().toJson()); // set this transaction to finalized so we don't add additional credits to it i.value()->setIsFinalized(true); @@ -1240,10 +1243,11 @@ void DomainServer::sendHeartbeatToDataServer(const QString& networkAddress) { QString domainUpdateJSON = QString("{\"domain\": %1 }").arg(QString(QJsonDocument(domainObject).toJson())); - AccountManager::getInstance().authenticatedRequest(DOMAIN_UPDATE.arg(uuidStringWithoutCurlyBraces(domainID)), - QNetworkAccessManager::PutOperation, - JSONCallbackParameters(), - domainUpdateJSON.toUtf8()); + AccountManager::getInstance().sendRequest(DOMAIN_UPDATE.arg(uuidStringWithoutCurlyBraces(domainID)), + AccountManagerAuth::Required, + QNetworkAccessManager::PutOperation, + JSONCallbackParameters(), + domainUpdateJSON.toUtf8()); } // todo: have data-web respond with ice-server hostname to use diff --git a/examples/edit.js b/examples/edit.js index 70e51823b2..8088b7cd74 100644 --- a/examples/edit.js +++ b/examples/edit.js @@ -76,7 +76,6 @@ var DEFAULT_DIMENSIONS = { 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"; @@ -338,7 +337,11 @@ var toolBar = (function () { return true; } if (browseModelsButton === toolBar.clicked(clickedOverlay)) { + if (marketplaceWindow.url != MARKETPLACE_URL) { + marketplaceWindow.setURL(MARKETPLACE_URL); + } marketplaceWindow.setVisible(true); + marketplaceWindow.raise(); return true; } @@ -540,7 +543,7 @@ function mousePressEvent(event) { mouseHasMovedSincePress = false; mouseCapturedByTool = false; - if (toolBar.mousePressEvent(event) || progressDialog.mousePressEvent(event)) { + if (propertyMenu.mousePressEvent(event) || toolBar.mousePressEvent(event) || progressDialog.mousePressEvent(event)) { mouseCapturedByTool = true; return; } @@ -549,18 +552,6 @@ function mousePressEvent(event) { // Event handled; do nothing. return; } - } else if (Menu.isOptionChecked(MENU_INSPECT_TOOL_ENABLED)) { - var result = findClickedEntity(event); - if (event.isRightButton) { - if (result !== null) { - var currentProperties = Entities.getEntityProperties(result.entityID); - cameraManager.enable(); - cameraManager.focus(currentProperties.position, null, Menu.isOptionChecked(MENU_EASE_ON_FOCUS)); - cameraManager.mousePressEvent(event); - } - } else { - cameraManager.mousePressEvent(event); - } } } @@ -572,6 +563,8 @@ var IDLE_MOUSE_TIMEOUT = 200; var DEFAULT_ENTITY_DRAG_DROP_DISTANCE = 2.0; function mouseMoveEvent(event) { + mouseHasMovedSincePress = true; + if (placingEntityID) { if (!placingEntityID.isKnownID) { placingEntityID = Entities.identifyEntity(placingEntityID); @@ -592,10 +585,8 @@ function mouseMoveEvent(event) { Script.clearTimeout(idleMouseTimerId); } - mouseHasMovedSincePress = true; - // allow the selectionDisplay and cameraManager to handle the event first, if it doesn't handle it, then do our own thing - if (selectionDisplay.mouseMoveEvent(event) || cameraManager.mouseMoveEvent(event)) { + if (selectionDisplay.mouseMoveEvent(event) || propertyMenu.mouseMoveEvent(event) || cameraManager.mouseMoveEvent(event)) { return; } @@ -640,7 +631,7 @@ function highlightEntityUnderCursor(position, accurateRay) { function mouseReleaseEvent(event) { - if (toolBar.mouseReleaseEvent(event)) { + if (propertyMenu.mouseReleaseEvent(event) || toolBar.mouseReleaseEvent(event)) { return true; } if (placingEntityID) { @@ -664,74 +655,93 @@ function mouseReleaseEvent(event) { } function mouseClickEvent(event) { - if (!event.isLeftButton || !isActive) { - return; - } - - var result = findClickedEntity(event); - if (result === null) { - if (!event.isShifted) { - selectionManager.clearSelections(); - } - return; - } - toolBar.setActive(true); - var pickRay = result.pickRay; - var foundEntity = result.entityID; - - var properties = Entities.getEntityProperties(foundEntity); - if (isLocked(properties)) { - print("Model locked " + properties.id); - } else { - var halfDiagonal = Vec3.length(properties.dimensions) / 2.0; - - print("Checking properties: " + properties.id + " " + properties.isKnownID + " - Half Diagonal:" + halfDiagonal); - // P P - Model - // /| A - Palm - // / | d B - unit vector toward tip - // / | X - base of the perpendicular line - // A---X----->B d - distance fom axis - // x x - distance from A - // - // |X-A| = (P-A).B - // X == A + ((P-A).B)B - // d = |P-X| - - var A = pickRay.origin; - var B = Vec3.normalize(pickRay.direction); - var P = properties.position; - - var x = Vec3.dot(Vec3.subtract(P, A), B); - var X = Vec3.sum(A, Vec3.multiply(B, x)); - var d = Vec3.length(Vec3.subtract(P, X)); - var halfDiagonal = Vec3.length(properties.dimensions) / 2.0; - - var angularSize = 2 * Math.atan(halfDiagonal / Vec3.distance(Camera.getPosition(), properties.position)) * 180 / 3.14; - - var sizeOK = (allowLargeModels || angularSize < MAX_ANGULAR_SIZE) - && (allowSmallModels || angularSize > MIN_ANGULAR_SIZE); - - if (0 < x && sizeOK) { - entitySelected = true; - selectedEntityID = foundEntity; - orientation = MyAvatar.orientation; - intersection = rayPlaneIntersection(pickRay, P, Quat.getFront(orientation)); - - + if (isActive && event.isLeftButton) { + var result = findClickedEntity(event); + if (result === null) { if (!event.isShifted) { - selectionManager.setSelections([foundEntity]); + selectionManager.clearSelections(); + } + return; + } + toolBar.setActive(true); + var pickRay = result.pickRay; + var foundEntity = result.entityID; + + var properties = Entities.getEntityProperties(foundEntity); + if (isLocked(properties)) { + print("Model locked " + properties.id); + } else { + var halfDiagonal = Vec3.length(properties.dimensions) / 2.0; + + print("Checking properties: " + properties.id + " " + properties.isKnownID + " - Half Diagonal:" + halfDiagonal); + // P P - Model + // /| A - Palm + // / | d B - unit vector toward tip + // / | X - base of the perpendicular line + // A---X----->B d - distance fom axis + // x x - distance from A + // + // |X-A| = (P-A).B + // X == A + ((P-A).B)B + // d = |P-X| + + var A = pickRay.origin; + var B = Vec3.normalize(pickRay.direction); + var P = properties.position; + + var x = Vec3.dot(Vec3.subtract(P, A), B); + var X = Vec3.sum(A, Vec3.multiply(B, x)); + var d = Vec3.length(Vec3.subtract(P, X)); + var halfDiagonal = Vec3.length(properties.dimensions) / 2.0; + + var angularSize = 2 * Math.atan(halfDiagonal / Vec3.distance(Camera.getPosition(), properties.position)) * 180 / 3.14; + + var sizeOK = (allowLargeModels || angularSize < MAX_ANGULAR_SIZE) + && (allowSmallModels || angularSize > MIN_ANGULAR_SIZE); + + if (0 < x && sizeOK) { + entitySelected = true; + selectedEntityID = foundEntity; + orientation = MyAvatar.orientation; + intersection = rayPlaneIntersection(pickRay, P, Quat.getFront(orientation)); + + + if (!event.isShifted) { + selectionManager.setSelections([foundEntity]); + } else { + selectionManager.addEntity(foundEntity, true); + } + + print("Model selected: " + foundEntity.id); + selectionDisplay.select(selectedEntityID, event); + + if (Menu.isOptionChecked(MENU_AUTO_FOCUS_ON_SELECT)) { + cameraManager.focus(selectionManager.worldPosition, + selectionManager.worldDimensions, + Menu.isOptionChecked(MENU_EASE_ON_FOCUS)); + } + } + } + } else if (event.isRightButton) { + var result = findClickedEntity(event); + if (result) { + var properties = Entities.getEntityProperties(result.entityID); + var data = {}; + try { + data = JSON.parse(properties.attribution); + } catch (e) { + } + if (data.marketplaceID) { + propertyMenu.marketplaceID = data.marketplaceID; + propertyMenu.updateMenuItemText(showMenuItem, "Show in Marketplace"); } else { - selectionManager.addEntity(foundEntity, true); - } - - print("Model selected: " + foundEntity.id); - selectionDisplay.select(selectedEntityID, event); - - if (Menu.isOptionChecked(MENU_AUTO_FOCUS_ON_SELECT)) { - cameraManager.focus(selectionManager.worldPosition, - selectionManager.worldDimensions, - Menu.isOptionChecked(MENU_EASE_ON_FOCUS)); + propertyMenu.marketplaceID = null; + propertyMenu.updateMenuItemText(showMenuItem, "No marketplace info"); } + propertyMenu.setPosition(event.x, event.y); + propertyMenu.show(); + } else { + propertyMenu.hide(); } } } @@ -776,7 +786,7 @@ function setupModelMenus() { Menu.addMenuItem({ menuName: "File", menuItemName: "Import Entities", shortcutKey: "CTRL+META+I", afterItem: "Export Entities" }); Menu.addMenuItem({ menuName: "File", menuItemName: "Import Entities from URL", shortcutKey: "CTRL+META+U", afterItem: "Import Entities" }); - Menu.addMenuItem({ menuName: "View", menuItemName: MENU_AUTO_FOCUS_ON_SELECT, afterItem: MENU_INSPECT_TOOL_ENABLED, + Menu.addMenuItem({ menuName: "View", menuItemName: MENU_AUTO_FOCUS_ON_SELECT, 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" }); @@ -807,7 +817,6 @@ function cleanupModelMenus() { Menu.removeMenuItem("File", "Import Entities"); Menu.removeMenuItem("File", "Import Entities from URL"); - 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); @@ -829,11 +838,21 @@ Script.scriptEnding.connect(function() { Overlays.deleteOverlay(importingSVOTextOverlay); }); +var lastOrientation = null; +var lastPosition = null; + // Do some stuff regularly, like check for placement of various overlays Script.update.connect(function (deltaTime) { toolBar.move(); progressDialog.move(); selectionDisplay.checkMove(); + var dOrientation = Math.abs(Quat.dot(Camera.orientation, lastOrientation) - 1); + var dPosition = Vec3.distance(Camera.position, lastPosition); + if (dOrientation > 0.001 || dPosition > 0.001) { + propertyMenu.hide(); + lastOrientation = Camera.orientation; + lastPosition = Camera.position; + } }); function insideBox(center, dimensions, point) { @@ -910,8 +929,8 @@ function handeMenuEvent(menuItem) { if (!selectionManager.hasSelection()) { Window.alert("No entities have been selected."); } else { - var filename = "models__" + Window.location.hostname + "__.svo"; - filename = Window.save("Select where to save", filename, "*.svo") + var filename = "entities__" + Window.location.hostname + ".svo.json"; + filename = Window.save("Select where to save", filename, "*.json") if (filename) { var success = Clipboard.exportEntities(filename, selectionManager.selections); if (!success) { @@ -923,7 +942,7 @@ function handeMenuEvent(menuItem) { var importURL; if (menuItem == "Import Entities") { - importURL = Window.browse("Select models to import", "", "*.svo"); + importURL = Window.browse("Select models to import", "", "*.json"); } else { importURL = Window.prompt("URL of SVO to import", ""); } @@ -1143,6 +1162,12 @@ PropertiesTool = function(opts) { } pushCommandForSelections(); selectionManager._update(); + } else if (data.type = "showMarketplace") { + if (marketplaceWindow.url != data.url) { + marketplaceWindow.setURL(data.url); + } + marketplaceWindow.setVisible(true); + marketplaceWindow.raise(); } else if (data.type == "action") { if (data.action == "moveSelectionToGrid") { if (selectionManager.hasSelection()) { @@ -1216,4 +1241,142 @@ PropertiesTool = function(opts) { return that; }; +PopupMenu = function() { + var self = this; + + var MENU_ITEM_HEIGHT = 21; + var MENU_ITEM_SPACING = 1; + var TEXT_MARGIN = 7; + + var overlays = []; + var overlayInfo = {}; + + var upColor = { red: 0, green: 0, blue: 0 }; + var downColor = { red: 192, green: 192, blue: 192 }; + var overColor = { red: 128, green: 128, blue: 128 }; + + self.onSelectMenuItem = function() { }; + + self.addMenuItem = function(name) { + var id = Overlays.addOverlay("text", { + text: name, + backgroundAlpha: 1.0, + backgroundColor: upColor, + topMargin: TEXT_MARGIN, + leftMargin: TEXT_MARGIN, + width: 210, + height: MENU_ITEM_HEIGHT, + font: { size: 12 }, + visible: false, + }); + overlays.push(id); + overlayInfo[id] = { name: name }; + return id; + }; + + self.updateMenuItemText = function(id, newText) { + Overlays.editOverlay(id, { text: newText }); + }; + + self.setPosition = function(x, y) { + for (var key in overlayInfo) { + Overlays.editOverlay(key, { + x: x, + y: y, + }); + y += MENU_ITEM_HEIGHT + MENU_ITEM_SPACING; + } + }; + + self.onSelected = function() { }; + + var pressingOverlay = null; + var hoveringOverlay = null; + + self.mousePressEvent = function(event) { + if (event.isLeftButton) { + var overlay = Overlays.getOverlayAtPoint({ x: event.x, y: event.y }); + if (overlay in overlayInfo) { + pressingOverlay = overlay; + Overlays.editOverlay(pressingOverlay, { backgroundColor: downColor }); + } else { + self.hide(); + } + return false; + } + }; + self.mouseMoveEvent = function(event) { + if (visible) { + var overlay = Overlays.getOverlayAtPoint({ x: event.x, y: event.y }); + if (!pressingOverlay) { + if (hoveringOverlay != null && overlay != hoveringOverlay) { + Overlays.editOverlay(hoveringOverlay, { backgroundColor: upColor}); + hoveringOverlay = null; + } + if (overlay != hoveringOverlay && overlay in overlayInfo) { + Overlays.editOverlay(overlay, { backgroundColor: overColor }); + hoveringOverlay = overlay; + } + } + } + return false; + }; + self.mouseReleaseEvent = function(event) { + var overlay = Overlays.getOverlayAtPoint({ x: event.x, y: event.y }); + if (pressingOverlay != null) { + if (overlay == pressingOverlay) { + self.onSelectMenuItem(overlayInfo[overlay].name); + } + Overlays.editOverlay(pressingOverlay, { backgroundColor: upColor }); + pressingOverlay = null; + self.hide(); + } + }; + + var visible = false; + + self.setVisible = function(newVisible) { + if (newVisible != visible) { + visible = newVisible; + for (var key in overlayInfo) { + Overlays.editOverlay(key, { visible: newVisible }); + } + } + } + self.show = function() { + self.setVisible(true); + } + self.hide = function() { + self.setVisible(false); + } + + function cleanup() { + for (var i = 0; i < overlays.length; i++) { + Overlays.deleteOverlay(overlays[i]); + } + } + + Controller.mousePressEvent.connect(self.mousePressEvent); + Controller.mouseMoveEvent.connect(self.mouseMoveEvent); + Controller.mouseReleaseEvent.connect(self.mouseReleaseEvent); + Script.scriptEnding.connect(cleanup); + + return this; +}; + +var propertyMenu = PopupMenu(); + +propertyMenu.onSelectMenuItem = function(name) { + if (propertyMenu.marketplaceID) { + var url = "https://metaverse.highfidelity.io/marketplace/items/" + propertyMenu.marketplaceID; + if (marketplaceWindow.url != url) { + marketplaceWindow.setURL(url); + } + marketplaceWindow.setVisible(true); + marketplaceWindow.raise(); + } +}; + +var showMenuItem = propertyMenu.addMenuItem("Show in Marketplace"); + propertiesTool = PropertiesTool(); diff --git a/examples/entityScripts/lightController.js b/examples/entityScripts/lightController.js index 624fc929b3..e6e4998aef 100644 --- a/examples/entityScripts/lightController.js +++ b/examples/entityScripts/lightController.js @@ -1,8 +1,32 @@ (function() { this.entityID = null; - this.properties = null; this.lightID = null; this.sound = null; + this.soundURLs = ["https://hifi-public.s3.amazonaws.com/sounds/Switches%20and%20sliders/lamp_switch_1.wav", + "https://hifi-public.s3.amazonaws.com/sounds/Switches%20and%20sliders/lamp_switch_2.wav", + "https://hifi-public.s3.amazonaws.com/sounds/Switches%20and%20sliders/lamp_switch_3.wav"] + + var DEFAULT_USER_DATA = { + creatingLight: false, + lightID: null, + lightDefaultProperties: { + type: "Light", + position: { x: 0, y: 0, z: 0 }, + dimensions: { x: 5, y: 5, z: 5 }, + isSpotlight: false, + color: { red: 255, green: 48, blue: 0 }, + diffuseColor: { red: 255, green: 255, blue: 255 }, + ambientColor: { red: 255, green: 255, blue: 255 }, + specularColor: { red: 0, green: 0, blue: 0 }, + constantAttenuation: 1, + linearAttenuation: 0, + quadraticAttenuation: 0, + intensity: 10, + exponent: 0, + cutoff: 180, // in degrees + }, + soundIndex: Math.floor(Math.random() * this.soundURLs.length) + }; function copyObject(object) { return JSON.parse(JSON.stringify(object)); @@ -33,7 +57,8 @@ // Download sound if needed this.maybeDownloadSound = function() { if (this.sound === null) { - this.sound = SoundCache.getSound("http://public.highfidelity.io/sounds/Footsteps/FootstepW3Left-12db.wav"); + var soundIndex = getUserData(this.entityID).soundIndex; + this.sound = SoundCache.getSound(this.soundURLs[soundIndex]); } } // Play switch sound @@ -47,17 +72,21 @@ print("Warning: Couldn't play sound."); } } - - // Toggles the associated light entity - this.toggleLight = function() { - if (this.lightID) { - var lightProperties = Entities.getEntityProperties(this.lightID); - Entities.editEntity(this.lightID, { visible: !lightProperties.visible }); - } else { - print("Warning: No light to turn on/off"); + + // Checks whether the userData is well-formed and updates it if not + this.checkUserData = function() { + var userData = getUserData(this.entityID); + if (!userData) { + userData = DEFAULT_USER_DATA; + } else if (!userData.lightDefaultProperties) { + userData.lightDefaultProperties = DEFAULT_USER_DATA.lightDefaultProperties; + } else if (!userData.soundIndex) { + userData.soundIndex = DEFAULT_USER_DATA.soundIndex; } + updateUserData(this.entityID, userData); } + // Create a Light entity this.createLight = function(userData) { var lightProperties = copyObject(userData.lightDefaultProperties); if (lightProperties) { @@ -74,56 +103,48 @@ } } + // Tries to find a valid light, creates one otherwise this.updateLightID = function() { - var userData = getUserData(this.entityID); - if (!userData) { - userData = { - lightID: null, - lightDefaultProperties: { - type: "Light", - position: { x: 0, y: 0, z: 0 }, - dimensions: { x: 5, y: 5, z: 5 }, - isSpotlight: false, - color: { red: 255, green: 48, blue: 0 }, - diffuseColor: { red: 255, green: 255, blue: 255 }, - ambientColor: { red: 255, green: 255, blue: 255 }, - specularColor: { red: 0, green: 0, blue: 0 }, - constantAttenuation: 1, - linearAttenuation: 0, - quadraticAttenuation: 0, - intensity: 10, - exponent: 0, - cutoff: 180, // in degrees - } - }; - updateUserData(this.entityID, userData); - } - // Find valid light if (doesEntityExistNow(this.lightID)) { - if (!didEntityExist(this.lightID)) { - // Light now has an ID, so update it in userData - this.lightID = getTrueID(this.lightID); - userData.lightID = this.lightID; - updateUserData(this.entityID, userData); - } return; } + var userData = getUserData(this.entityID); if (doesEntityExistNow(userData.lightID)) { - this.lightID = getTrueID(userData.lightID); + this.lightID = userData.lightID; return; } - // No valid light, create one - this.lightID = this.createLight(userData); - print("Created new light entity"); - - // Update user data with new ID + if (!userData.creatingLight) { + // No valid light, create one + userData.creatingLight = true; + updateUserData(this.entityID, userData); + this.lightID = this.createLight(userData); + this.maybeUpdateLightIDInUserData(); + print("Created new light entity"); + } + } + + this.maybeUpdateLightIDInUserData = function() { + if (getTrueID(this.lightID).isKnownID) { + this.lightID = getTrueID(this.lightID); + this.updateLightIDInUserData(); + } else { + var that = this; + Script.setTimeout(function() { that.maybeUpdateLightIDInUserData() }, 500); + } + } + + // Update user data with new lightID + this.updateLightIDInUserData = function() { + var userData = getUserData(this.entityID); userData.lightID = this.lightID; + userData.creatingLight = false; updateUserData(this.entityID, userData); } + // Moves light entity if the lamp entity moved this.maybeMoveLight = function() { var entityProperties = Entities.getEntityProperties(this.entityID); var lightProperties = Entities.getEntityProperties(this.lightID); @@ -139,8 +160,9 @@ } } + // Stores light entity relative position in the lamp metadata this.updateRelativeLightPosition = function() { - if (!doesEntityExistNow(this.entityID) || !doesEntityExistNow(this.lightID)) { + if (!doesEntityExistNow(this.lightID)) { print("Warning: ID invalid, couldn't save relative position."); return; } @@ -168,21 +190,37 @@ updateUserData(this.entityID, userData); print("Relative properties of light entity saved."); } + + // This function should be called before any callback is executed + this.preOperation = function(entityID) { + this.entityID = entityID; + + this.checkUserData(); + this.maybeDownloadSound(); + } + + // Toggles the associated light entity + this.toggleLight = function() { + if (this.lightID) { + var lightProperties = Entities.getEntityProperties(this.lightID); + Entities.editEntity(this.lightID, { visible: !lightProperties.visible }); + this.playSound(); + } else { + print("Warning: No light to turn on/off"); + } + } this.preload = function(entityID) { - this.entityID = entityID; - this.maybeDownloadSound(); + this.preOperation(entityID); }; this.clickReleaseOnEntity = function(entityID, mouseEvent) { - this.entityID = entityID; - this.maybeDownloadSound(); + this.preOperation(entityID); if (mouseEvent.isLeftButton) { this.updateLightID(); this.maybeMoveLight(); this.toggleLight(); - this.playSound(); } else if (mouseEvent.isRightButton) { this.updateRelativeLightPosition(); } diff --git a/examples/libraries/entitySelectionTool.js b/examples/libraries/entitySelectionTool.js index c261e0d8a6..b6c536f063 100644 --- a/examples/libraries/entitySelectionTool.js +++ b/examples/libraries/entitySelectionTool.js @@ -1263,11 +1263,13 @@ SelectionDisplay = (function () { duplicatedEntityIDs = []; for (var otherEntityID in SelectionManager.savedProperties) { var properties = SelectionManager.savedProperties[otherEntityID]; - var entityID = Entities.addEntity(properties); - duplicatedEntityIDs.push({ - entityID: entityID, - properties: properties, - }); + if (!properties.locked) { + var entityID = Entities.addEntity(properties); + duplicatedEntityIDs.push({ + entityID: entityID, + properties: properties, + }); + } } } else { duplicatedEntityIDs = null; @@ -1361,11 +1363,13 @@ SelectionDisplay = (function () { duplicatedEntityIDs = []; for (var otherEntityID in SelectionManager.savedProperties) { var properties = SelectionManager.savedProperties[otherEntityID]; - var entityID = Entities.addEntity(properties); - duplicatedEntityIDs.push({ - entityID: entityID, - properties: properties, - }); + if (!properties.locked) { + var entityID = Entities.addEntity(properties); + duplicatedEntityIDs.push({ + entityID: entityID, + properties: properties, + }); + } } } else { duplicatedEntityIDs = null; diff --git a/examples/look.js b/examples/look.js index bcdfcf9e44..6bba57e3ad 100644 --- a/examples/look.js +++ b/examples/look.js @@ -80,7 +80,6 @@ function touchBeginEvent(event) { yawFromTouch = 0; pitchFromTouch = 0; startedTouching = true; - print("TOUCH BEGIN"); } function touchEndEvent(event) { @@ -88,7 +87,6 @@ function touchEndEvent(event) { print("touchEndEvent event.x,y=" + event.x + ", " + event.y); } startedTouching = false; - print("TOUCH END"); } function touchUpdateEvent(event) { diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index c1f3698326..dc8fa08c4e 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1755,7 +1755,7 @@ bool Application::exportEntities(const QString& filename, const QVectorgetEntityItemID(), properties); } - exportTree.writeToSVOFile(filename.toLocal8Bit().constData()); + exportTree.writeToJSONFile(filename.toLocal8Bit().constData()); // restore the main window's active state _window->activateWindow(); @@ -1909,8 +1909,6 @@ void Application::init() { _physicsEngine.init(&_entityEditSender); - _physicsEngine.setAvatarData(_myAvatar); - auto entityScriptingInterface = DependencyManager::get(); connect(&_physicsEngine, &EntitySimulation::entityCollisionWithEntity, @@ -2197,6 +2195,7 @@ void Application::update(float deltaTime) { { PerformanceTimer perfTimer("physics"); + _myAvatar->preSimulation(); _physicsEngine.stepSimulation(); } @@ -3600,6 +3599,7 @@ void Application::initializeAcceptedFiles() { if (_acceptedExtensions.size() == 0) { _acceptedExtensions[SNAPSHOT_EXTENSION] = &Application::acceptSnapshot; _acceptedExtensions[SVO_EXTENSION] = &Application::importSVOFromURL; + _acceptedExtensions[SVO_JSON_EXTENSION] = &Application::importSVOFromURL; _acceptedExtensions[JS_EXTENSION] = &Application::askToLoadScript; _acceptedExtensions[FST_EXTENSION] = &Application::askToSetAvatarUrl; } @@ -4208,7 +4208,7 @@ void Application::checkSkeleton() { _myAvatar->setSkeletonModelURL(DEFAULT_BODY_MODEL_URL); _myAvatar->sendIdentityPacket(); } else { - _myAvatar->updateLocalAABox(); - _physicsEngine.setAvatarData(_myAvatar); + _myAvatar->updateCharacterController(); + _physicsEngine.setCharacterController(_myAvatar->getCharacterController()); } } diff --git a/interface/src/Application.h b/interface/src/Application.h index 898db76f07..7764f297d3 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -95,6 +95,7 @@ static const float NODE_KILLED_BLUE = 0.0f; static const QString SNAPSHOT_EXTENSION = ".jpg"; static const QString SVO_EXTENSION = ".svo"; +static const QString SVO_JSON_EXTENSION = ".svo.json"; static const QString JS_EXTENSION = ".js"; static const QString FST_EXTENSION = ".fst"; diff --git a/interface/src/DiscoverabilityManager.cpp b/interface/src/DiscoverabilityManager.cpp index 49850e4ef6..791e5667c4 100644 --- a/interface/src/DiscoverabilityManager.cpp +++ b/interface/src/DiscoverabilityManager.cpp @@ -61,17 +61,21 @@ void DiscoverabilityManager::updateLocation() { uuidStringWithoutCurlyBraces(domainHandler.getUUID())); } + const QString FRIENDS_ONLY_KEY_IN_LOCATION = "friends_only"; + locationObject.insert(FRIENDS_ONLY_KEY_IN_LOCATION, (_mode.get() == Discoverability::Friends)); + rootObject.insert(LOCATION_KEY_IN_ROOT, locationObject); - accountManager.authenticatedRequest(API_USER_LOCATION_PATH, QNetworkAccessManager::PutOperation, - JSONCallbackParameters(), QJsonDocument(rootObject).toJson()); + accountManager.sendRequest(API_USER_LOCATION_PATH, AccountManagerAuth::Required, + QNetworkAccessManager::PutOperation, + JSONCallbackParameters(), QJsonDocument(rootObject).toJson()); } } } void DiscoverabilityManager::removeLocation() { AccountManager& accountManager = AccountManager::getInstance(); - accountManager.authenticatedRequest(API_USER_LOCATION_PATH, QNetworkAccessManager::DeleteOperation); + accountManager.sendRequest(API_USER_LOCATION_PATH, AccountManagerAuth::Required, QNetworkAccessManager::DeleteOperation); } void DiscoverabilityManager::setDiscoverabilityMode(Discoverability::Mode discoverabilityMode) { diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index ab4989a651..2f42544f28 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -70,7 +70,6 @@ MyAvatar::MyAvatar() : Avatar(), _turningKeyPressTime(0.0f), _gravity(0.0f, 0.0f, 0.0f), - _shouldJump(false), _wasPushing(false), _isPushing(false), _isBraking(false), @@ -82,6 +81,8 @@ MyAvatar::MyAvatar() : _scriptedMotorTimescale(DEFAULT_SCRIPTED_MOTOR_TIMESCALE), _scriptedMotorFrame(SCRIPTED_MOTOR_CAMERA_FRAME), _motionBehaviors(AVATAR_MOTION_DEFAULTS), + _enablePhysics(false), + _characterController(this), _lookAtTargetAvatar(), _shouldRender(true), _billboardValid(false), @@ -954,15 +955,15 @@ glm::vec3 MyAvatar::getSkeletonPosition() const { return Avatar::getPosition(); } -void MyAvatar::updateLocalAABox() { +void MyAvatar::updateCharacterController() { + // compute localAABox 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; + corner += _skeletonModel.getBoundingShapeOffset(); glm::vec3 scale(2.0f * radius, height, 2.0f * radius); - _localAABox.setBox(corner, scale); + _characterController.setLocalBoundingBox(corner, scale); } QString MyAvatar::getScriptedMotorFrame() const { @@ -1580,6 +1581,10 @@ glm::vec3 MyAvatar::getLaserPointerTipPosition(const PalmData* palm) { return palm->getPosition(); } +void MyAvatar::preSimulation() { + _characterController.setEnabled(_enablePhysics); +} + void MyAvatar::clearDriveKeys() { for (int i = 0; i < MAX_DRIVE_KEYS; ++i) { _driveKeys[i] = 0.0f; diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 08c0228f1e..a37d1c6a30 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -13,6 +13,7 @@ #define hifi_MyAvatar_h #include +#include #include "Avatar.h" @@ -88,7 +89,7 @@ public: void clearDriveKeys(); void setDriveKeys(int key, float val) { _driveKeys[key] = val; }; bool getDriveKeys(int key) { return _driveKeys[key] != 0.0f; }; - void jump() { _shouldJump = true; }; + void jump() { _characterController.jump(); } bool isMyAvatar() { return true; } @@ -122,6 +123,8 @@ public: virtual glm::vec3 getSkeletonPosition() const; void updateLocalAABox(); + CharacterController* getCharacterController() { return &_characterController; } + void updateCharacterController(); void clearJointAnimationPriorities(); @@ -145,6 +148,11 @@ public: const RecorderPointer getRecorder() const { return _recorder; } const PlayerPointer getPlayer() const { return _player; } + + void togglePhysicsEnabled() { _enablePhysics = !_enablePhysics; } + bool isPhysicsEnabled() { return _enablePhysics; } + void setPhysicsEnabled(bool enablePhysics) { _enablePhysics = enablePhysics; } + void preSimulation(); public slots: void increaseSize(); @@ -186,7 +194,6 @@ private: float _turningKeyPressTime; glm::vec3 _gravity; - bool _shouldJump; float _driveKeys[MAX_DRIVE_KEYS]; bool _wasPushing; bool _isPushing; @@ -202,6 +209,9 @@ private: int _scriptedMotorFrame; quint32 _motionBehaviors; + bool _enablePhysics; + CharacterController _characterController; + QWeakPointer _lookAtTargetAvatar; glm::vec3 _targetAvatarPosition; bool _shouldRender; diff --git a/interface/src/devices/SixenseManager.cpp b/interface/src/devices/SixenseManager.cpp index ce6ca57c69..0a89ad0e37 100644 --- a/interface/src/devices/SixenseManager.cpp +++ b/interface/src/devices/SixenseManager.cpp @@ -223,7 +223,7 @@ void SixenseManager::update(float deltaTime) { palm->setJoystick(data->joystick_x, data->joystick_y); // Emulate the mouse so we can use scripts - if (Menu::getInstance()->isOptionChecked(MenuOption::SixenseMouseInput)) { + if (Menu::getInstance()->isOptionChecked(MenuOption::SixenseMouseInput) && !_controllersAtBase) { emulateMouse(palm, numActiveControllers - 1); } diff --git a/interface/src/scripting/WebWindowClass.cpp b/interface/src/scripting/WebWindowClass.cpp index ae0e327cb1..61b3ce225f 100644 --- a/interface/src/scripting/WebWindowClass.cpp +++ b/interface/src/scripting/WebWindowClass.cpp @@ -55,6 +55,7 @@ WebWindowClass::WebWindowClass(const QString& title, const QString& url, int wid _windowWidget = dockWidget; } else { _windowWidget = new QWidget(Application::getInstance()->getWindow(), Qt::Window); + _windowWidget->setWindowTitle(title); _windowWidget->setMinimumSize(width, height); auto layout = new QVBoxLayout(_windowWidget); @@ -96,6 +97,18 @@ void WebWindowClass::setVisible(bool visible) { QMetaObject::invokeMethod(_windowWidget, "setVisible", Qt::BlockingQueuedConnection, Q_ARG(bool, visible)); } +void WebWindowClass::setURL(const QString& url) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "setURL", Qt::BlockingQueuedConnection, Q_ARG(QString, url)); + return; + } + _webView->setUrl(url); +} + +void WebWindowClass::raise() { + QMetaObject::invokeMethod(_windowWidget, "raise", Qt::BlockingQueuedConnection); +} + QScriptValue WebWindowClass::constructor(QScriptContext* context, QScriptEngine* engine) { WebWindowClass* retVal; QString file = context->argument(0).toString(); diff --git a/interface/src/scripting/WebWindowClass.h b/interface/src/scripting/WebWindowClass.h index c923fdd748..429b054966 100644 --- a/interface/src/scripting/WebWindowClass.h +++ b/interface/src/scripting/WebWindowClass.h @@ -34,6 +34,7 @@ signals: class WebWindowClass : public QObject { Q_OBJECT Q_PROPERTY(QObject* eventBridge READ getEventBridge) + Q_PROPERTY(QString url READ getURL) public: WebWindowClass(const QString& title, const QString& url, int width, int height, bool isToolWindow = false); ~WebWindowClass(); @@ -42,6 +43,9 @@ public: public slots: void setVisible(bool visible); + QString getURL() const { return _webView->url().url(); } + void setURL(const QString& url); + void raise(); ScriptEventBridge* getEventBridge() const { return _eventBridge; } void addEventBridgeToWindowObject(); diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 28123124a0..a2feb98798 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -300,16 +300,6 @@ public: const AABox& getLocalAABox() const { return _localAABox; } const Referential* getReferential() const { return _referential; } - void togglePhysicsEnabled() { _enablePhysics = !_enablePhysics; } - bool isPhysicsEnabled() { return _enablePhysics; } - void setPhysicsEnabled(bool enablePhysics) { _enablePhysics = enablePhysics; } - - void lockForRead() { _lock.lockForRead(); } - bool tryLockForRead() { return _lock.tryLockForRead(); } - void lockForWrite() { _lock.lockForWrite(); } - bool tryLockForWrite() { return _lock.tryLockForWrite(); } - void unlock() { _lock.unlock(); } - void setVelocity(const glm::vec3 velocity) { _velocity = velocity; } Q_INVOKABLE glm::vec3 getVelocity() const { return _velocity; } @@ -409,9 +399,6 @@ private: // privatize the copy constructor and assignment operator so they cannot be called AvatarData(const AvatarData&); AvatarData& operator= (const AvatarData&); - - QReadWriteLock _lock; - bool _enablePhysics = false; }; Q_DECLARE_METATYPE(AvatarData*) diff --git a/libraries/entities/src/EntityItemID.cpp b/libraries/entities/src/EntityItemID.cpp index aaf6e33128..cd2202eead 100644 --- a/libraries/entities/src/EntityItemID.cpp +++ b/libraries/entities/src/EntityItemID.cpp @@ -25,7 +25,7 @@ EntityItemID::EntityItemID() : creatorTokenID(UNKNOWN_ENTITY_TOKEN), isKnownID(false) { -}; +} EntityItemID::EntityItemID(const EntityItemID& other) : id(other.id), diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp index 2a809f2a7c..5858c0926d 100644 --- a/libraries/networking/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -115,7 +115,7 @@ void AccountManager::updateBalance() { callbackParameters.jsonCallbackReceiver = &_accountInfo; callbackParameters.jsonCallbackMethod = "setBalanceFromJSON"; - authenticatedRequest("/api/v1/wallets/mine", QNetworkAccessManager::GetOperation, callbackParameters); + sendRequest("/api/v1/wallets/mine", AccountManagerAuth::Required, QNetworkAccessManager::GetOperation, callbackParameters); } } @@ -159,50 +159,30 @@ void AccountManager::setAuthURL(const QUrl& authURL) { } } -void AccountManager::authenticatedRequest(const QString& path, QNetworkAccessManager::Operation operation, - const JSONCallbackParameters& callbackParams, - const QByteArray& dataByteArray, - QHttpMultiPart* dataMultiPart, - const QVariantMap& propertyMap) { +void AccountManager::sendRequest(const QString& path, + AccountManagerAuth::Type authType, + QNetworkAccessManager::Operation operation, + const JSONCallbackParameters& callbackParams, + const QByteArray& dataByteArray, + QHttpMultiPart* dataMultiPart, + const QVariantMap& propertyMap) { - QMetaObject::invokeMethod(this, "invokedRequest", - Q_ARG(const QString&, path), - Q_ARG(bool, true), - Q_ARG(QNetworkAccessManager::Operation, operation), - Q_ARG(const JSONCallbackParameters&, callbackParams), - Q_ARG(const QByteArray&, dataByteArray), - Q_ARG(QHttpMultiPart*, dataMultiPart), - Q_ARG(QVariantMap, propertyMap)); -} - -void AccountManager::unauthenticatedRequest(const QString& path, QNetworkAccessManager::Operation operation, - const JSONCallbackParameters& callbackParams, - const QByteArray& dataByteArray, - QHttpMultiPart* dataMultiPart, - const QVariantMap& propertyMap) { + if (thread() != QThread::currentThread()) { + QMetaObject::invokeMethod(this, "sendRequest", + Q_ARG(const QString&, path), + Q_ARG(AccountManagerAuth::Type, AccountManagerAuth::Required), + Q_ARG(QNetworkAccessManager::Operation, operation), + Q_ARG(const JSONCallbackParameters&, callbackParams), + Q_ARG(const QByteArray&, dataByteArray), + Q_ARG(QHttpMultiPart*, dataMultiPart), + Q_ARG(QVariantMap, propertyMap)); + } - QMetaObject::invokeMethod(this, "invokedRequest", - Q_ARG(const QString&, path), - Q_ARG(bool, false), - Q_ARG(QNetworkAccessManager::Operation, operation), - Q_ARG(const JSONCallbackParameters&, callbackParams), - Q_ARG(const QByteArray&, dataByteArray), - Q_ARG(QHttpMultiPart*, dataMultiPart), - Q_ARG(QVariantMap, propertyMap)); -} - -void AccountManager::invokedRequest(const QString& path, - bool requiresAuthentication, - QNetworkAccessManager::Operation operation, - const JSONCallbackParameters& callbackParams, - const QByteArray& dataByteArray, QHttpMultiPart* dataMultiPart, - const QVariantMap& propertyMap) { - QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); - + QNetworkRequest networkRequest; networkRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); - + QUrl requestURL = _authURL; if (path.startsWith("/")) { @@ -211,13 +191,17 @@ void AccountManager::invokedRequest(const QString& path, requestURL.setPath("/" + path); } - if (requiresAuthentication) { + if (authType != AccountManagerAuth::None ) { if (hasValidAccessToken()) { networkRequest.setRawHeader(ACCESS_TOKEN_AUTHORIZATION_HEADER, _accountInfo.getAccessToken().authorizationHeaderValue()); } else { - qDebug() << "No valid access token present. Bailing on authenticated invoked request."; - return; + if (authType == AccountManagerAuth::Required) { + qDebug() << "No valid access token present. Bailing on invoked request to" + << path << "that requires authentication"; + return; + } + } } @@ -540,8 +524,8 @@ void AccountManager::processGeneratedKeypair(const QByteArray& publicKey, const requestMultiPart->append(keyPart); - authenticatedRequest(PUBLIC_KEY_UPDATE_PATH, QNetworkAccessManager::PutOperation, - JSONCallbackParameters(), QByteArray(), requestMultiPart); + sendRequest(PUBLIC_KEY_UPDATE_PATH, AccountManagerAuth::Required, QNetworkAccessManager::PutOperation, + JSONCallbackParameters(), QByteArray(), requestMultiPart); // get rid of the keypair generator now that we don't need it anymore sender()->deleteLater(); diff --git a/libraries/networking/src/AccountManager.h b/libraries/networking/src/AccountManager.h index 2c9a441db1..b9c85d71e0 100644 --- a/libraries/networking/src/AccountManager.h +++ b/libraries/networking/src/AccountManager.h @@ -37,6 +37,16 @@ public: QString updateSlot; }; +namespace AccountManagerAuth { + enum Type { + None, + Required, + Optional + }; +} + +Q_DECLARE_METATYPE(AccountManagerAuth::Type); + const QByteArray ACCESS_TOKEN_AUTHORIZATION_HEADER = "Authorization"; class AccountManager : public QObject { @@ -44,19 +54,13 @@ class AccountManager : public QObject { public: static AccountManager& getInstance(bool forceReset = false); - void authenticatedRequest(const QString& path, - QNetworkAccessManager::Operation operation = QNetworkAccessManager::GetOperation, - const JSONCallbackParameters& callbackParams = JSONCallbackParameters(), - const QByteArray& dataByteArray = QByteArray(), - QHttpMultiPart* dataMultiPart = NULL, - const QVariantMap& propertyMap = QVariantMap()); - - void unauthenticatedRequest(const QString& path, - QNetworkAccessManager::Operation operation = QNetworkAccessManager::GetOperation, - const JSONCallbackParameters& callbackParams = JSONCallbackParameters(), - const QByteArray& dataByteArray = QByteArray(), - QHttpMultiPart* dataMultiPart = NULL, - const QVariantMap& propertyMap = QVariantMap()) ; + Q_INVOKABLE void sendRequest(const QString& path, + AccountManagerAuth::Type authType, + QNetworkAccessManager::Operation operation = QNetworkAccessManager::GetOperation, + const JSONCallbackParameters& callbackParams = JSONCallbackParameters(), + const QByteArray& dataByteArray = QByteArray(), + QHttpMultiPart* dataMultiPart = NULL, + const QVariantMap& propertyMap = QVariantMap()); const QUrl& getAuthURL() const { return _authURL; } void setAuthURL(const QUrl& authURL); @@ -107,14 +111,6 @@ private: void passSuccessToCallback(QNetworkReply* reply); void passErrorToCallback(QNetworkReply* reply); - Q_INVOKABLE void invokedRequest(const QString& path, - bool requiresAuthentication, - QNetworkAccessManager::Operation operation, - const JSONCallbackParameters& callbackParams, - const QByteArray& dataByteArray, - QHttpMultiPart* dataMultiPart, - const QVariantMap& propertyMap); - QUrl _authURL; QMap _pendingCallbackMap; diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp index 0458a5e912..3c27c4644c 100644 --- a/libraries/networking/src/AddressManager.cpp +++ b/libraries/networking/src/AddressManager.cpp @@ -294,12 +294,11 @@ void AddressManager::attemptPlaceNameLookup(const QString& lookupString, const Q requestParams.insert(OVERRIDE_PATH_KEY, overridePath); } - AccountManager::getInstance().unauthenticatedRequest(GET_PLACE.arg(placeName), - QNetworkAccessManager::GetOperation, - apiCallbackParameters(), - QByteArray(), - NULL, - requestParams); + AccountManager::getInstance().sendRequest(GET_PLACE.arg(placeName), + AccountManagerAuth::None, + QNetworkAccessManager::GetOperation, + apiCallbackParameters(), + QByteArray(), NULL, requestParams); } bool AddressManager::handleNetworkAddress(const QString& lookupString) { @@ -439,9 +438,10 @@ void AddressManager::setDomainInfo(const QString& hostname, quint16 port) { void AddressManager::goToUser(const QString& username) { QString formattedUsername = QUrl::toPercentEncoding(username); // this is a username - pull the captured name and lookup that user's location - AccountManager::getInstance().unauthenticatedRequest(GET_USER_LOCATION.arg(formattedUsername), - QNetworkAccessManager::GetOperation, - apiCallbackParameters()); + AccountManager::getInstance().sendRequest(GET_USER_LOCATION.arg(formattedUsername), + AccountManagerAuth::Optional, + QNetworkAccessManager::GetOperation, + apiCallbackParameters()); } void AddressManager::copyAddress() { diff --git a/libraries/networking/src/AddressManager.h b/libraries/networking/src/AddressManager.h index bfc3ace657..a2522afcbc 100644 --- a/libraries/networking/src/AddressManager.h +++ b/libraries/networking/src/AddressManager.h @@ -23,7 +23,7 @@ #include "AccountManager.h" const QString HIFI_URL_SCHEME = "hifi"; -const QString DEFAULT_HIFI_ADDRESS = "hifi://sandbox"; +const QString DEFAULT_HIFI_ADDRESS = "hifi://entry"; typedef const glm::vec3& (*PositionGetter)(); typedef glm::quat (*OrientationGetter)(); diff --git a/libraries/networking/src/UserActivityLogger.cpp b/libraries/networking/src/UserActivityLogger.cpp index 64828708b2..89c0bd34bd 100644 --- a/libraries/networking/src/UserActivityLogger.cpp +++ b/libraries/networking/src/UserActivityLogger.cpp @@ -62,11 +62,10 @@ void UserActivityLogger::logAction(QString action, QJsonObject details, JSONCall params.errorCallbackMethod = "requestError"; } - accountManager.authenticatedRequest(USER_ACTIVITY_URL, - QNetworkAccessManager::PostOperation, - params, - NULL, - multipart); + accountManager.sendRequest(USER_ACTIVITY_URL, + AccountManagerAuth::Required, + QNetworkAccessManager::PostOperation, + params, NULL, multipart); } void UserActivityLogger::requestFinished(QNetworkReply& requestReply) { diff --git a/libraries/physics/src/CharacterController.cpp b/libraries/physics/src/CharacterController.cpp index fb200b2c57..e84df7d644 100644 --- a/libraries/physics/src/CharacterController.cpp +++ b/libraries/physics/src/CharacterController.cpp @@ -1,15 +1,16 @@ /* Bullet Continuous Collision Detection and Physics Library Copyright (c) 2003-2008 Erwin Coumans http://bulletphysics.com +2015.03.25 -- modified by Andrew Meadows andrew@highfidelity.io 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, +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 +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. @@ -21,10 +22,14 @@ subject to the following restrictions: #include "BulletUtil.h" #include "CharacterController.h" +const uint32_t PENDING_FLAG_ADD_TO_SIMULATION = 1U << 0; +const uint32_t PENDING_FLAG_REMOVE_FROM_SIMULATION = 1U << 1; +const uint32_t PENDING_FLAG_UPDATE_SHAPE = 1U << 2; +const uint32_t PENDING_FLAG_JUMP = 1U << 3; // static helper method static btVector3 getNormalizedVector(const btVector3& v) { - // NOTE: check the length first, then normalize + // NOTE: check the length first, then normalize // --> avoids assert when trying to normalize zero-length vectors btScalar vLength = v.length(); if (vLength < FLT_EPSILON) { @@ -35,46 +40,18 @@ static btVector3 getNormalizedVector(const btVector3& v) { 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) + , _me(me) + , _up(up) + , _minSlopeDot(minSlopeDot) { } virtual btScalar addSingleResult(btCollisionWorld::LocalConvexResult& convexResult, bool normalInWorldSpace) { - if (convexResult.m_hitCollisionObject == m_me) { + if (convexResult.m_hitCollisionObject == _me) { return btScalar(1.0); } @@ -91,10 +68,10 @@ class btKinematicClosestNotMeConvexResultCallback : public btCollisionWorld::Clo } // Note: hitNormalWorld points into character, away from object - // and m_up points opposite to movement + // and _up points opposite to movement - btScalar dotUp = m_up.dot(hitNormalWorld); - if (dotUp < m_minSlopeDot) { + btScalar dotUp = _up.dot(hitNormalWorld); + if (dotUp < _minSlopeDot) { return btScalar(1.0); } @@ -102,16 +79,16 @@ class btKinematicClosestNotMeConvexResultCallback : public btCollisionWorld::Clo } protected: - btCollisionObject* m_me; - const btVector3 m_up; - btScalar m_minSlopeDot; + btCollisionObject* _me; + const btVector3 _up; + btScalar _minSlopeDot; }; class StepDownConvexResultCallback : public btCollisionWorld::ClosestConvexResultCallback { // special convex sweep callback for character during the stepDown() phase public: - StepDownConvexResultCallback(btCollisionObject* me, - const btVector3& up, + StepDownConvexResultCallback(btCollisionObject* me, + const btVector3& up, const btVector3& start, const btVector3& step, const btVector3& pushDirection, @@ -119,19 +96,19 @@ class StepDownConvexResultCallback : public btCollisionWorld::ClosestConvexResul btScalar radius, btScalar halfHeight) : btCollisionWorld::ClosestConvexResultCallback(btVector3(0.0, 0.0, 0.0), btVector3(0.0, 0.0, 0.0)) - , m_me(me) - , m_up(up) - , m_start(start) - , m_step(step) - , m_pushDirection(pushDirection) - , m_minSlopeDot(minSlopeDot) - , m_radius(radius) - , m_halfHeight(halfHeight) + , _me(me) + , _up(up) + , _start(start) + , _step(step) + , _pushDirection(pushDirection) + , _minSlopeDot(minSlopeDot) + , _radius(radius) + , _halfHeight(halfHeight) { } virtual btScalar addSingleResult(btCollisionWorld::LocalConvexResult& convexResult, bool normalInWorldSpace) { - if (convexResult.m_hitCollisionObject == m_me) { + if (convexResult.m_hitCollisionObject == _me) { return btScalar(1.0); } @@ -148,26 +125,26 @@ class StepDownConvexResultCallback : public btCollisionWorld::ClosestConvexResul } // Note: hitNormalWorld points into character, away from object - // and m_up points opposite to movement + // and _up points opposite to movement - btScalar dotUp = m_up.dot(hitNormalWorld); - if (dotUp < m_minSlopeDot) { - if (hitNormalWorld.dot(m_pushDirection) > 0.0f) { + btScalar dotUp = _up.dot(hitNormalWorld); + if (dotUp < _minSlopeDot) { + if (hitNormalWorld.dot(_pushDirection) > 0.0f) { // ignore hits that push in same direction as character is moving // which helps character NOT snag when stepping off ledges return btScalar(1.0f); } // compute the angle between "down" and the line from character center to "hit" point - btVector3 fractionalStep = convexResult.m_hitFraction * m_step; - btVector3 localHit = convexResult.m_hitPointLocal - m_start + fractionalStep; - btScalar angle = localHit.angle(-m_up); + btVector3 fractionalStep = convexResult.m_hitFraction * _step; + btVector3 localHit = convexResult.m_hitPointLocal - _start + fractionalStep; + btScalar angle = localHit.angle(-_up); - // compute a maxAngle based on size of m_step - btVector3 side(m_radius, - (m_halfHeight - m_step.length() + fractionalStep.dot(m_up)), 0.0f); - btScalar maxAngle = side.angle(-m_up); + // compute a maxAngle based on size of _step + btVector3 side(_radius, - (_halfHeight - _step.length() + fractionalStep.dot(_up)), 0.0f); + btScalar maxAngle = side.angle(-_up); - // Ignore hits that are larger than maxAngle. Effectively what is happening here is: + // Ignore hits that are larger than maxAngle. Effectively what is happening here is: // we're ignoring hits at contacts that have non-vertical normals... if they hit higher // than the character's "feet". Ignoring the contact allows the character to slide down // for these hits. In other words, vertical walls against the character's torso will @@ -182,14 +159,14 @@ class StepDownConvexResultCallback : public btCollisionWorld::ClosestConvexResul } protected: - btCollisionObject* m_me; - const btVector3 m_up; - btVector3 m_start; - btVector3 m_step; - btVector3 m_pushDirection; - btScalar m_minSlopeDot; - btScalar m_radius; - btScalar m_halfHeight; + btCollisionObject* _me; + const btVector3 _up; + btVector3 _start; + btVector3 _step; + btVector3 _pushDirection; + btScalar _minSlopeDot; + btScalar _radius; + btScalar _halfHeight; }; /* @@ -216,45 +193,38 @@ btVector3 CharacterController::perpindicularComponent(const btVector3& direction return direction - parallelComponent(direction, normal); } +const btVector3 LOCAL_UP_AXIS(0.0f, 1.0f, 0.0f); + CharacterController::CharacterController(AvatarData* avatarData) { assert(avatarData); - m_avatarData = avatarData; + _avatarData = avatarData; - // cache the "PhysicsEnabled" state of m_avatarData - m_avatarData->lockForRead(); - m_enabled = m_avatarData->isPhysicsEnabled(); - m_avatarData->unlock(); + // cache the "PhysicsEnabled" state of _avatarData + _enabled = false; - createShapeAndGhost(); + _ghostObject = NULL; + _convexShape = NULL; - m_upAxis = 1; // HACK: hard coded to be 1 for now (yAxis) - - m_addedMargin = 0.02f; - m_walkDirection.setValue(0.0f,0.0f,0.0f); - m_turnAngle = btScalar(0.0f); - m_useWalkDirection = true; // use walk direction by default, legacy behavior - m_velocityTimeInterval = 0.0f; - m_verticalVelocity = 0.0f; - m_verticalOffset = 0.0f; - m_gravity = 9.8f; - m_maxFallSpeed = 55.0f; // Terminal velocity of a sky diver in m/s. - m_jumpSpeed = 10.0f; // ? - m_wasOnGround = false; - m_wasJumping = false; - m_interpolateUp = true; + _addedMargin = 0.02f; + _walkDirection.setValue(0.0f,0.0f,0.0f); + _velocityTimeInterval = 0.0f; + _verticalVelocity = 0.0f; + _verticalOffset = 0.0f; + _gravity = 9.8f; + _maxFallSpeed = 55.0f; // Terminal velocity of a sky diver in m/s. + _jumpSpeed = 7.0f; + _wasOnGround = false; + _wasJumping = false; setMaxSlope(btRadians(45.0f)); - m_lastStepUp = 0.0f; - - // internal state data members - full_drop = false; - bounce_fix = false; + _lastStepUp = 0.0f; + _pendingFlags = 0; } CharacterController::~CharacterController() { } btPairCachingGhostObject* CharacterController::getGhostObject() { - return m_ghostObject; + return _ghostObject; } bool CharacterController::recoverFromPenetration(btCollisionWorld* collisionWorld) { @@ -267,26 +237,26 @@ bool CharacterController::recoverFromPenetration(btCollisionWorld* collisionWorl // 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, + _convexShape->getAabb(_ghostObject->getWorldTransform(), minAabb, maxAabb); + collisionWorld->getBroadphase()->setAabb(_ghostObject->getBroadphaseHandle(), + minAabb, + maxAabb, collisionWorld->getDispatcher()); bool penetration = false; - collisionWorld->getDispatcher()->dispatchAllCollisionPairs(m_ghostObject->getOverlappingPairCache(), collisionWorld->getDispatchInfo(), collisionWorld->getDispatcher()); + collisionWorld->getDispatcher()->dispatchAllCollisionPairs(_ghostObject->getOverlappingPairCache(), collisionWorld->getDispatchInfo(), collisionWorld->getDispatcher()); - m_currentPosition = m_ghostObject->getWorldTransform().getOrigin(); - btVector3 up = getUpAxisDirections()[m_upAxis]; + _currentPosition = _ghostObject->getWorldTransform().getOrigin(); + btVector3 up = quatRotate(_currentRotation, LOCAL_UP_AXIS); - btVector3 currentPosition = m_currentPosition; + btVector3 currentPosition = _currentPosition; btScalar maxPen = btScalar(0.0); - for (int i = 0; i < m_ghostObject->getOverlappingPairCache()->getNumOverlappingPairs(); i++) { - m_manifoldArray.resize(0); + for (int i = 0; i < _ghostObject->getOverlappingPairCache()->getNumOverlappingPairs(); i++) { + _manifoldArray.resize(0); - btBroadphasePair* collisionPair = &m_ghostObject->getOverlappingPairCache()->getOverlappingPairArray()[i]; + btBroadphasePair* collisionPair = &_ghostObject->getOverlappingPairCache()->getOverlappingPairArray()[i]; btCollisionObject* obj0 = static_cast(collisionPair->m_pProxy0->m_clientObject); btCollisionObject* obj1 = static_cast(collisionPair->m_pProxy1->m_clientObject); @@ -296,12 +266,12 @@ bool CharacterController::recoverFromPenetration(btCollisionWorld* collisionWorl } if (collisionPair->m_algorithm) { - collisionPair->m_algorithm->getAllContactManifolds(m_manifoldArray); + collisionPair->m_algorithm->getAllContactManifolds(_manifoldArray); } - for (int j = 0;j < m_manifoldArray.size(); j++) { - btPersistentManifold* manifold = m_manifoldArray[j]; - btScalar directionSign = (manifold->getBody0() == m_ghostObject) ? btScalar(1.0) : btScalar(-1.0); + for (int j = 0;j < _manifoldArray.size(); j++) { + btPersistentManifold* manifold = _manifoldArray[j]; + btScalar directionSign = (manifold->getBody0() == _ghostObject) ? btScalar(1.0) : btScalar(-1.0); for (int p = 0;p < manifold->getNumContacts(); p++) { const btManifoldPoint&pt = manifold->getContactPoint(p); @@ -313,7 +283,7 @@ bool CharacterController::recoverFromPenetration(btCollisionWorld* collisionWorl normal *= directionSign; // always points from object to character btScalar normalDotUp = normal.dot(up); - if (normalDotUp < m_maxSlopeCosine) { + if (normalDotUp < _maxSlopeCosine) { // this contact has a non-vertical normal... might need to ignored btVector3 collisionPoint; if (directionSign > 0.0) { @@ -323,83 +293,79 @@ bool CharacterController::recoverFromPenetration(btCollisionWorld* collisionWorl } // we do math in frame where character base is origin - btVector3 characterBase = currentPosition - (m_radius + m_halfHeight) * up; + btVector3 characterBase = currentPosition - (_radius + _halfHeight) * up; collisionPoint -= characterBase; btScalar collisionHeight = collisionPoint.dot(up); - if (collisionHeight < m_lastStepUp) { + if (collisionHeight < _lastStepUp) { // This contact is below the lastStepUp, so we ignore it for penetration resolution, - // otherwise it may prevent the character from getting close enough to find any available + // otherwise it may prevent the character from getting close enough to find any available // horizontal foothold that would allow it to climbe the ledge. In other words, we're // making the character's "feet" soft for collisions against steps, but not floors. useContact = false; - } + } } if (useContact) { - + if (dist < maxPen) { maxPen = dist; - m_floorNormal = normal; + _floorNormal = normal; } const btScalar INCREMENTAL_RESOLUTION_FACTOR = 0.2f; - m_currentPosition += normal * (fabsf(dist) * INCREMENTAL_RESOLUTION_FACTOR); + _currentPosition += normal * (fabsf(dist) * INCREMENTAL_RESOLUTION_FACTOR); penetration = true; } } } } } - btTransform newTrans = m_ghostObject->getWorldTransform(); - newTrans.setOrigin(m_currentPosition); - m_ghostObject->setWorldTransform(newTrans); + btTransform newTrans = _ghostObject->getWorldTransform(); + newTrans.setOrigin(_currentPosition); + _ghostObject->setWorldTransform(newTrans); return penetration; } -void CharacterController::stepUp( btCollisionWorld* world) { +void CharacterController::stepUp(btCollisionWorld* world) { // phase 1: up // compute start and end btTransform start, end; start.setIdentity(); - start.setOrigin(m_currentPosition + getUpAxisDirections()[m_upAxis] * (m_convexShape->getMargin() + m_addedMargin)); + btVector3 up = quatRotate(_currentRotation, LOCAL_UP_AXIS); + start.setOrigin(_currentPosition + up * (_convexShape->getMargin() + _addedMargin)); - //m_targetPosition = m_currentPosition + getUpAxisDirections()[m_upAxis] * (m_stepHeight + (m_verticalOffset > 0.0f ? m_verticalOffset : 0.0f)); - m_targetPosition = m_currentPosition + getUpAxisDirections()[m_upAxis] * m_stepHeight; + _targetPosition = _currentPosition + up * _stepHeight; end.setIdentity(); - end.setOrigin(m_targetPosition); + end.setOrigin(_targetPosition); // sweep up - btVector3 sweepDirNegative = -getUpAxisDirections()[m_upAxis]; - btKinematicClosestNotMeConvexResultCallback callback(m_ghostObject, sweepDirNegative, btScalar(0.7071)); + btVector3 sweepDirNegative = - up; + btKinematicClosestNotMeConvexResultCallback callback(_ghostObject, sweepDirNegative, btScalar(0.7071)); callback.m_collisionFilterGroup = getGhostObject()->getBroadphaseHandle()->m_collisionFilterGroup; callback.m_collisionFilterMask = getGhostObject()->getBroadphaseHandle()->m_collisionFilterMask; - m_ghostObject->convexSweepTest(m_convexShape, start, end, callback, world->getDispatchInfo().m_allowedCcdPenetration); + _ghostObject->convexSweepTest(_convexShape, start, end, callback, world->getDispatchInfo().m_allowedCcdPenetration); if (callback.hasHit()) { // we hit something, so zero our vertical velocity - m_verticalVelocity = 0.0; - m_verticalOffset = 0.0; + _verticalVelocity = 0.0f; + _verticalOffset = 0.0f; // 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) { - m_lastStepUp = m_stepHeight * callback.m_closestHitFraction; - if (m_interpolateUp == true) { - m_currentPosition.setInterpolate3 (m_currentPosition, m_targetPosition, callback.m_closestHitFraction); - } else { - m_currentPosition = m_targetPosition; - } + if (callback.m_hitNormalWorld.dot(up) > 0.0f) { + _lastStepUp = _stepHeight * callback.m_closestHitFraction; + _currentPosition.setInterpolate3(_currentPosition, _targetPosition, callback.m_closestHitFraction); } else { - m_lastStepUp = m_stepHeight; - m_currentPosition = m_targetPosition; + _lastStepUp = _stepHeight; + _currentPosition = _targetPosition; } } else { - m_currentPosition = m_targetPosition; - m_lastStepUp = m_stepHeight; + _currentPosition = _targetPosition; + _lastStepUp = _stepHeight; } } void CharacterController::updateTargetPositionBasedOnCollision(const btVector3& hitNormal, btScalar tangentMag, btScalar normalMag) { - btVector3 movementDirection = m_targetPosition - m_currentPosition; + btVector3 movementDirection = _targetPosition - _currentPosition; btScalar movementLength = movementDirection.length(); if (movementLength > SIMD_EPSILON) { movementDirection.normalize(); @@ -412,97 +378,93 @@ void CharacterController::updateTargetPositionBasedOnCollision(const btVector3& parallelDir = parallelComponent(reflectDir, hitNormal); perpindicularDir = perpindicularComponent(reflectDir, hitNormal); - m_targetPosition = m_currentPosition; + _targetPosition = _currentPosition; //if (tangentMag != 0.0) { if (0) { btVector3 parComponent = parallelDir * btScalar(tangentMag * movementLength); - m_targetPosition += parComponent; + _targetPosition += parComponent; } if (normalMag != 0.0) { btVector3 perpComponent = perpindicularDir * btScalar(normalMag * movementLength); - m_targetPosition += perpComponent; + _targetPosition += perpComponent; } } } -void CharacterController::stepForward( btCollisionWorld* collisionWorld, const btVector3& movement) { +void CharacterController::stepForward(btCollisionWorld* collisionWorld, const btVector3& movement) { // phase 2: forward - m_targetPosition = m_currentPosition + movement; + _targetPosition = _currentPosition + movement; btTransform start, end; start.setIdentity(); end.setIdentity(); /* TODO: experiment with this to see if we can use this to help direct motion when a floor is available - if (m_touchingContact) { - if (m_normalizedDirection.dot(m_floorNormal) < btScalar(0.0)) { - updateTargetPositionBasedOnCollision(m_floorNormal, 1.0f, 1.0f); + if (_touchingContact) { + if (_normalizedDirection.dot(_floorNormal) < btScalar(0.0)) { + updateTargetPositionBasedOnCollision(_floorNormal, 1.0f, 1.0f); } }*/ // modify shape's margin for the sweeps - btScalar margin = m_convexShape->getMargin(); - m_convexShape->setMargin(margin + m_addedMargin); + btScalar margin = _convexShape->getMargin(); + _convexShape->setMargin(margin + _addedMargin); const btScalar MIN_STEP_DISTANCE = 0.0001f; - btVector3 step = m_targetPosition - m_currentPosition; + btVector3 step = _targetPosition - _currentPosition; btScalar stepLength2 = step.length2(); int maxIter = 10; while (stepLength2 > MIN_STEP_DISTANCE && maxIter-- > 0) { - start.setOrigin(m_currentPosition); - end.setOrigin(m_targetPosition); + start.setOrigin(_currentPosition); + end.setOrigin(_targetPosition); // sweep forward - btVector3 sweepDirNegative(m_currentPosition - m_targetPosition); - btKinematicClosestNotMeConvexResultCallback callback(m_ghostObject, sweepDirNegative, btScalar(0.0)); + btVector3 sweepDirNegative(_currentPosition - _targetPosition); + btKinematicClosestNotMeConvexResultCallback callback(_ghostObject, sweepDirNegative, btScalar(0.0)); callback.m_collisionFilterGroup = getGhostObject()->getBroadphaseHandle()->m_collisionFilterGroup; callback.m_collisionFilterMask = getGhostObject()->getBroadphaseHandle()->m_collisionFilterMask; - m_ghostObject->convexSweepTest(m_convexShape, start, end, callback, collisionWorld->getDispatchInfo().m_allowedCcdPenetration); + _ghostObject->convexSweepTest(_convexShape, start, end, callback, collisionWorld->getDispatchInfo().m_allowedCcdPenetration); if (callback.hasHit()) { // we hit soemthing! // Compute new target position by removing portion cut-off by collision, which will produce a new target // that is the closest approach of the the obstacle plane to the original target. - step = m_targetPosition - m_currentPosition; + step = _targetPosition - _currentPosition; btScalar stepDotNormal = step.dot(callback.m_hitNormalWorld); // we expect this dot to be negative step += (stepDotNormal * (1.0f - callback.m_closestHitFraction)) * callback.m_hitNormalWorld; - m_targetPosition = m_currentPosition + step; + _targetPosition = _currentPosition + step; stepLength2 = step.length2(); } else { // we swept to the end without hitting anything - m_currentPosition = m_targetPosition; + _currentPosition = _targetPosition; break; } } // restore shape's margin - m_convexShape->setMargin(margin); + _convexShape->setMargin(margin); } -void CharacterController::stepDown( btCollisionWorld* collisionWorld, btScalar dt) { +void CharacterController::stepDown(btCollisionWorld* collisionWorld, btScalar dt) { // phase 3: down // - // The "stepDown" phase first makes a normal sweep down that cancels the lift from the "stepUp" phase. + // The "stepDown" phase first makes a normal sweep down that cancels the lift from the "stepUp" phase. // If it hits a ledge then it stops otherwise it makes another sweep down in search of a floor within // reach of the character's feet. - btScalar downSpeed = (m_verticalVelocity < 0.0f) ? -m_verticalVelocity : 0.0f; - if (downSpeed > 0.0f && downSpeed > m_maxFallSpeed && (m_wasOnGround || !m_wasJumping)) { - downSpeed = m_maxFallSpeed; - } - // first sweep for ledge - btVector3 step = getUpAxisDirections()[m_upAxis] * (-(m_lastStepUp + downSpeed * dt)); + btVector3 up = quatRotate(_currentRotation, LOCAL_UP_AXIS); + btVector3 step = (_verticalVelocity * dt - _lastStepUp) * up; - StepDownConvexResultCallback callback(m_ghostObject, - getUpAxisDirections()[m_upAxis], - m_currentPosition, step, - m_walkDirection, - m_maxSlopeCosine, - m_radius, m_halfHeight); + StepDownConvexResultCallback callback(_ghostObject, + up, + _currentPosition, step, + _walkDirection, + _maxSlopeCosine, + _radius, _halfHeight); callback.m_collisionFilterGroup = getGhostObject()->getBroadphaseHandle()->m_collisionFilterGroup; callback.m_collisionFilterMask = getGhostObject()->getBroadphaseHandle()->m_collisionFilterMask; @@ -510,75 +472,79 @@ void CharacterController::stepDown( btCollisionWorld* collisionWorld, btScalar d start.setIdentity(); end.setIdentity(); - start.setOrigin(m_currentPosition); - m_targetPosition = m_currentPosition + step; - end.setOrigin(m_targetPosition); - m_ghostObject->convexSweepTest(m_convexShape, start, end, callback, collisionWorld->getDispatchInfo().m_allowedCcdPenetration); + start.setOrigin(_currentPosition); + _targetPosition = _currentPosition + step; + end.setOrigin(_targetPosition); + _ghostObject->convexSweepTest(_convexShape, start, end, callback, collisionWorld->getDispatchInfo().m_allowedCcdPenetration); if (callback.hasHit()) { - m_currentPosition += callback.m_closestHitFraction * step; - m_verticalVelocity = 0.0f; - m_verticalOffset = 0.0f; - m_wasJumping = false; - } else { + _currentPosition += callback.m_closestHitFraction * step; + _verticalVelocity = 0.0f; + _verticalOffset = 0.0f; + _wasJumping = false; + } else if (!_wasJumping) { // sweep again for floor within downStep threshold - StepDownConvexResultCallback callback2 (m_ghostObject, - getUpAxisDirections()[m_upAxis], - m_currentPosition, step, - m_walkDirection, - m_maxSlopeCosine, - m_radius, m_halfHeight); + StepDownConvexResultCallback callback2 (_ghostObject, + up, + _currentPosition, step, + _walkDirection, + _maxSlopeCosine, + _radius, _halfHeight); callback2.m_collisionFilterGroup = getGhostObject()->getBroadphaseHandle()->m_collisionFilterGroup; callback2.m_collisionFilterMask = getGhostObject()->getBroadphaseHandle()->m_collisionFilterMask; - m_currentPosition = m_targetPosition; - btVector3 oldPosition = m_currentPosition; - step = (- m_stepHeight) * getUpAxisDirections()[m_upAxis]; - m_targetPosition = m_currentPosition + step; + _currentPosition = _targetPosition; + btVector3 oldPosition = _currentPosition; + step = (- _stepHeight) * up; + _targetPosition = _currentPosition + step; - start.setOrigin(m_currentPosition); - end.setOrigin(m_targetPosition); - m_ghostObject->convexSweepTest(m_convexShape, start, end, callback2, collisionWorld->getDispatchInfo().m_allowedCcdPenetration); + start.setOrigin(_currentPosition); + end.setOrigin(_targetPosition); + _ghostObject->convexSweepTest(_convexShape, start, end, callback2, collisionWorld->getDispatchInfo().m_allowedCcdPenetration); if (callback2.hasHit()) { - m_currentPosition += callback2.m_closestHitFraction * step; - m_verticalVelocity = 0.0f; - m_verticalOffset = 0.0f; - m_wasJumping = false; + _currentPosition += callback2.m_closestHitFraction * step; + _verticalVelocity = 0.0f; + _verticalOffset = 0.0f; + _wasJumping = false; } else { // nothing to step down on, so remove the stepUp effect - m_currentPosition = oldPosition - m_lastStepUp * getUpAxisDirections()[m_upAxis]; - m_lastStepUp = 0.0f; + _currentPosition = oldPosition - _lastStepUp * up; + _lastStepUp = 0.0f; } + } else { + // we're jumping, and didn't hit anything, so our target position is where we would have fallen to + _currentPosition = _targetPosition; } } void CharacterController::setWalkDirection(const btVector3& walkDirection) { - m_useWalkDirection = true; - m_walkDirection = walkDirection; - m_normalizedDirection = getNormalizedVector(m_walkDirection); + // This must be implemented to satisfy base-class interface but does nothing. + // Use setVelocityForTimeInterval() instead. + assert(false); } void CharacterController::setVelocityForTimeInterval(const btVector3& velocity, btScalar timeInterval) { - m_useWalkDirection = false; - m_walkDirection = velocity; - m_normalizedDirection = getNormalizedVector(m_walkDirection); - m_velocityTimeInterval += timeInterval; + _walkDirection = velocity; + _normalizedDirection = getNormalizedVector(_walkDirection); + _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; +void CharacterController::reset(btCollisionWorld* collisionWorld) { + _verticalVelocity = 0.0; + _verticalOffset = 0.0; + _wasOnGround = false; + _wasJumping = false; + _walkDirection.setValue(0,0,0); + _velocityTimeInterval = 0.0; //clear pair cache - btHashedOverlappingPairCache *cache = m_ghostObject->getOverlappingPairCache(); + btHashedOverlappingPairCache *cache = _ghostObject->getOverlappingPairCache(); while (cache->getOverlappingPairArray().size() > 0) { - cache->removeOverlappingPair(cache->getOverlappingPairArray()[0].m_pProxy0, cache->getOverlappingPairArray()[0].m_pProxy1, collisionWorld->getDispatcher()); + cache->removeOverlappingPair(cache->getOverlappingPairArray()[0].m_pProxy0, + cache->getOverlappingPairArray()[0].m_pProxy1, + collisionWorld->getDispatcher()); } } @@ -586,46 +552,48 @@ void CharacterController::warp(const btVector3& origin) { btTransform xform; xform.setIdentity(); xform.setOrigin(origin); - m_ghostObject->setWorldTransform(xform); + _ghostObject->setWorldTransform(xform); } -void CharacterController::preStep( btCollisionWorld* collisionWorld) { - if (!m_enabled) { +void CharacterController::preStep(btCollisionWorld* collisionWorld) { + if (!_enabled) { return; } int numPenetrationLoops = 0; - m_touchingContact = false; + _touchingContact = false; while (recoverFromPenetration(collisionWorld)) { numPenetrationLoops++; - m_touchingContact = true; + _touchingContact = true; if (numPenetrationLoops > 4) { break; } } - m_currentPosition = m_ghostObject->getWorldTransform().getOrigin(); - m_targetPosition = m_currentPosition; + const btTransform& transform = _ghostObject->getWorldTransform(); + _currentRotation = transform.getRotation(); + _currentPosition = transform.getOrigin(); + _targetPosition = _currentPosition; } -void CharacterController::playerStep( btCollisionWorld* collisionWorld, btScalar dt) { - if (!m_enabled || (!m_useWalkDirection && m_velocityTimeInterval <= 0.0)) { +void CharacterController::playerStep(btCollisionWorld* collisionWorld, btScalar dt) { + if (!_enabled) { return; // no motion } - m_wasOnGround = onGround(); + _wasOnGround = onGround(); // Update fall velocity. - m_verticalVelocity -= m_gravity * dt; - if (m_verticalVelocity > m_jumpSpeed) { - m_verticalVelocity = m_jumpSpeed; - } else if (m_verticalVelocity < -m_maxFallSpeed) { - m_verticalVelocity = -m_maxFallSpeed; + _verticalVelocity -= _gravity * dt; + if (_verticalVelocity > _jumpSpeed) { + _verticalVelocity = _jumpSpeed; + } else if (_verticalVelocity < -_maxFallSpeed) { + _verticalVelocity = -_maxFallSpeed; } - m_verticalOffset = m_verticalVelocity * dt; + _verticalOffset = _verticalVelocity * dt; btTransform xform; - xform = m_ghostObject->getWorldTransform(); + xform = _ghostObject->getWorldTransform(); // the algorithm is as follows: // (1) step the character up a little bit so that its forward step doesn't hit the floor @@ -633,33 +601,31 @@ void CharacterController::playerStep( btCollisionWorld* collisionWorld, btScala // (3) step the character down looking for new ledges, the original floor, or a floor one step below where we started stepUp(collisionWorld); - if (m_useWalkDirection) { - stepForward(collisionWorld, m_walkDirection); - } else { - // compute substep and decrement total interval - btScalar dtMoving = (dt < m_velocityTimeInterval) ? dt : m_velocityTimeInterval; - m_velocityTimeInterval -= dt; - // stepForward substep - btVector3 move = m_walkDirection * dtMoving; - stepForward(collisionWorld, move); - } + // compute substep and decrement total interval + btScalar dtMoving = (dt < _velocityTimeInterval) ? dt : _velocityTimeInterval; + _velocityTimeInterval -= dt; + + // stepForward substep + btVector3 move = _walkDirection * dtMoving; + stepForward(collisionWorld, move); + stepDown(collisionWorld, dt); - xform.setOrigin(m_currentPosition); - m_ghostObject->setWorldTransform(xform); + xform.setOrigin(_currentPosition); + _ghostObject->setWorldTransform(xform); } void CharacterController::setMaxFallSpeed(btScalar speed) { - m_maxFallSpeed = speed; + _maxFallSpeed = speed; } void CharacterController::setJumpSpeed(btScalar jumpSpeed) { - m_jumpSpeed = jumpSpeed; + _jumpSpeed = jumpSpeed; } void CharacterController::setMaxJumpHeight(btScalar maxJumpHeight) { - m_maxJumpHeight = maxJumpHeight; + _maxJumpHeight = maxJumpHeight; } bool CharacterController::canJump() const { @@ -667,160 +633,185 @@ bool CharacterController::canJump() const { } 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 + _pendingFlags |= PENDING_FLAG_JUMP; } void CharacterController::setGravity(btScalar gravity) { - m_gravity = gravity; + _gravity = gravity; } btScalar CharacterController::getGravity() const { - return m_gravity; + return _gravity; } void CharacterController::setMaxSlope(btScalar slopeRadians) { - m_maxSlopeRadians = slopeRadians; - m_maxSlopeCosine = btCos(slopeRadians); + _maxSlopeRadians = slopeRadians; + _maxSlopeCosine = btCos(slopeRadians); } btScalar CharacterController::getMaxSlope() const { - return m_maxSlopeRadians; + return _maxSlopeRadians; } bool CharacterController::onGround() const { - return m_enabled && 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; + return _enabled && _verticalVelocity == 0.0f && _verticalOffset == 0.0f; } void CharacterController::debugDraw(btIDebugDraw* debugDrawer) { } void CharacterController::setUpInterpolate(bool value) { - m_interpolateUp = value; + // This method is required by btCharacterControllerInterface, but it does nothing. + // What it used to do was determine whether stepUp() would: stop where it hit the ceiling + // (interpolate = true, and now default behavior) or happily penetrate objects above the avatar. } -// protected -void CharacterController::createShapeAndGhost() { - // get new dimensions from avatar - m_avatarData->lockForRead(); - AABox box = m_avatarData->getLocalAABox(); +void CharacterController::setLocalBoundingBox(const glm::vec3& corner, const glm::vec3& scale) { + _boxScale = scale; - // create new ghost - m_ghostObject = new btPairCachingGhostObject(); - m_ghostObject->setWorldTransform(btTransform(glmToBullet(m_avatarData->getOrientation()), - glmToBullet(m_avatarData->getPosition()))); - m_avatarData->unlock(); - - const glm::vec3& diagonal = box.getScale(); - m_radius = 0.5f * sqrtf(0.5f * (diagonal.x * diagonal.x + diagonal.z * diagonal.z)); - m_halfHeight = 0.5f * diagonal.y - m_radius; - float MIN_HALF_HEIGHT = 0.1f; - if (m_halfHeight < MIN_HALF_HEIGHT) { - m_halfHeight = MIN_HALF_HEIGHT; - } - glm::vec3 offset = box.getCorner() + 0.5f * diagonal; - m_shapeLocalOffset = offset; - - // stepHeight affects the heights of ledges that the character can ascend - // however the actual ledge height is some function of m_stepHeight - // due to character shape and this CharacterController algorithm - // (the function is approximately 2*m_stepHeight) - m_stepHeight = 0.1f * (m_radius + m_halfHeight) + 0.1f; - - // create new shape - m_convexShape = new btCapsuleShape(m_radius, 2.0f * m_halfHeight); - m_ghostObject->setCollisionShape(m_convexShape); - m_ghostObject->setCollisionFlags(btCollisionObject::CF_CHARACTER_OBJECT); -} - -bool CharacterController::needsShapeUpdate() { - // get new dimensions from avatar - m_avatarData->lockForRead(); - AABox box = m_avatarData->getLocalAABox(); - m_avatarData->unlock(); - - 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 - radius; + float x = _boxScale.x; + float z = _boxScale.z; + float radius = 0.5f * sqrtf(0.5f * (x * x + z * z)); + float halfHeight = 0.5f * _boxScale.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; - // compare dimensions (and offset) - float radiusDelta = glm::abs(radius - m_radius); - float heightDelta = glm::abs(halfHeight - m_halfHeight); + // compare dimensions + float radiusDelta = glm::abs(radius - _radius); + float heightDelta = glm::abs(halfHeight - _halfHeight); if (radiusDelta < FLT_EPSILON && heightDelta < FLT_EPSILON) { // shape hasn't changed --> nothing to do - float offsetDelta = glm::distance(offset, m_shapeLocalOffset); - if (offsetDelta > FLT_EPSILON) { - // if only the offset changes then we can update it --> no need to rebuild shape - m_shapeLocalOffset = offset; + } else { + // we always need to: REMOVE when UPDATE_SHAPE, to avoid deleting shapes out from under the PhysicsEngine + _pendingFlags |= PENDING_FLAG_REMOVE_FROM_SIMULATION + | PENDING_FLAG_UPDATE_SHAPE; + // but only need to ADD back when we happen to be enabled + if (_enabled) { + _pendingFlags |= PENDING_FLAG_ADD_TO_SIMULATION; } - return false; } - return true; + + // it's ok to change offset immediately -- there are no thread safety issues here + _shapeLocalOffset = corner + 0.5f * _boxScale; } -void CharacterController::updateShape() { - // DANGER: make sure this CharacterController and its GhostShape have been removed from - // the PhysicsEngine before calling this. +bool CharacterController::needsAddition() const { + return (bool)(_pendingFlags & PENDING_FLAG_ADD_TO_SIMULATION); +} - // delete shape and GhostObject - delete m_ghostObject; - m_ghostObject = NULL; - delete m_convexShape; - m_convexShape = NULL; - - createShapeAndGhost(); +bool CharacterController::needsRemoval() const { + return (bool)(_pendingFlags & PENDING_FLAG_REMOVE_FROM_SIMULATION); +} + +void CharacterController::setEnabled(bool enabled) { + if (enabled != _enabled) { + if (enabled) { + // Don't bother clearing REMOVE bit since it might be paired with an UPDATE_SHAPE bit. + // Setting the ADD bit here works for all cases so we don't even bother checking other bits. + _pendingFlags |= PENDING_FLAG_ADD_TO_SIMULATION; + } else { + // Always set REMOVE bit when going disabled, and we always clear the ADD bit just in case + // it was previously set by something else (e.g. an UPDATE_SHAPE event). + _pendingFlags |= PENDING_FLAG_REMOVE_FROM_SIMULATION; + _pendingFlags &= ~ PENDING_FLAG_ADD_TO_SIMULATION; + } + _enabled = enabled; + } +} + +void CharacterController::setDynamicsWorld(btDynamicsWorld* world) { + if (_dynamicsWorld != world) { + if (_dynamicsWorld) { + _dynamicsWorld->removeCollisionObject(getGhostObject()); + _dynamicsWorld->removeAction(this); + } + _dynamicsWorld = world; + if (_dynamicsWorld) { + _pendingFlags &= ~ (PENDING_FLAG_ADD_TO_SIMULATION | PENDING_FLAG_JUMP); + _dynamicsWorld->addCollisionObject(getGhostObject(), + btBroadphaseProxy::CharacterFilter, + btBroadphaseProxy::StaticFilter | btBroadphaseProxy::DefaultFilter); + _dynamicsWorld->addAction(this); + reset(_dynamicsWorld); + } else { + _pendingFlags &= ~ PENDING_FLAG_REMOVE_FROM_SIMULATION; + } + } else { + _pendingFlags &= ~ (PENDING_FLAG_REMOVE_FROM_SIMULATION | PENDING_FLAG_ADD_TO_SIMULATION); + } +} + +void CharacterController::updateShapeIfNecessary() { + if (_pendingFlags & PENDING_FLAG_UPDATE_SHAPE) { + assert(!(_pendingFlags & PENDING_FLAG_REMOVE_FROM_SIMULATION)); + _pendingFlags &= ~ PENDING_FLAG_UPDATE_SHAPE; + // make sure there is NO pending removal from simulation at this point + // (don't want to delete _ghostObject out from under the simulation) + // delete shape and GhostObject + delete _ghostObject; + _ghostObject = NULL; + delete _convexShape; + _convexShape = NULL; + + // compute new dimensions from avatar's bounding box + float x = _boxScale.x; + float z = _boxScale.z; + _radius = 0.5f * sqrtf(0.5f * (x * x + z * z)); + _halfHeight = 0.5f * _boxScale.y - _radius; + float MIN_HALF_HEIGHT = 0.1f; + if (_halfHeight < MIN_HALF_HEIGHT) { + _halfHeight = MIN_HALF_HEIGHT; + } + // NOTE: _shapeLocalOffset is already computed + + if (_radius > 0.0f) { + // create new ghost + _ghostObject = new btPairCachingGhostObject(); + _ghostObject->setWorldTransform(btTransform(glmToBullet(_avatarData->getOrientation()), + glmToBullet(_avatarData->getPosition()))); + // stepHeight affects the heights of ledges that the character can ascend + // however the actual ledge height is some function of _stepHeight + // due to character shape and this CharacterController algorithm + // (the function is approximately 2*_stepHeight) + _stepHeight = 0.1f * (_radius + _halfHeight) + 0.1f; + + // create new shape + _convexShape = new btCapsuleShape(_radius, 2.0f * _halfHeight); + _ghostObject->setCollisionShape(_convexShape); + _ghostObject->setCollisionFlags(btCollisionObject::CF_CHARACTER_OBJECT); + } else { + // TODO: handle this failure case + } + } } void CharacterController::preSimulation(btScalar timeStep) { - m_avatarData->lockForRead(); + if (_enabled && _dynamicsWorld) { + glm::quat rotation = _avatarData->getOrientation(); + glm::vec3 position = _avatarData->getPosition() + rotation * _shapeLocalOffset; + btVector3 walkVelocity = glmToBullet(_avatarData->getVelocity()); - // cache the "PhysicsEnabled" state of m_avatarData here - // and use the cached value for the rest of the simulation step - m_enabled = m_avatarData->isPhysicsEnabled(); - - glm::quat rotation = m_avatarData->getOrientation(); - glm::vec3 position = m_avatarData->getPosition() + rotation * m_shapeLocalOffset; - m_ghostObject->setWorldTransform(btTransform(glmToBullet(rotation), glmToBullet(position))); - btVector3 walkVelocity = glmToBullet(m_avatarData->getVelocity()); - setVelocityForTimeInterval(walkVelocity, timeStep); - - m_avatarData->unlock(); -} - -void CharacterController::postSimulation() { - if (m_enabled) { - m_avatarData->lockForWrite(); - const btTransform& avatarTransform = m_ghostObject->getWorldTransform(); - glm::quat rotation = bulletToGLM(avatarTransform.getRotation()); - glm::vec3 offset = rotation * m_shapeLocalOffset; - m_avatarData->setOrientation(rotation); - m_avatarData->setPosition(bulletToGLM(avatarTransform.getOrigin()) - offset); - m_avatarData->unlock(); + _ghostObject->setWorldTransform(btTransform(glmToBullet(rotation), glmToBullet(position))); + setVelocityForTimeInterval(walkVelocity, timeStep); + if (_pendingFlags & PENDING_FLAG_JUMP) { + _pendingFlags &= ~ PENDING_FLAG_JUMP; + if (canJump()) { + _verticalVelocity = _jumpSpeed; + _wasJumping = true; + } + } + } +} + +void CharacterController::postSimulation() { + if (_enabled) { + const btTransform& avatarTransform = _ghostObject->getWorldTransform(); + glm::quat rotation = bulletToGLM(avatarTransform.getRotation()); + glm::vec3 offset = rotation * _shapeLocalOffset; + _avatarData->setOrientation(rotation); + _avatarData->setPosition(bulletToGLM(avatarTransform.getOrigin()) - offset); } } diff --git a/libraries/physics/src/CharacterController.h b/libraries/physics/src/CharacterController.h index 7969fffd9d..323529b1cd 100644 --- a/libraries/physics/src/CharacterController.h +++ b/libraries/physics/src/CharacterController.h @@ -1,6 +1,7 @@ /* Bullet Continuous Collision Detection and Physics Library Copyright (c) 2003-2008 Erwin Coumans http://bulletphysics.com +2015.03.25 -- modified by Andrew Meadows andrew@highfidelity.io 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. @@ -37,59 +38,57 @@ class btPairCachingGhostObject; ///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: - AvatarData* m_avatarData = NULL; - btPairCachingGhostObject* m_ghostObject; - glm::vec3 m_shapeLocalOffset; + AvatarData* _avatarData = NULL; + btPairCachingGhostObject* _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_radius; - btScalar m_halfHeight; + btConvexShape* _convexShape;//is also in _ghostObject, but it needs to be convex, so we store it here to avoid upcast + btScalar _radius; + btScalar _halfHeight; - btScalar m_verticalVelocity; - btScalar m_verticalOffset; // fall distance from velocity this frame - btScalar m_maxFallSpeed; - 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 _verticalVelocity; + btScalar _verticalOffset; // fall distance from velocity this frame + btScalar _maxFallSpeed; + btScalar _jumpSpeed; + btScalar _maxJumpHeight; + btScalar _maxSlopeRadians; // Slope angle that is set (used for returning the exact value) + btScalar _maxSlopeCosine; // Cosine equivalent of _maxSlopeRadians (calculated once when set, for optimization) + btScalar _gravity; - btScalar m_turnAngle; + btScalar _stepHeight; // height of stepUp prior to stepForward - btScalar m_stepHeight; // height of stepUp prior to stepForward - - btScalar m_addedMargin;//@todo: remove this and fix the code + btScalar _addedMargin;//@todo: remove this and fix the code ///this is the desired walk direction, set by the user - btVector3 m_walkDirection; - btVector3 m_normalizedDirection; + btVector3 _walkDirection; + btVector3 _normalizedDirection; //some internal variables - btVector3 m_currentPosition; - btVector3 m_targetPosition; - btScalar m_lastStepUp; + btVector3 _currentPosition; + btQuaternion _currentRotation; + btVector3 _targetPosition; + btScalar _lastStepUp; ///keep track of the contact manifolds - btManifoldArray m_manifoldArray; + btManifoldArray _manifoldArray; - bool m_touchingContact; - btVector3 m_floorNormal; // points from object to character + bool _touchingContact; + btVector3 _floorNormal; // points from object to character - bool m_enabled; - bool m_wasOnGround; - bool m_wasJumping; - bool m_useWalkDirection; - btScalar m_velocityTimeInterval; - int m_upAxis; + bool _enabled; + bool _wasOnGround; + bool _wasJumping; + btScalar _velocityTimeInterval; + uint32_t _pendingFlags; - static btVector3* getUpAxisDirections(); - bool m_interpolateUp; - bool full_drop; - bool bounce_fix; + glm::vec3 _shapeLocalOffset; + glm::vec3 _boxScale; // used to compute capsule shape + + btDynamicsWorld* _dynamicsWorld = NULL; btVector3 computeReflectionDirection(const btVector3& direction, const btVector3& normal); btVector3 parallelComponent(const btVector3& direction, const btVector3& normal); @@ -118,14 +117,6 @@ public: ///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 @@ -141,18 +132,19 @@ public: virtual void setVelocityForTimeInterval(const btVector3& velocity, btScalar timeInterval); - void reset(btCollisionWorld* collisionWorld ); - void warp(const btVector3& origin); + virtual void reset(btCollisionWorld* collisionWorld ); + virtual void warp(const btVector3& origin); - void preStep(btCollisionWorld* collisionWorld); - void playerStep(btCollisionWorld* collisionWorld, btScalar dt); + virtual void preStep(btCollisionWorld* collisionWorld); + virtual void playerStep(btCollisionWorld* collisionWorld, btScalar dt); + + virtual bool canJump() const; + virtual void jump(); + virtual bool onGround() const; void setMaxFallSpeed(btScalar speed); void setJumpSpeed(btScalar jumpSpeed); void setMaxJumpHeight(btScalar maxJumpHeight); - bool canJump() const; - - void jump(); void setGravity(btScalar gravity); btScalar getGravity() const; @@ -164,11 +156,16 @@ public: btPairCachingGhostObject* getGhostObject(); - bool onGround() const; void setUpInterpolate(bool value); - bool needsShapeUpdate(); - void updateShape(); + bool needsRemoval() const; + bool needsAddition() const; + void setEnabled(bool enabled); + void setDynamicsWorld(btDynamicsWorld* world); + + void setLocalBoundingBox(const glm::vec3& corner, const glm::vec3& scale); + bool needsShapeUpdate() const; + void updateShapeIfNecessary(); void preSimulation(btScalar timeStep); void postSimulation(); diff --git a/libraries/physics/src/PhysicsEngine.cpp b/libraries/physics/src/PhysicsEngine.cpp index a46ba9f819..9ca718e19a 100644 --- a/libraries/physics/src/PhysicsEngine.cpp +++ b/libraries/physics/src/PhysicsEngine.cpp @@ -280,12 +280,12 @@ void PhysicsEngine::init(EntityEditPacketSender* packetSender) { void PhysicsEngine::stepSimulation() { lock(); // NOTE: the grand order of operations is: - // (1) relay incoming changes + // (1) pull incoming changes // (2) step simulation // (3) synchronize outgoing motion states // (4) send outgoing packets - // This is step (1). + // This is step (1) pull incoming changes relayIncomingChangesToSimulation(); const int MAX_NUM_SUBSTEPS = 4; @@ -294,16 +294,25 @@ void PhysicsEngine::stepSimulation() { _clock.reset(); float timeStep = btMin(dt, MAX_TIMESTEP); - // This is step (2). + // TODO: move character->preSimulation() into relayIncomingChanges if (_characterController) { + if (_characterController->needsRemoval()) { + _characterController->setDynamicsWorld(NULL); + } + _characterController->updateShapeIfNecessary(); + if (_characterController->needsAddition()) { + _characterController->setDynamicsWorld(_dynamicsWorld); + } _characterController->preSimulation(timeStep); } + // This is step (2) step simulation int numSubsteps = _dynamicsWorld->stepSimulation(timeStep, MAX_NUM_SUBSTEPS, PHYSICS_ENGINE_FIXED_SUBSTEP); _numSubsteps += (uint32_t)numSubsteps; stepNonPhysicalKinematics(usecTimestampNow()); unlock(); + // TODO: make all of this harvest stuff into one function: relayOutgoingChanges() if (numSubsteps > 0) { // This is step (3) which is done outside of stepSimulation() so we can lock _entityTree. // @@ -598,34 +607,10 @@ bool PhysicsEngine::updateObjectHard(btRigidBody* body, ObjectMotionState* motio return true; } -void PhysicsEngine::setAvatarData(AvatarData *avatarData) { - if (_characterController) { - bool needsShapeUpdate = _characterController->needsShapeUpdate(); - if (needsShapeUpdate) { - lock(); - // remove old info - _dynamicsWorld->removeCollisionObject(_characterController->getGhostObject()); - _dynamicsWorld->removeAction(_characterController); - // update shape - _characterController->updateShape(); - // insert new info - _dynamicsWorld->addCollisionObject(_characterController->getGhostObject(), - btBroadphaseProxy::CharacterFilter, - btBroadphaseProxy::StaticFilter | btBroadphaseProxy::DefaultFilter); - _dynamicsWorld->addAction(_characterController); - _characterController->reset(_dynamicsWorld); - unlock(); - } - } else { - // initialize _characterController - assert(avatarData); // don't pass NULL argument +void PhysicsEngine::setCharacterController(CharacterController* character) { + if (!_characterController) { lock(); - _characterController = new CharacterController(avatarData); - _dynamicsWorld->addCollisionObject(_characterController->getGhostObject(), - btBroadphaseProxy::CharacterFilter, - btBroadphaseProxy::StaticFilter | btBroadphaseProxy::DefaultFilter); - _dynamicsWorld->addAction(_characterController); - _characterController->reset(_dynamicsWorld); + _characterController = character; unlock(); } } diff --git a/libraries/physics/src/PhysicsEngine.h b/libraries/physics/src/PhysicsEngine.h index cb637c60b9..0661b47d3a 100644 --- a/libraries/physics/src/PhysicsEngine.h +++ b/libraries/physics/src/PhysicsEngine.h @@ -17,9 +17,7 @@ #include #include #include -//#include -#include #include #include @@ -86,7 +84,7 @@ public: /// process queue of changed from external sources void relayIncomingChangesToSimulation(); - void setAvatarData(AvatarData *avatarData); + void setCharacterController(CharacterController* character); private: /// \param motionState pointer to Object's MotionState