diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 176fd51eea..dae6af3fc5 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -149,7 +149,8 @@ void AvatarMixer::broadcastAvatarData() { // about a given otherNode to this node // FIXME does this mean we should sort the othernodes by distance before iterating // over them? - float outputBandwidth = node->getOutboundBandwidth(); + // float outputBandwidth = + node->getOutboundBandwidth(); // this is an AGENT we have received head data from // send back a packet with other active node data to this node @@ -169,7 +170,7 @@ void AvatarMixer::broadcastAvatarData() { return true; }, [&](const SharedNodePointer& otherNode) { - AvatarMixerClientData* otherNodeData = otherNodeData = reinterpret_cast(otherNode->getLinkedData()); + AvatarMixerClientData* otherNodeData = reinterpret_cast(otherNode->getLinkedData()); MutexTryLocker lock(otherNodeData->getMutex()); if (!lock.isLocked()) { return; diff --git a/cmake/externals/tbb/CMakeLists.txt b/cmake/externals/tbb/CMakeLists.txt index 06da60ac04..ad46196b95 100644 --- a/cmake/externals/tbb/CMakeLists.txt +++ b/cmake/externals/tbb/CMakeLists.txt @@ -8,8 +8,8 @@ if (ANDROID) ExternalProject_Add( ${EXTERNAL_NAME} - URL http://hifi-public.s3.amazonaws.com/dependencies/tbb43_20150209oss_src.tgz - URL_MD5 f09c9abe8ec74e6558c1f89cebbe2893 + URL http://hifi-public.s3.amazonaws.com/dependencies/tbb43_20150316oss_src.tgz + URL_MD5 bf090eaa86cf89ea014b7b462786a440 BUILD_COMMAND ${NDK_BUILD_COMMAND} --directory=jni target=android tbb tbbmalloc arch=arm BUILD_IN_SOURCE 1 CONFIGURE_COMMAND "" @@ -20,19 +20,20 @@ if (ANDROID) ) else () if (APPLE) - set(DOWNLOAD_URL http://s3.amazonaws.com/hifi-public/dependencies/tbb43_20150209oss_osx.tgz) - set(DOWNLOAD_MD5 3e683c19792582b61382e0d760ea5db2) + set(DOWNLOAD_URL http://hifi-public.s3.amazonaws.com/dependencies/tbb43_20150316oss_osx.tgz) + set(DOWNLOAD_MD5 25a36ebff070ff801760ec658079f6aa) elseif (WIN32) - set(DOWNLOAD_URL http://s3.amazonaws.com/hifi-public/dependencies/tbb43_20150209oss_win.zip) - set(DOWNLOAD_MD5 e19c184f2bb0e944fc5f397f1e34ca84) + set(DOWNLOAD_URL http://s3.amazonaws.com/hifi-public/dependencies/tbb43_20150316oss_win.zip) + set(DOWNLOAD_MD5 d250d40bb93b255f75bcbb19e976a440) else () - set(DOWNLOAD_URL http://s3.amazonaws.com/hifi-public/dependencies/tbb43_20150209oss_lin.tgz) - set(DOWNLOAD_MD5 d9c2a6f7807df364be44a8c3c05e8457) + set(DOWNLOAD_URL http://s3.amazonaws.com/hifi-public/dependencies/tbb43_20150316oss_lin.tgz) + set(DOWNLOAD_MD5 7830ba2bc62438325fba2ec0c95367a5) endif () ExternalProject_Add( ${EXTERNAL_NAME} URL ${DOWNLOAD_URL} + URL_MD5 ${DOWNLOAD_MD5} BUILD_COMMAND "" CONFIGURE_COMMAND "" INSTALL_COMMAND "" @@ -94,9 +95,15 @@ elseif (UNIX) endif () if (DEFINED _TBB_LIB_DIR) - set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG ${_TBB_LIB_DIR}/${_LIB_PREFIX}tbb_debug.${_LIB_EXT} CACHE FILEPATH "TBB debug library location") + if (NOT APPLE) + set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG ${_TBB_LIB_DIR}/${_LIB_PREFIX}tbb_debug.${_LIB_EXT} CACHE FILEPATH "TBB debug library location") + set(${EXTERNAL_NAME_UPPER}_MALLOC_LIBRARY_DEBUG ${_TBB_LIB_DIR}/${_LIB_PREFIX}tbbmalloc_debug.${_LIB_EXT} CACHE FILEPATH "TBB malloc debug library location") + else () + set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG "" CACHE FILEPATH "TBB debug library location") + set(${EXTERNAL_NAME_UPPER}_MALLOC_LIBRARY_DEBUG "" CACHE FILEPATH "TBB malloc debug library location") + endif () + set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${_TBB_LIB_DIR}/${_LIB_PREFIX}tbb.${_LIB_EXT} CACHE FILEPATH "TBB release library location") - set(${EXTERNAL_NAME_UPPER}_MALLOC_LIBRARY_DEBUG ${_TBB_LIB_DIR}/${_LIB_PREFIX}tbbmalloc_debug.${_LIB_EXT} CACHE FILEPATH "TBB malloc debug library location") set(${EXTERNAL_NAME_UPPER}_MALLOC_LIBRARY_RELEASE ${_TBB_LIB_DIR}/${_LIB_PREFIX}tbbmalloc.${_LIB_EXT} CACHE FILEPATH "TBB malloc release library location") endif () diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index 4636c61367..0082bd84ae 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -6,7 +6,7 @@ { "name": "access_token", "label": "Access Token", - "help": "This is an access token generated on the My Security page of your High Fidelity account.
Generate a token with the 'domains' scope and paste it here.
This is required to associate this domain-server with a domain in your account." + "help": "This is an access token generated on the My Security page of your High Fidelity account.
Generate a token with the 'domains' scope and paste it here.
This is required to associate this domain-server with a domain in your account." }, { "name": "id", @@ -30,7 +30,7 @@ }, { "value": "disabled", - "label": "None: use the network information I have entered for this domain at metaverse.highfidelity.io" + "label": "None: use the network information I have entered for this domain at metaverse.highfidelity.com" } ] }, diff --git a/domain-server/resources/web/js/settings.js b/domain-server/resources/web/js/settings.js index f62515c863..4fac753959 100644 --- a/domain-server/resources/web/js/settings.js +++ b/domain-server/resources/web/js/settings.js @@ -652,7 +652,7 @@ function chooseFromHighFidelityDomains(clickedButton) { clickedButton.attr('disabled', 'disabled') // get a list of user domains from data-web - data_web_domains_url = "https://metaverse.highfidelity.io/api/v1/domains?access_token=" + data_web_domains_url = "https://metaverse.highfidelity.com/api/v1/domains?access_token=" $.getJSON(data_web_domains_url + Settings.initialValues.metaverse.access_token, function(data){ modal_buttons = { @@ -682,7 +682,7 @@ function chooseFromHighFidelityDomains(clickedButton) { modal_buttons["success"] = { label: 'Create new domain', callback: function() { - window.open("https://metaverse.highfidelity.io/user/domains", '_blank'); + window.open("https://metaverse.highfidelity.com/user/domains", '_blank'); } } modal_body = "

You do not have any domains in your High Fidelity account." + @@ -708,4 +708,4 @@ function chooseFromHighFidelityDomains(clickedButton) { title: "Access token required" }) } -} \ No newline at end of file +} 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/defaultScripts.js b/examples/defaultScripts.js index f52d6be87e..05ffb0bd3f 100644 --- a/examples/defaultScripts.js +++ b/examples/defaultScripts.js @@ -18,4 +18,3 @@ Script.load("lobby.js"); Script.load("notifications.js"); Script.load("look.js"); Script.load("users.js"); -Script.load("utilities/LODWarning.js"); diff --git a/examples/edit.js b/examples/edit.js index 72938e5ed4..1ea7647a54 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"; @@ -89,13 +88,7 @@ var SETTING_SHOW_LIGHTS_IN_EDIT_MODE = "showLightsInEditMode"; var INSUFFICIENT_PERMISSIONS_ERROR_MSG = "You do not have the necessary permissions to edit on this domain." var modelURLs = [ - HIFI_PUBLIC_BUCKET + "models/entities/2-Terrain:%20Alder.fbx", - HIFI_PUBLIC_BUCKET + "models/entities/2-Terrain:%20Bush1.fbx", - HIFI_PUBLIC_BUCKET + "models/entities/2-Terrain:%20Bush6.fbx", - HIFI_PUBLIC_BUCKET + "models/entities/3-Buildings-1-Rustic-Shed.fbx", - HIFI_PUBLIC_BUCKET + "models/entities/3-Buildings-1-Rustic-Shed2.fbx", - HIFI_PUBLIC_BUCKET + "models/entities/3-Buildings-1-Rustic-Shed4.fbx", - HIFI_PUBLIC_BUCKET + "models/entities/3-Buildings-1-Rustic-Shed7.fbx" + "Insert the URL to your FBX" ]; var mode = 0; @@ -130,7 +123,7 @@ var importingSVOTextOverlay = Overlays.addOverlay("text", { visible: false, }); -var MARKETPLACE_URL = "https://metaverse.highfidelity.io/marketplace"; +var MARKETPLACE_URL = "https://metaverse.highfidelity.com/marketplace"; var marketplaceWindow = new WebWindow('Marketplace', MARKETPLACE_URL, 900, 700, false); marketplaceWindow.setVisible(false); @@ -338,7 +331,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; } @@ -384,9 +381,7 @@ var toolBar = (function () { position: grid.snapToSurface(grid.snapToGrid(position, false, DEFAULT_LIGHT_DIMENSIONS), DEFAULT_LIGHT_DIMENSIONS), dimensions: DEFAULT_LIGHT_DIMENSIONS, isSpotlight: false, - diffuseColor: { red: 255, green: 255, blue: 255 }, - ambientColor: { red: 255, green: 255, blue: 255 }, - specularColor: { red: 0, green: 0, blue: 0 }, + color: { red: 150, green: 150, blue: 150 }, constantAttenuation: 1, linearAttenuation: 0, @@ -540,7 +535,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 +544,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 +555,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 +577,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 +623,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 +647,88 @@ 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); + if (properties.marketplaceID) { + propertyMenu.marketplaceID = properties.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 +773,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 +804,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 +825,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 +916,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 +929,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 +1149,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 +1228,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 new file mode 100644 index 0000000000..e6e4998aef --- /dev/null +++ b/examples/entityScripts/lightController.js @@ -0,0 +1,228 @@ +(function() { + this.entityID = 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)); + } + function didEntityExist(entityID) { + return entityID && entityID.isKnownID; + } + function doesEntityExistNow(entityID) { + return entityID && getTrueID(entityID).isKnownID; + } + function getTrueID(entityID) { + var properties = Entities.getEntityProperties(entityID); + return { id: properties.id, creatorTokenID: properties.creatorTokenID, isKnownID: properties.isKnownID }; + } + function getUserData(entityID) { + var properties = Entities.getEntityProperties(entityID); + if (properties.userData) { + return JSON.parse(properties.userData); + } else { + print("Warning: light controller has no user data."); + return null; + } + } + function updateUserData(entityID, userData) { + Entities.editEntity(entityID, { userData: JSON.stringify(userData) }); + } + + // Download sound if needed + this.maybeDownloadSound = function() { + if (this.sound === null) { + var soundIndex = getUserData(this.entityID).soundIndex; + this.sound = SoundCache.getSound(this.soundURLs[soundIndex]); + } + } + // Play switch sound + this.playSound = function() { + if (this.sound && this.sound.downloaded) { + Audio.playSound(this.sound, { + position: Entities.getEntityProperties(this.entityID).position, + volume: 0.2 + }); + } else { + print("Warning: Couldn't play sound."); + } + } + + // 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) { + var entityProperties = Entities.getEntityProperties(this.entityID); + + lightProperties.visible = false; + lightProperties.position = Vec3.sum(entityProperties.position, + Vec3.multiplyQbyV(entityProperties.rotation, + lightProperties.position)); + return Entities.addEntity(lightProperties); + } else { + print("Warning: light controller has no default light."); + return null; + } + } + + // Tries to find a valid light, creates one otherwise + this.updateLightID = function() { + // Find valid light + if (doesEntityExistNow(this.lightID)) { + return; + } + + var userData = getUserData(this.entityID); + if (doesEntityExistNow(userData.lightID)) { + this.lightID = userData.lightID; + return; + } + + 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); + var lightDefaultProperties = getUserData(this.entityID).lightDefaultProperties; + + var position = Vec3.sum(entityProperties.position, + Vec3.multiplyQbyV(entityProperties.rotation, + lightDefaultProperties.position)); + + if (!Vec3.equal(position, lightProperties.position)) { + print("Lamp entity moved, moving light entity as well"); + Entities.editEntity(this.lightID, { position: position }); + } + } + + // Stores light entity relative position in the lamp metadata + this.updateRelativeLightPosition = function() { + if (!doesEntityExistNow(this.lightID)) { + print("Warning: ID invalid, couldn't save relative position."); + return; + } + + var userData = getUserData(this.entityID); + var entityProperties = Entities.getEntityProperties(this.entityID); + var lightProperties = Entities.getEntityProperties(this.lightID); + var newProperties = {}; + + // Copy only meaningful properties (trying to save space in userData here) + for (var key in userData.lightDefaultProperties) { + if (userData.lightDefaultProperties.hasOwnProperty(key)) { + newProperties[key] = lightProperties[key]; + } + } + + // Compute new relative position + newProperties.position = Quat.multiply(Quat.inverse(entityProperties.rotation), + Vec3.subtract(lightProperties.position, + entityProperties.position)); + // inverse "visible" because right after we loaded the properties, the light entity is toggled. + newProperties.visible = !lightProperties.visible; + + userData.lightDefaultProperties = copyObject(newProperties); + 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.preOperation(entityID); + }; + + this.clickReleaseOnEntity = function(entityID, mouseEvent) { + this.preOperation(entityID); + + if (mouseEvent.isLeftButton) { + this.updateLightID(); + this.maybeMoveLight(); + this.toggleLight(); + } else if (mouseEvent.isRightButton) { + this.updateRelativeLightPosition(); + } + }; +}) \ No newline at end of file diff --git a/examples/entityScripts/portal.js b/examples/entityScripts/portal.js index 5dd7849c38..4e2ec6ddaf 100644 --- a/examples/entityScripts/portal.js +++ b/examples/entityScripts/portal.js @@ -9,15 +9,20 @@ this.preload = function(entityID) { teleport = SoundCache.getSound("http://s3.amazonaws.com/hifi-public/birarda/teleport.raw"); - + var properties = Entities.getEntityProperties(entityID); - portalDestination = properties.userData; animationURL = properties.modelURL; print("The portal destination is " + portalDestination); } this.enterEntity = function(entityID) { + + var properties = Entities.getEntityProperties(entityID); // in case the userData/portalURL has changed + portalDestination = properties.userData; + + print("enterEntity() .... The portal destination is " + portalDestination); + if (portalDestination.length > 0) { print("Teleporting to hifi://" + portalDestination); Window.location = "hifi://" + portalDestination; diff --git a/examples/example/entities/makeHouses.js b/examples/example/entities/makeHouses.js new file mode 100644 index 0000000000..37bc1d5a8e --- /dev/null +++ b/examples/example/entities/makeHouses.js @@ -0,0 +1,157 @@ +// +// makeHouses.js +// +// +// Created by Stojce Slavkovski on March 14, 2015 +// Copyright 2015 High Fidelity, Inc. +// +// This sample script that creates house entities based on parameters. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +(function () { + + /** options **/ + var numHouses = 100; + var xRange = 300; + var yRange = 300; + + var sizeOfTheHouse = { + x: 10, + y: 15, + z: 10 + }; + + var randomizeModels = false; + /**/ + + var modelUrlPrefix = "http://public.highfidelity.io/load_testing/3-Buildings-2-SanFranciscoHouse-"; + var modelurlExt = ".fbx"; + var modelVariations = 100; + + var houses = []; + + function addHouseAt(position, rotation) { + // get house model + var modelNumber = randomizeModels ? + 1 + Math.floor(Math.random() * (modelVariations - 1)) : + (houses.length + 1) % modelVariations; + + if (modelNumber == 0) { + modelNumber = modelVariations; + } + + var modelUrl = modelUrlPrefix + (modelNumber + "") + modelurlExt; + print("Model ID:" + modelNumber); + print("Model URL:" + modelUrl); + + var properties = { + type: "Model", + position: position, + rotation: rotation, + dimensions: sizeOfTheHouse, + modelURL: modelUrl + }; + + return Entities.addEntity(properties); + } + + // calculate initial position + var posX = MyAvatar.position.x - (xRange / 2); + var measures = calculateParcels(numHouses, xRange, yRange); + var dd = 0; + + // avatar facing rotation + var rotEven = Quat.fromPitchYawRollDegrees(0, 270.0 + MyAvatar.bodyYaw, 0.0); + + // avatar opposite rotation + var rotOdd = Quat.fromPitchYawRollDegrees(0, 90.0 + MyAvatar.bodyYaw, 0.0); + var housePos = Vec3.sum(MyAvatar.position, Quat.getFront(Camera.getOrientation())); + + for (var j = 0; j < measures.rows; j++) { + + var posX1 = 0 - (xRange / 2); + dd += measures.parcelLength; + + for (var i = 0; i < measures.cols; i++) { + + // skip reminder of houses + if (houses.length > numHouses) { + break; + } + + var posShift = { + x: posX1, + y: 0, + z: dd + }; + + print("House nr.:" + (houses.length + 1)); + houses.push( + addHouseAt(Vec3.sum(housePos, posShift), (j % 2 == 0) ? rotEven : rotOdd) + ); + posX1 += measures.parcelWidth; + } + } + + // calculate rows and columns in area, and dimension of single parcel + function calculateParcels(items, areaWidth, areaLength) { + + var idealSize = Math.min(Math.sqrt(areaWidth * areaLength / items), areaWidth, areaLength); + + var baseWidth = Math.min(Math.floor(areaWidth / idealSize), items); + var baseLength = Math.min(Math.floor(areaLength / idealSize), items); + + var sirRows = baseWidth; + var sirCols = Math.ceil(items / sirRows); + var sirW = areaWidth / sirRows; + var sirL = areaLength / sirCols; + + var visCols = baseLength; + var visRows = Math.ceil(items / visCols); + var visW = areaWidth / visRows; + var visL = areaLength / visCols; + + var rows = 0; + var cols = 0; + var parcelWidth = 0; + var parcelLength = 0; + + if (Math.min(sirW, sirL) > Math.min(visW, visL)) { + rows = sirRows; + cols = sirCols; + parcelWidth = sirW; + parcelLength = sirL; + } else { + rows = visRows; + cols = visCols; + parcelWidth = visW; + parcelLength = visL; + } + + print("rows:" + rows); + print("cols:" + cols); + print("parcelWidth:" + parcelWidth); + print("parcelLength:" + parcelLength); + + return { + rows: rows, + cols: cols, + parcelWidth: parcelWidth, + parcelLength: parcelLength + }; + } + + function cleanup() { + while (houses.length > 0) { + if (!houses[0].isKnownID) { + houses[0] = Entities.identifyEntity(houses[0]); + } + Entities.deleteEntity(houses.shift()); + } + } + + Script.scriptEnding.connect(cleanup); +})(); diff --git a/examples/hmdDefaults.js b/examples/hmdDefaults.js index 1e96d41713..0096b11777 100644 --- a/examples/hmdDefaults.js +++ b/examples/hmdDefaults.js @@ -13,5 +13,4 @@ Script.load("progress.js"); Script.load("lobby.js"); Script.load("notifications.js"); Script.load("controllers/oculus/goTo.js"); -Script.load("utilities/LODWarning.js"); //Script.load("scripts.js"); // Not created yet 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/libraries/modelUploader.js b/examples/libraries/modelUploader.js index fcc96854ab..64a9e91203 100644 --- a/examples/libraries/modelUploader.js +++ b/examples/libraries/modelUploader.js @@ -21,8 +21,8 @@ modelUploader = (function () { //svoBuffer, mapping, geometry, - API_URL = "https://metaverse.highfidelity.io/api/v1/models", - MODEL_URL = "http://public.highfidelity.io/models/content", + API_URL = "https://metaverse.highfidelity.com/api/v1/models", + MODEL_URL = "http://public.highfidelity.com/models/content", NAME_FIELD = "name", SCALE_FIELD = "scale", FILENAME_FIELD = "filename", @@ -690,4 +690,4 @@ modelUploader = (function () { }; return that; -}()); \ No newline at end of file +}()); diff --git a/examples/lobby.js b/examples/lobby.js index 5d687dc07a..381107d65a 100644 --- a/examples/lobby.js +++ b/examples/lobby.js @@ -158,7 +158,7 @@ var places = {}; function changeLobbyTextures() { var req = new XMLHttpRequest(); - req.open("GET", "https://metaverse.highfidelity.io/api/v1/places?limit=21", false); + req.open("GET", "https://metaverse.highfidelity.com/api/v1/places?limit=21", false); req.send(); places = JSON.parse(req.responseText).data.places; 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/examples/notifications.js b/examples/notifications.js index 0c2a06c878..5ee6874521 100644 --- a/examples/notifications.js +++ b/examples/notifications.js @@ -43,7 +43,6 @@ // after that we will send it to createNotification(text). // If the message is 42 chars or less you should bypass wordWrap() and call createNotification() directly. - // To add a keypress driven notification: // // 1. Add a key to the keyPressEvent(key). @@ -85,16 +84,21 @@ var PLAY_NOTIFICATION_SOUNDS_MENU_ITEM = "Play Notification Sounds"; var NOTIFICATION_MENU_ITEM_POST = " Notifications"; var PLAY_NOTIFICATION_SOUNDS_SETTING = "play_notification_sounds"; var PLAY_NOTIFICATION_SOUNDS_TYPE_SETTING_PRE = "play_notification_sounds_type_"; +var lodTextID = false; var NotificationType = { UNKNOWN: 0, MUTE_TOGGLE: 1, SNAPSHOT: 2, WINDOW_RESIZE: 3, + LOD_WARNING: 4, + CONNECTION_REFUSED: 5, properties: [ { text: "Mute Toggle" }, { text: "Snapshot" }, - { text: "Window Resize" } + { text: "Window Resize" }, + { text: "Level of Detail" }, + { text: "Connection Refused" } ], getTypeFromMenuItem: function(menuItemName) { if (menuItemName.substr(menuItemName.length - NOTIFICATION_MENU_ITEM_POST.length) !== NOTIFICATION_MENU_ITEM_POST) { @@ -143,6 +147,10 @@ function createArrays(notice, button, createTime, height, myAlpha) { // This handles the final dismissal of a notification after fading function dismiss(firstNoteOut, firstButOut, firstOut) { + if (firstNoteOut == lodTextID) { + lodTextID = false; + } + Overlays.deleteOverlay(firstNoteOut); Overlays.deleteOverlay(firstButOut); notifications.splice(firstOut, 1); @@ -261,7 +269,8 @@ function notify(notice, button, height) { height: noticeHeight }); } else { - notifications.push((Overlays.addOverlay("text", notice))); + var notificationText = Overlays.addOverlay("text", notice); + notifications.push((notificationText)); buttons.push((Overlays.addOverlay("image", button))); } @@ -272,6 +281,7 @@ function notify(notice, button, height) { last = notifications.length - 1; createArrays(notifications[last], buttons[last], times[last], heights[last], myAlpha[last]); fadeIn(notifications[last], buttons[last]); + return notificationText; } // This function creates and sizes the overlays @@ -331,11 +341,15 @@ function createNotification(text, notificationType) { randomSounds.playRandom(); } - notify(noticeProperties, buttonProperties, height); + return notify(noticeProperties, buttonProperties, height); } function deleteNotification(index) { - Overlays.deleteOverlay(notifications[index]); + var notificationTextID = notifications[index]; + if (notificationTextID == lodTextID) { + lodTextID = false; + } + Overlays.deleteOverlay(notificationTextID); Overlays.deleteOverlay(buttons[index]); notifications.splice(index, 1); buttons.splice(index, 1); @@ -489,6 +503,10 @@ function onMuteStateChanged() { createNotification(muteString, NotificationType.MUTE_TOGGLE); } +function onDomainConnectionRefused(reason) { + createNotification("Connection refused: " + reason, NotificationType.CONNECTION_REFUSED ); +} + // handles mouse clicks on buttons function mousePressEvent(event) { var pickRay, @@ -575,6 +593,20 @@ function menuItemEvent(menuItem) { } } +LODManager.LODDecreased.connect(function() { + var warningText = "\n" + + "Due to the complexity of the content, the \n" + + "level of detail has been decreased." + + "You can now see: \n" + + LODManager.getLODFeedbackText(); + + if (lodTextID == false) { + lodTextID = createNotification(warningText, NotificationType.LOD_WARNING); + } else { + Overlays.editOverlay(lodTextID, { text: warningText }); + } +}); + AudioDevice.muteToggled.connect(onMuteStateChanged); Controller.keyPressEvent.connect(keyPressEvent); Controller.mousePressEvent.connect(mousePressEvent); @@ -582,5 +614,6 @@ Controller.keyReleaseEvent.connect(keyReleaseEvent); Script.update.connect(update); Script.scriptEnding.connect(scriptEnding); Menu.menuItemEvent.connect(menuItemEvent); +Window.domainConnectionRefused.connect(onDomainConnectionRefused); setup(); diff --git a/examples/users.js b/examples/users.js index dec141127e..ebd59886aa 100644 --- a/examples/users.js +++ b/examples/users.js @@ -11,7 +11,7 @@ var usersWindow = (function () { - var WINDOW_WIDTH_2D = 150, + var WINDOW_WIDTH_2D = 160, WINDOW_MARGIN_2D = 12, WINDOW_FONT_2D = { size: 12 }, WINDOW_FOREGROUND_COLOR_2D = { red: 240, green: 240, blue: 240 }, @@ -22,6 +22,17 @@ var usersWindow = (function () { WINDOW_BACKGROUND_ALPHA_2D = 0.7, windowPane2D, windowHeading2D, + SCROLLBAR_BACKGROUND_WIDTH_2D = 12, + SCROLLBAR_BACKGROUND_COLOR_2D = { red: 80, green: 80, blue: 80 }, + SCROLLBAR_BACKGROUND_ALPHA_2D = 0.8, + scrollbarBackground2D, + SCROLLBAR_BAR_MIN_HEIGHT = 5, + SCROLLBAR_BAR_COLOR_2D = { red: 180, green: 180, blue: 180 }, + SCROLLBAR_BAR_ALPHA_2D = 0.8, + SCROLLBAR_BAR_SELECTED_ALPHA_2D = 0.9, + scrollbarBar2D, + scrollbarBackgroundHeight, + scrollbarBarHeight, VISIBILITY_SPACER_2D = 12, // Space between list of users and visibility controls visibilityHeading2D, VISIBILITY_RADIO_SPACE = 16, @@ -33,8 +44,10 @@ var usersWindow = (function () { usersOnline, // Raw users data linesOfUsers = [], // Array of indexes pointing into usersOnline + numUsersToDisplay = 0, + firstUserToDisplay = 0, - API_URL = "https://metaverse.highfidelity.io/api/v1/users?status=online", + API_URL = "https://metaverse.highfidelity.com/api/v1/users?status=online", HTTP_GET_TIMEOUT = 60000, // ms = 1 minute usersRequest, processUsers, @@ -54,6 +67,15 @@ var usersWindow = (function () { isVisible = true, viewportHeight, + isMirrorDisplay = false, + isFullscreenMirror = false, + + isUsingScrollbars = false, + isMovingScrollbar = false, + scrollbarBackgroundPosition = {}, + scrollbarBarPosition = {}, + scrollbarBarClickedAt, // 0.0 .. 1.0 + scrollbarValue = 0.0, // 0.0 .. 1.0 HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/", RADIO_BUTTON_SVG = HIFI_PUBLIC_BUCKET + "images/radio-button.svg", @@ -62,10 +84,32 @@ var usersWindow = (function () { radioButtonDiameter; function calculateWindowHeight() { + var AUDIO_METER_HEIGHT = 52, + MIRROR_HEIGHT = 220, + nonUsersHeight, + maxWindowHeight; + // Reserve 5 lines for window heading plus visibility heading and controls // Subtract windowLineSpacing for both end of user list and end of controls - windowHeight = (linesOfUsers.length > 0 ? linesOfUsers.length + 5 : 5) * windowLineHeight - - 2 * windowLineSpacing + VISIBILITY_SPACER_2D + 2 * WINDOW_MARGIN_2D; + nonUsersHeight = 5 * windowLineHeight - 2 * windowLineSpacing + VISIBILITY_SPACER_2D + 2 * WINDOW_MARGIN_2D; + + // Limit window to height of viewport minus VU meter and mirror if displayed + windowHeight = linesOfUsers.length * windowLineHeight + nonUsersHeight; + maxWindowHeight = viewportHeight - AUDIO_METER_HEIGHT; + if (isMirrorDisplay && !isFullscreenMirror) { + maxWindowHeight -= MIRROR_HEIGHT; + } + windowHeight = Math.max(Math.min(windowHeight, maxWindowHeight), nonUsersHeight); + + // Corresponding number of users to actually display + numUsersToDisplay = Math.max(Math.round((windowHeight - nonUsersHeight) / windowLineHeight), 0); + isUsingScrollbars = 0 < numUsersToDisplay && numUsersToDisplay < linesOfUsers.length; + if (isUsingScrollbars) { + firstUserToDisplay = Math.floor(scrollbarValue * (linesOfUsers.length - numUsersToDisplay)); + } else { + firstUserToDisplay = 0; + scrollbarValue = 0.0; + } } function updateOverlayPositions() { @@ -78,6 +122,16 @@ var usersWindow = (function () { Overlays.editOverlay(windowHeading2D, { y: viewportHeight - windowHeight + WINDOW_MARGIN_2D }); + + scrollbarBackgroundPosition.y = viewportHeight - windowHeight + WINDOW_MARGIN_2D + windowTextHeight; + Overlays.editOverlay(scrollbarBackground2D, { + y: scrollbarBackgroundPosition.y + }); + scrollbarBarPosition.y = scrollbarBackgroundPosition.y + 1 + + scrollbarValue * (scrollbarBackgroundHeight - scrollbarBarHeight - 2); + Overlays.editOverlay(scrollbarBar2D, { + y: scrollbarBarPosition.y + }); Overlays.editOverlay(visibilityHeading2D, { y: viewportHeight - 4 * windowLineHeight + windowLineSpacing - WINDOW_MARGIN_2D }); @@ -104,57 +158,59 @@ var usersWindow = (function () { function updateUsersDisplay() { var displayText = "", - myUsername, user, userText, textWidth, maxTextWidth, + ellipsisWidth, + reducedTextWidth, i; - myUsername = GlobalServices.username; - linesOfUsers = []; - for (i = 0; i < usersOnline.length; i += 1) { - user = usersOnline[i]; - if (user.username !== myUsername && user.online) { - userText = user.username; - if (user.location.root) { - userText += " @ " + user.location.root.name; - } - textWidth = Overlays.textSize(windowPane2D, userText).width; + maxTextWidth = WINDOW_WIDTH_2D - (isUsingScrollbars ? SCROLLBAR_BACKGROUND_WIDTH_2D : 0) - 2 * WINDOW_MARGIN_2D; + ellipsisWidth = Overlays.textSize(windowPane2D, "...").width; + reducedTextWidth = maxTextWidth - ellipsisWidth; - maxTextWidth = WINDOW_WIDTH_2D - 2 * WINDOW_MARGIN_2D; - if (textWidth > maxTextWidth) { - // Trim and append "..." to fit window width - maxTextWidth = maxTextWidth - Overlays.textSize(windowPane2D, "...").width; - while (textWidth > maxTextWidth) { - userText = userText.slice(0, -1); - textWidth = Overlays.textSize(windowPane2D, userText).width; - } - userText += "..."; + for (i = 0; i < numUsersToDisplay; i += 1) { + user = usersOnline[linesOfUsers[firstUserToDisplay + i]]; + userText = user.text; + textWidth = user.textWidth; + + if (textWidth > maxTextWidth) { + // Trim and append "..." to fit window width + maxTextWidth = maxTextWidth - Overlays.textSize(windowPane2D, "...").width; + while (textWidth > reducedTextWidth) { + userText = userText.slice(0, -1); textWidth = Overlays.textSize(windowPane2D, userText).width; } - - usersOnline[i].textWidth = textWidth; - linesOfUsers.push(i); - displayText += "\n" + userText; + userText += "..."; } + + displayText += "\n" + userText; } displayText = displayText.slice(1); // Remove leading "\n". - calculateWindowHeight(); - Overlays.editOverlay(windowPane2D, { - y: viewportHeight - windowHeight, height: windowHeight, text: displayText }); Overlays.editOverlay(windowHeading2D, { - y: viewportHeight - windowHeight + WINDOW_MARGIN_2D, text: linesOfUsers.length > 0 ? "Users online" : "No users online" }); + scrollbarBackgroundHeight = numUsersToDisplay * windowLineHeight - windowLineSpacing / 2; + Overlays.editOverlay(scrollbarBackground2D, { + height: scrollbarBackgroundHeight, + visible: isUsingScrollbars + }); + scrollbarBarHeight = Math.max(numUsersToDisplay / linesOfUsers.length * scrollbarBackgroundHeight, + SCROLLBAR_BAR_MIN_HEIGHT); + Overlays.editOverlay(scrollbarBar2D, { + height: scrollbarBarHeight, + visible: isUsingScrollbars + }); + updateOverlayPositions(); } @@ -168,7 +224,11 @@ var usersWindow = (function () { } processUsers = function () { - var response; + var response, + myUsername, + user, + userText, + i; if (usersRequest.readyState === usersRequest.DONE) { if (usersRequest.status === 200) { @@ -185,7 +245,26 @@ var usersWindow = (function () { } usersOnline = response.data.users; + myUsername = GlobalServices.username; + linesOfUsers = []; + for (i = 0; i < usersOnline.length; i += 1) { + user = usersOnline[i]; + if (user.username !== myUsername && user.online) { + userText = user.username; + if (user.location.root) { + userText += " @ " + user.location.root.name; + } + + usersOnline[i].text = userText; + usersOnline[i].textWidth = Overlays.textSize(windowPane2D, userText).width; + + linesOfUsers.push(i); + } + } + + calculateWindowHeight(); updateUsersDisplay(); + } else { print("Error: Request for users status returned " + usersRequest.status + " " + usersRequest.statusText); usersTimer = Script.setTimeout(pollUsers, HTTP_GET_TIMEOUT); // Try again after a longer delay. @@ -226,6 +305,8 @@ var usersWindow = (function () { Overlays.editOverlay(windowPane2D, { visible: isVisible }); Overlays.editOverlay(windowHeading2D, { visible: isVisible }); + Overlays.editOverlay(scrollbarBackground2D, { visible: isVisible && isUsingScrollbars }); + Overlays.editOverlay(scrollbarBar2D, { visible: isVisible && isUsingScrollbars }); Overlays.editOverlay(visibilityHeading2D, { visible: isVisible }); for (i = 0; i < visibilityControls2D.length; i += 1) { Overlays.editOverlay(visibilityControls2D[i].radioOverlay, { visible: isVisible }); @@ -247,8 +328,10 @@ var usersWindow = (function () { minY, maxY, lineClicked, + userClicked, i, - visibilityChanged; + visibilityChanged, + delta; if (!isVisible) { return; @@ -261,7 +344,7 @@ var usersWindow = (function () { overlayX = event.x - WINDOW_MARGIN_2D; overlayY = event.y - viewportHeight + windowHeight - WINDOW_MARGIN_2D - windowLineHeight; - numLinesBefore = Math.floor(overlayY / windowLineHeight); + numLinesBefore = Math.round(overlayY / windowLineHeight); minY = numLinesBefore * windowLineHeight; maxY = minY + windowTextHeight; @@ -270,10 +353,12 @@ var usersWindow = (function () { lineClicked = numLinesBefore; } - if (0 <= lineClicked && lineClicked < linesOfUsers.length - && 0 <= overlayX && overlayX <= usersOnline[linesOfUsers[lineClicked]].textWidth) { - //print("Go to " + usersOnline[linesOfUsers[lineClicked]].username); - location.goToUser(usersOnline[linesOfUsers[lineClicked]].username); + userClicked = firstUserToDisplay + lineClicked; + + if (0 <= userClicked && userClicked < linesOfUsers.length + && 0 <= overlayX && overlayX <= usersOnline[linesOfUsers[userClicked]].textWidth) { + //print("Go to " + usersOnline[linesOfUsers[userClicked]].username); + location.goToUser(usersOnline[linesOfUsers[userClicked]].username); } } @@ -292,13 +377,74 @@ var usersWindow = (function () { } updateVisibilityControls(); } + + if (clickedOverlay === scrollbarBar2D) { + scrollbarBarClickedAt = (event.y - scrollbarBarPosition.y) / scrollbarBarHeight; + Overlays.editOverlay(scrollbarBar2D, { + backgroundAlpha: SCROLLBAR_BAR_SELECTED_ALPHA_2D + }); + isMovingScrollbar = true; + } + + if (clickedOverlay === scrollbarBackground2D) { + delta = scrollbarBarHeight / (scrollbarBackgroundHeight - scrollbarBarHeight); + + if (event.y < scrollbarBarPosition.y) { + scrollbarValue = Math.max(scrollbarValue - delta, 0.0); + } else { + scrollbarValue = Math.min(scrollbarValue + delta, 1.0); + } + + firstUserToDisplay = Math.floor(scrollbarValue * (linesOfUsers.length - numUsersToDisplay)); + updateOverlayPositions(); + updateUsersDisplay(); + } + } + + function onMouseMoveEvent(event) { + if (isMovingScrollbar) { + if (scrollbarBackgroundPosition.x - WINDOW_MARGIN_2D <= event.x + && event.x <= scrollbarBackgroundPosition.x + SCROLLBAR_BACKGROUND_WIDTH_2D + WINDOW_MARGIN_2D + && scrollbarBackgroundPosition.y - WINDOW_MARGIN_2D <= event.y + && event.y <= scrollbarBackgroundPosition.y + scrollbarBackgroundHeight + WINDOW_MARGIN_2D) { + scrollbarValue = (event.y - scrollbarBarClickedAt * scrollbarBarHeight - scrollbarBackgroundPosition.y) + / (scrollbarBackgroundHeight - scrollbarBarHeight - 2); + scrollbarValue = Math.min(Math.max(scrollbarValue, 0.0), 1.0); + firstUserToDisplay = Math.floor(scrollbarValue * (linesOfUsers.length - numUsersToDisplay)); + updateOverlayPositions(); + updateUsersDisplay(); + } else { + Overlays.editOverlay(scrollbarBar2D, { + backgroundAlpha: SCROLLBAR_BAR_ALPHA_2D + }); + isMovingScrollbar = false; + } + } + } + + function onMouseReleaseEvent() { + Overlays.editOverlay(scrollbarBar2D, { + backgroundAlpha: SCROLLBAR_BAR_ALPHA_2D + }); + isMovingScrollbar = false; } function onScriptUpdate() { - var oldViewportHeight = viewportHeight; + var oldViewportHeight = viewportHeight, + oldIsMirrorDisplay = isMirrorDisplay, + oldIsFullscreenMirror = isFullscreenMirror, + MIRROR_MENU_ITEM = "Mirror", + FULLSCREEN_MIRROR_MENU_ITEM = "Fullscreen Mirror"; viewportHeight = Controller.getViewportDimensions().y; - if (viewportHeight !== oldViewportHeight) { + isMirrorDisplay = Menu.isOptionChecked(MIRROR_MENU_ITEM); + isFullscreenMirror = Menu.isOptionChecked(FULLSCREEN_MIRROR_MENU_ITEM); + + if (viewportHeight !== oldViewportHeight + || isMirrorDisplay !== oldIsMirrorDisplay + || isFullscreenMirror !== oldIsFullscreenMirror) { + calculateWindowHeight(); + updateUsersDisplay(); updateOverlayPositions(); } } @@ -314,10 +460,10 @@ var usersWindow = (function () { radioButtonDiameter = RADIO_BUTTON_DISPLAY_SCALE * windowTextHeight; Overlays.deleteOverlay(textSizeOverlay); - calculateWindowHeight(); - viewportHeight = Controller.getViewportDimensions().y; + calculateWindowHeight(); + windowPane2D = Overlays.addOverlay("text", { x: 0, y: viewportHeight, // Start up off-screen @@ -349,6 +495,36 @@ var usersWindow = (function () { visible: isVisible }); + scrollbarBackgroundPosition = { + x: WINDOW_WIDTH_2D - 0.5 * WINDOW_MARGIN_2D - SCROLLBAR_BACKGROUND_WIDTH_2D, + y: viewportHeight + }; + scrollbarBackground2D = Overlays.addOverlay("text", { + x: scrollbarBackgroundPosition.x, + y: scrollbarBackgroundPosition.y, + width: SCROLLBAR_BACKGROUND_WIDTH_2D, + height: windowTextHeight, + backgroundColor: SCROLLBAR_BACKGROUND_COLOR_2D, + backgroundAlpha: SCROLLBAR_BACKGROUND_ALPHA_2D, + text: "", + visible: isVisible && isUsingScrollbars + }); + + scrollbarBarPosition = { + x: WINDOW_WIDTH_2D - 0.5 * WINDOW_MARGIN_2D - SCROLLBAR_BACKGROUND_WIDTH_2D + 1, + y: viewportHeight + }; + scrollbarBar2D = Overlays.addOverlay("text", { + x: scrollbarBarPosition.x, + y: scrollbarBarPosition.y, + width: SCROLLBAR_BACKGROUND_WIDTH_2D - 2, + height: windowTextHeight, + backgroundColor: SCROLLBAR_BAR_COLOR_2D, + backgroundAlpha: SCROLLBAR_BAR_ALPHA_2D, + text: "", + visible: isVisible && isUsingScrollbars + }); + visibilityHeading2D = Overlays.addOverlay("text", { x: WINDOW_MARGIN_2D, y: viewportHeight, @@ -390,7 +566,7 @@ var usersWindow = (function () { textOverlay: Overlays.addOverlay("text", { x: WINDOW_MARGIN_2D, y: viewportHeight, - width: WINDOW_WIDTH_2D - 2 * WINDOW_MARGIN_2D, + width: WINDOW_WIDTH_2D - SCROLLBAR_BACKGROUND_WIDTH_2D - 2 * WINDOW_MARGIN_2D, height: windowTextHeight, topMargin: 0, leftMargin: VISIBILITY_RADIO_SPACE, @@ -429,6 +605,8 @@ var usersWindow = (function () { updateVisibilityControls(); Controller.mousePressEvent.connect(onMousePressEvent); + Controller.mouseMoveEvent.connect(onMouseMoveEvent); + Controller.mouseReleaseEvent.connect(onMouseReleaseEvent); Menu.addMenuItem({ menuName: MENU_NAME, @@ -456,6 +634,8 @@ var usersWindow = (function () { Script.clearTimeout(usersTimer); Overlays.deleteOverlay(windowPane2D); Overlays.deleteOverlay(windowHeading2D); + Overlays.deleteOverlay(scrollbarBackground2D); + Overlays.deleteOverlay(scrollbarBar2D); Overlays.deleteOverlay(visibilityHeading2D); for (i = 0; i <= visibilityControls2D.length; i += 1) { Overlays.deleteOverlay(visibilityControls2D[i].textOverlay); diff --git a/examples/utilities/LODWarning.js b/examples/utilities/LODWarning.js deleted file mode 100644 index 644d98ebf4..0000000000 --- a/examples/utilities/LODWarning.js +++ /dev/null @@ -1,115 +0,0 @@ -// LODWarning.js -// examples -// -// Created by Brad Hefta-Gaub on 3/17/15. -// Copyright 2015 High Fidelity, Inc. -// -// This script will display a warning when the LOD is adjusted to do scene complexity. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -var DISPLAY_WARNING_FOR = 3; // in seconds -var DISTANCE_FROM_CAMERA = 2; -var SHOW_LOD_UP_MESSAGE = false; // By default we only display the LOD message when reducing LOD - - -var warningIsVisible = false; // initially the warning is hidden -var warningShownAt = 0; -var billboardPosition = Vec3.sum(Camera.getPosition(), - Vec3.multiply(DISTANCE_FROM_CAMERA, Quat.getFront(Camera.getOrientation()))); - -var warningOverlay = Overlays.addOverlay("text3d", { - position: billboardPosition, - dimensions: { x: 2, y: 1.25 }, - width: 2, - height: 1.25, - backgroundColor: { red: 0, green: 0, blue: 0 }, - color: { red: 255, green: 255, blue: 255}, - topMargin: 0.1, - leftMargin: 0.1, - lineHeight: 0.07, - text: "", - alpha: 0.5, - backgroundAlpha: 0.7, - isFacingAvatar: true, - visible: warningIsVisible, - }); - -// Handle moving the billboard to remain in front of the camera -var billboardNeedsMoving = false; -Script.update.connect(function() { - - if (warningIsVisible) { - var bestBillboardPosition = Vec3.sum(Camera.getPosition(), - Vec3.multiply(DISTANCE_FROM_CAMERA, Quat.getFront(Camera.getOrientation()))); - - var MAX_DISTANCE = 0.5; - var CLOSE_ENOUGH = 0.01; - if (!billboardNeedsMoving && Vec3.distance(bestBillboardPosition, billboardPosition) > MAX_DISTANCE) { - billboardNeedsMoving = true; - } - - if (billboardNeedsMoving && Vec3.distance(bestBillboardPosition, billboardPosition) <= CLOSE_ENOUGH) { - billboardNeedsMoving = false; - } - - if (billboardNeedsMoving) { - // slurp the billboard to the best location - moveVector = Vec3.multiply(0.05, Vec3.subtract(bestBillboardPosition, billboardPosition)); - billboardPosition = Vec3.sum(billboardPosition, moveVector); - Overlays.editOverlay(warningOverlay, { position: billboardPosition }); - } - - var now = new Date(); - var sinceWarningShown = now - warningShownAt; - if (sinceWarningShown > 1000 * DISPLAY_WARNING_FOR) { - warningIsVisible = false; - Overlays.editOverlay(warningOverlay, { visible: warningIsVisible }); - } - } -}); - -LODManager.LODIncreased.connect(function() { - if (SHOW_LOD_UP_MESSAGE) { - // if the warning wasn't visible, then move it before showing it. - if (!warningIsVisible) { - billboardPosition = Vec3.sum(Camera.getPosition(), - Vec3.multiply(DISTANCE_FROM_CAMERA, Quat.getFront(Camera.getOrientation()))); - Overlays.editOverlay(warningOverlay, { position: billboardPosition }); - } - - warningShownAt = new Date(); - warningIsVisible = true; - warningText = "Level of detail has been increased. \n" - + "You can now see: \n" - + LODManager.getLODFeedbackText(); - - Overlays.editOverlay(warningOverlay, { visible: warningIsVisible, text: warningText }); - } -}); - -LODManager.LODDecreased.connect(function() { - // if the warning wasn't visible, then move it before showing it. - if (!warningIsVisible) { - billboardPosition = Vec3.sum(Camera.getPosition(), - Vec3.multiply(DISTANCE_FROM_CAMERA, Quat.getFront(Camera.getOrientation()))); - Overlays.editOverlay(warningOverlay, { position: billboardPosition }); - } - - warningShownAt = new Date(); - warningIsVisible = true; - warningText = "\n" - + "Due to the complexity of the content, the \n" - + "level of detail has been decreased. \n" - + "You can now see: \n" - + LODManager.getLODFeedbackText(); - - Overlays.editOverlay(warningOverlay, { visible: warningIsVisible, text: warningText }); -}); - - -Script.scriptEnding.connect(function() { - Overlays.deleteOverlay(warningOverlay); -}); \ No newline at end of file diff --git a/interface/external/sixense/readme.txt b/interface/external/sixense/readme.txt index 29d1bbc2f9..a4790caa5e 100644 --- a/interface/external/sixense/readme.txt +++ b/interface/external/sixense/readme.txt @@ -2,7 +2,7 @@ Instructions for adding the Sixense driver to Interface Andrzej Kapolka, November 18, 2013 -1. Copy the Sixense sdk folders (lib, include) into the interface/external/Sixense folder. This readme.txt should be there as well. +1. Copy the Sixense sdk folders (bin, include, lib, and samples) into the interface/external/Sixense folder. This readme.txt should be there as well. You may optionally choose to copy the SDK folders to a location outside the repository (so you can re-use with different checkouts and different projects). If so our CMake find module expects you to set the ENV variable 'HIFI_LIB_DIR' to a directory containing a subfolder 'sixense' that contains the folders mentioned above. diff --git a/interface/resources/html/edit-commands.html b/interface/resources/html/edit-commands.html new file mode 100644 index 0000000000..65b985fb6a --- /dev/null +++ b/interface/resources/html/edit-commands.html @@ -0,0 +1,2058 @@ + +

+ + + + +Edit Entity Help + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/interface/resources/html/edit-entities-commands.html b/interface/resources/html/edit-entities-commands.html deleted file mode 100644 index afa662f089..0000000000 --- a/interface/resources/html/edit-entities-commands.html +++ /dev/null @@ -1,2036 +0,0 @@ - -
- - - - - -Edit Entity Help - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
diff --git a/interface/resources/html/interface-welcome.html b/interface/resources/html/interface-welcome.html index ed905eb392..26ae6ff5c0 100644 --- a/interface/resources/html/interface-welcome.html +++ b/interface/resources/html/interface-welcome.html @@ -138,7 +138,7 @@

Import models

- Use the editEntitles.js script to
+ Use the edit.js script to
add FBX models in-world. You
can use grids and fine tune
placement-related parameters
diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index cc84951666..e37643d118 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -297,7 +297,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : _lastSendDownstreamAudioStats(usecTimestampNow()), _isVSyncOn(true), _aboutToQuit(false), - _notifiedPacketVersionMismatchThisDomain(false) + _notifiedPacketVersionMismatchThisDomain(false), + _domainConnectionRefusals(QList()) { #ifdef Q_OS_WIN installNativeEventFilter(&MyNativeEventFilter::getInstance()); @@ -344,6 +345,13 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : // put the NodeList and datagram processing on the node thread nodeList->moveToThread(nodeThread); + // geometry background downloads need to happen on the Datagram Processor Thread. The idle loop will + // emit checkBackgroundDownloads to cause the GeometryCache to check it's queue for requested background + // downloads. + QSharedPointer geometryCacheP = DependencyManager::get(); + ResourceCache *geometryCache = geometryCacheP.data(); + connect(this, &Application::checkBackgroundDownloads, geometryCache, &ResourceCache::checkAsynchronousGets); + // connect the DataProcessor processDatagrams slot to the QUDPSocket readyRead() signal connect(&nodeList->getNodeSocket(), &QUdpSocket::readyRead, _datagramProcessor, &DatagramProcessor::processDatagrams); @@ -382,6 +390,10 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : auto discoverabilityManager = DependencyManager::get(); connect(locationUpdateTimer, &QTimer::timeout, discoverabilityManager.data(), &DiscoverabilityManager::updateLocation); locationUpdateTimer->start(DATA_SERVER_LOCATION_CHANGE_UPDATE_MSECS); + + // if we get a domain change, immediately attempt update location in metaverse server + connect(&nodeList->getDomainHandler(), &DomainHandler::connectedToDomain, + discoverabilityManager.data(), &DiscoverabilityManager::updateLocation); connect(nodeList.data(), &NodeList::nodeAdded, this, &Application::nodeAdded); connect(nodeList.data(), &NodeList::nodeKilled, this, &Application::nodeKilled); @@ -781,7 +793,7 @@ void Application::paintGL() { DependencyManager::get()->render(); - if (Menu::getInstance()->isOptionChecked(MenuOption::UserInterface)) { + { PerformanceTimer perfTimer("renderOverlay"); _applicationOverlay.renderOverlay(true); _applicationOverlay.displayOverlayTexture(); @@ -979,12 +991,6 @@ void Application::keyPressEvent(QKeyEvent* event) { resetSensors(); break; - case Qt::Key_G: - if (isShifted) { - Menu::getInstance()->triggerOption(MenuOption::ObeyEnvironmentalGravity); - } - break; - case Qt::Key_A: if (isShifted) { Menu::getInstance()->triggerOption(MenuOption::Atmosphere); @@ -1119,13 +1125,10 @@ void Application::keyPressEvent(QKeyEvent* event) { Menu::getInstance()->triggerOption(MenuOption::FullscreenMirror); } break; - case Qt::Key_Slash: - Menu::getInstance()->triggerOption(MenuOption::UserInterface); - break; case Qt::Key_P: Menu::getInstance()->triggerOption(MenuOption::FirstPerson); break; - case Qt::Key_Percent: + case Qt::Key_Slash: Menu::getInstance()->triggerOption(MenuOption::Stats); break; case Qt::Key_Plus: @@ -1160,10 +1163,6 @@ void Application::keyPressEvent(QKeyEvent* event) { break; } - case Qt::Key_Comma: { - _myAvatar->togglePhysicsEnabled(); - } - default: event->ignore(); break; @@ -1561,6 +1560,9 @@ void Application::idle() { idleTimer->start(2); } } + + // check for any requested background downloads. + emit checkBackgroundDownloads(); } void Application::setFullscreen(bool fullscreen) { @@ -1747,7 +1749,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(); @@ -1782,6 +1784,7 @@ bool Application::exportEntities(const QString& filename, float x, float y, floa void Application::loadSettings() { DependencyManager::get()->loadSettings(); + DependencyManager::get()->loadSettings(); Menu::getInstance()->loadSettings(); _myAvatar->loadData(); @@ -1789,6 +1792,7 @@ void Application::loadSettings() { void Application::saveSettings() { DependencyManager::get()->saveSettings(); + DependencyManager::get()->saveSettings(); Menu::getInstance()->saveSettings(); _myAvatar->saveData(); @@ -1899,8 +1903,6 @@ void Application::init() { _physicsEngine.init(&_entityEditSender); - _physicsEngine.setAvatarData(_myAvatar); - auto entityScriptingInterface = DependencyManager::get(); connect(&_physicsEngine, &EntitySimulation::entityCollisionWithEntity, @@ -1965,7 +1967,7 @@ bool Application::isLookingAtMyAvatar(Avatar* avatar) { void Application::updateLOD() { PerformanceTimer perfTimer("LOD"); // adjust it unless we were asked to disable this feature, or if we're currently in throttleRendering mode - if (!Menu::getInstance()->isOptionChecked(MenuOption::DisableAutoAdjustLOD) && !isThrottleRendering()) { + if (!isThrottleRendering()) { DependencyManager::get()->autoAdjustLOD(_fps); } else { DependencyManager::get()->resetLODAdjust(); @@ -3010,8 +3012,7 @@ void Application::displaySide(Camera& theCamera, bool selfAvatarOnly, RenderArgs _nodeBoundsDisplay.draw(); // Render the world box - if (theCamera.getMode() != CAMERA_MODE_MIRROR && Menu::getInstance()->isOptionChecked(MenuOption::Stats) && - Menu::getInstance()->isOptionChecked(MenuOption::UserInterface)) { + if (theCamera.getMode() != CAMERA_MODE_MIRROR && Menu::getInstance()->isOptionChecked(MenuOption::Stats)) { PerformanceTimer perfTimer("worldBox"); renderWorldBox(); } @@ -3131,7 +3132,7 @@ void Application::renderRearViewMirror(const QRect& region, bool billboard) { int viewport[4]; glGetIntegerv(GL_VIEWPORT, viewport); - bool eyeRelativeCamera = false; + // bool eyeRelativeCamera = false; if (billboard) { _mirrorCamera.setFieldOfView(BILLBOARD_FIELD_OF_VIEW); // degees _mirrorCamera.setPosition(_myAvatar->getPosition() + @@ -3284,6 +3285,14 @@ void Application::clearDomainOctreeDetails() { void Application::domainChanged(const QString& domainHostname) { updateWindowTitle(); clearDomainOctreeDetails(); + _domainConnectionRefusals.clear(); +} + +void Application::domainConnectionDenied(const QString& reason) { + if (!_domainConnectionRefusals.contains(reason)) { + _domainConnectionRefusals.append(reason); + emit domainConnectionRefused(reason); + } } void Application::connectedToDomain(const QString& hostname) { @@ -3291,11 +3300,6 @@ void Application::connectedToDomain(const QString& hostname) { const QUuid& domainID = DependencyManager::get()->getDomainHandler().getUUID(); if (accountManager.isLoggedIn() && !domainID.isNull()) { - // update our data-server with the domain-server we're logged in with - QString domainPutJsonString = "{\"location\":{\"domain_id\":\"" + uuidStringWithoutCurlyBraces(domainID) + "\"}}"; - accountManager.authenticatedRequest("/api/v1/user/location", QNetworkAccessManager::PutOperation, - JSONCallbackParameters(), domainPutJsonString.toUtf8()); - _notifiedPacketVersionMismatchThisDomain = false; } } @@ -3595,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; } @@ -4203,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 4038b21739..84b443b876 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"; @@ -113,7 +114,7 @@ static const float MIRROR_FIELD_OF_VIEW = 30.0f; static const quint64 TOO_LONG_SINCE_LAST_SEND_DOWNSTREAM_AUDIO_STATS = 1 * USECS_PER_SECOND; static const QString INFO_HELP_PATH = "html/interface-welcome.html"; -static const QString INFO_EDIT_ENTITIES_PATH = "html/edit-entities-commands.html"; +static const QString INFO_EDIT_ENTITIES_PATH = "html/edit-commands.html"; #ifdef Q_OS_WIN static const UINT UWM_IDENTIFY_INSTANCES = @@ -332,6 +333,9 @@ signals: void svoImportRequested(const QString& url); + void checkBackgroundDownloads(); + void domainConnectionRefused(const QString& reason); + public slots: void domainChanged(const QString& domainHostname); void updateWindowTitle(); @@ -382,6 +386,8 @@ public slots: void setActiveFaceTracker(); + void domainConnectionDenied(const QString& reason); + private slots: void clearDomainOctreeDetails(); void checkFPS(); @@ -606,6 +612,8 @@ private: int _menuBarHeight; QHash _acceptedExtensions; + + QList _domainConnectionRefusals; }; #endif // hifi_Application_h diff --git a/interface/src/DatagramProcessor.cpp b/interface/src/DatagramProcessor.cpp index 475ce406bb..9ac2b51097 100644 --- a/interface/src/DatagramProcessor.cpp +++ b/interface/src/DatagramProcessor.cpp @@ -127,6 +127,7 @@ void DatagramProcessor::processDatagrams() { // and check and signal for an access token so that we can make sure they are logged in qDebug() << "The domain-server denied a connection request: " << reason; qDebug() << "You may need to re-log to generate a keypair so you can provide a username signature."; + application->domainConnectionDenied(reason); AccountManager::getInstance().checkAndSignalForAccessToken(); break; } diff --git a/interface/src/DiscoverabilityManager.cpp b/interface/src/DiscoverabilityManager.cpp index 49850e4ef6..13697ff8be 100644 --- a/interface/src/DiscoverabilityManager.cpp +++ b/interface/src/DiscoverabilityManager.cpp @@ -30,8 +30,9 @@ DiscoverabilityManager::DiscoverabilityManager() : const QString API_USER_LOCATION_PATH = "/api/v1/user/location"; void DiscoverabilityManager::updateLocation() { + AccountManager& accountManager = AccountManager::getInstance(); + if (_mode.get() != Discoverability::None) { - AccountManager& accountManager = AccountManager::getInstance(); auto addressManager = DependencyManager::get(); DomainHandler& domainHandler = DependencyManager::get()->getDomainHandler(); @@ -61,17 +62,25 @@ 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()); } + } else { + // we still send a heartbeat to the metaverse server for stats collection + const QString API_USER_HEARTBEAT_PATH = "/api/v1/user/heartbeat"; + accountManager.sendRequest(API_USER_HEARTBEAT_PATH, AccountManagerAuth::Required, QNetworkAccessManager::PutOperation); } } 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/LODManager.cpp b/interface/src/LODManager.cpp index 063ba13492..cb4913df72 100644 --- a/interface/src/LODManager.cpp +++ b/interface/src/LODManager.cpp @@ -17,96 +17,165 @@ #include "LODManager.h" -Setting::Handle automaticAvatarLOD("automaticAvatarLOD", true); -Setting::Handle avatarLODDecreaseFPS("avatarLODDecreaseFPS", DEFAULT_ADJUST_AVATAR_LOD_DOWN_FPS); -Setting::Handle avatarLODIncreaseFPS("avatarLODIncreaseFPS", ADJUST_LOD_UP_FPS); -Setting::Handle avatarLODDistanceMultiplier("avatarLODDistanceMultiplier", - DEFAULT_AVATAR_LOD_DISTANCE_MULTIPLIER); -Setting::Handle boundaryLevelAdjust("boundaryLevelAdjust", 0); -Setting::Handle octreeSizeScale("octreeSizeScale", DEFAULT_OCTREE_SIZE_SCALE); +Setting::Handle desktopLODDecreaseFPS("desktopLODDecreaseFPS", DEFAULT_DESKTOP_LOD_DOWN_FPS); +Setting::Handle hmdLODDecreaseFPS("hmdLODDecreaseFPS", DEFAULT_HMD_LOD_DOWN_FPS); + +LODManager::LODManager() { + calculateAvatarLODDistanceMultiplier(); +} + +float LODManager::getLODDecreaseFPS() { + if (Application::getInstance()->isHMDMode()) { + return getHMDLODDecreaseFPS(); + } + return getDesktopLODDecreaseFPS(); +} + +float LODManager::getLODIncreaseFPS() { + if (Application::getInstance()->isHMDMode()) { + return getHMDLODIncreaseFPS(); + } + return getDesktopLODIncreaseFPS(); +} void LODManager::autoAdjustLOD(float currentFPS) { + // NOTE: our first ~100 samples at app startup are completely all over the place, and we don't // really want to count them in our average, so we will ignore the real frame rates and stuff // our moving average with simulated good data const int IGNORE_THESE_SAMPLES = 100; - const float ASSUMED_FPS = 60.0f; - if (_fpsAverage.getSampleCount() < IGNORE_THESE_SAMPLES) { + if (_fpsAverageUpWindow.getSampleCount() < IGNORE_THESE_SAMPLES) { currentFPS = ASSUMED_FPS; + _lastStable = _lastUpShift = _lastDownShift = usecTimestampNow(); } - _fpsAverage.updateAverage(currentFPS); - _fastFPSAverage.updateAverage(currentFPS); + + _fpsAverageStartWindow.updateAverage(currentFPS); + _fpsAverageDownWindow.updateAverage(currentFPS); + _fpsAverageUpWindow.updateAverage(currentFPS); quint64 now = usecTimestampNow(); - - const quint64 ADJUST_AVATAR_LOD_DOWN_DELAY = 1000 * 1000; - if (_automaticAvatarLOD) { - if (_fastFPSAverage.getAverage() < _avatarLODDecreaseFPS) { - if (now - _lastAvatarDetailDrop > ADJUST_AVATAR_LOD_DOWN_DELAY) { - // attempt to lower the detail in proportion to the fps difference - float targetFps = (_avatarLODDecreaseFPS + _avatarLODIncreaseFPS) * 0.5f; - float averageFps = _fastFPSAverage.getAverage(); - const float MAXIMUM_MULTIPLIER_SCALE = 2.0f; - _avatarLODDistanceMultiplier = qMin(MAXIMUM_AVATAR_LOD_DISTANCE_MULTIPLIER, _avatarLODDistanceMultiplier * - (averageFps < EPSILON ? MAXIMUM_MULTIPLIER_SCALE : - qMin(MAXIMUM_MULTIPLIER_SCALE, targetFps / averageFps))); - _lastAvatarDetailDrop = now; - } - } else if (_fastFPSAverage.getAverage() > _avatarLODIncreaseFPS) { - // let the detail level creep slowly upwards - const float DISTANCE_DECREASE_RATE = 0.05f; - _avatarLODDistanceMultiplier = qMax(MINIMUM_AVATAR_LOD_DISTANCE_MULTIPLIER, - _avatarLODDistanceMultiplier - DISTANCE_DECREASE_RATE); - } - } - - bool changed = false; - quint64 elapsed = now - _lastAdjust; - - if (elapsed > ADJUST_LOD_DOWN_DELAY && _fpsAverage.getAverage() < ADJUST_LOD_DOWN_FPS - && _octreeSizeScale > ADJUST_LOD_MIN_SIZE_SCALE) { - - _octreeSizeScale *= ADJUST_LOD_DOWN_BY; - - if (_octreeSizeScale < ADJUST_LOD_MIN_SIZE_SCALE) { - _octreeSizeScale = ADJUST_LOD_MIN_SIZE_SCALE; - } - changed = true; - _lastAdjust = now; - qDebug() << "adjusting LOD down... average fps for last approximately 5 seconds=" << _fpsAverage.getAverage() - << "_octreeSizeScale=" << _octreeSizeScale; - - emit LODDecreased(); - } - - if (elapsed > ADJUST_LOD_UP_DELAY && _fpsAverage.getAverage() > ADJUST_LOD_UP_FPS - && _octreeSizeScale < ADJUST_LOD_MAX_SIZE_SCALE) { - _octreeSizeScale *= ADJUST_LOD_UP_BY; - if (_octreeSizeScale > ADJUST_LOD_MAX_SIZE_SCALE) { - _octreeSizeScale = ADJUST_LOD_MAX_SIZE_SCALE; - } - changed = true; - _lastAdjust = now; - qDebug() << "adjusting LOD up... average fps for last approximately 5 seconds=" << _fpsAverage.getAverage() - << "_octreeSizeScale=" << _octreeSizeScale; - emit LODIncreased(); - } + bool changed = false; + quint64 elapsedSinceDownShift = now - _lastDownShift; + quint64 elapsedSinceUpShift = now - _lastUpShift; + + quint64 lastStableOrUpshift = glm::max(_lastUpShift, _lastStable); + quint64 elapsedSinceStableOrUpShift = now - lastStableOrUpshift; - if (changed) { - _shouldRenderTableNeedsRebuilding = true; - auto lodToolsDialog = DependencyManager::get()->getLodToolsDialog(); - if (lodToolsDialog) { - lodToolsDialog->reloadSliders(); + if (_automaticLODAdjust) { + + // LOD Downward adjustment + // If we've been downshifting, we watch a shorter downshift window so that we will quickly move toward our + // target frame rate. But if we haven't just done a downshift (either because our last shift was an upshift, + // or because we've just started out) then we look at a much longer window to consider whether or not to start + // downshifting. + bool doDownShift = false; + + if (_isDownshifting) { + // only consider things if our DOWN_SHIFT time has elapsed... + if (elapsedSinceDownShift > DOWN_SHIFT_ELPASED) { + doDownShift = _fpsAverageDownWindow.getAverage() < getLODDecreaseFPS(); + + if (!doDownShift) { + qDebug() << "---- WE APPEAR TO BE DONE DOWN SHIFTING -----"; + _isDownshifting = false; + _lastStable = now; + } + } + } else { + doDownShift = (elapsedSinceStableOrUpShift > START_SHIFT_ELPASED + && _fpsAverageStartWindow.getAverage() < getLODDecreaseFPS()); + } + + if (doDownShift) { + + // Octree items... stepwise adjustment + if (_octreeSizeScale > ADJUST_LOD_MIN_SIZE_SCALE) { + _octreeSizeScale *= ADJUST_LOD_DOWN_BY; + if (_octreeSizeScale < ADJUST_LOD_MIN_SIZE_SCALE) { + _octreeSizeScale = ADJUST_LOD_MIN_SIZE_SCALE; + } + changed = true; + } + + if (changed) { + if (_isDownshifting) { + // subsequent downshift + qDebug() << "adjusting LOD DOWN..." + << "average fps for last "<< DOWN_SHIFT_WINDOW_IN_SECS <<"seconds was " + << _fpsAverageDownWindow.getAverage() + << "minimum is:" << getLODDecreaseFPS() + << "elapsedSinceDownShift:" << elapsedSinceDownShift + << " NEW _octreeSizeScale=" << _octreeSizeScale; + } else { + // first downshift + qDebug() << "adjusting LOD DOWN after initial delay..." + << "average fps for last "<< START_DELAY_WINDOW_IN_SECS <<"seconds was " + << _fpsAverageStartWindow.getAverage() + << "minimum is:" << getLODDecreaseFPS() + << "elapsedSinceUpShift:" << elapsedSinceUpShift + << " NEW _octreeSizeScale=" << _octreeSizeScale; + } + + _lastDownShift = now; + _isDownshifting = true; + + emit LODDecreased(); + } + } else { + + // LOD Upward adjustment + if (elapsedSinceUpShift > UP_SHIFT_ELPASED) { + + if (_fpsAverageUpWindow.getAverage() > getLODIncreaseFPS()) { + + // Octee items... stepwise adjustment + if (_octreeSizeScale < ADJUST_LOD_MAX_SIZE_SCALE) { + if (_octreeSizeScale < ADJUST_LOD_MIN_SIZE_SCALE) { + _octreeSizeScale = ADJUST_LOD_MIN_SIZE_SCALE; + } else { + _octreeSizeScale *= ADJUST_LOD_UP_BY; + } + if (_octreeSizeScale > ADJUST_LOD_MAX_SIZE_SCALE) { + _octreeSizeScale = ADJUST_LOD_MAX_SIZE_SCALE; + } + changed = true; + } + } + + if (changed) { + qDebug() << "adjusting LOD UP... average fps for last "<< UP_SHIFT_WINDOW_IN_SECS <<"seconds was " + << _fpsAverageUpWindow.getAverage() + << "upshift point is:" << getLODIncreaseFPS() + << "elapsedSinceUpShift:" << elapsedSinceUpShift + << " NEW _octreeSizeScale=" << _octreeSizeScale; + + _lastUpShift = now; + _isDownshifting = false; + + emit LODIncreased(); + } + } + } + + if (changed) { + calculateAvatarLODDistanceMultiplier(); + _shouldRenderTableNeedsRebuilding = true; + auto lodToolsDialog = DependencyManager::get()->getLodToolsDialog(); + if (lodToolsDialog) { + lodToolsDialog->reloadSliders(); + } } } } void LODManager::resetLODAdjust() { - _fpsAverage.reset(); - _fastFPSAverage.reset(); - _lastAvatarDetailDrop = _lastAdjust = usecTimestampNow(); + _fpsAverageStartWindow.reset(); + _fpsAverageDownWindow.reset(); + _fpsAverageUpWindow.reset(); + _lastUpShift = _lastDownShift = usecTimestampNow(); + _isDownshifting = false; } QString LODManager::getLODFeedbackText() { @@ -116,29 +185,33 @@ QString LODManager::getLODFeedbackText() { switch (boundaryLevelAdjust) { case 0: { - granularityFeedback = QString("at standard granularity."); + granularityFeedback = QString("."); } break; case 1: { - granularityFeedback = QString("at half of standard granularity."); + granularityFeedback = QString(" at half of standard granularity."); } break; case 2: { - granularityFeedback = QString("at a third of standard granularity."); + granularityFeedback = QString(" at a third of standard granularity."); } break; default: { - granularityFeedback = QString("at 1/%1th of standard granularity.").arg(boundaryLevelAdjust + 1); + granularityFeedback = QString(" at 1/%1th of standard granularity.").arg(boundaryLevelAdjust + 1); } break; } // distance feedback float octreeSizeScale = getOctreeSizeScale(); float relativeToDefault = octreeSizeScale / DEFAULT_OCTREE_SIZE_SCALE; + int relativeToTwentyTwenty = 20 / relativeToDefault; + QString result; if (relativeToDefault > 1.01) { - result = QString("%1 further %2").arg(relativeToDefault,8,'f',2).arg(granularityFeedback); + result = QString("20:%1 or %2 times further than average vision%3").arg(relativeToTwentyTwenty).arg(relativeToDefault,0,'f',2).arg(granularityFeedback); } else if (relativeToDefault > 0.99) { - result = QString("the default distance %1").arg(granularityFeedback); + result = QString("20:20 or the default distance for average vision%1").arg(granularityFeedback); + } else if (relativeToDefault > 0.01) { + result = QString("20:%1 or %2 of default distance for average vision%3").arg(relativeToTwentyTwenty).arg(relativeToDefault,0,'f',3).arg(granularityFeedback); } else { - result = QString("%1 of default %2").arg(relativeToDefault,8,'f',3).arg(granularityFeedback); + result = QString("%2 of default distance for average vision%3").arg(relativeToDefault,0,'f',3).arg(granularityFeedback); } return result; } @@ -184,9 +257,14 @@ bool LODManager::shouldRenderMesh(float largestDimension, float distanceToCamera void LODManager::setOctreeSizeScale(float sizeScale) { _octreeSizeScale = sizeScale; + calculateAvatarLODDistanceMultiplier(); _shouldRenderTableNeedsRebuilding = true; } +void LODManager::calculateAvatarLODDistanceMultiplier() { + _avatarLODDistanceMultiplier = AVATAR_TO_ENTITY_RATIO / (_octreeSizeScale / DEFAULT_OCTREE_SIZE_SCALE); +} + void LODManager::setBoundaryLevelAdjust(int boundaryLevelAdjust) { _boundaryLevelAdjust = boundaryLevelAdjust; _shouldRenderTableNeedsRebuilding = true; @@ -194,21 +272,13 @@ void LODManager::setBoundaryLevelAdjust(int boundaryLevelAdjust) { void LODManager::loadSettings() { - setAutomaticAvatarLOD(automaticAvatarLOD.get()); - setAvatarLODDecreaseFPS(avatarLODDecreaseFPS.get()); - setAvatarLODIncreaseFPS(avatarLODIncreaseFPS.get()); - setAvatarLODDistanceMultiplier(avatarLODDistanceMultiplier.get()); - setBoundaryLevelAdjust(boundaryLevelAdjust.get()); - setOctreeSizeScale(octreeSizeScale.get()); + setDesktopLODDecreaseFPS(desktopLODDecreaseFPS.get()); + setHMDLODDecreaseFPS(hmdLODDecreaseFPS.get()); } void LODManager::saveSettings() { - automaticAvatarLOD.set(getAutomaticAvatarLOD()); - avatarLODDecreaseFPS.set(getAvatarLODDecreaseFPS()); - avatarLODIncreaseFPS.set(getAvatarLODIncreaseFPS()); - avatarLODDistanceMultiplier.set(getAvatarLODDistanceMultiplier()); - boundaryLevelAdjust.set(getBoundaryLevelAdjust()); - octreeSizeScale.set(getOctreeSizeScale()); + desktopLODDecreaseFPS.set(getDesktopLODDecreaseFPS()); + hmdLODDecreaseFPS.set(getHMDLODDecreaseFPS()); } diff --git a/interface/src/LODManager.h b/interface/src/LODManager.h index 61c24bf5af..77f156531a 100644 --- a/interface/src/LODManager.h +++ b/interface/src/LODManager.h @@ -17,12 +17,24 @@ #include #include -const float ADJUST_LOD_DOWN_FPS = 40.0; -const float ADJUST_LOD_UP_FPS = 55.0; -const float DEFAULT_ADJUST_AVATAR_LOD_DOWN_FPS = 30.0f; +const float DEFAULT_DESKTOP_LOD_DOWN_FPS = 30.0; +const float DEFAULT_HMD_LOD_DOWN_FPS = 60.0; +const float MAX_LIKELY_DESKTOP_FPS = 59.0; // this is essentially, V-synch - 1 fps +const float MAX_LIKELY_HMD_FPS = 74.0; // this is essentially, V-synch - 1 fps +const float INCREASE_LOD_GAP = 15.0f; -const quint64 ADJUST_LOD_DOWN_DELAY = 1000 * 1000 * 0.5; // Consider adjusting LOD down after half a second -const quint64 ADJUST_LOD_UP_DELAY = ADJUST_LOD_DOWN_DELAY * 2; +const float START_DELAY_WINDOW_IN_SECS = 3.0f; // wait at least this long after steady state/last upshift to consider downshifts +const float DOWN_SHIFT_WINDOW_IN_SECS = 0.5f; +const float UP_SHIFT_WINDOW_IN_SECS = 2.5f; + +const int ASSUMED_FPS = 60; +const quint64 START_SHIFT_ELPASED = USECS_PER_SECOND * START_DELAY_WINDOW_IN_SECS; +const quint64 DOWN_SHIFT_ELPASED = USECS_PER_SECOND * DOWN_SHIFT_WINDOW_IN_SECS; // Consider adjusting LOD down after half a second +const quint64 UP_SHIFT_ELPASED = USECS_PER_SECOND * UP_SHIFT_WINDOW_IN_SECS; + +const int START_DELAY_SAMPLES_OF_FRAMES = ASSUMED_FPS * START_DELAY_WINDOW_IN_SECS; +const int DOWN_SHIFT_SAMPLES_OF_FRAMES = ASSUMED_FPS * DOWN_SHIFT_WINDOW_IN_SECS; +const int UP_SHIFT_SAMPLES_OF_FRAMES = ASSUMED_FPS * UP_SHIFT_WINDOW_IN_SECS; const float ADJUST_LOD_DOWN_BY = 0.9f; const float ADJUST_LOD_UP_BY = 1.1f; @@ -33,12 +45,9 @@ const float ADJUST_LOD_UP_BY = 1.1f; const float ADJUST_LOD_MIN_SIZE_SCALE = 1.0f; const float ADJUST_LOD_MAX_SIZE_SCALE = DEFAULT_OCTREE_SIZE_SCALE; -const float MINIMUM_AVATAR_LOD_DISTANCE_MULTIPLIER = 0.1f; -const float MAXIMUM_AVATAR_LOD_DISTANCE_MULTIPLIER = 15.0f; -const float DEFAULT_AVATAR_LOD_DISTANCE_MULTIPLIER = 1.0f; - -const int ONE_SECOND_OF_FRAMES = 60; -const int FIVE_SECONDS_OF_FRAMES = 5 * ONE_SECOND_OF_FRAMES; +// The ratio of "visibility" of avatars to other content. A value larger than 1 will mean Avatars "cull" later than entities +// do. But both are still culled using the same angular size logic. +const float AVATAR_TO_ENTITY_RATIO = 2.0f; class LODManager : public QObject, public Dependency { @@ -46,14 +55,18 @@ class LODManager : public QObject, public Dependency { SINGLETON_DEPENDENCY public: - void setAutomaticAvatarLOD(bool automaticAvatarLOD) { _automaticAvatarLOD = automaticAvatarLOD; } - bool getAutomaticAvatarLOD() const { return _automaticAvatarLOD; } - void setAvatarLODDecreaseFPS(float avatarLODDecreaseFPS) { _avatarLODDecreaseFPS = avatarLODDecreaseFPS; } - float getAvatarLODDecreaseFPS() const { return _avatarLODDecreaseFPS; } - void setAvatarLODIncreaseFPS(float avatarLODIncreaseFPS) { _avatarLODIncreaseFPS = avatarLODIncreaseFPS; } - float getAvatarLODIncreaseFPS() const { return _avatarLODIncreaseFPS; } - void setAvatarLODDistanceMultiplier(float multiplier) { _avatarLODDistanceMultiplier = multiplier; } - float getAvatarLODDistanceMultiplier() const { return _avatarLODDistanceMultiplier; } + Q_INVOKABLE void setAutomaticLODAdjust(bool value) { _automaticLODAdjust = value; } + Q_INVOKABLE bool getAutomaticLODAdjust() const { return _automaticLODAdjust; } + + Q_INVOKABLE void setDesktopLODDecreaseFPS(float value) { _desktopLODDecreaseFPS = value; } + Q_INVOKABLE float getDesktopLODDecreaseFPS() const { return _desktopLODDecreaseFPS; } + Q_INVOKABLE float getDesktopLODIncreaseFPS() const { return glm::min(_desktopLODDecreaseFPS + INCREASE_LOD_GAP, MAX_LIKELY_DESKTOP_FPS); } + + Q_INVOKABLE void setHMDLODDecreaseFPS(float value) { _hmdLODDecreaseFPS = value; } + Q_INVOKABLE float getHMDLODDecreaseFPS() const { return _hmdLODDecreaseFPS; } + Q_INVOKABLE float getHMDLODIncreaseFPS() const { return glm::min(_hmdLODDecreaseFPS + INCREASE_LOD_GAP, MAX_LIKELY_HMD_FPS); } + + Q_INVOKABLE float getAvatarLODDistanceMultiplier() const { return _avatarLODDistanceMultiplier; } // User Tweakable LOD Items Q_INVOKABLE QString getLODFeedbackText(); @@ -63,35 +76,40 @@ public: Q_INVOKABLE void setBoundaryLevelAdjust(int boundaryLevelAdjust); Q_INVOKABLE int getBoundaryLevelAdjust() const { return _boundaryLevelAdjust; } - void autoAdjustLOD(float currentFPS); - Q_INVOKABLE void resetLODAdjust(); - Q_INVOKABLE float getFPSAverage() const { return _fpsAverage.getAverage(); } - Q_INVOKABLE float getFastFPSAverage() const { return _fastFPSAverage.getAverage(); } + Q_INVOKABLE float getLODDecreaseFPS(); + Q_INVOKABLE float getLODIncreaseFPS(); bool shouldRenderMesh(float largestDimension, float distanceToCamera); + void autoAdjustLOD(float currentFPS); void loadSettings(); void saveSettings(); + void resetLODAdjust(); signals: void LODIncreased(); void LODDecreased(); private: - LODManager() {} - - bool _automaticAvatarLOD = true; - float _avatarLODDecreaseFPS = DEFAULT_ADJUST_AVATAR_LOD_DOWN_FPS; - float _avatarLODIncreaseFPS = ADJUST_LOD_UP_FPS; - float _avatarLODDistanceMultiplier = DEFAULT_AVATAR_LOD_DISTANCE_MULTIPLIER; + LODManager(); + void calculateAvatarLODDistanceMultiplier(); + bool _automaticLODAdjust = true; + float _desktopLODDecreaseFPS = DEFAULT_DESKTOP_LOD_DOWN_FPS; + float _hmdLODDecreaseFPS = DEFAULT_HMD_LOD_DOWN_FPS; + + float _avatarLODDistanceMultiplier; float _octreeSizeScale = DEFAULT_OCTREE_SIZE_SCALE; int _boundaryLevelAdjust = 0; - quint64 _lastAdjust = 0; - quint64 _lastAvatarDetailDrop = 0; - SimpleMovingAverage _fpsAverage = FIVE_SECONDS_OF_FRAMES; - SimpleMovingAverage _fastFPSAverage = ONE_SECOND_OF_FRAMES; + quint64 _lastDownShift = 0; + quint64 _lastUpShift = 0; + quint64 _lastStable = 0; + bool _isDownshifting = false; // start out as if we're not downshifting + + SimpleMovingAverage _fpsAverageStartWindow = START_DELAY_SAMPLES_OF_FRAMES; + SimpleMovingAverage _fpsAverageDownWindow = DOWN_SHIFT_SAMPLES_OF_FRAMES; + SimpleMovingAverage _fpsAverageUpWindow = UP_SHIFT_SAMPLES_OF_FRAMES; bool _shouldRenderTableNeedsRebuilding = true; QMap _shouldRenderTable; diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 2823e8eb23..5867dd29e2 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -201,9 +201,7 @@ Menu::Menu() { addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::NamesAboveHeads, 0, true); addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::GlowWhenSpeaking, 0, true); addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::BlueSpeechSphere, 0, true); - addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::ObeyEnvironmentalGravity, Qt::SHIFT | Qt::Key_G, false, - avatar, SLOT(updateMotionBehavior())); - addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::StandOnNearbyFloors, 0, true, + addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::EnableCharacterController, 0, true, avatar, SLOT(updateMotionBehavior())); addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::ShiftHipsForIdleAnimations, 0, false, avatar, SLOT(updateMotionBehavior())); @@ -230,7 +228,6 @@ Menu::Menu() { addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Mirror, Qt::SHIFT | Qt::Key_H, true); addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::FullscreenMirror, Qt::Key_H, false, qApp, SLOT(cameraMenuChanged())); - addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::UserInterface, Qt::Key_Slash, true); addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::HMDTools, Qt::META | Qt::Key_H, false, @@ -259,14 +256,13 @@ Menu::Menu() { addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::TurnWithHead, 0, false); - addDisabledActionAndSeparator(viewMenu, "Stats"); - addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Stats, Qt::Key_Percent); + addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Stats, Qt::Key_Slash); addActionToQMenuAndActionHash(viewMenu, MenuOption::Log, Qt::CTRL | Qt::Key_L, qApp, SLOT(toggleLogDialog())); addActionToQMenuAndActionHash(viewMenu, MenuOption::BandwidthDetails, 0, dialogsManager.data(), SLOT(bandwidthDetails())); addActionToQMenuAndActionHash(viewMenu, MenuOption::OctreeStats, 0, dialogsManager.data(), SLOT(octreeStatsDetails())); - addActionToQMenuAndActionHash(viewMenu, MenuOption::EditEntitiesHelp, 0, qApp, SLOT(showEditEntitiesHelp())); + QMenu* developerMenu = addMenu("Developer"); @@ -276,7 +272,6 @@ Menu::Menu() { addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Entities, 0, true); addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::AmbientOcclusion); addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::DontFadeOnOctreeServerChanges); - addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::DisableAutoAdjustLOD); QMenu* ambientLightMenu = renderOptionsMenu->addMenu(MenuOption::RenderAmbientLight); QActionGroup* ambientLightGroup = new QActionGroup(ambientLightMenu); @@ -539,10 +534,12 @@ Menu::Menu() { statsRenderer.data(), SLOT(toggleShowInjectedStreams())); -#ifndef Q_OS_MAC QMenu* helpMenu = addMenu("Help"); - QAction* helpAction = helpMenu->addAction(MenuOption::AboutApp); - connect(helpAction, SIGNAL(triggered()), qApp, SLOT(aboutApp())); + addActionToQMenuAndActionHash(helpMenu, MenuOption::EditEntitiesHelp, 0, qApp, SLOT(showEditEntitiesHelp())); + +#ifndef Q_OS_MAC + QAction* aboutAction = helpMenu->addAction(MenuOption::AboutApp); + connect(aboutAction, SIGNAL(triggered()), qApp, SLOT(aboutApp())); #endif } diff --git a/interface/src/Menu.h b/interface/src/Menu.h index fc1347fa27..a4e644f20f 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -136,7 +136,6 @@ namespace MenuOption { const QString DecreaseAvatarSize = "Decrease Avatar Size"; const QString DeleteBookmark = "Delete Bookmark..."; const QString DisableActivityLogger = "Disable Activity Logger"; - const QString DisableAutoAdjustLOD = "Disable Automatically Adjusting LOD"; const QString DisableLightEntities = "Disable Light Entities"; const QString DisableNackPackets = "Disable NACK Packets"; const QString DiskCacheEditor = "Disk Cache Editor"; @@ -154,6 +153,7 @@ namespace MenuOption { const QString EchoServerAudio = "Echo Server Audio"; const QString EditEntitiesHelp = "Edit Entities Help..."; const QString Enable3DTVMode = "Enable 3DTV Mode"; + const QString EnableCharacterController = "Enable avatar collisions"; const QString EnableGlowEffect = "Enable Glow Effect (Warning: Poor Oculus Performance)"; const QString EnableVRMode = "Enable VR Mode"; const QString Entities = "Entities"; @@ -185,12 +185,9 @@ namespace MenuOption { const QString Mirror = "Mirror"; const QString MuteAudio = "Mute Microphone"; const QString MuteEnvironment = "Mute Environment"; - const QString NewVoxelCullingMode = "New Voxel Culling Mode"; const QString NoFaceTracking = "None"; - const QString ObeyEnvironmentalGravity = "Obey Environmental Gravity"; - const QString OctreeStats = "Voxel and Entity Statistics"; + const QString OctreeStats = "Entity Statistics"; const QString OffAxisProjection = "Off-Axis Projection"; - const QString OldVoxelCullingMode = "Old Voxel Culling Mode"; const QString OnlyDisplayTopTen = "Only Display Top Ten"; const QString Pair = "Pair"; const QString PipelineWarnings = "Log Render Pipeline Warnings"; @@ -234,13 +231,11 @@ namespace MenuOption { const QString ScriptEditor = "Script Editor..."; const QString ScriptedMotorControl = "Enable Scripted Motor Control"; const QString ShowBordersEntityNodes = "Show Entity Nodes"; - const QString ShowBordersVoxelNodes = "Show Voxel Nodes"; const QString ShowIKConstraints = "Show IK Constraints"; const QString SimpleShadows = "Simple"; const QString SixenseEnabled = "Enable Hydra Support"; const QString SixenseMouseInput = "Enable Sixense Mouse Input"; const QString SixenseLasers = "Enable Sixense UI Lasers"; - const QString StandOnNearbyFloors = "Stand on nearby floors"; const QString ShiftHipsForIdleAnimations = "Shift hips for idle animations"; const QString Stars = "Stars"; const QString Stats = "Stats"; @@ -252,7 +247,6 @@ namespace MenuOption { const QString TransmitterDrive = "Transmitter Drive"; const QString TurnWithHead = "Turn using Head"; const QString PackageModel = "Package Model"; - const QString UserInterface = "User Interface"; const QString Visage = "Visage"; const QString Wireframe = "Wireframe"; } diff --git a/interface/src/ModelPackager.cpp b/interface/src/ModelPackager.cpp index 49d4ae566f..f552d67a98 100644 --- a/interface/src/ModelPackager.cpp +++ b/interface/src/ModelPackager.cpp @@ -14,6 +14,8 @@ #include #include +#include + #include "ModelSelector.h" #include "ModelPropertiesDialog.h" diff --git a/interface/src/ModelPackager.h b/interface/src/ModelPackager.h index c62388f196..2c90395e56 100644 --- a/interface/src/ModelPackager.h +++ b/interface/src/ModelPackager.h @@ -15,8 +15,6 @@ #include #include -#include - #include "ui/ModelsBrowser.h" class ModelPackager : public QObject { diff --git a/interface/src/ModelPropertiesDialog.cpp b/interface/src/ModelPropertiesDialog.cpp index de98407a2a..81fe9ce7fd 100644 --- a/interface/src/ModelPropertiesDialog.cpp +++ b/interface/src/ModelPropertiesDialog.cpp @@ -19,6 +19,7 @@ #include #include +#include #include #include "ModelPropertiesDialog.h" diff --git a/interface/src/ModelPropertiesDialog.h b/interface/src/ModelPropertiesDialog.h index 65c5be6c21..5af4d173f1 100644 --- a/interface/src/ModelPropertiesDialog.h +++ b/interface/src/ModelPropertiesDialog.h @@ -23,19 +23,6 @@ class QComboBox; class QCheckBox; class QVBoxLayout; -static const QString NAME_FIELD = "name"; -static const QString FILENAME_FIELD = "filename"; -static const QString TEXDIR_FIELD = "texdir"; -static const QString LOD_FIELD = "lod"; -static const QString JOINT_INDEX_FIELD = "jointIndex"; -static const QString SCALE_FIELD = "scale"; -static const QString TRANSLATION_X_FIELD = "tx"; -static const QString TRANSLATION_Y_FIELD = "ty"; -static const QString TRANSLATION_Z_FIELD = "tz"; -static const QString JOINT_FIELD = "joint"; -static const QString FREE_JOINT_FIELD = "freeJoint"; -static const QString BLENDSHAPE_FIELD = "bs"; - /// A dialog that allows customization of various model properties. class ModelPropertiesDialog : public QDialog { Q_OBJECT diff --git a/interface/src/avatar/FaceModel.cpp b/interface/src/avatar/FaceModel.cpp index c80772ef49..722f998f86 100644 --- a/interface/src/avatar/FaceModel.cpp +++ b/interface/src/avatar/FaceModel.cpp @@ -87,7 +87,8 @@ void FaceModel::maybeUpdateEyeRotation(Model* model, const JointState& parentSta void FaceModel::updateJointState(int index) { JointState& state = _jointStates[index]; const FBXJoint& joint = state.getFBXJoint(); - if (joint.parentIndex != -1) { + // guard against out-of-bounds access to _jointStates + if (joint.parentIndex != -1 && joint.parentIndex >= 0 && joint.parentIndex < _jointStates.size()) { const JointState& parentState = _jointStates.at(joint.parentIndex); const FBXGeometry& geometry = _geometry->getFBXGeometry(); if (index == geometry.neckJointIndex) { diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index ab4989a651..26b777b35b 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,7 @@ MyAvatar::MyAvatar() : _scriptedMotorTimescale(DEFAULT_SCRIPTED_MOTOR_TIMESCALE), _scriptedMotorFrame(SCRIPTED_MOTOR_CAMERA_FRAME), _motionBehaviors(AVATAR_MOTION_DEFAULTS), + _characterController(this), _lookAtTargetAvatar(), _shouldRender(true), _billboardValid(false), @@ -100,6 +100,7 @@ MyAvatar::MyAvatar() : // connect to AddressManager signal for location jumps connect(DependencyManager::get().data(), &AddressManager::locationChangeRequired, this, &MyAvatar::goToLocation); + _characterController.setEnabled(true); } MyAvatar::~MyAvatar() { @@ -146,10 +147,6 @@ void MyAvatar::update(float deltaTime) { head->setAudioLoudness(audio->getLastInputLoudness()); head->setAudioAverageLoudness(audio->getAudioAverageInputLoudness()); - if (_motionBehaviors & AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY) { - setGravity(Application::getInstance()->getEnvironment()->getGravity(getPosition())); - } - simulate(deltaTime); if (_feetTouchFloor) { _skeletonModel.updateStandingFoot(); @@ -173,11 +170,7 @@ void MyAvatar::simulate(float deltaTime) { { PerformanceTimer perfTimer("transform"); updateOrientation(deltaTime); - if (isPhysicsEnabled()) { - updatePositionWithPhysics(deltaTime); - } else { - updatePosition(deltaTime); - } + updatePosition(deltaTime); } { @@ -483,26 +476,6 @@ void MyAvatar::loadLastRecording() { _player->loadRecording(_recorder->getRecording()); } -void MyAvatar::setLocalGravity(glm::vec3 gravity) { - _motionBehaviors |= AVATAR_MOTION_OBEY_LOCAL_GRAVITY; - // Environmental and Local gravities are incompatible. Since Local is being set here - // the environmental setting must be removed. - _motionBehaviors &= ~AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY; - setGravity(gravity); -} - -void MyAvatar::setGravity(const glm::vec3& gravity) { - _gravity = gravity; - - // use the gravity to determine the new world up direction, if possible - float gravityLength = glm::length(gravity); - if (gravityLength > EPSILON) { - _worldUpDirection = _gravity / -gravityLength; - } - // NOTE: the else case here it to leave _worldUpDirection unchanged - // so it continues to point opposite to the previous gravity setting. -} - AnimationHandlePointer MyAvatar::addAnimationHandle() { AnimationHandlePointer handle = _skeletonModel.createAnimationHandle(); _animationHandles.append(handle); @@ -954,15 +927,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 { @@ -1257,128 +1230,38 @@ glm::vec3 MyAvatar::applyScriptedMotor(float deltaTime, const glm::vec3& localVe return localVelocity + motorEfficiency * deltaVelocity; } -const float NEARBY_FLOOR_THRESHOLD = 5.0f; - void MyAvatar::updatePosition(float deltaTime) { - - // check for floor by casting a ray straight down from avatar's position - float heightAboveFloor = FLT_MAX; - bool hasFloor = false; - const CapsuleShape& boundingShape = _skeletonModel.getBoundingShape(); - const float maxFloorDistance = boundingShape.getBoundingRadius() * NEARBY_FLOOR_THRESHOLD; - - RayIntersectionInfo intersection; - // NOTE: avatar is center of PhysicsSimulation, so rayStart is the origin for the purposes of the raycast - intersection._rayStart = glm::vec3(0.0f); - intersection._rayDirection = - _worldUpDirection; - intersection._rayLength = 4.0f * boundingShape.getBoundingRadius(); - - // velocity is initialized to the measured _velocity but will be modified by friction, external thrust, etc - glm::vec3 velocity = _velocity; - - bool pushingUp = (_driveKeys[UP] - _driveKeys[DOWN] > 0.0f) || _scriptedMotorVelocity.y > 0.0f; - if (_motionBehaviors & AVATAR_MOTION_STAND_ON_NEARBY_FLOORS) { - const float MAX_SPEED_UNDER_GRAVITY = 2.0f * _scale * MAX_WALKING_SPEED; - if (pushingUp || glm::length2(velocity) > MAX_SPEED_UNDER_GRAVITY * MAX_SPEED_UNDER_GRAVITY) { - // we're pushing up or moving quickly, so disable gravity - setLocalGravity(glm::vec3(0.0f)); - hasFloor = false; - } else { - if (heightAboveFloor > maxFloorDistance) { - // disable local gravity when floor is too far away - setLocalGravity(glm::vec3(0.0f)); - hasFloor = false; - } else { - // enable gravity - setLocalGravity(-_worldUpDirection); - } - } - } - - bool zeroDownwardVelocity = false; - bool gravityEnabled = (glm::length2(_gravity) > EPSILON); - if (gravityEnabled) { - const float SLOP = 0.002f; - if (heightAboveFloor < SLOP) { - if (heightAboveFloor < 0.0) { - // Gravity is in effect so we assume that the avatar is colliding against the world and we need - // to lift avatar out of floor, but we don't want to do it too fast (keep it smooth). - float distanceToLift = glm::min(-heightAboveFloor, MAX_WALKING_SPEED * deltaTime); - - // We don't use applyPositionDelta() for this lift distance because we don't want the avatar - // to come flying out of the floor. Instead we update position directly, and set a boolean - // that will remind us later to zero any downward component of the velocity. - _position += distanceToLift * _worldUpDirection; - } - zeroDownwardVelocity = true; - } - if (!zeroDownwardVelocity) { - velocity += (deltaTime * GRAVITY_EARTH) * _gravity; - } - } - - // rotate velocity into camera frame - glm::quat rotation = getHead()->getCameraOrientation(); - glm::vec3 localVelocity = glm::inverse(rotation) * velocity; - - // apply motors in camera frame - glm::vec3 newLocalVelocity = applyKeyboardMotor(deltaTime, localVelocity, hasFloor); - newLocalVelocity = applyScriptedMotor(deltaTime, newLocalVelocity); - - // rotate back into world-frame - velocity = rotation * newLocalVelocity; - - // apply thrust - velocity += _thrust * deltaTime; - _thrust = glm::vec3(0.0f); - - // remove downward velocity so we don't push into floor - if (zeroDownwardVelocity) { - float verticalSpeed = glm::dot(velocity, _worldUpDirection); - if (verticalSpeed < 0.0f || !pushingUp) { - velocity -= verticalSpeed * _worldUpDirection; - } - } - - // cap avatar speed - float speed = glm::length(velocity); - if (speed > MAX_AVATAR_SPEED) { - velocity *= MAX_AVATAR_SPEED / speed; - speed = MAX_AVATAR_SPEED; - } - - // update position - if (speed > MIN_AVATAR_SPEED) { - applyPositionDelta(deltaTime * velocity); - } - - // update _moving flag based on speed - const float MOVING_SPEED_THRESHOLD = 0.01f; - _moving = speed > MOVING_SPEED_THRESHOLD; - - measureMotionDerivatives(deltaTime); -} - -void MyAvatar::updatePositionWithPhysics(float deltaTime) { // rotate velocity into camera frame glm::quat rotation = getHead()->getCameraOrientation(); glm::vec3 localVelocity = glm::inverse(rotation) * _velocity; - bool hasFloor = false; - glm::vec3 newLocalVelocity = applyKeyboardMotor(deltaTime, localVelocity, hasFloor); + bool isOnGround = _characterController.onGround(); + glm::vec3 newLocalVelocity = applyKeyboardMotor(deltaTime, localVelocity, isOnGround); newLocalVelocity = applyScriptedMotor(deltaTime, newLocalVelocity); - // cap avatar speed - float speed = glm::length(newLocalVelocity); - if (speed > MAX_WALKING_SPEED) { - newLocalVelocity *= MAX_WALKING_SPEED / speed; - } - // rotate back into world-frame _velocity = rotation * newLocalVelocity; _velocity += _thrust * deltaTime; _thrust = glm::vec3(0.0f); + + // cap avatar speed + float speed = glm::length(_velocity); + if (speed > MAX_AVATAR_SPEED) { + _velocity *= MAX_AVATAR_SPEED / speed; + speed = MAX_AVATAR_SPEED; + } + + if (speed > MIN_AVATAR_SPEED && !_characterController.isEnabled()) { + // update position ourselves + applyPositionDelta(deltaTime * _velocity); + measureMotionDerivatives(deltaTime); + } // else physics will move avatar later + + // update _moving flag based on speed + const float MOVING_SPEED_THRESHOLD = 0.01f; + _moving = speed > MOVING_SPEED_THRESHOLD; + } void MyAvatar::updateCollisionSound(const glm::vec3 &penetration, float deltaTime, float frequency) { @@ -1495,23 +1378,6 @@ void MyAvatar::goToLocation(const glm::vec3& newPosition, void MyAvatar::updateMotionBehavior() { Menu* menu = Menu::getInstance(); - if (menu->isOptionChecked(MenuOption::ObeyEnvironmentalGravity)) { - _motionBehaviors |= AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY; - // Environmental and Local gravities are incompatible. Environmental setting trumps local. - _motionBehaviors &= ~AVATAR_MOTION_OBEY_LOCAL_GRAVITY; - } else { - _motionBehaviors &= ~AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY; - } - if (! (_motionBehaviors & (AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY | AVATAR_MOTION_OBEY_LOCAL_GRAVITY))) { - setGravity(glm::vec3(0.0f)); - } - if (menu->isOptionChecked(MenuOption::StandOnNearbyFloors)) { - _motionBehaviors |= AVATAR_MOTION_STAND_ON_NEARBY_FLOORS; - // standing on floors requires collision with voxels - // TODO: determine what to do with this now that voxels are gone - } else { - _motionBehaviors &= ~AVATAR_MOTION_STAND_ON_NEARBY_FLOORS; - } if (menu->isOptionChecked(MenuOption::KeyboardMotorControl)) { _motionBehaviors |= AVATAR_MOTION_KEYBOARD_MOTOR_ENABLED; } else { @@ -1522,6 +1388,7 @@ void MyAvatar::updateMotionBehavior() { } else { _motionBehaviors &= ~AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED; } + _characterController.setEnabled(menu->isOptionChecked(MenuOption::EnableCharacterController)); _feetTouchFloor = menu->isOptionChecked(MenuOption::ShiftHipsForIdleAnimations); } diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 08c0228f1e..99c0bdb5df 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" @@ -24,7 +25,7 @@ class MyAvatar : public Avatar { Q_PROPERTY(glm::vec3 motorVelocity READ getScriptedMotorVelocity WRITE setScriptedMotorVelocity) Q_PROPERTY(float motorTimescale READ getScriptedMotorTimescale WRITE setScriptedMotorTimescale) Q_PROPERTY(QString motorReferenceFrame READ getScriptedMotorFrame WRITE setScriptedMotorFrame) - Q_PROPERTY(glm::vec3 gravity READ getGravity WRITE setLocalGravity) + //TODO: make gravity feature work Q_PROPERTY(glm::vec3 gravity READ getGravity WRITE setGravity) public: MyAvatar(); @@ -43,13 +44,11 @@ public: // setters void setLeanScale(float scale) { _leanScale = scale; } - void setLocalGravity(glm::vec3 gravity); void setShouldRenderLocally(bool shouldRender) { _shouldRender = shouldRender; } void setRealWorldFieldOfView(float realWorldFov) { _realWorldFieldOfView.set(realWorldFov); } // getters float getLeanScale() const { return _leanScale; } - glm::vec3 getGravity() const { return _gravity; } Q_INVOKABLE glm::vec3 getDefaultEyePosition() const; bool getShouldRenderLocally() const { return _shouldRender; } float getRealWorldFieldOfView() { return _realWorldFieldOfView.get(); } @@ -88,7 +87,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 +121,8 @@ public: virtual glm::vec3 getSkeletonPosition() const; void updateLocalAABox(); + CharacterController* getCharacterController() { return &_characterController; } + void updateCharacterController(); void clearJointAnimationPriorities(); @@ -186,7 +187,6 @@ private: float _turningKeyPressTime; glm::vec3 _gravity; - bool _shouldJump; float _driveKeys[MAX_DRIVE_KEYS]; bool _wasPushing; bool _isPushing; @@ -202,6 +202,8 @@ private: int _scriptedMotorFrame; quint32 _motionBehaviors; + CharacterController _characterController; + QWeakPointer _lookAtTargetAvatar; glm::vec3 _targetAvatarPosition; bool _shouldRender; @@ -224,10 +226,8 @@ private: glm::vec3 applyKeyboardMotor(float deltaTime, const glm::vec3& velocity, bool walkingOnFloor); glm::vec3 applyScriptedMotor(float deltaTime, const glm::vec3& velocity); void updatePosition(float deltaTime); - void updatePositionWithPhysics(float deltaTime); void updateCollisionSound(const glm::vec3& penetration, float deltaTime, float frequency); void maybeUpdateBillboard(); - void setGravity(const glm::vec3& gravity); }; #endif // hifi_MyAvatar_h 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/devices/TV3DManager.cpp b/interface/src/devices/TV3DManager.cpp index b5f57301f1..f082c6de47 100644 --- a/interface/src/devices/TV3DManager.cpp +++ b/interface/src/devices/TV3DManager.cpp @@ -104,7 +104,6 @@ void TV3DManager::display(Camera& whichCamera) { // We only need to render the overlays to a texture once, then we just render the texture as a quad // PrioVR will only work if renderOverlay is called, calibration is connected to Application::renderingOverlay() applicationOverlay.renderOverlay(true); - const bool displayOverlays = Menu::getInstance()->isOptionChecked(MenuOption::UserInterface); DependencyManager::get()->prepare(); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); @@ -135,9 +134,7 @@ void TV3DManager::display(Camera& whichCamera) { eyeCamera.setEyeOffsetPosition(glm::vec3(-_activeEye->modelTranslation,0,0)); Application::getInstance()->displaySide(eyeCamera, false, RenderArgs::MONO); - if (displayOverlays) { - applicationOverlay.displayOverlayTexture3DTV(whichCamera, _aspect, fov); - } + applicationOverlay.displayOverlayTexture3DTV(whichCamera, _aspect, fov); _activeEye = NULL; } glPopMatrix(); @@ -166,9 +163,7 @@ void TV3DManager::display(Camera& whichCamera) { eyeCamera.setEyeOffsetPosition(glm::vec3(-_activeEye->modelTranslation,0,0)); Application::getInstance()->displaySide(eyeCamera, false, RenderArgs::MONO); - if (displayOverlays) { - applicationOverlay.displayOverlayTexture3DTV(whichCamera, _aspect, fov); - } + applicationOverlay.displayOverlayTexture3DTV(whichCamera, _aspect, fov); _activeEye = NULL; } glPopMatrix(); diff --git a/interface/src/scripting/ControllerScriptingInterface.cpp b/interface/src/scripting/ControllerScriptingInterface.cpp index 35c24346d2..7d6012c880 100644 --- a/interface/src/scripting/ControllerScriptingInterface.cpp +++ b/interface/src/scripting/ControllerScriptingInterface.cpp @@ -353,7 +353,7 @@ void InputController::update() { // TODO for now the InputController is only supporting a JointTracker from a MotionTracker MotionTracker* motionTracker = dynamic_cast< MotionTracker*> (DeviceTracker::getDevice(_deviceTrackerId)); if (motionTracker) { - if (_subTrackerId < motionTracker->numJointTrackers()) { + if ((int)_subTrackerId < motionTracker->numJointTrackers()) { const MotionTracker::JointTracker* joint = motionTracker->getJointTracker(_subTrackerId); if (joint->isActive()) { 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/interface/src/scripting/WindowScriptingInterface.cpp b/interface/src/scripting/WindowScriptingInterface.cpp index 7f4b5ddf45..a5b8128d1e 100644 --- a/interface/src/scripting/WindowScriptingInterface.cpp +++ b/interface/src/scripting/WindowScriptingInterface.cpp @@ -33,6 +33,7 @@ WindowScriptingInterface::WindowScriptingInterface() : const DomainHandler& domainHandler = DependencyManager::get()->getDomainHandler(); connect(&domainHandler, &DomainHandler::hostnameChanged, this, &WindowScriptingInterface::domainChanged); connect(Application::getInstance(), &Application::svoImportRequested, this, &WindowScriptingInterface::svoImportRequested); + connect(Application::getInstance(), &Application::domainConnectionRefused, this, &WindowScriptingInterface::domainConnectionRefused); } WebWindowClass* WindowScriptingInterface::doCreateWebWindow(const QString& title, const QString& url, int width, int height, bool isToolWindow) { diff --git a/interface/src/scripting/WindowScriptingInterface.h b/interface/src/scripting/WindowScriptingInterface.h index 6a812f14e3..9bc8a834bd 100644 --- a/interface/src/scripting/WindowScriptingInterface.h +++ b/interface/src/scripting/WindowScriptingInterface.h @@ -62,6 +62,7 @@ signals: void inlineButtonClicked(const QString& name); void nonBlockingFormClosed(); void svoImportRequested(const QString& url); + void domainConnectionRefused(const QString& reason); private slots: QScriptValue showAlert(const QString& message); diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index 0c5941366f..633eafc202 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -139,6 +139,7 @@ ApplicationOverlay::ApplicationOverlay() : _magnifier(true), _alpha(1.0f), _oculusUIRadius(1.0f), + _trailingAudioLoudness(0.0f), _crosshairTexture(0), _previousBorderWidth(-1), _previousBorderHeight(-1), @@ -176,18 +177,7 @@ void ApplicationOverlay::renderOverlay(bool renderToTexture) { _textureAspectRatio = (float)glCanvas->getDeviceWidth() / (float)glCanvas->getDeviceHeight(); //Handle fading and deactivation/activation of UI - if (Menu::getInstance()->isOptionChecked(MenuOption::UserInterface)) { - _alpha += FADE_SPEED; - if (_alpha > 1.0f) { - _alpha = 1.0f; - } - } else { - _alpha -= FADE_SPEED; - if (_alpha <= 0.0f) { - _alpha = 0.0f; - } - } - + // Render 2D overlay glMatrixMode(GL_PROJECTION); glDisable(GL_DEPTH_TEST); diff --git a/interface/src/ui/ApplicationOverlay.h b/interface/src/ui/ApplicationOverlay.h index 64161e4a31..58b79adcda 100644 --- a/interface/src/ui/ApplicationOverlay.h +++ b/interface/src/ui/ApplicationOverlay.h @@ -114,7 +114,7 @@ private: quint64 _lastMouseMove; bool _magnifier; - float _alpha; + float _alpha = 1.0f; float _oculusUIRadius; float _trailingAudioLoudness; diff --git a/interface/src/ui/LodToolsDialog.cpp b/interface/src/ui/LodToolsDialog.cpp index 277d634735..378a1391f4 100644 --- a/interface/src/ui/LodToolsDialog.cpp +++ b/interface/src/ui/LodToolsDialog.cpp @@ -35,9 +35,24 @@ LodToolsDialog::LodToolsDialog(QWidget* parent) : // Create layouter QFormLayout* form = new QFormLayout(this); + // Create a label with feedback... + _feedback = new QLabel(this); + QPalette palette = _feedback->palette(); + const unsigned redish = 0xfff00000; + palette.setColor(QPalette::WindowText, QColor::fromRgb(redish)); + _feedback->setPalette(palette); + _feedback->setText(lodManager->getLODFeedbackText()); + const int FEEDBACK_WIDTH = 350; + _feedback->setFixedWidth(FEEDBACK_WIDTH); + form->addRow("You can see... ", _feedback); + + form->addRow("Manually Adjust Level of Detail:", _manualLODAdjust = new QCheckBox(this)); + _manualLODAdjust->setChecked(!lodManager->getAutomaticLODAdjust()); + connect(_manualLODAdjust, SIGNAL(toggled(bool)), SLOT(updateAutomaticLODAdjust())); + _lodSize = new QSlider(Qt::Horizontal, this); const int MAX_LOD_SIZE = MAX_LOD_SIZE_MULTIPLIER; - const int MIN_LOD_SIZE = 0; + const int MIN_LOD_SIZE = ADJUST_LOD_MIN_SIZE_SCALE; const int STEP_LOD_SIZE = 1; const int PAGE_STEP_LOD_SIZE = 100; const int SLIDER_WIDTH = 300; @@ -50,55 +65,8 @@ LodToolsDialog::LodToolsDialog(QWidget* parent) : _lodSize->setPageStep(PAGE_STEP_LOD_SIZE); int sliderValue = lodManager->getOctreeSizeScale() / TREE_SCALE; _lodSize->setValue(sliderValue); - form->addRow("LOD Size Scale:", _lodSize); + form->addRow("Level of Detail:", _lodSize); connect(_lodSize,SIGNAL(valueChanged(int)),this,SLOT(sizeScaleValueChanged(int))); - - _boundaryLevelAdjust = new QSlider(Qt::Horizontal, this); - const int MAX_ADJUST = 10; - const int MIN_ADJUST = 0; - const int STEP_ADJUST = 1; - _boundaryLevelAdjust->setMaximum(MAX_ADJUST); - _boundaryLevelAdjust->setMinimum(MIN_ADJUST); - _boundaryLevelAdjust->setSingleStep(STEP_ADJUST); - _boundaryLevelAdjust->setTickInterval(STEP_ADJUST); - _boundaryLevelAdjust->setTickPosition(QSlider::TicksBelow); - _boundaryLevelAdjust->setFixedWidth(SLIDER_WIDTH); - sliderValue = lodManager->getBoundaryLevelAdjust(); - _boundaryLevelAdjust->setValue(sliderValue); - form->addRow("Boundary Level Adjust:", _boundaryLevelAdjust); - connect(_boundaryLevelAdjust,SIGNAL(valueChanged(int)),this,SLOT(boundaryLevelValueChanged(int))); - - // Create a label with feedback... - _feedback = new QLabel(this); - QPalette palette = _feedback->palette(); - const unsigned redish = 0xfff00000; - palette.setColor(QPalette::WindowText, QColor::fromRgb(redish)); - _feedback->setPalette(palette); - _feedback->setText(lodManager->getLODFeedbackText()); - const int FEEDBACK_WIDTH = 350; - _feedback->setFixedWidth(FEEDBACK_WIDTH); - form->addRow("You can see... ", _feedback); - - form->addRow("Automatic Avatar LOD Adjustment:", _automaticAvatarLOD = new QCheckBox(this)); - _automaticAvatarLOD->setChecked(lodManager->getAutomaticAvatarLOD()); - connect(_automaticAvatarLOD, SIGNAL(toggled(bool)), SLOT(updateAvatarLODControls())); - - form->addRow("Decrease Avatar LOD Below FPS:", _avatarLODDecreaseFPS = new QDoubleSpinBox(this)); - _avatarLODDecreaseFPS->setValue(lodManager->getAvatarLODDecreaseFPS()); - _avatarLODDecreaseFPS->setDecimals(0); - connect(_avatarLODDecreaseFPS, SIGNAL(valueChanged(double)), SLOT(updateAvatarLODValues())); - - form->addRow("Increase Avatar LOD Above FPS:", _avatarLODIncreaseFPS = new QDoubleSpinBox(this)); - _avatarLODIncreaseFPS->setValue(lodManager->getAvatarLODIncreaseFPS()); - _avatarLODIncreaseFPS->setDecimals(0); - connect(_avatarLODIncreaseFPS, SIGNAL(valueChanged(double)), SLOT(updateAvatarLODValues())); - - form->addRow("Avatar LOD:", _avatarLOD = new QDoubleSpinBox(this)); - _avatarLOD->setDecimals(3); - _avatarLOD->setRange(1.0 / MAXIMUM_AVATAR_LOD_DISTANCE_MULTIPLIER, 1.0 / MINIMUM_AVATAR_LOD_DISTANCE_MULTIPLIER); - _avatarLOD->setSingleStep(0.001); - _avatarLOD->setValue(1.0 / lodManager->getAvatarLODDistanceMultiplier()); - connect(_avatarLOD, SIGNAL(valueChanged(double)), SLOT(updateAvatarLODValues())); // Add a button to reset QPushButton* resetButton = new QPushButton("Reset", this); @@ -107,49 +75,19 @@ LodToolsDialog::LodToolsDialog(QWidget* parent) : this->QDialog::setLayout(form); - updateAvatarLODControls(); + updateAutomaticLODAdjust(); } void LodToolsDialog::reloadSliders() { auto lodManager = DependencyManager::get(); _lodSize->setValue(lodManager->getOctreeSizeScale() / TREE_SCALE); - _boundaryLevelAdjust->setValue(lodManager->getBoundaryLevelAdjust()); _feedback->setText(lodManager->getLODFeedbackText()); } -void LodToolsDialog::updateAvatarLODControls() { - QFormLayout* form = static_cast(layout()); - +void LodToolsDialog::updateAutomaticLODAdjust() { auto lodManager = DependencyManager::get(); - lodManager->setAutomaticAvatarLOD(_automaticAvatarLOD->isChecked()); - - _avatarLODDecreaseFPS->setVisible(_automaticAvatarLOD->isChecked()); - form->labelForField(_avatarLODDecreaseFPS)->setVisible(_automaticAvatarLOD->isChecked()); - - _avatarLODIncreaseFPS->setVisible(_automaticAvatarLOD->isChecked()); - form->labelForField(_avatarLODIncreaseFPS)->setVisible(_automaticAvatarLOD->isChecked()); - - _avatarLOD->setVisible(!_automaticAvatarLOD->isChecked()); - form->labelForField(_avatarLOD)->setVisible(!_automaticAvatarLOD->isChecked()); - - if (!_automaticAvatarLOD->isChecked()) { - _avatarLOD->setValue(1.0 / lodManager->getAvatarLODDistanceMultiplier()); - } - - if (isVisible()) { - adjustSize(); - } -} - -void LodToolsDialog::updateAvatarLODValues() { - auto lodManager = DependencyManager::get(); - if (_automaticAvatarLOD->isChecked()) { - lodManager->setAvatarLODDecreaseFPS(_avatarLODDecreaseFPS->value()); - lodManager->setAvatarLODIncreaseFPS(_avatarLODIncreaseFPS->value()); - - } else { - lodManager->setAvatarLODDistanceMultiplier(1.0 / _avatarLOD->value()); - } + lodManager->setAutomaticLODAdjust(!_manualLODAdjust->isChecked()); + _lodSize->setEnabled(_manualLODAdjust->isChecked()); } void LodToolsDialog::sizeScaleValueChanged(int value) { @@ -160,20 +98,13 @@ void LodToolsDialog::sizeScaleValueChanged(int value) { _feedback->setText(lodManager->getLODFeedbackText()); } -void LodToolsDialog::boundaryLevelValueChanged(int value) { - auto lodManager = DependencyManager::get(); - lodManager->setBoundaryLevelAdjust(value); - _feedback->setText(lodManager->getLODFeedbackText()); -} - void LodToolsDialog::resetClicked(bool checked) { + int sliderValue = DEFAULT_OCTREE_SIZE_SCALE / TREE_SCALE; - //sizeScaleValueChanged(sliderValue); _lodSize->setValue(sliderValue); - _boundaryLevelAdjust->setValue(0); - _automaticAvatarLOD->setChecked(true); - _avatarLODDecreaseFPS->setValue(DEFAULT_ADJUST_AVATAR_LOD_DOWN_FPS); - _avatarLODIncreaseFPS->setValue(ADJUST_LOD_UP_FPS); + _manualLODAdjust->setChecked(false); + + updateAutomaticLODAdjust(); // tell our LOD manager about the reset } void LodToolsDialog::reject() { @@ -184,6 +115,15 @@ void LodToolsDialog::reject() { void LodToolsDialog::closeEvent(QCloseEvent* event) { this->QDialog::closeEvent(event); emit closed(); + auto lodManager = DependencyManager::get(); + + // always revert back to automatic LOD adjustment when closed + lodManager->setAutomaticLODAdjust(true); + + // if the user adjusted the LOD above "normal" then always revert back to default + if (lodManager->getOctreeSizeScale() > DEFAULT_OCTREE_SIZE_SCALE) { + lodManager->setOctreeSizeScale(DEFAULT_OCTREE_SIZE_SCALE); + } } diff --git a/interface/src/ui/LodToolsDialog.h b/interface/src/ui/LodToolsDialog.h index 772027790c..e5a2dae836 100644 --- a/interface/src/ui/LodToolsDialog.h +++ b/interface/src/ui/LodToolsDialog.h @@ -31,11 +31,9 @@ signals: public slots: void reject(); void sizeScaleValueChanged(int value); - void boundaryLevelValueChanged(int value); void resetClicked(bool checked); void reloadSliders(); - void updateAvatarLODControls(); - void updateAvatarLODValues(); + void updateAutomaticLODAdjust(); protected: @@ -44,11 +42,13 @@ protected: private: QSlider* _lodSize; - QSlider* _boundaryLevelAdjust; - QCheckBox* _automaticAvatarLOD; - QDoubleSpinBox* _avatarLODDecreaseFPS; - QDoubleSpinBox* _avatarLODIncreaseFPS; - QDoubleSpinBox* _avatarLOD; + + QCheckBox* _manualLODAdjust; + + QDoubleSpinBox* _desktopLODDecreaseFPS; + + QDoubleSpinBox* _hmdLODDecreaseFPS; + QLabel* _feedback; }; diff --git a/interface/src/ui/LoginDialog.cpp b/interface/src/ui/LoginDialog.cpp index 004f863901..4e7a4a7dc9 100644 --- a/interface/src/ui/LoginDialog.cpp +++ b/interface/src/ui/LoginDialog.cpp @@ -23,7 +23,7 @@ #include "LoginDialog.h" #include "UIUtil.h" -const QString FORGOT_PASSWORD_URL = "https://metaverse.highfidelity.io/users/password/new"; +const QString FORGOT_PASSWORD_URL = "https://metaverse.highfidelity.com/users/password/new"; LoginDialog::LoginDialog(QWidget* parent) : FramelessDialog(parent, 0, FramelessDialog::POSITION_TOP), diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index fa63079290..a07de371a2 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -19,6 +19,7 @@ #include "Application.h" #include "MainWindow.h" +#include "LODManager.h" #include "Menu.h" #include "ModelsBrowser.h" #include "PreferencesDialog.h" @@ -174,6 +175,10 @@ void PreferencesDialog::loadPreferences() { ui.sixenseReticleMoveSpeedSpin->setValue(sixense.getReticleMoveSpeed()); ui.invertSixenseButtonsCheckBox->setChecked(sixense.getInvertButtons()); + // LOD items + auto lodManager = DependencyManager::get(); + ui.desktopMinimumFPSSpin->setValue(lodManager->getDesktopLODDecreaseFPS()); + ui.hmdMinimumFPSSpin->setValue(lodManager->getHMDLODDecreaseFPS()); } void PreferencesDialog::savePreferences() { @@ -275,4 +280,9 @@ void PreferencesDialog::savePreferences() { audio->setOutputStarveDetectionPeriod(ui.outputStarveDetectionPeriodSpinner->value()); Application::getInstance()->resizeGL(glCanvas->width(), glCanvas->height()); + + // LOD items + auto lodManager = DependencyManager::get(); + lodManager->setDesktopLODDecreaseFPS(ui.desktopMinimumFPSSpin->value()); + lodManager->setHMDLODDecreaseFPS(ui.hmdMinimumFPSSpin->value()); } diff --git a/interface/ui/loginDialog.ui b/interface/ui/loginDialog.ui index c986db7f50..58ff353a16 100644 --- a/interface/ui/loginDialog.ui +++ b/interface/ui/loginDialog.ui @@ -136,7 +136,7 @@ <style type="text/css"> a { text-decoration: none; color: #267077;} </style> -Invalid username or password. <a href="https://metaverse.highfidelity.io/password/new">Recover?</a> +Invalid username or password. <a href="https://metaverse.highfidelity.com/password/new">Recover?</a> Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter @@ -458,7 +458,7 @@ border-radius: 4px; padding-top: 1px; <style type="text/css"> a { text-decoration: none; color: #267077;} </style> -<a href="https://metaverse.highfidelity.io/password/new">Recover password?</a> +<a href="https://metaverse.highfidelity.com/password/new">Recover password?</a> true diff --git a/interface/ui/preferencesDialog.ui b/interface/ui/preferencesDialog.ui index 13894a2592..d295d094c2 100644 --- a/interface/ui/preferencesDialog.ui +++ b/interface/ui/preferencesDialog.ui @@ -701,6 +701,219 @@ + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 20 + + + + + + + + + + Arial + 18 + 75 + true + + + + color:#29967e + + + Level of Detail Tuning + + + Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft + + + + + + + + 0 + + + 7 + + + 0 + + + 7 + + + + + + Arial + + + + + + + Minimum Desktop FPS + + + 0 + + + + + + + + + Arial + + + + Qt::Horizontal + + + + 0 + 0 + + + + + + + + + 100 + 0 + + + + + 95 + 36 + + + + + Arial + + + + 0 + + + 120 + + + + + + + + + + 0 + + + 7 + + + 0 + + + 7 + + + + + + Arial + + + + + + + Minimum HMD FPS + + + 0 + + + + + + + + + Arial + + + + Qt::Horizontal + + + + 0 + 0 + + + + + + + + + 100 + 0 + + + + + 95 + 36 + + + + + Arial + + + + 0 + + + 120 + + + + + + + @@ -717,6 +930,7 @@ + @@ -738,6 +952,7 @@ + @@ -820,6 +1035,9 @@ + + + diff --git a/libraries/audio/src/AudioBuffer.h b/libraries/audio/src/AudioBuffer.h index 4849289743..d2f7c50c91 100644 --- a/libraries/audio/src/AudioBuffer.h +++ b/libraries/audio/src/AudioBuffer.h @@ -88,9 +88,9 @@ template< typename T > void AudioFrameBuffer< T >::deallocateFrames() { if (_frameBuffer) { for (uint32_t i = 0; i < _channelCountMax; ++i) { - delete _frameBuffer[i]; + delete[] _frameBuffer[i]; } - delete _frameBuffer; + delete[] _frameBuffer; } _frameBuffer = NULL; } diff --git a/libraries/audio/src/AudioFilterBank.h b/libraries/audio/src/AudioFilterBank.h index 723fa6b270..7b3b45f56b 100644 --- a/libraries/audio/src/AudioFilterBank.h +++ b/libraries/audio/src/AudioFilterBank.h @@ -88,7 +88,7 @@ public: } void loadProfile(int profileIndex) { - if (profileIndex >= 0 && profileIndex < _profileCount) { + if (profileIndex >= 0 && profileIndex < (int)_profileCount) { for (uint32_t i = 0; i < _filterCount; ++i) { FilterParameter p = _profiles[profileIndex][i]; diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 28123124a0..ad90c88aaa 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -61,21 +61,13 @@ typedef unsigned long long quint64; const quint32 AVATAR_MOTION_KEYBOARD_MOTOR_ENABLED = 1U << 0; const quint32 AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED = 1U << 1; -const quint32 AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY = 1U << 2; -const quint32 AVATAR_MOTION_OBEY_LOCAL_GRAVITY = 1U << 3; -const quint32 AVATAR_MOTION_STAND_ON_NEARBY_FLOORS = 1U << 4; - const quint32 AVATAR_MOTION_DEFAULTS = AVATAR_MOTION_KEYBOARD_MOTOR_ENABLED | - AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED | - AVATAR_MOTION_STAND_ON_NEARBY_FLOORS; + AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED; // these bits will be expanded as features are exposed const quint32 AVATAR_MOTION_SCRIPTABLE_BITS = - AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED | - AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY | - AVATAR_MOTION_OBEY_LOCAL_GRAVITY | - AVATAR_MOTION_STAND_ON_NEARBY_FLOORS; + AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED; // Bitset of state flags - we store the key state, hand state, faceshift, chat circling, and existance of @@ -300,16 +292,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; } @@ -375,8 +357,8 @@ protected: HeadData* _headData; HandData* _handData; - QUrl _faceModelURL = DEFAULT_HEAD_MODEL_URL; - QUrl _skeletonModelURL = DEFAULT_BODY_MODEL_URL; + QUrl _faceModelURL; // These need to be empty so that on first time setting them they will not short circuit + QUrl _skeletonModelURL; // These need to be empty so that on first time setting them they will not short circuit QVector _attachmentData; QString _displayName; @@ -409,9 +391,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-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index b005f67f5e..3faa06fc53 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -277,7 +277,6 @@ void EntityTreeRenderer::update() { void EntityTreeRenderer::checkEnterLeaveEntities() { if (_tree && !_shuttingDown) { - _tree->lockForWrite(); // so that our scripts can do edits if they want glm::vec3 avatarPosition = _viewState->getAvatarPosition(); if (avatarPosition != _lastAvatarPosition) { @@ -286,6 +285,7 @@ void EntityTreeRenderer::checkEnterLeaveEntities() { QVector entitiesContainingAvatar; // find the entities near us + _tree->lockForRead(); // don't let someone else change our tree while we search static_cast(_tree)->findEntities(avatarPosition, radius, foundEntities); // create a list of entities that actually contain the avatar's position @@ -294,6 +294,11 @@ void EntityTreeRenderer::checkEnterLeaveEntities() { entitiesContainingAvatar << entity->getEntityItemID(); } } + _tree->unlock(); + + // Note: at this point we don't need to worry about the tree being locked, because we only deal with + // EntityItemIDs from here. The loadEntityScript() method is robust against attempting to load scripts + // for entity IDs that no longer exist. // for all of our previous containing entities, if they are no longer containing then send them a leave event foreach(const EntityItemID& entityID, _currentEntitiesInside) { @@ -322,14 +327,12 @@ void EntityTreeRenderer::checkEnterLeaveEntities() { _currentEntitiesInside = entitiesContainingAvatar; _lastAvatarPosition = avatarPosition; } - _tree->unlock(); } } void EntityTreeRenderer::leaveAllEntities() { if (_tree && !_shuttingDown) { - _tree->lockForWrite(); // so that our scripts can do edits if they want - + // for all of our previous containing entities, if they are no longer containing then send them a leave event foreach(const EntityItemID& entityID, _currentEntitiesInside) { emit leaveEntity(entityID); @@ -344,7 +347,6 @@ void EntityTreeRenderer::leaveAllEntities() { // make sure our "last avatar position" is something other than our current position, so that on our // first chance, we'll check for enter/leave entity events. _lastAvatarPosition = _viewState->getAvatarPosition() + glm::vec3((float)TREE_SCALE); - _tree->unlock(); } } void EntityTreeRenderer::render(RenderArgs::RenderMode renderMode, RenderArgs::RenderSide renderSide) { diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index d4eefc0986..eb6706e27f 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -266,6 +266,26 @@ bool RenderableModelEntityItem::findDetailedRayIntersection(const glm::vec3& ori return _model->findRayIntersectionAgainstSubMeshes(origin, direction, distance, face, extraInfo, precisionPicking); } +void RenderableModelEntityItem::setCollisionModelURL(const QString& url) { + ModelEntityItem::setCollisionModelURL(url); + if (_model) { + _model->setCollisionModelURL(QUrl(url)); + } +} + +bool RenderableModelEntityItem::hasCollisionModel() const { + if (_model) { + return ! _model->getCollisionURL().isEmpty(); + } else { + return !_collisionModelURL.isEmpty(); + } +} + +const QString& RenderableModelEntityItem::getCollisionModelURL() const { + assert (!_model || _collisionModelURL == _model->getCollisionURL().toString()); + return _collisionModelURL; +} + bool RenderableModelEntityItem::isReadyToComputeShape() { if (!_model) { @@ -294,13 +314,67 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) { const QSharedPointer collisionNetworkGeometry = _model->getCollisionGeometry(); const FBXGeometry& fbxGeometry = collisionNetworkGeometry->getFBXGeometry(); + AABox aaBox; _points.clear(); + unsigned int i = 0; foreach (const FBXMesh& mesh, fbxGeometry.meshes) { - _points << mesh.vertices; + + foreach (const FBXMeshPart &meshPart, mesh.parts) { + QVector pointsInPart; + unsigned int triangleCount = meshPart.triangleIndices.size() / 3; + assert((unsigned int)meshPart.triangleIndices.size() == triangleCount*3); + for (unsigned int j = 0; j < triangleCount; j++) { + unsigned int p0Index = meshPart.triangleIndices[j*3]; + unsigned int p1Index = meshPart.triangleIndices[j*3+1]; + unsigned int p2Index = meshPart.triangleIndices[j*3+2]; + + assert(p0Index < (unsigned int)mesh.vertices.size()); + assert(p1Index < (unsigned int)mesh.vertices.size()); + assert(p2Index < (unsigned int)mesh.vertices.size()); + + glm::vec3 p0 = mesh.vertices[p0Index]; + glm::vec3 p1 = mesh.vertices[p1Index]; + glm::vec3 p2 = mesh.vertices[p2Index]; + + aaBox += p0; + aaBox += p1; + aaBox += p2; + + if (!pointsInPart.contains(p0)) { + pointsInPart << p0; + } + if (!pointsInPart.contains(p1)) { + pointsInPart << p1; + } + if (!pointsInPart.contains(p2)) { + pointsInPart << p2; + } + } + + QVector newMeshPoints; + _points << newMeshPoints; + _points[i++] << pointsInPart; + } } - info.setParams(getShapeType(), 0.5f * getDimensions(), _collisionModelURL); - info.setConvexHull(_points); + // make sure we aren't about to divide by zero + glm::vec3 aaBoxDim = aaBox.getDimensions(); + aaBoxDim = glm::clamp(aaBoxDim, glm::vec3(FLT_EPSILON), aaBoxDim); + + glm::vec3 scale = _dimensions / aaBoxDim; + + // multiply each point by scale before handing the point-set off to the physics engine + for (int i = 0; i < _points.size(); i++) { + for (int j = 0; j < _points[i].size(); j++) { + // compensate for registraion + _points[i][j] += _model->getOffset(); + // scale so the collision points match the model points + _points[i][j] *= scale; + } + } + + info.setParams(getShapeType(), _dimensions, _collisionModelURL); + info.setConvexHulls(_points); } } @@ -308,7 +382,9 @@ ShapeType RenderableModelEntityItem::getShapeType() const { // XXX make hull an option in edit.js ? if (!_model || _model->getCollisionURL().isEmpty()) { return _shapeType; - } else { + } else if (_points.size() == 1) { return SHAPE_TYPE_CONVEX_HULL; + } else { + return SHAPE_TYPE_COMPOUND; } } diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.h b/libraries/entities-renderer/src/RenderableModelEntityItem.h index f02dd537fb..9146a04cf8 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.h +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.h @@ -52,6 +52,10 @@ public: bool needsToCallUpdate() const; + virtual void setCollisionModelURL(const QString& url); + virtual bool hasCollisionModel() const; + virtual const QString& getCollisionModelURL() const; + bool isReadyToComputeShape(); void computeShapeInfo(ShapeInfo& info); ShapeType getShapeType() const; @@ -66,7 +70,7 @@ private: QString _currentTextures; QStringList _originalTextures; bool _originalTexturesRead; - QVector _points; + QVector> _points; }; #endif // hifi_RenderableModelEntityItem_h diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 5df0bec562..a73f652282 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -57,6 +57,7 @@ void EntityItem::initFromEntityItemID(const EntityItemID& entityItemID) { _collisionsWillMove = ENTITY_ITEM_DEFAULT_COLLISIONS_WILL_MOVE; _locked = ENTITY_ITEM_DEFAULT_LOCKED; _userData = ENTITY_ITEM_DEFAULT_USER_DATA; + _marketplaceID = ENTITY_ITEM_DEFAULT_MARKETPLACE_ID; } EntityItem::EntityItem(const EntityItemID& entityItemID) { @@ -116,6 +117,7 @@ EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& param requestedProperties += PROP_COLLISIONS_WILL_MOVE; requestedProperties += PROP_LOCKED; requestedProperties += PROP_USER_DATA; + requestedProperties += PROP_MARKETPLACE_ID; return requestedProperties; } @@ -238,6 +240,7 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet APPEND_ENTITY_PROPERTY(PROP_COLLISIONS_WILL_MOVE, appendValue, getCollisionsWillMove()); APPEND_ENTITY_PROPERTY(PROP_LOCKED, appendValue, getLocked()); APPEND_ENTITY_PROPERTY(PROP_USER_DATA, appendValue, getUserData()); + APPEND_ENTITY_PROPERTY(PROP_MARKETPLACE_ID, appendValue, getMarketplaceID()); appendSubclassData(packetData, params, entityTreeElementExtraEncodeData, requestedProperties, @@ -550,10 +553,25 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef READ_ENTITY_PROPERTY_SETTER(PROP_IGNORE_FOR_COLLISIONS, bool, updateIgnoreForCollisions); READ_ENTITY_PROPERTY_SETTER(PROP_COLLISIONS_WILL_MOVE, bool, updateCollisionsWillMove); READ_ENTITY_PROPERTY(PROP_LOCKED, bool, _locked); - READ_ENTITY_PROPERTY_STRING(PROP_USER_DATA,setUserData); + READ_ENTITY_PROPERTY_STRING(PROP_USER_DATA, setUserData); + + if (args.bitstreamVersion >= VERSION_ENTITIES_HAS_MARKETPLACE_ID) { + READ_ENTITY_PROPERTY_STRING(PROP_MARKETPLACE_ID, setMarketplaceID); + } bytesRead += readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args, propertyFlags, overwriteLocalData); + //////////////////////////////////// + // WARNING: Do not add stream content here after the subclass. Always add it before the subclass + // + // NOTE: we had a bad version of the stream that we added stream data after the subclass. We can attempt to recover + // by doing this parsing here... but it's not likely going to fully recover the content. + // + // TODO: Remove this conde once we've sufficiently migrated content past this damaged version + if (args.bitstreamVersion == VERSION_ENTITIES_HAS_MARKETPLACE_ID_DAMAGED) { + READ_ENTITY_PROPERTY_STRING(PROP_MARKETPLACE_ID, setMarketplaceID); + } + if (overwriteLocalData && (getDirtyFlags() & (EntityItem::DIRTY_POSITION | EntityItem::DIRTY_VELOCITY))) { // NOTE: This code is attempting to "repair" the old data we just got from the server to make it more // closely match where the entities should be if they'd stepped forward in time to "now". The server @@ -820,6 +838,7 @@ EntityItemProperties EntityItem::getProperties() const { COPY_ENTITY_PROPERTY_TO_PROPERTIES(collisionsWillMove, getCollisionsWillMove); COPY_ENTITY_PROPERTY_TO_PROPERTIES(locked, getLocked); COPY_ENTITY_PROPERTY_TO_PROPERTIES(userData, getUserData); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(marketplaceID, getMarketplaceID); properties._defaultSettings = false; @@ -848,6 +867,7 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) { SET_ENTITY_PROPERTY_FROM_PROPERTIES(collisionsWillMove, updateCollisionsWillMove); SET_ENTITY_PROPERTY_FROM_PROPERTIES(locked, setLocked); SET_ENTITY_PROPERTY_FROM_PROPERTIES(userData, setUserData); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(marketplaceID, setMarketplaceID); if (somethingChanged) { somethingChangedNotification(); // notify derived classes that something has changed diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 03e6868cc8..840bc54a2f 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -251,6 +251,9 @@ public: const QString& getUserData() const { return _userData; } void setUserData(const QString& value) { _userData = value; } + const QString& getMarketplaceID() const { return _marketplaceID; } + void setMarketplaceID(const QString& value) { _marketplaceID = value; } + // TODO: get rid of users of getRadius()... float getRadius() const; @@ -339,6 +342,7 @@ protected: bool _collisionsWillMove; bool _locked; QString _userData; + QString _marketplaceID; // NOTE: Damping is applied like this: v *= pow(1 - damping, dt) // 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/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index dbb3cfa338..6e6e897230 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -70,6 +70,7 @@ EntityItemProperties::EntityItemProperties() : CONSTRUCT_PROPERTY(emitStrength, ParticleEffectEntityItem::DEFAULT_EMIT_STRENGTH), CONSTRUCT_PROPERTY(localGravity, ParticleEffectEntityItem::DEFAULT_LOCAL_GRAVITY), CONSTRUCT_PROPERTY(particleRadius, ParticleEffectEntityItem::DEFAULT_PARTICLE_RADIUS), + CONSTRUCT_PROPERTY(marketplaceID, ENTITY_ITEM_DEFAULT_MARKETPLACE_ID), _id(UNKNOWN_ENTITY_ID), _idSet(false), @@ -259,6 +260,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_EMIT_STRENGTH, emitStrength); CHECK_PROPERTY_CHANGE(PROP_LOCAL_GRAVITY, localGravity); CHECK_PROPERTY_CHANGE(PROP_PARTICLE_RADIUS, particleRadius); + CHECK_PROPERTY_CHANGE(PROP_MARKETPLACE_ID, marketplaceID); return changedProperties; } @@ -321,6 +323,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine) cons COPY_PROPERTY_TO_QSCRIPTVALUE(emitStrength); COPY_PROPERTY_TO_QSCRIPTVALUE(localGravity); COPY_PROPERTY_TO_QSCRIPTVALUE(particleRadius); + COPY_PROPERTY_TO_QSCRIPTVALUE(marketplaceID); // Sitting properties support QScriptValue sittingPoints = engine->newObject(); @@ -402,6 +405,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object) { COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(emitStrength, setEmitStrength); COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(localGravity, setLocalGravity); COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(particleRadius, setParticleRadius); + COPY_PROPERTY_FROM_QSCRIPTVALUE_STRING(marketplaceID, setMarketplaceID); _lastEdited = usecTimestampNow(); } @@ -586,6 +590,8 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem APPEND_ENTITY_PROPERTY(PROP_LOCAL_GRAVITY, appendValue, properties.getLocalGravity()); APPEND_ENTITY_PROPERTY(PROP_PARTICLE_RADIUS, appendValue, properties.getParticleRadius()); } + + APPEND_ENTITY_PROPERTY(PROP_MARKETPLACE_ID, appendValue, properties.getMarketplaceID()); } if (propertyCount > 0) { int endOfEntityItemData = packetData->getUncompressedByteOffset(); @@ -816,6 +822,8 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_PARTICLE_RADIUS, float, setParticleRadius); } + READ_ENTITY_PROPERTY_STRING_TO_PROPERTIES(PROP_MARKETPLACE_ID, setMarketplaceID); + return valid; } @@ -896,6 +904,8 @@ void EntityItemProperties::markAllChanged() { _emitStrengthChanged = true; _localGravityChanged = true; _particleRadiusChanged = true; + + _marketplaceIDChanged = true; } /// The maximum bounding cube for the entity, independent of it's rotation. diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index 308a2d23bb..3f492d649c 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -93,16 +93,26 @@ enum EntityPropertyList { PROP_LOCAL_GRAVITY, PROP_PARTICLE_RADIUS, - // NOTE: add new properties ABOVE this line and then modify PROP_LAST_ITEM below - PROP_LAST_ITEM = PROP_PARTICLE_RADIUS, + PROP_COLLISION_MODEL_URL, + PROP_MARKETPLACE_ID, + + //////////////////////////////////////////////////////////////////////////////////////////////////// + // ATTENTION: add new properties ABOVE this line and then modify PROP_LAST_ITEM below + PROP_LAST_ITEM = PROP_MARKETPLACE_ID, + //////////////////////////////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////////////////////////////// + // WARNING! Do not add props here unless you intentionally mean to reuse PROP_ indexes + // // These properties of TextEntity piggy back off of properties of ModelEntities, the type doesn't matter // since the derived class knows how to interpret it's own properties and knows the types it expects PROP_TEXT_COLOR = PROP_COLOR, PROP_TEXT = PROP_MODEL_URL, PROP_LINE_HEIGHT = PROP_ANIMATION_URL, PROP_BACKGROUND_COLOR = PROP_ANIMATION_FPS, - PROP_COLLISION_MODEL_URL, + PROP_COLLISION_MODEL_URL_OLD_VERSION = PROP_ANIMATION_FPS + 1 + // WARNING!!! DO NOT ADD PROPS_xxx here unless you really really meant to.... Add them UP above }; typedef PropertyFlags EntityPropertyFlags; @@ -195,6 +205,7 @@ public: DEFINE_PROPERTY(PROP_EMIT_STRENGTH, EmitStrength, emitStrength, float); DEFINE_PROPERTY(PROP_LOCAL_GRAVITY, LocalGravity, localGravity, float); DEFINE_PROPERTY(PROP_PARTICLE_RADIUS, ParticleRadius, particleRadius, float); + DEFINE_PROPERTY_REF(PROP_MARKETPLACE_ID, MarketplaceID, marketplaceID, QString); public: float getMaxDimension() const { return glm::max(_dimensions.x, _dimensions.y, _dimensions.z); } @@ -322,6 +333,7 @@ inline QDebug operator<<(QDebug debug, const EntityItemProperties& properties) { DEBUG_PROPERTY_IF_CHANGED(debug, properties, EmitStrength, emitStrength, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, LocalGravity, localGravity, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, ParticleRadius, particleRadius, ""); + DEBUG_PROPERTY_IF_CHANGED(debug, properties, MarketplaceID, marketplaceID, ""); debug << " last edited:" << properties.getLastEdited() << "\n"; debug << " edited ago:" << properties.getEditedAgo() << "\n"; diff --git a/libraries/entities/src/EntityItemPropertiesDefaults.h b/libraries/entities/src/EntityItemPropertiesDefaults.h index b184d510b3..aa7c77ede4 100644 --- a/libraries/entities/src/EntityItemPropertiesDefaults.h +++ b/libraries/entities/src/EntityItemPropertiesDefaults.h @@ -22,6 +22,7 @@ const glm::vec3 ENTITY_ITEM_ZERO_VEC3(0.0f); const bool ENTITY_ITEM_DEFAULT_LOCKED = false; const QString ENTITY_ITEM_DEFAULT_USER_DATA = QString(""); +const QString ENTITY_ITEM_DEFAULT_MARKETPLACE_ID = QString(""); const float ENTITY_ITEM_DEFAULT_LOCAL_RENDER_ALPHA = 1.0f; const float ENTITY_ITEM_DEFAULT_GLOW_LEVEL = 0.0f; diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 3ccff46a04..266aa2bdce 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -148,6 +148,7 @@ bool EntityTree::updateEntityWithElement(EntityItem* entity, const EntityItemPro } } } else { + QString entityScriptBefore = entity->getScript(); uint32_t preFlags = entity->getDirtyFlags(); UpdateEntityOperator theOperator(this, containingElement, entity, properties); recurseTreeWithOperator(&theOperator); @@ -166,6 +167,11 @@ bool EntityTree::updateEntityWithElement(EntityItem* entity, const EntityItemPro entity->clearDirtyFlags(); } } + + QString entityScriptAfter = entity->getScript(); + if (entityScriptBefore != entityScriptAfter) { + emitEntityScriptChanging(entity->getEntityItemID()); // the entity script has changed + } } // TODO: this final containingElement check should eventually be removed (or wrapped in an #ifdef DEBUG). diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index 8536e74e9a..29fecc88b4 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -95,7 +95,6 @@ public: void deleteEntity(const EntityItemID& entityID, bool force = false, bool ignoreWarnings = false); void deleteEntities(QSet entityIDs, bool force = false, bool ignoreWarnings = false); - void removeEntityFromSimulation(EntityItem* entity); /// \param position point of query in world-frame (meters) /// \param targetRadius radius of query (meters) diff --git a/libraries/entities/src/ModelEntityItem.cpp b/libraries/entities/src/ModelEntityItem.cpp index ce009988b1..55d809e70e 100644 --- a/libraries/entities/src/ModelEntityItem.cpp +++ b/libraries/entities/src/ModelEntityItem.cpp @@ -93,16 +93,18 @@ int ModelEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesRead = 0; const unsigned char* dataAt = data; - + READ_ENTITY_PROPERTY_COLOR(PROP_COLOR, _color); READ_ENTITY_PROPERTY_STRING(PROP_MODEL_URL, setModelURL); if (args.bitstreamVersion < VERSION_ENTITIES_HAS_COLLISION_MODEL) { setCollisionModelURL(""); + } else if (args.bitstreamVersion == VERSION_ENTITIES_HAS_COLLISION_MODEL) { + READ_ENTITY_PROPERTY_STRING(PROP_COLLISION_MODEL_URL_OLD_VERSION, setCollisionModelURL); } else { READ_ENTITY_PROPERTY_STRING(PROP_COLLISION_MODEL_URL, setCollisionModelURL); } READ_ENTITY_PROPERTY_STRING(PROP_ANIMATION_URL, setAnimationURL); - + // Because we're using AnimationLoop which will reset the frame index if you change it's running state // we want to read these values in the order they appear in the buffer, but call our setters in an // order that allows AnimationLoop to preserve the correct frame rate. @@ -279,6 +281,13 @@ void ModelEntityItem::updateShapeType(ShapeType type) { } } +void ModelEntityItem::setCollisionModelURL(const QString& url) { + if (_collisionModelURL != url) { + _collisionModelURL = url; + _dirtyFlags |= EntityItem::DIRTY_SHAPE | EntityItem::DIRTY_MASS; + } +} + void ModelEntityItem::setAnimationURL(const QString& url) { _dirtyFlags |= EntityItem::DIRTY_UPDATEABLE; _animationURL = url; diff --git a/libraries/entities/src/ModelEntityItem.h b/libraries/entities/src/ModelEntityItem.h index 081cb429ed..9e34de445b 100644 --- a/libraries/entities/src/ModelEntityItem.h +++ b/libraries/entities/src/ModelEntityItem.h @@ -57,13 +57,13 @@ public: const rgbColor& getColor() const { return _color; } xColor getXColor() const { xColor color = { _color[RED_INDEX], _color[GREEN_INDEX], _color[BLUE_INDEX] }; return color; } bool hasModel() const { return !_modelURL.isEmpty(); } - bool hasCollisionModel() const { return !_collisionModelURL.isEmpty(); } + virtual bool hasCollisionModel() const { return !_collisionModelURL.isEmpty(); } static const QString DEFAULT_MODEL_URL; const QString& getModelURL() const { return _modelURL; } static const QString DEFAULT_COLLISION_MODEL_URL; - const QString& getCollisionModelURL() const { return _collisionModelURL; } + virtual const QString& getCollisionModelURL() const { return _collisionModelURL; } bool hasAnimation() const { return !_animationURL.isEmpty(); } static const QString DEFAULT_ANIMATION_URL; @@ -78,7 +78,7 @@ public: // model related properties void setModelURL(const QString& url) { _modelURL = url; } - void setCollisionModelURL(const QString& url) { _collisionModelURL = url; } + virtual void setCollisionModelURL(const QString& url); void setAnimationURL(const QString& url); static const float DEFAULT_ANIMATION_FRAME_INDEX; void setAnimationFrameIndex(float value); diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp index a2c217c97d..1cd3bba73a 100644 --- a/libraries/fbx/src/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -458,41 +458,6 @@ FBXNode parseFBX(QIODevice* device) { return top; } -QVariantHash parseMapping(QIODevice* device) { - QVariantHash properties; - - QByteArray line; - while (!(line = device->readLine()).isEmpty()) { - if ((line = line.trimmed()).startsWith('#')) { - continue; // comment - } - QList sections = line.split('='); - if (sections.size() < 2) { - continue; - } - QByteArray name = sections.at(0).trimmed(); - if (sections.size() == 2) { - properties.insertMulti(name, sections.at(1).trimmed()); - - } else if (sections.size() == 3) { - QVariantHash heading = properties.value(name).toHash(); - heading.insertMulti(sections.at(1).trimmed(), sections.at(2).trimmed()); - properties.insert(name, heading); - - } else if (sections.size() >= 4) { - QVariantHash heading = properties.value(name).toHash(); - QVariantList contents; - for (int i = 2; i < sections.size(); i++) { - contents.append(sections.at(i).trimmed()); - } - heading.insertMulti(sections.at(1).trimmed(), contents); - properties.insert(name, heading); - } - } - - return properties; -} - QVector createVec3Vector(const QVector& doubleVector) { QVector values; for (const double* it = doubleVector.constData(), *end = it + (doubleVector.size() / 3 * 3); it != end; ) { @@ -2473,39 +2438,6 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping, return geometry; } -QVariantHash readMapping(const QByteArray& data) { - QBuffer buffer(const_cast(&data)); - buffer.open(QIODevice::ReadOnly); - return parseMapping(&buffer); -} - -QByteArray writeMapping(const QVariantHash& mapping) { - QBuffer buffer; - buffer.open(QIODevice::WriteOnly); - for (QVariantHash::const_iterator first = mapping.constBegin(); first != mapping.constEnd(); first++) { - QByteArray key = first.key().toUtf8() + " = "; - QVariantHash hashValue = first.value().toHash(); - if (hashValue.isEmpty()) { - buffer.write(key + first.value().toByteArray() + "\n"); - continue; - } - for (QVariantHash::const_iterator second = hashValue.constBegin(); second != hashValue.constEnd(); second++) { - QByteArray extendedKey = key + second.key().toUtf8(); - QVariantList listValue = second.value().toList(); - if (listValue.isEmpty()) { - buffer.write(extendedKey + " = " + second.value().toByteArray() + "\n"); - continue; - } - buffer.write(extendedKey); - for (QVariantList::const_iterator third = listValue.constBegin(); third != listValue.constEnd(); third++) { - buffer.write(" = " + third->toByteArray()); - } - buffer.write("\n"); - } - } - return buffer.data(); -} - FBXGeometry readFBX(const QByteArray& model, const QVariantHash& mapping, bool loadLightmaps, float lightmapLevel) { QBuffer buffer(const_cast(&model)); buffer.open(QIODevice::ReadOnly); diff --git a/libraries/fbx/src/FBXReader.h b/libraries/fbx/src/FBXReader.h index ecce607575..6cb6b19c05 100644 --- a/libraries/fbx/src/FBXReader.h +++ b/libraries/fbx/src/FBXReader.h @@ -261,12 +261,6 @@ public: Q_DECLARE_METATYPE(FBXGeometry) -/// Reads an FST mapping from the supplied data. -QVariantHash readMapping(const QByteArray& data); - -/// Writes an FST mapping to a byte array. -QByteArray writeMapping(const QVariantHash& mapping); - /// Reads FBX geometry from the supplied model and mapping data. /// \exception QString if an error occurs in parsing FBXGeometry readFBX(const QByteArray& model, const QVariantHash& mapping, bool loadLightmaps = true, float lightmapLevel = 1.0f); diff --git a/libraries/fbx/src/FSTReader.cpp b/libraries/fbx/src/FSTReader.cpp new file mode 100644 index 0000000000..f1ffe11996 --- /dev/null +++ b/libraries/fbx/src/FSTReader.cpp @@ -0,0 +1,99 @@ +// +// FSTReader.cpp +// +// +// Created by Clement on 3/26/15. +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include + +#include "FSTReader.h" + +QVariantHash parseMapping(QIODevice* device) { + QVariantHash properties; + + QByteArray line; + while (!(line = device->readLine()).isEmpty()) { + if ((line = line.trimmed()).startsWith('#')) { + continue; // comment + } + QList sections = line.split('='); + if (sections.size() < 2) { + continue; + } + QByteArray name = sections.at(0).trimmed(); + if (sections.size() == 2) { + properties.insertMulti(name, sections.at(1).trimmed()); + + } else if (sections.size() == 3) { + QVariantHash heading = properties.value(name).toHash(); + heading.insertMulti(sections.at(1).trimmed(), sections.at(2).trimmed()); + properties.insert(name, heading); + + } else if (sections.size() >= 4) { + QVariantHash heading = properties.value(name).toHash(); + QVariantList contents; + for (int i = 2; i < sections.size(); i++) { + contents.append(sections.at(i).trimmed()); + } + heading.insertMulti(sections.at(1).trimmed(), contents); + properties.insert(name, heading); + } + } + + return properties; +} + +QVariantHash readMapping(const QByteArray& data) { + QBuffer buffer(const_cast(&data)); + buffer.open(QIODevice::ReadOnly); + return parseMapping(&buffer); +} + +void writeVariant(QBuffer& buffer, QVariantHash::const_iterator& it) { + QByteArray key = it.key().toUtf8() + " = "; + QVariantHash hashValue = it.value().toHash(); + if (hashValue.isEmpty()) { + buffer.write(key + it.value().toByteArray() + "\n"); + return; + } + for (QVariantHash::const_iterator second = hashValue.constBegin(); second != hashValue.constEnd(); second++) { + QByteArray extendedKey = key + second.key().toUtf8(); + QVariantList listValue = second.value().toList(); + if (listValue.isEmpty()) { + buffer.write(extendedKey + " = " + second.value().toByteArray() + "\n"); + continue; + } + buffer.write(extendedKey); + for (QVariantList::const_iterator third = listValue.constBegin(); third != listValue.constEnd(); third++) { + buffer.write(" = " + third->toByteArray()); + } + buffer.write("\n"); + } +} + +QByteArray writeMapping(const QVariantHash& mapping) { + static const QStringList PREFERED_ORDER = QStringList() << NAME_FIELD << SCALE_FIELD << FILENAME_FIELD + << TEXDIR_FIELD << JOINT_FIELD << FREE_JOINT_FIELD + << BLENDSHAPE_FIELD << JOINT_INDEX_FIELD; + QBuffer buffer; + buffer.open(QIODevice::WriteOnly); + + for (auto key : PREFERED_ORDER) { + auto it = mapping.find(key); + if (it != mapping.constEnd()) { + writeVariant(buffer, it); + } + } + + for (auto it = mapping.constBegin(); it != mapping.constEnd(); it++) { + if (!PREFERED_ORDER.contains(it.key())) { + writeVariant(buffer, it); + } + } + return buffer.data(); +} \ No newline at end of file diff --git a/libraries/fbx/src/FSTReader.h b/libraries/fbx/src/FSTReader.h new file mode 100644 index 0000000000..59559dea74 --- /dev/null +++ b/libraries/fbx/src/FSTReader.h @@ -0,0 +1,36 @@ +// +// FSTReader.h +// +// +// Created by Clement on 3/26/15. +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_FSTReader_h +#define hifi_FSTReader_h + +#include + +static const QString NAME_FIELD = "name"; +static const QString FILENAME_FIELD = "filename"; +static const QString TEXDIR_FIELD = "texdir"; +static const QString LOD_FIELD = "lod"; +static const QString JOINT_INDEX_FIELD = "jointIndex"; +static const QString SCALE_FIELD = "scale"; +static const QString TRANSLATION_X_FIELD = "tx"; +static const QString TRANSLATION_Y_FIELD = "ty"; +static const QString TRANSLATION_Z_FIELD = "tz"; +static const QString JOINT_FIELD = "joint"; +static const QString FREE_JOINT_FIELD = "freeJoint"; +static const QString BLENDSHAPE_FIELD = "bs"; + +/// Reads an FST mapping from the supplied data. +QVariantHash readMapping(const QByteArray& data); + +/// Writes an FST mapping to a byte array. +QByteArray writeMapping(const QVariantHash& mapping); + +#endif // hifi_FSTReader_h \ No newline at end of file diff --git a/libraries/fbx/src/OBJReader.cpp b/libraries/fbx/src/OBJReader.cpp index 0aaf8772a2..db2733f27b 100644 --- a/libraries/fbx/src/OBJReader.cpp +++ b/libraries/fbx/src/OBJReader.cpp @@ -249,7 +249,20 @@ bool parseOBJGroup(OBJTokenizer &tokenizer, const QVariantHash& mapping, } else if (indices.count() == 4) { meshPart.quadIndices << indices; } else { - qDebug() << "no support for more than 4 vertices on a face in OBJ files"; + // some obj writers (maya) will write a face with lots of points. + for (int i = 1; i < indices.count() - 1; i++) { + // break the face into triangles + meshPart.triangleIndices.append(indices[0]); + meshPart.triangleIndices.append(indices[i]); + meshPart.triangleIndices.append(indices[i+1]); + } + if (indices.count() == normalIndices.count()) { + for (int i = 1; i < normalIndices.count() - 1; i++) { + faceNormalIndexes.append(normalIndices[0]); + faceNormalIndexes.append(normalIndices[i]); + faceNormalIndexes.append(normalIndices[i+1]); + } + } } } else { // something we don't (yet) care about diff --git a/libraries/gpu/src/gpu/Batch.h b/libraries/gpu/src/gpu/Batch.h index 84358b7ae1..3468e75738 100644 --- a/libraries/gpu/src/gpu/Batch.h +++ b/libraries/gpu/src/gpu/Batch.h @@ -56,8 +56,11 @@ enum Primitive { }; enum ReservedSlot { - TRANSFORM_OBJECT_SLOT = 6, +/* TRANSFORM_OBJECT_SLOT = 6, TRANSFORM_CAMERA_SLOT = 7, + */ + TRANSFORM_OBJECT_SLOT = 1, + TRANSFORM_CAMERA_SLOT = 2, }; class Batch { diff --git a/libraries/gpu/src/gpu/Config.slh b/libraries/gpu/src/gpu/Config.slh index b17bd7b2b8..7a51de594a 100644 --- a/libraries/gpu/src/gpu/Config.slh +++ b/libraries/gpu/src/gpu/Config.slh @@ -12,10 +12,16 @@ <@def GPU_CONFIG_SLH@> <@if GLPROFILE == PC_GL @> + <@def GPU_FEATURE_PROFILE GPU_CORE@> + <@def GPU_TRANSFORM_PROFILE GPU_CORE@> <@def VERSION_HEADER #version 330 compatibility@> <@elif GLPROFILE == MAC_GL @> + <@def GPU_FEATURE_PROFILE GPU_LEGACY@> + <@def GPU_TRANSFORM_PROFILE GPU_LEGACY@> <@def VERSION_HEADER #version 120@> <@else@> + <@def GPU_FEATURE_PROFILE GPU_LEGACY@> + <@def GPU_TRANSFORM_PROFILE GPU_LEGACY@> <@def VERSION_HEADER #version 120@> <@endif@> diff --git a/libraries/gpu/src/gpu/GLBackendPipeline.cpp b/libraries/gpu/src/gpu/GLBackendPipeline.cpp index eba904ae4f..aee3f009d1 100755 --- a/libraries/gpu/src/gpu/GLBackendPipeline.cpp +++ b/libraries/gpu/src/gpu/GLBackendPipeline.cpp @@ -55,19 +55,17 @@ void GLBackend::do_setUniformBuffer(Batch& batch, uint32 paramOffset) { BufferPointer uniformBuffer = batch._buffers.get(batch._params[paramOffset + 2]._uint); GLintptr rangeStart = batch._params[paramOffset + 1]._uint; GLsizeiptr rangeSize = batch._params[paramOffset + 0]._uint; -#if defined(Q_OS_MAC) + +#if (GPU_FEATURE_PROFILE == GPU_CORE) + GLuint bo = getBufferID(*uniformBuffer); + glBindBufferRange(GL_UNIFORM_BUFFER, slot, bo, rangeStart, rangeSize); +#else GLfloat* data = (GLfloat*) (uniformBuffer->getData() + rangeStart); glUniform4fv(slot, rangeSize / sizeof(GLfloat[4]), data); // NOT working so we ll stick to the uniform float array until we move to core profile // GLuint bo = getBufferID(*uniformBuffer); //glUniformBufferEXT(_shader._program, slot, bo); -#elif defined(Q_OS_WIN) - GLuint bo = getBufferID(*uniformBuffer); - glBindBufferRange(GL_UNIFORM_BUFFER, slot, bo, rangeStart, rangeSize); -#else - GLfloat* data = (GLfloat*) (uniformBuffer->getData() + rangeStart); - glUniform4fv(slot, rangeSize / sizeof(GLfloat[4]), data); #endif CHECK_GL_ERROR(); } diff --git a/libraries/gpu/src/gpu/GLBackendShader.cpp b/libraries/gpu/src/gpu/GLBackendShader.cpp index 9bcc278d8e..bcfdc4f36c 100755 --- a/libraries/gpu/src/gpu/GLBackendShader.cpp +++ b/libraries/gpu/src/gpu/GLBackendShader.cpp @@ -41,6 +41,12 @@ void makeBindings(GLBackend::GLShader* shader) { glBindAttribLocation(glprogram, gpu::Stream::POSITION, "position"); } + //Check for gpu specific attribute slotBindings + loc = glGetAttribLocation(glprogram, "gl_Vertex"); + if (loc >= 0) { + glBindAttribLocation(glprogram, gpu::Stream::POSITION, "position"); + } + loc = glGetAttribLocation(glprogram, "normal"); if (loc >= 0) { glBindAttribLocation(glprogram, gpu::Stream::NORMAL, "normal"); @@ -88,7 +94,7 @@ void makeBindings(GLBackend::GLShader* shader) { // now assign the ubo binding, then DON't relink! //Check for gpu specific uniform slotBindings -#if defined(Q_OS_WIN) +#if (GPU_TRANSFORM_PROFILE == GPU_CORE) loc = glGetUniformBlockIndex(glprogram, "transformObjectBuffer"); if (loc >= 0) { glUniformBlockBinding(glprogram, loc, gpu::TRANSFORM_OBJECT_SLOT); @@ -503,6 +509,12 @@ ElementResource getFormatFromGLUniform(GLenum gltype) { int makeUniformSlots(GLuint glprogram, const Shader::BindingSet& slotBindings, Shader::SlotSet& uniforms, Shader::SlotSet& textures, Shader::SlotSet& samplers) { GLint uniformsCount = 0; +#if (GPU_FEATURE_PROFILE == GPU_LEGACY) + GLint currentProgram = 0; + glGetIntegerv(GL_CURRENT_PROGRAM, ¤tProgram); + glUseProgram(glprogram); +#endif + glGetProgramiv(glprogram, GL_ACTIVE_UNIFORMS, &uniformsCount); for (int i = 0; i < uniformsCount; i++) { @@ -520,18 +532,36 @@ int makeUniformSlots(GLuint glprogram, const Shader::BindingSet& slotBindings, S // The uniform as a standard var type if (location != INVALID_UNIFORM_LOCATION) { + // Let's make sure the name doesn't contains an array element + std::string sname(name); + auto foundBracket = sname.find_first_of('['); + if (foundBracket != std::string::npos) { + // std::string arrayname = sname.substr(0, foundBracket); + + if (sname[foundBracket + 1] == '0') { + sname = sname.substr(0, foundBracket); + } else { + // skip this uniform since it's not the first element of an array + continue; + } + } + if (elementResource._resource == Resource::BUFFER) { - uniforms.insert(Shader::Slot(name, location, elementResource._element, elementResource._resource)); + uniforms.insert(Shader::Slot(sname, location, elementResource._element, elementResource._resource)); } else { // For texture/Sampler, the location is the actual binding value GLint binding = -1; glGetUniformiv(glprogram, location, &binding); - auto requestedBinding = slotBindings.find(std::string(name)); + auto requestedBinding = slotBindings.find(std::string(sname)); if (requestedBinding != slotBindings.end()) { if (binding != (*requestedBinding)._location) { binding = (*requestedBinding)._location; +#if (GPU_FEATURE_PROFILE == GPU_LEGACY) glUniform1i(location, binding); +#else + glProgramUniform1i(glprogram, location, binding); +#endif } } @@ -541,6 +571,10 @@ int makeUniformSlots(GLuint glprogram, const Shader::BindingSet& slotBindings, S } } +#if (GPU_FEATURE_PROFILE == GPU_LEGACY) + glUseProgram(currentProgram); +#endif + return uniformsCount; } @@ -551,7 +585,9 @@ bool isUnusedSlot(GLint binding) { int makeUniformBlockSlots(GLuint glprogram, const Shader::BindingSet& slotBindings, Shader::SlotSet& buffers) { GLint buffersCount = 0; -#if defined(Q_OS_WIN) + +#if (GPU_FEATURE_PROFILE == GPU_CORE) + glGetProgramiv(glprogram, GL_ACTIVE_UNIFORM_BLOCKS, &buffersCount); // fast exit diff --git a/libraries/gpu/src/gpu/GLBackendShared.h b/libraries/gpu/src/gpu/GLBackendShared.h index 1853573522..f51e455447 100644 --- a/libraries/gpu/src/gpu/GLBackendShared.h +++ b/libraries/gpu/src/gpu/GLBackendShared.h @@ -49,7 +49,10 @@ static const GLenum _elementTypeToGLType[NUM_TYPES]= { GL_UNSIGNED_BYTE }; +#if _DEBUG #define CHECK_GL_ERROR() ::gpu::GLBackend::checkGLError() -//#define CHECK_GL_ERROR() +#else +#define CHECK_GL_ERROR() +#endif #endif diff --git a/libraries/gpu/src/gpu/GLBackendTransform.cpp b/libraries/gpu/src/gpu/GLBackendTransform.cpp index 6e928bcf09..f0318a66aa 100755 --- a/libraries/gpu/src/gpu/GLBackendTransform.cpp +++ b/libraries/gpu/src/gpu/GLBackendTransform.cpp @@ -32,7 +32,7 @@ void GLBackend::do_setProjectionTransform(Batch& batch, uint32 paramOffset) { } void GLBackend::initTransform() { -#if defined(Q_OS_WIN) + #if (GPU_TRANSFORM_PROFILE == GPU_CORE) glGenBuffers(1, &_transform._transformObjectBuffer); glGenBuffers(1, &_transform._transformCameraBuffer); @@ -49,7 +49,7 @@ void GLBackend::initTransform() { } void GLBackend::killTransform() { -#if defined(Q_OS_WIN) + #if (GPU_TRANSFORM_PROFILE == GPU_CORE) glDeleteBuffers(1, &_transform._transformObjectBuffer); glDeleteBuffers(1, &_transform._transformCameraBuffer); #else @@ -77,34 +77,30 @@ void GLBackend::updateTransform() { _transform._transformCamera._projectionViewUntranslated = _transform._transformCamera._projection * viewUntranslated; } + #if (GPU_TRANSFORM_PROFILE == GPU_CORE) if (_transform._invalidView || _transform._invalidProj) { -#if defined(Q_OS_WIN) glBindBufferBase(GL_UNIFORM_BUFFER, TRANSFORM_CAMERA_SLOT, 0); glBindBuffer(GL_ARRAY_BUFFER, _transform._transformCameraBuffer); glBufferData(GL_ARRAY_BUFFER, sizeof(_transform._transformCamera), (const void*) &_transform._transformCamera, GL_DYNAMIC_DRAW); glBindBuffer(GL_ARRAY_BUFFER, 0); CHECK_GL_ERROR(); -#endif } if (_transform._invalidModel) { -#if defined(Q_OS_WIN) glBindBufferBase(GL_UNIFORM_BUFFER, TRANSFORM_OBJECT_SLOT, 0); glBindBuffer(GL_ARRAY_BUFFER, _transform._transformObjectBuffer); glBufferData(GL_ARRAY_BUFFER, sizeof(_transform._transformObject), (const void*) &_transform._transformObject, GL_DYNAMIC_DRAW); glBindBuffer(GL_ARRAY_BUFFER, 0); CHECK_GL_ERROR(); -#endif } -#if defined(Q_OS_WIN) glBindBufferBase(GL_UNIFORM_BUFFER, TRANSFORM_OBJECT_SLOT, _transform._transformObjectBuffer); glBindBufferBase(GL_UNIFORM_BUFFER, TRANSFORM_CAMERA_SLOT, _transform._transformCameraBuffer); CHECK_GL_ERROR(); #endif -#if defined(Q_OS_MAC) || defined(Q_OS_LINUX) +#if (GPU_TRANSFORM_PROFILE == GPU_LEGACY) // Do it again for fixed pipeline until we can get rid of it if (_transform._invalidProj) { if (_transform._lastMode != GL_PROJECTION) { diff --git a/libraries/gpu/src/gpu/GPUConfig.h b/libraries/gpu/src/gpu/GPUConfig.h index 5894d2a91d..0b2d93b18d 100644 --- a/libraries/gpu/src/gpu/GPUConfig.h +++ b/libraries/gpu/src/gpu/GPUConfig.h @@ -14,21 +14,32 @@ #define GL_GLEXT_PROTOTYPES 1 +#define GPU_CORE 1 +#define GPU_LEGACY 0 + #if defined(__APPLE__) #include #include +#define GPU_FEATURE_PROFILE GPU_LEGACY +#define GPU_TRANSFORM_PROFILE GPU_LEGACY + #elif defined(WIN32) #include #include #include +#define GPU_FEATURE_PROFILE GPU_CORE +#define GPU_TRANSFORM_PROFILE GPU_CORE + #elif defined(ANDROID) #else #include #include +#define GPU_FEATURE_PROFILE GPU_LEGACY +#define GPU_TRANSFORM_PROFILE GPU_LEGACY #endif diff --git a/libraries/gpu/src/gpu/Shader.h b/libraries/gpu/src/gpu/Shader.h index 9a5bec313b..c8db7cfd39 100755 --- a/libraries/gpu/src/gpu/Shader.h +++ b/libraries/gpu/src/gpu/Shader.h @@ -41,31 +41,58 @@ public: Language _lang = GLSL; }; + static const int32 INVALID_LOCATION = -1; + class Slot { public: std::string _name; - uint32 _location; + int32 _location{INVALID_LOCATION}; Element _element; - uint16 _resourceType; + uint16 _resourceType{Resource::BUFFER}; - Slot(const std::string& name, uint16 location, const Element& element, uint16 resourceType = Resource::BUFFER) : + Slot(const Slot& s) : _name(s._name), _location(s._location), _element(s._element), _resourceType(s._resourceType) {} + Slot(Slot&& s) : _name(s._name), _location(s._location), _element(s._element), _resourceType(s._resourceType) {} + Slot(const std::string& name, int32 location, const Element& element, uint16 resourceType = Resource::BUFFER) : _name(name), _location(location), _element(element), _resourceType(resourceType) {} - + Slot(const std::string& name) : _name(name) {} + + Slot& operator= (const Slot& s) { + _name = s._name; + _location = s._location; + _element = s._element; + _resourceType = s._resourceType; + return (*this); } }; class Binding { public: std::string _name; - uint32 _location; - Binding(const std::string&& name, uint32 loc = 0) : _name(name), _location(loc) {} + int32 _location; + Binding(const std::string& name, int32 loc = INVALID_LOCATION) : _name(name), _location(loc) {} }; template class Less { public: bool operator() (const T& x, const T& y) const { return x._name < y._name; } }; - typedef std::set> SlotSet; + + class SlotSet : public std::set> { + public: + Slot findSlot(const std::string& name) const { + auto key = Slot(name); + auto found = static_cast>*>(this)->find(key); + if (found != end()) { + return (*found); + } + return key; + } + int32 findLocation(const std::string& name) const { + return findSlot(name)._location; + } + protected: + }; + typedef std::set> BindingSet; diff --git a/libraries/gpu/src/gpu/Transform.slh b/libraries/gpu/src/gpu/Transform.slh index d3d2629c2e..042964f0f7 100644 --- a/libraries/gpu/src/gpu/Transform.slh +++ b/libraries/gpu/src/gpu/Transform.slh @@ -10,6 +10,7 @@ <@if not GPU_TRANSFORM_STATE_SLH@> <@def GPU_TRANSFORM_STATE_SLH@> +<@func declareStandardTransform()@> struct TransformObject { mat4 _model; mat4 _modelInverse; @@ -23,76 +24,100 @@ struct TransformCamera { vec4 _viewport; }; -vec4 transformModelToClipPos(TransformCamera camera, TransformObject object, vec4 pos) { -<@if GLPROFILE == MAC_GL@> - return gl_ModelViewProjectionMatrix * pos; -<@elif GLPROFILE == PC_GL@> - vec4 epos = (object._model * pos) + vec4(-pos.w * camera._viewInverse[3].xyz, 0.0); - return camera._projectionViewUntranslated * epos; - // Equivalent to the following but hoppefully a bit more accurate - // return camera._projection * camera._view * object._model * pos; -<@else@> - return gl_ModelViewProjectionMatrix * pos; -<@endif@> -} - -vec3 transformModelToEyeDir(TransformCamera camera, TransformObject object, vec3 dir) { -<@if GLPROFILE == MAC_GL@> - return gl_NormalMatrix * dir; -<@elif GLPROFILE == PC_GL@> - vec3 mr0 = vec3(object._modelInverse[0].x, object._modelInverse[1].x, object._modelInverse[2].x); - vec3 mr1 = vec3(object._modelInverse[0].y, object._modelInverse[1].y, object._modelInverse[2].y); - vec3 mr2 = vec3(object._modelInverse[0].z, object._modelInverse[1].z, object._modelInverse[2].z); - - vec3 mvc0 = vec3(dot(camera._viewInverse[0].xyz, mr0), dot(camera._viewInverse[0].xyz, mr1), dot(camera._viewInverse[0].xyz, mr2)); - vec3 mvc1 = vec3(dot(camera._viewInverse[1].xyz, mr0), dot(camera._viewInverse[1].xyz, mr1), dot(camera._viewInverse[1].xyz, mr2)); - vec3 mvc2 = vec3(dot(camera._viewInverse[2].xyz, mr0), dot(camera._viewInverse[2].xyz, mr1), dot(camera._viewInverse[2].xyz, mr2)); - - vec3 result = vec3(dot(mvc0, dir), dot(mvc1, dir), dot(mvc2, dir)); - - return result; -<@else@> - return gl_NormalMatrix * dir; -<@endif@> -} - -<@if GLPROFILE == PC_GL@> +<@if GPU_TRANSFORM_PROFILE == GPU_CORE@> uniform transformObjectBuffer { - TransformObject object; + TransformObject _object; }; TransformObject getTransformObject() { - return object; + return _object; } uniform transformCameraBuffer { - TransformCamera camera; + TransformCamera _camera; }; TransformCamera getTransformCamera() { - return camera; -} -<@elif GLPROFILE == MAC_GL@> -TransformObject getTransformObject() { - TransformObject object; - return object; + return _camera; } -TransformCamera getTransformCamera() { - TransformCamera camera; - return camera; -} <@else@> +//uniform vec4 transformObjectBuffer[8]; TransformObject getTransformObject() { TransformObject object; + /* object._model[0] = transformObjectBuffer[0]; + object._model[1] = transformObjectBuffer[1]; + object._model[2] = transformObjectBuffer[2]; + object._model[3] = transformObjectBuffer[3]; + + object._modelInverse[0] = transformObjectBuffer[4]; + object._modelInverse[1] = transformObjectBuffer[5]; + object._modelInverse[2] = transformObjectBuffer[6]; + object._modelInverse[3] = transformObjectBuffer[7]; +*/ return object; } +//uniform vec4 transformCameraBuffer[17]; TransformCamera getTransformCamera() { TransformCamera camera; +/* camera._view[0] = transformCameraBuffer[0]; + camera._view[1] = transformCameraBuffer[1]; + camera._view[2] = transformCameraBuffer[2]; + camera._view[3] = transformCameraBuffer[3]; + + camera._viewInverse[0] = transformCameraBuffer[4]; + camera._viewInverse[1] = transformCameraBuffer[5]; + camera._viewInverse[2] = transformCameraBuffer[6]; + camera._viewInverse[3] = transformCameraBuffer[7]; + + camera._projectionViewUntranslated[0] = transformCameraBuffer[8]; + camera._projectionViewUntranslated[1] = transformCameraBuffer[9]; + camera._projectionViewUntranslated[2] = transformCameraBuffer[10]; + camera._projectionViewUntranslated[3] = transformCameraBuffer[11]; + + camera._projection[0] = transformCameraBuffer[12]; + camera._projection[1] = transformCameraBuffer[13]; + camera._projection[2] = transformCameraBuffer[14]; + camera._projection[3] = transformCameraBuffer[15]; + + camera._viewport = transformCameraBuffer[16]; +*/ return camera; } <@endif@> +<@endfunc@> +<@func transformModelToClipPos(cameraTransform, objectTransform, modelPos, clipPos)@> +<@if GPU_TRANSFORM_PROFILE == GPU_CORE@> + + { // transformModelToClipPos + vec4 _eyepos = (<$objectTransform$>._model * <$modelPos$>) + vec4(-<$modelPos$>.w * <$cameraTransform$>._viewInverse[3].xyz, 0.0); + <$clipPos$> = <$cameraTransform$>._projectionViewUntranslated * _eyepos; + } +<@else@> + <$clipPos$> = gl_ModelViewProjectionMatrix * <$modelPos$>; +<@endif@> +<@endfunc@> + +<@func transformModelToEyeDir(cameraTransform, objectTransform, modelDir, eyeDir)@> +<@if GPU_TRANSFORM_PROFILE == GPU_CORE@> + { // transformModelToEyeDir + vec3 mr0 = vec3(<$objectTransform$>._modelInverse[0].x, <$objectTransform$>._modelInverse[1].x, <$objectTransform$>._modelInverse[2].x); + vec3 mr1 = vec3(<$objectTransform$>._modelInverse[0].y, <$objectTransform$>._modelInverse[1].y, <$objectTransform$>._modelInverse[2].y); + vec3 mr2 = vec3(<$objectTransform$>._modelInverse[0].z, <$objectTransform$>._modelInverse[1].z, <$objectTransform$>._modelInverse[2].z); + + vec3 mvc0 = vec3(dot(<$cameraTransform$>._viewInverse[0].xyz, mr0), dot(<$cameraTransform$>._viewInverse[0].xyz, mr1), dot(<$cameraTransform$>._viewInverse[0].xyz, mr2)); + vec3 mvc1 = vec3(dot(<$cameraTransform$>._viewInverse[1].xyz, mr0), dot(<$cameraTransform$>._viewInverse[1].xyz, mr1), dot(<$cameraTransform$>._viewInverse[1].xyz, mr2)); + vec3 mvc2 = vec3(dot(<$cameraTransform$>._viewInverse[2].xyz, mr0), dot(<$cameraTransform$>._viewInverse[2].xyz, mr1), dot(<$cameraTransform$>._viewInverse[2].xyz, mr2)); + + <$eyeDir$> = vec3(dot(mvc0, <$modelDir$>), dot(mvc1, <$modelDir$>), dot(mvc2, <$modelDir$>)); + } +<@else@> + <$eyeDir$> = gl_NormalMatrix * <$modelDir$>; +<@endif@> +<@endfunc@> + <@endif@> diff --git a/libraries/model/src/model/Light.h b/libraries/model/src/model/Light.h index 2ef2bf3036..8f6c663668 100755 --- a/libraries/model/src/model/Light.h +++ b/libraries/model/src/model/Light.h @@ -252,27 +252,18 @@ public: // Schema to access the attribute values of the light class Schema { public: - Vec4 _position; - Vec3 _direction; - float _spare0; - Color _color; - float _intensity; - Vec4 _attenuation; - Vec4 _spot; - Vec4 _shadow; + Vec4 _position{0.0f, 0.0f, 0.0f, 1.0f}; + Vec3 _direction{0.0f, 0.0f, -1.0f}; + float _spare0{0.0f}; + Color _color{1.0f}; + float _intensity{1.0f}; + Vec4 _attenuation{1.0f}; + Vec4 _spot{0.0f, 0.0f, 0.0f, 3.0f}; + Vec4 _shadow{0.0f}; - Vec4 _control; + Vec4 _control{0.0f}; - Schema() : - _position(0.0f, 0.0f, 0.0f, 1.0f), - _direction(0.0f, 0.0f, -1.0f), - _spare0(0.f), - _color(1.0f), - _intensity(1.0f), - _attenuation(1.0f, 1.0f, 1.0f, 1.0f), - _spot(0.0f, 0.0f, 0.0f, 3.0f), - _control(0.0f) - {} + Schema() {} }; const UniformBufferView& getSchemaBuffer() const { return _schemaBuffer; } diff --git a/libraries/model/src/model/Light.slh b/libraries/model/src/model/Light.slh index fc17e94050..41c6e075cf 100644 --- a/libraries/model/src/model/Light.slh +++ b/libraries/model/src/model/Light.slh @@ -19,7 +19,6 @@ struct Light { vec4 _spot; vec4 _shadow; - vec4 _control; }; @@ -65,29 +64,15 @@ float getLightShowContour(Light l) { return l._control.w; } -<@if GLPROFILE == PC_GL@> +<@if GPU_FEATURE_PROFILE == GPU_CORE @> uniform lightBuffer { Light light; }; Light getLight() { return light; } -<@elif GLPROFILE == MAC_GL@> -uniform vec4 lightBuffer[9]; -Light getLight() { - Light light; - light._position = lightBuffer[0]; - light._direction = lightBuffer[1]; - light._color = lightBuffer[2]; - light._attenuation = lightBuffer[3]; - light._spot = lightBuffer[4]; - light._shadow = lightBuffer[5]; - light._control = lightBuffer[6]; - - return light; -} <@else@> -uniform vec4 lightBuffer[9]; +uniform vec4 lightBuffer[7]; Light getLight() { Light light; light._position = lightBuffer[0]; diff --git a/libraries/model/src/model/Material.h b/libraries/model/src/model/Material.h index 2718b1dfa8..9262f23746 100755 --- a/libraries/model/src/model/Material.h +++ b/libraries/model/src/model/Material.h @@ -79,20 +79,15 @@ public: class Schema { public: - Color _diffuse; - float _opacity; - Color _specular; - float _shininess; - Color _emissive; - float _spare0; + Color _diffuse{0.5f}; + float _opacity{1.f}; + Color _specular{0.03f}; + float _shininess{0.1f}; + Color _emissive{0.0f}; + float _spare0{0.0f}; + glm::vec4 _spareVec4{0.0f}; // for alignment beauty, Material size == Mat4x4 - Schema() : - _diffuse(0.5f), - _opacity(1.0f), - _specular(0.03f), - _shininess(0.1f), - _emissive(0.0f) - {} + Schema() {} }; const UniformBufferView& getSchemaBuffer() const { return _schemaBuffer; } diff --git a/libraries/model/src/model/Material.slh b/libraries/model/src/model/Material.slh index 9ea269f214..6b8eea18a6 100644 --- a/libraries/model/src/model/Material.slh +++ b/libraries/model/src/model/Material.slh @@ -14,7 +14,8 @@ struct Material { vec4 _diffuse; vec4 _specular; - + vec4 _emissive; + vec4 _spare; }; float getMaterialOpacity(Material m) { return m._diffuse.a; } @@ -24,36 +25,21 @@ float getMaterialShininess(Material m) { return m._specular.a; } -<@if GLPROFILE == PC_GL@> +<@if GPU_FEATURE_PROFILE == GPU_CORE@> uniform materialBuffer { Material _mat; }; Material getMaterial() { return _mat; } -<@elif GLPROFILE == MAC_GL@> -uniform vec4 materialBuffer[2]; -Material getMaterial() { - Material mat; - mat._diffuse = materialBuffer[0]; - mat._specular = materialBuffer[1]; - return mat; -} - <@else@> -uniform vec4 materialBuffer[2]; +uniform vec4 materialBuffer[4]; Material getMaterial() { Material mat; mat._diffuse = materialBuffer[0]; mat._specular = materialBuffer[1]; + mat._emissive = materialBuffer[2]; + mat._spare = materialBuffer[3]; return mat; } <@endif@> diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp index 2a809f2a7c..46c6418c63 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; + } + } } @@ -298,7 +282,7 @@ void AccountManager::processReply() { } else { passErrorToCallback(requestReply); } - delete requestReply; + requestReply->deleteLater(); } void AccountManager::passSuccessToCallback(QNetworkReply* requestReply) { @@ -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/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index 98e1ed0572..ebd10b67a6 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -36,7 +36,7 @@ const char SOLO_NODE_TYPES[2] = { NodeType::AudioMixer }; -const QUrl DEFAULT_NODE_AUTH_URL = QUrl("https://metaverse.highfidelity.io"); +const QUrl DEFAULT_NODE_AUTH_URL = QUrl("https://metaverse.highfidelity.com"); LimitedNodeList::LimitedNodeList(unsigned short socketListenPort, unsigned short dtlsListenPort) : linkedDataCreateCallback(NULL), diff --git a/libraries/networking/src/PacketHeaders.cpp b/libraries/networking/src/PacketHeaders.cpp index a14191bf09..78a27af686 100644 --- a/libraries/networking/src/PacketHeaders.cpp +++ b/libraries/networking/src/PacketHeaders.cpp @@ -74,7 +74,7 @@ PacketVersion versionForPacketType(PacketType type) { return 1; case PacketTypeEntityAddOrEdit: case PacketTypeEntityData: - return VERSION_ENTITIES_HAS_COLLISION_MODEL; + return VERSION_ENTITIES_HAS_MARKETPLACE_ID; case PacketTypeEntityErase: return 2; case PacketTypeAudioStreamStats: diff --git a/libraries/networking/src/PacketHeaders.h b/libraries/networking/src/PacketHeaders.h index f930fd9632..924d219b75 100644 --- a/libraries/networking/src/PacketHeaders.h +++ b/libraries/networking/src/PacketHeaders.h @@ -132,6 +132,8 @@ const PacketVersion VERSION_ENTITIES_LIGHT_HAS_INTENSITY_AND_COLOR_PROPERTIES = const PacketVersion VERSION_ENTITIES_HAS_PARTICLES = 10; const PacketVersion VERSION_ENTITIES_USE_METERS_AND_RADIANS = 11; const PacketVersion VERSION_ENTITIES_HAS_COLLISION_MODEL = 12; +const PacketVersion VERSION_ENTITIES_HAS_MARKETPLACE_ID_DAMAGED = 13; +const PacketVersion VERSION_ENTITIES_HAS_MARKETPLACE_ID = 14; const PacketVersion VERSION_OCTREE_HAS_FILE_BREAKS = 1; #endif // hifi_PacketHeaders_h diff --git a/libraries/networking/src/ResourceCache.cpp b/libraries/networking/src/ResourceCache.cpp index b574eb1aeb..739e587f5f 100644 --- a/libraries/networking/src/ResourceCache.cpp +++ b/libraries/networking/src/ResourceCache.cpp @@ -18,6 +18,7 @@ #include #include +#include #include "NetworkAccessManager.h" #include "ResourceCache.h" @@ -48,32 +49,40 @@ void ResourceCache::refresh(const QUrl& url) { } } +void ResourceCache::getResourceAsynchronously(const QUrl& url) { + qDebug() << "ResourceCache::getResourceAsynchronously" << url.toString(); + _resourcesToBeGottenLock.lockForWrite(); + _resourcesToBeGotten.enqueue(QUrl(url)); + _resourcesToBeGottenLock.unlock(); +} + +void ResourceCache::checkAsynchronousGets() { + assert(QThread::currentThread() == thread()); + if (!_resourcesToBeGotten.isEmpty()) { + _resourcesToBeGottenLock.lockForWrite(); + QUrl url = _resourcesToBeGotten.dequeue(); + _resourcesToBeGottenLock.unlock(); + getResource(url); + } +} + QSharedPointer ResourceCache::getResource(const QUrl& url, const QUrl& fallback, - bool delayLoad, void* extra, bool block) { + bool delayLoad, void* extra) { + QSharedPointer resource = _resources.value(url); + if (!resource.isNull()) { + return resource; + } + if (QThread::currentThread() != thread()) { - // This will re-call this method in the main thread. If block is true and the main thread - // is waiting on a lock, we'll deadlock here. - if (block) { - QSharedPointer result; - QMetaObject::invokeMethod(this, "getResource", Qt::BlockingQueuedConnection, - Q_RETURN_ARG(QSharedPointer, result), Q_ARG(const QUrl&, url), - Q_ARG(const QUrl&, fallback), Q_ARG(bool, delayLoad), Q_ARG(void*, extra)); - return result; - } else { - // Queue the re-invocation of this method, but if the main thread is blocked, don't wait. The - // return value may be NULL -- it's expected that this will be called again later, in order - // to receive the actual Resource. - QMetaObject::invokeMethod(this, "getResource", Qt::QueuedConnection, - Q_ARG(const QUrl&, url), - Q_ARG(const QUrl&, fallback), Q_ARG(bool, delayLoad), Q_ARG(void*, extra)); - return _resources.value(url); - } + assert(delayLoad); + getResourceAsynchronously(url); + return QSharedPointer(); } if (!url.isValid() && !url.isEmpty() && fallback.isValid()) { return getResource(fallback, QUrl(), delayLoad); } - QSharedPointer resource = _resources.value(url); + if (resource.isNull()) { resource = createResource(url, fallback.isValid() ? getResource(fallback, QUrl(), true) : QSharedPointer(), delayLoad, extra); diff --git a/libraries/networking/src/ResourceCache.h b/libraries/networking/src/ResourceCache.h index d4aa9a7fd9..c7aceb2e1a 100644 --- a/libraries/networking/src/ResourceCache.h +++ b/libraries/networking/src/ResourceCache.h @@ -21,6 +21,8 @@ #include #include #include +#include +#include #include @@ -79,6 +81,9 @@ public: void refresh(const QUrl& url); +public slots: + void checkAsynchronousGets(); + protected: qint64 _unusedResourcesMaxSize = DEFAULT_UNUSED_MAX_SIZE; qint64 _unusedResourcesSize = 0; @@ -89,7 +94,7 @@ protected: /// \param delayLoad if true, don't load the resource immediately; wait until load is first requested /// \param extra extra data to pass to the creator, if appropriate Q_INVOKABLE QSharedPointer getResource(const QUrl& url, const QUrl& fallback = QUrl(), - bool delayLoad = false, void* extra = NULL, bool block = true); + bool delayLoad = false, void* extra = NULL); /// Creates a new resource. virtual QSharedPointer createResource(const QUrl& url, @@ -109,6 +114,11 @@ private: int _lastLRUKey = 0; static int _requestLimit; + + void getResourceAsynchronously(const QUrl& url); + QReadWriteLock _resourcesToBeGottenLock; + QQueue _resourcesToBeGotten; + }; /// Base class for resources. 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/octree/src/OctreeConstants.h b/libraries/octree/src/OctreeConstants.h index a975469053..1246aeffd4 100644 --- a/libraries/octree/src/OctreeConstants.h +++ b/libraries/octree/src/OctreeConstants.h @@ -23,7 +23,7 @@ const int TREE_SCALE = 16384; // ~10 miles.. This is the number of meters of t const float DEFAULT_OCTREE_SIZE_SCALE = TREE_SCALE * 400.0f; // This is used in the LOD Tools to translate between the size scale slider and the values used to set the OctreeSizeScale -const float MAX_LOD_SIZE_MULTIPLIER = 2000.0f; +const float MAX_LOD_SIZE_MULTIPLIER = 800.0f; const int NUMBER_OF_CHILDREN = 8; diff --git a/libraries/physics/src/CharacterController.cpp b/libraries/physics/src/CharacterController.cpp index 5173b368c1..148746a76e 100644 --- a/libraries/physics/src/CharacterController.cpp +++ b/libraries/physics/src/CharacterController.cpp @@ -1,31 +1,35 @@ /* 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. */ -//#include - #include "BulletCollision/CollisionDispatch/btGhostObject.h" +#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) { @@ -36,47 +40,36 @@ 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 -{ +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; -} + btKinematicClosestNotMeRayResultCallback (btCollisionObject* me) : + btCollisionWorld::ClosestRayResultCallback(btVector3(0.0f, 0.0f, 0.0f), btVector3(0.0f, 0.0f, 0.0f)) { + _me = me; + } -virtual btScalar addSingleResult(btCollisionWorld::LocalRayResult& rayResult, bool normalInWorldSpace) -{ -if(rayResult.m_collisionObject == m_me) -return 1.0; - -return ClosestRayResultCallback::addSingleResult(rayResult, normalInWorldSpace); -} + virtual btScalar addSingleResult(btCollisionWorld::LocalRayResult& rayResult,bool normalInWorldSpace) { + if (rayResult.m_collisionObject == _me) { + return 1.0f; + } + return ClosestRayResultCallback::addSingleResult (rayResult, normalInWorldSpace); + } protected: -btCollisionObject* m_me; + btCollisionObject* _me; }; -*/ -class btKinematicClosestNotMeConvexResultCallback : public btCollisionWorld::ClosestConvexResultCallback -{ + +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); } @@ -92,8 +85,11 @@ class btKinematicClosestNotMeConvexResultCallback : public btCollisionWorld::Clo hitNormalWorld = convexResult.m_hitCollisionObject->getWorldTransform().getBasis()*convexResult.m_hitNormalLocal; } - btScalar dotUp = m_up.dot(hitNormalWorld); - if (dotUp < m_minSlopeDot) { + // Note: hitNormalWorld points into character, away from object + // and _up points opposite to movement + + btScalar dotUp = _up.dot(hitNormalWorld); + if (dotUp < _minSlopeDot) { return btScalar(1.0); } @@ -101,9 +97,94 @@ 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, + const btVector3& start, + const btVector3& step, + const btVector3& pushDirection, + btScalar minSlopeDot, + btScalar radius, + btScalar halfHeight) + : btCollisionWorld::ClosestConvexResultCallback(btVector3(0.0, 0.0, 0.0), btVector3(0.0, 0.0, 0.0)) + , _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 == _me) { + return btScalar(1.0); + } + + if (!convexResult.m_hitCollisionObject->hasContactResponse()) { + return btScalar(1.0); + } + + btVector3 hitNormalWorld; + if (normalInWorldSpace) { + hitNormalWorld = convexResult.m_hitNormalLocal; + } else { + ///need to transform normal into worldspace + hitNormalWorld = convexResult.m_hitCollisionObject->getWorldTransform().getBasis() * convexResult.m_hitNormalLocal; + } + + // Note: hitNormalWorld points into character, away from object + // and _up points opposite to movement + + 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 * _step; + btVector3 localHit = convexResult.m_hitPointLocal - _start + fractionalStep; + btScalar angle = localHit.angle(-_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: + // 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 + // not prevent them from "stepping down" to find the floor. + if (angle > maxAngle) { + return btScalar(1.0f); + } + } + + btScalar fraction = ClosestConvexResultCallback::addSingleResult(convexResult, normalInWorldSpace); + return fraction; + } + +protected: + btCollisionObject* _me; + const btVector3 _up; + btVector3 _start; + btVector3 _step; + btVector3 _pushDirection; + btScalar _minSlopeDot; + btScalar _radius; + btScalar _halfHeight; }; /* @@ -111,16 +192,14 @@ protected: * * from: http://www-cs-students.stanford.edu/~adityagp/final/node3.html */ -btVector3 CharacterController::computeReflectionDirection(const btVector3& direction, const btVector3& normal) -{ +btVector3 CharacterController::computeReflectionDirection(const btVector3& direction, const btVector3& normal) { return direction - (btScalar(2.0) * direction.dot(normal)) * normal; } /* * Returns the portion of 'direction' that is parallel to 'normal' */ -btVector3 CharacterController::parallelComponent(const btVector3& direction, const btVector3& normal) -{ +btVector3 CharacterController::parallelComponent(const btVector3& direction, const btVector3& normal) { btScalar magnitude = direction.dot(normal); return normal * magnitude; } @@ -128,47 +207,44 @@ btVector3 CharacterController::parallelComponent(const btVector3& direction, con /* * Returns the portion of 'direction' that is perpindicular to 'normal' */ -btVector3 CharacterController::perpindicularComponent(const btVector3& direction, const btVector3& normal) -{ +btVector3 CharacterController::perpindicularComponent(const btVector3& direction, const btVector3& normal) { return direction - parallelComponent(direction, normal); } -CharacterController::CharacterController( - btPairCachingGhostObject* ghostObject, - btConvexShape* convexShape, - btScalar stepHeight, - int upAxis) { - m_upAxis = upAxis; - m_addedMargin = 0.02; - m_walkDirection.setValue(0,0,0); - m_useGhostObjectSweepTest = true; - m_ghostObject = ghostObject; - m_stepHeight = stepHeight; - m_turnAngle = btScalar(0.0); - m_convexShape = convexShape; - m_useWalkDirection = true; // use walk direction by default, legacy behavior - m_velocityTimeInterval = 0.0; - m_verticalVelocity = 0.0; - m_verticalOffset = 0.0; - m_gravity = 9.8 * 3 ; // 3G acceleration. - m_fallSpeed = 55.0; // Terminal velocity of a sky diver in m/s. - m_jumpSpeed = 10.0; // ? - m_wasOnGround = false; - m_wasJumping = false; - m_interpolateUp = true; - setMaxSlope(btRadians(45.0)); - m_currentStepOffset = 0; +const btVector3 LOCAL_UP_AXIS(0.0f, 1.0f, 0.0f); - // internal state data members - full_drop = false; - bounce_fix = false; +CharacterController::CharacterController(AvatarData* avatarData) { + assert(avatarData); + _avatarData = avatarData; + + _enabled = false; + _ghostObject = NULL; + _convexShape = NULL; + + _addedMargin = 0.02f; + _walkDirection.setValue(0.0f,0.0f,0.0f); + _velocityTimeInterval = 0.0f; + _verticalVelocity = 0.0f; + _verticalOffset = 0.0f; + _gravity = 5.0f; // slower than Earth's + _maxFallSpeed = 55.0f; // Terminal velocity of a sky diver in m/s. + _jumpSpeed = 5.0f; + _isOnGround = false; + _isJumping = false; + _isHovering = true; + _jumpToHoverStart = 0; + setMaxSlope(btRadians(45.0f)); + _lastStepUp = 0.0f; + + _pendingFlags = PENDING_FLAG_UPDATE_SHAPE; + updateShapeIfNecessary(); } CharacterController::~CharacterController() { } btPairCachingGhostObject* CharacterController::getGhostObject() { - return m_ghostObject; + return _ghostObject; } bool CharacterController::recoverFromPenetration(btCollisionWorld* collisionWorld) { @@ -181,23 +257,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(); + _currentPosition = _ghostObject->getWorldTransform().getOrigin(); + btVector3 up = quatRotate(_currentRotation, LOCAL_UP_AXIS); + + 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); @@ -207,86 +286,128 @@ bool CharacterController::recoverFromPenetration(btCollisionWorld* collisionWorl } if (collisionPair->m_algorithm) { - collisionPair->m_algorithm->getAllContactManifolds(m_manifoldArray); + collisionPair->m_algorithm->getAllContactManifolds(_manifoldArray); } - for (int j=0;jgetBody0() == m_ghostObject ? btScalar(-1.0) : btScalar(1.0); - for (int p=0;pgetNumContacts();p++) { + 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); btScalar dist = pt.getDistance(); if (dist < 0.0) { - if (dist < maxPen) { - maxPen = dist; - m_touchingNormal = pt.m_normalWorldOnB * directionSign;//?? + bool useContact = true; + btVector3 normal = pt.m_normalWorldOnB; + normal *= directionSign; // always points from object to character + btScalar normalDotUp = normal.dot(up); + if (normalDotUp < _maxSlopeCosine) { + // this contact has a non-vertical normal... might need to ignored + btVector3 collisionPoint; + if (directionSign > 0.0) { + collisionPoint = pt.getPositionWorldOnB(); + } else { + collisionPoint = pt.getPositionWorldOnA(); + } + + // we do math in frame where character base is origin + btVector3 characterBase = currentPosition - (_radius + _halfHeight) * up; + collisionPoint -= characterBase; + btScalar collisionHeight = collisionPoint.dot(up); + + 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 + // 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; + _floorNormal = normal; + } + const btScalar INCREMENTAL_RESOLUTION_FACTOR = 0.2f; + _currentPosition += normal * (fabsf(dist) * INCREMENTAL_RESOLUTION_FACTOR); + penetration = true; } - m_currentPosition += pt.m_normalWorldOnB * directionSign * dist * btScalar(0.2); - penetration = true; - } else { - //printf("touching %f\n", dist); } } - - //manifold->clearManifold(); } } - btTransform newTrans = m_ghostObject->getWorldTransform(); - newTrans.setOrigin(m_currentPosition); - m_ghostObject->setWorldTransform(newTrans); - //printf("m_touchingNormal = %f,%f,%f\n", m_touchingNormal[0], m_touchingNormal[1], m_touchingNormal[2]); + btTransform newTrans = _ghostObject->getWorldTransform(); + newTrans.setOrigin(_currentPosition); + _ghostObject->setWorldTransform(newTrans); return penetration; } -void CharacterController::stepUp( btCollisionWorld* world) { - // phase 1: up - btTransform start, end; - m_targetPosition = m_currentPosition + getUpAxisDirections()[m_upAxis] * (m_stepHeight + (m_verticalOffset > 0.f?m_verticalOffset:0.f)); - - start.setIdentity(); - end.setIdentity(); - - /* FIXME: Handle penetration properly */ - start.setOrigin(m_currentPosition + getUpAxisDirections()[m_upAxis] * (m_convexShape->getMargin() + m_addedMargin)); - end.setOrigin(m_targetPosition); - - btKinematicClosestNotMeConvexResultCallback callback(m_ghostObject, -getUpAxisDirections()[m_upAxis], btScalar(0.7071)); +void CharacterController::scanDown(btCollisionWorld* world) { + // we test with downward raycast and if we don't find floor close enough then turn on "hover" + btKinematicClosestNotMeRayResultCallback callback(_ghostObject); callback.m_collisionFilterGroup = getGhostObject()->getBroadphaseHandle()->m_collisionFilterGroup; callback.m_collisionFilterMask = getGhostObject()->getBroadphaseHandle()->m_collisionFilterMask; - if (m_useGhostObjectSweepTest) { - m_ghostObject->convexSweepTest(m_convexShape, start, end, callback, world->getDispatchInfo().m_allowedCcdPenetration); - } - else { - world->convexSweepTest(m_convexShape, start, end, callback); + btVector3 up = quatRotate(_currentRotation, LOCAL_UP_AXIS); + btVector3 start = _currentPosition; + const btScalar MAX_SCAN_HEIGHT = 20.0f + _halfHeight + _radius; // closest possible floor for disabling hover + const btScalar MIN_HOVER_HEIGHT = 3.0f + _halfHeight + _radius; // distance to floor for enabling hover + btVector3 end = start - MAX_SCAN_HEIGHT * up; + + world->rayTest(start, end, callback); + if (!callback.hasHit()) { + _isHovering = true; + } else if (_isHovering && callback.m_closestHitFraction * MAX_SCAN_HEIGHT < MIN_HOVER_HEIGHT) { + _isHovering = false; } +} + +void CharacterController::stepUp(btCollisionWorld* world) { + // phase 1: up + + // compute start and end + btTransform start, end; + start.setIdentity(); + btVector3 up = quatRotate(_currentRotation, LOCAL_UP_AXIS); + start.setOrigin(_currentPosition + up * (_convexShape->getMargin() + _addedMargin)); + + _targetPosition = _currentPosition + up * _stepUpHeight; + end.setIdentity(); + end.setOrigin(_targetPosition); + + // sweep up + btVector3 sweepDirNegative = - up; + btKinematicClosestNotMeConvexResultCallback callback(_ghostObject, sweepDirNegative, btScalar(0.7071)); + callback.m_collisionFilterGroup = getGhostObject()->getBroadphaseHandle()->m_collisionFilterGroup; + callback.m_collisionFilterMask = getGhostObject()->getBroadphaseHandle()->m_collisionFilterMask; + _ghostObject->convexSweepTest(_convexShape, start, end, callback, world->getDispatchInfo().m_allowedCcdPenetration); if (callback.hasHit()) { + // we hit something, so zero our vertical velocity + _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) { - // we moved up only a fraction of the step height - m_currentStepOffset = m_stepHeight * callback.m_closestHitFraction; - if (m_interpolateUp == true) { - m_currentPosition.setInterpolate3 (m_currentPosition, m_targetPosition, callback.m_closestHitFraction); - } else { - m_currentPosition = m_targetPosition; - } + if (callback.m_hitNormalWorld.dot(up) > 0.0f) { + _lastStepUp = _stepUpHeight * callback.m_closestHitFraction; + _currentPosition.setInterpolate3(_currentPosition, _targetPosition, callback.m_closestHitFraction); + } else { + _lastStepUp = _stepUpHeight; + _currentPosition = _targetPosition; } - m_verticalVelocity = 0.0; - m_verticalOffset = 0.0; } else { - m_currentStepOffset = m_stepHeight; - m_currentPosition = m_targetPosition; + _currentPosition = _targetPosition; + _lastStepUp = _stepUpHeight; } } 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) { + if (movementLength > SIMD_EPSILON) { movementDirection.normalize(); btVector3 reflectDir = computeReflectionDirection(movementDirection, hitNormal); @@ -297,262 +418,174 @@ 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); - //printf("parComponent=%f,%f,%f\n", parComponent[0], parComponent[1], parComponent[2]); - m_targetPosition += parComponent; + btVector3 parComponent = parallelDir * btScalar(tangentMag * movementLength); + _targetPosition += parComponent; } if (normalMag != 0.0) { - btVector3 perpComponent = perpindicularDir * btScalar(normalMag*movementLength); - //printf("perpComponent=%f,%f,%f\n", perpComponent[0], perpComponent[1], perpComponent[2]); - m_targetPosition += perpComponent; + btVector3 perpComponent = perpindicularDir * btScalar(normalMag * movementLength); + _targetPosition += perpComponent; } - } else { - //printf("movementLength don't normalize a zero vector\n"); } } -void CharacterController::stepForwardAndStrafe( btCollisionWorld* collisionWorld, const btVector3& walkMove) { - //printf("m_normalizedDirection=%f,%f,%f\n", - // m_normalizedDirection[0], m_normalizedDirection[1], m_normalizedDirection[2]); - // phase 2: forward and strafe - btTransform start, end; - m_targetPosition = m_currentPosition + walkMove; +void CharacterController::stepForward(btCollisionWorld* collisionWorld, const btVector3& movement) { + // phase 2: forward + _targetPosition = _currentPosition + movement; + btTransform start, end; start.setIdentity(); end.setIdentity(); - btScalar fraction = 1.0; - btScalar distance2 = (m_currentPosition-m_targetPosition).length2(); - //printf("distance2=%f\n", distance2); - - if (m_touchingContact) { - if (m_normalizedDirection.dot(m_touchingNormal) > btScalar(0.0)) { - //interferes with step movement - //updateTargetPositionBasedOnCollision(m_touchingNormal); + /* TODO: experiment with this to see if we can use this to help direct motion when a floor is available + if (_touchingContact) { + if (_normalizedDirection.dot(_floorNormal) < btScalar(0.0)) { + updateTargetPositionBasedOnCollision(_floorNormal, 1.0f, 1.0f); } - } + }*/ + // modify shape's margin for the sweeps + btScalar margin = _convexShape->getMargin(); + _convexShape->setMargin(margin + _addedMargin); + + const btScalar MIN_STEP_DISTANCE_SQUARED = 1.0e-6f; + btVector3 step = _targetPosition - _currentPosition; + btScalar stepLength2 = step.length2(); int maxIter = 10; - while (fraction > btScalar(0.01) && maxIter-- > 0) { - start.setOrigin(m_currentPosition); - end.setOrigin(m_targetPosition); - btVector3 sweepDirNegative(m_currentPosition - m_targetPosition); + while (stepLength2 > MIN_STEP_DISTANCE_SQUARED && maxIter-- > 0) { + start.setOrigin(_currentPosition); + end.setOrigin(_targetPosition); - btKinematicClosestNotMeConvexResultCallback callback(m_ghostObject, sweepDirNegative, btScalar(0.0)); + // sweep forward + 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; - - - btScalar margin = m_convexShape->getMargin(); - m_convexShape->setMargin(margin + m_addedMargin); - - - if (m_useGhostObjectSweepTest) { - m_ghostObject->convexSweepTest(m_convexShape, start, end, callback, collisionWorld->getDispatchInfo().m_allowedCcdPenetration); - } else { - collisionWorld->convexSweepTest(m_convexShape, start, end, callback, collisionWorld->getDispatchInfo().m_allowedCcdPenetration); - } - - m_convexShape->setMargin(margin); - - - fraction -= callback.m_closestHitFraction; + _ghostObject->convexSweepTest(_convexShape, start, end, callback, collisionWorld->getDispatchInfo().m_allowedCcdPenetration); if (callback.hasHit()) { - // we moved only a fraction - //btScalar hitDistance; - //hitDistance = (callback.m_hitPointWorld - m_currentPosition).length(); + // 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 = _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; + _targetPosition = _currentPosition + step; - //m_currentPosition.setInterpolate3 (m_currentPosition, m_targetPosition, callback.m_closestHitFraction); - - updateTargetPositionBasedOnCollision(callback.m_hitNormalWorld); - btVector3 currentDir = m_targetPosition - m_currentPosition; - distance2 = currentDir.length2(); - if (distance2 > SIMD_EPSILON) { - currentDir.normalize(); - /* See Quake2: "If velocity is against original velocity, stop ead to avoid tiny oscilations in sloping corners." */ - if (currentDir.dot(m_normalizedDirection) <= btScalar(0.0)) { - break; - } - } else { - //printf("currentDir: don't normalize a zero vector\n"); - break; - } + stepLength2 = step.length2(); } else { - // we moved whole way - m_currentPosition = m_targetPosition; + // we swept to the end without hitting anything + _currentPosition = _targetPosition; + break; } - - //if (callback.m_closestHitFraction == 0.f) { - // break; - //} - } + + // restore shape's margin + _convexShape->setMargin(margin); } -void CharacterController::stepDown( btCollisionWorld* collisionWorld, btScalar dt) { - btTransform start, end, end_double; - bool runonce = false; - +void CharacterController::stepDown(btCollisionWorld* collisionWorld, btScalar dt) { // phase 3: down - /*btScalar additionalDownStep = (m_wasOnGround && !onGround()) ? m_stepHeight : 0.0; - btVector3 step_drop = getUpAxisDirections()[m_upAxis] * (m_currentStepOffset + additionalDownStep); - btScalar downVelocity = (additionalDownStep == 0.0 && m_verticalVelocity<0.0?-m_verticalVelocity:0.0) * dt; - btVector3 gravity_drop = getUpAxisDirections()[m_upAxis] * downVelocity; - m_targetPosition -= (step_drop + gravity_drop);*/ + // + // 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. - btVector3 orig_position = m_targetPosition; + // first sweep for ledge + btVector3 up = quatRotate(_currentRotation, LOCAL_UP_AXIS); + btVector3 step = (_verticalVelocity * dt - _lastStepUp) * up; - btScalar downVelocity = (m_verticalVelocity<0.f?-m_verticalVelocity:0.f) * dt; - - if (downVelocity > 0.0 && downVelocity > m_fallSpeed && (m_wasOnGround || !m_wasJumping)) { - downVelocity = m_fallSpeed; - } - - btVector3 step_drop = getUpAxisDirections()[m_upAxis] * (m_currentStepOffset + downVelocity); - m_targetPosition -= step_drop; - - btKinematicClosestNotMeConvexResultCallback callback(m_ghostObject, getUpAxisDirections()[m_upAxis], m_maxSlopeCosine); + StepDownConvexResultCallback callback(_ghostObject, + up, + _currentPosition, step, + _walkDirection, + _maxSlopeCosine, + _radius, _halfHeight); callback.m_collisionFilterGroup = getGhostObject()->getBroadphaseHandle()->m_collisionFilterGroup; callback.m_collisionFilterMask = getGhostObject()->getBroadphaseHandle()->m_collisionFilterMask; - btKinematicClosestNotMeConvexResultCallback callback2 (m_ghostObject, getUpAxisDirections()[m_upAxis], m_maxSlopeCosine); - callback2.m_collisionFilterGroup = getGhostObject()->getBroadphaseHandle()->m_collisionFilterGroup; - callback2.m_collisionFilterMask = getGhostObject()->getBroadphaseHandle()->m_collisionFilterMask; + btTransform start, end; + start.setIdentity(); + end.setIdentity(); - while (1) { - start.setIdentity(); - end.setIdentity(); + start.setOrigin(_currentPosition); + _targetPosition = _currentPosition + step; + end.setOrigin(_targetPosition); + _ghostObject->convexSweepTest(_convexShape, start, end, callback, collisionWorld->getDispatchInfo().m_allowedCcdPenetration); - end_double.setIdentity(); + _isOnGround = false; + if (callback.hasHit()) { + _currentPosition += callback.m_closestHitFraction * step; + _verticalVelocity = 0.0f; + _verticalOffset = 0.0f; + _isJumping = false; + _isOnGround = true; + } else if (!_isJumping) { + // sweep again for floor within downStep threshold + step = -_stepDownHeight * up; + StepDownConvexResultCallback callback2 (_ghostObject, + up, + _currentPosition, step, + _walkDirection, + _maxSlopeCosine, + _radius, _halfHeight); - start.setOrigin(m_currentPosition); - end.setOrigin(m_targetPosition); + callback2.m_collisionFilterGroup = getGhostObject()->getBroadphaseHandle()->m_collisionFilterGroup; + callback2.m_collisionFilterMask = getGhostObject()->getBroadphaseHandle()->m_collisionFilterMask; - //set double test for 2x the step drop, to check for a large drop vs small drop - end_double.setOrigin(m_targetPosition - step_drop); + _currentPosition = _targetPosition; + _targetPosition = _currentPosition + step; - if (m_useGhostObjectSweepTest) { - m_ghostObject->convexSweepTest(m_convexShape, start, end, callback, collisionWorld->getDispatchInfo().m_allowedCcdPenetration); + start.setOrigin(_currentPosition); + end.setOrigin(_targetPosition); + _ghostObject->convexSweepTest(_convexShape, start, end, callback2, collisionWorld->getDispatchInfo().m_allowedCcdPenetration); - if (!callback.hasHit()) { - //test a double fall height, to see if the character should interpolate it's fall (full) or not (partial) - m_ghostObject->convexSweepTest(m_convexShape, start, end_double, callback2, collisionWorld->getDispatchInfo().m_allowedCcdPenetration); - } + if (callback2.hasHit()) { + _currentPosition += callback2.m_closestHitFraction * step; + _verticalVelocity = 0.0f; + _verticalOffset = 0.0f; + _isJumping = false; + _isOnGround = true; } else { - collisionWorld->convexSweepTest(m_convexShape, start, end, callback, collisionWorld->getDispatchInfo().m_allowedCcdPenetration); - - if (!callback.hasHit()) { - //test a double fall height, to see if the character should interpolate it's fall (large) or not (small) - collisionWorld->convexSweepTest(m_convexShape, start, end_double, callback2, collisionWorld->getDispatchInfo().m_allowedCcdPenetration); - } + // nothing to step down on + _lastStepUp = 0.0f; } - - btScalar downVelocity2 = (m_verticalVelocity<0.f?-m_verticalVelocity:0.f) * dt; - bool has_hit = false; - if(bounce_fix == true) { - has_hit = callback.hasHit() || callback2.hasHit(); - } else { - has_hit = callback2.hasHit(); - } - - if(downVelocity2 > 0.0 && downVelocity2 < m_stepHeight && has_hit == true && runonce == false - && (m_wasOnGround || !m_wasJumping)) { - //redo the velocity calculation when falling a small amount, for fast stairs motion - //for larger falls, use the smoother/slower interpolated movement by not touching the target position - - m_targetPosition = orig_position; - downVelocity = m_stepHeight; - - btVector3 step_drop = getUpAxisDirections()[m_upAxis] * (m_currentStepOffset + downVelocity); - m_targetPosition -= step_drop; - runonce = true; - continue; //re-run previous tests - } - break; - } - - if (callback.hasHit() || runonce == true) { - // we dropped a fraction of the height -> hit floor - - btScalar fraction = (m_currentPosition.getY() - callback.m_hitPointWorld.getY()) / 2; - - //printf("hitpoint: %g - pos %g\n", callback.m_hitPointWorld.getY(), m_currentPosition.getY()); - - if (bounce_fix == true) { - if (full_drop == true) { - m_currentPosition.setInterpolate3 (m_currentPosition, m_targetPosition, callback.m_closestHitFraction); - } else { - //due to errors in the closestHitFraction variable when used with large polygons, calculate the hit fraction manually - m_currentPosition.setInterpolate3 (m_currentPosition, m_targetPosition, fraction); - } - } - else - m_currentPosition.setInterpolate3 (m_currentPosition, m_targetPosition, callback.m_closestHitFraction); - - full_drop = false; - - m_verticalVelocity = 0.0; - m_verticalOffset = 0.0; - m_wasJumping = false; } else { - // we dropped the full height - - full_drop = true; - - if (bounce_fix == true) { - downVelocity = (m_verticalVelocity<0.f?-m_verticalVelocity:0.f) * dt; - if (downVelocity > m_fallSpeed && (m_wasOnGround || !m_wasJumping)) { - m_targetPosition += step_drop; //undo previous target change - downVelocity = m_fallSpeed; - step_drop = getUpAxisDirections()[m_upAxis] * (m_currentStepOffset + downVelocity); - m_targetPosition -= step_drop; - } - } - //printf("full drop - %g, %g\n", m_currentPosition.getY(), m_targetPosition.getY()); - - m_currentPosition = m_targetPosition; + // 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) { - //printf("setVelocity!\n"); - //printf(" interval: %f\n", timeInterval); - //printf(" velocity: (%f, %f, %f)\n", velocity.x(), velocity.y(), velocity.z()); - - m_useWalkDirection = false; - m_walkDirection = velocity; - m_normalizedDirection = getNormalizedVector(m_walkDirection); - m_velocityTimeInterval += timeInterval; + _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; + _isOnGround = false; + _isJumping = 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()); } } @@ -560,147 +593,315 @@ 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) { +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) { - //printf("character could not recover from penetration = %d\n", numPenetrationLoops); break; } } - m_currentPosition = m_ghostObject->getWorldTransform().getOrigin(); - m_targetPosition = m_currentPosition; - //printf("m_targetPosition=%f,%f,%f\n", m_targetPosition[0], m_targetPosition[1], m_targetPosition[2]); + const btTransform& transform = _ghostObject->getWorldTransform(); + _currentRotation = transform.getRotation(); + _currentPosition = transform.getOrigin(); + _targetPosition = _currentPosition; } -void CharacterController::playerStep( btCollisionWorld* collisionWorld, btScalar dt) { - //printf("playerStep(): "); - //printf(" dt = %f", dt); - - // quick check... - if (!m_useWalkDirection && m_velocityTimeInterval <= 0.0) { - //printf("\n"); +void CharacterController::playerStep(btCollisionWorld* collisionWorld, btScalar dt) { + if (!_enabled) { return; // no motion } - m_wasOnGround = onGround(); - // Update fall velocity. - m_verticalVelocity -= m_gravity * dt; - if (m_verticalVelocity > 0.0 && m_verticalVelocity > m_jumpSpeed) { - m_verticalVelocity = m_jumpSpeed; + if (_isHovering) { + const btScalar HOVER_RELAXATION_TIMESCALE = 1.0f; + _verticalVelocity *= (1.0f - dt / HOVER_RELAXATION_TIMESCALE); + } else { + _verticalVelocity -= _gravity * dt; + if (_verticalVelocity > _jumpSpeed) { + _verticalVelocity = _jumpSpeed; + } else if (_verticalVelocity < -_maxFallSpeed) { + _verticalVelocity = -_maxFallSpeed; + } } - if (m_verticalVelocity < 0.0 && btFabs(m_verticalVelocity) > btFabs(m_fallSpeed)) { - m_verticalVelocity = -btFabs(m_fallSpeed); - } - m_verticalOffset = m_verticalVelocity * dt; - + _verticalOffset = _verticalVelocity * dt; btTransform xform; - xform = m_ghostObject->getWorldTransform(); + xform = _ghostObject->getWorldTransform(); - //printf("walkDirection(%f,%f,%f)\n", walkDirection[0], walkDirection[1], walkDirection[2]); - //printf("walkSpeed=%f\n", walkSpeed); + // the algorithm is as follows: + // (1) step the character up a little bit so that its forward step doesn't hit the floor + // (2) step the character forward + // (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) { - stepForwardAndStrafe(collisionWorld, m_walkDirection); - } else { - //printf(" time: %f", m_velocityTimeInterval); - // still have some time left for moving! - btScalar dtMoving = - (dt < m_velocityTimeInterval) ? dt : m_velocityTimeInterval; - m_velocityTimeInterval -= dt; + scanDown(collisionWorld); - // how far will we move while we are moving? - btVector3 move = m_walkDirection * dtMoving; + stepUp(collisionWorld); - //printf(" dtMoving: %f", dtMoving); + // compute substep and decrement total interval + btScalar dtMoving = (dt < _velocityTimeInterval) ? dt : _velocityTimeInterval; + _velocityTimeInterval -= dt; + _stepDt += dt; + + // stepForward substep + btVector3 move = _walkDirection * dtMoving; + stepForward(collisionWorld, move); - // okay, step - stepForwardAndStrafe(collisionWorld, move); - } stepDown(collisionWorld, dt); - //printf("\n"); - - xform.setOrigin(m_currentPosition); - m_ghostObject->setWorldTransform(xform); + xform.setOrigin(_currentPosition); + _ghostObject->setWorldTransform(xform); } -void CharacterController::setFallSpeed(btScalar fallSpeed) { - m_fallSpeed = fallSpeed; +void CharacterController::setMaxFallSpeed(btScalar 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 { - return onGround(); + return _isOnGround; } void CharacterController::jump() { - if (!canJump()) { - return; + _pendingFlags |= PENDING_FLAG_JUMP; + + // check for case where user is holding down "jump" key... + // we'll eventually tansition to "hover" + if (!_isHovering) { + if (!_isJumping) { + _jumpToHoverStart = usecTimestampNow(); + } else { + quint64 now = usecTimestampNow(); + const quint64 JUMP_TO_HOVER_PERIOD = USECS_PER_SECOND; + if (now - _jumpToHoverStart < JUMP_TO_HOVER_PERIOD) { + _isHovering = true; + } + } } - - m_verticalVelocity = m_jumpSpeed; - m_wasJumping = true; - -#if 0 - currently no jumping. - btTransform xform; - m_rigidBody->getMotionState()->getWorldTransform(xform); - btVector3 up = xform.getBasis()[1]; - up.normalize(); - btScalar magnitude = (btScalar(1.0)/m_rigidBody->getInvMass()) * btScalar(8.0); - m_rigidBody->applyCentralImpulse (up * magnitude); -#endif } void CharacterController::setGravity(btScalar gravity) { - m_gravity = gravity; + _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_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 _isOnGround; } 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. } + +void CharacterController::setLocalBoundingBox(const glm::vec3& corner, const glm::vec3& scale) { + _boxScale = scale; + + 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; + } + + // 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 + } else { + if (_dynamicsWorld) { + // must REMOVE from world prior to shape update + _pendingFlags |= PENDING_FLAG_REMOVE_FROM_SIMULATION; + } + _pendingFlags |= PENDING_FLAG_UPDATE_SHAPE; + // only need to ADD back when we happen to be enabled + if (_enabled) { + _pendingFlags |= PENDING_FLAG_ADD_TO_SIMULATION; + } + } + + // it's ok to change offset immediately -- there are no thread safety issues here + _shapeLocalOffset = corner + 0.5f * _boxScale; +} + +bool CharacterController::needsAddition() const { + return (bool)(_pendingFlags & PENDING_FLAG_ADD_TO_SIMULATION); +} + +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; + _isHovering = true; + } else { + if (_dynamicsWorld) { + _pendingFlags |= PENDING_FLAG_REMOVE_FROM_SIMULATION; + } + _pendingFlags &= ~ PENDING_FLAG_ADD_TO_SIMULATION; + _isOnGround = false; + } + _enabled = enabled; + } +} + +void CharacterController::setDynamicsWorld(btDynamicsWorld* world) { + if (_dynamicsWorld != world) { + if (_dynamicsWorld) { + if (_ghostObject) { + _dynamicsWorld->removeCollisionObject(_ghostObject); + _dynamicsWorld->removeAction(this); + } + _dynamicsWorld = NULL; + } + if (world && _ghostObject) { + _dynamicsWorld = world; + _pendingFlags &= ~ PENDING_FLAG_JUMP; + _dynamicsWorld->addCollisionObject(_ghostObject, + btBroadphaseProxy::CharacterFilter, + btBroadphaseProxy::StaticFilter | btBroadphaseProxy::DefaultFilter); + _dynamicsWorld->addAction(this); + reset(_dynamicsWorld); + } + } + if (_dynamicsWorld) { + if (_pendingFlags & PENDING_FLAG_UPDATE_SHAPE) { + // shouldn't fall in here, but if we do make sure both ADD and REMOVE bits are still set + _pendingFlags |= PENDING_FLAG_ADD_TO_SIMULATION | PENDING_FLAG_REMOVE_FROM_SIMULATION; + } else { + _pendingFlags &= ~PENDING_FLAG_ADD_TO_SIMULATION; + } + } else { + _pendingFlags &= ~ PENDING_FLAG_REMOVE_FROM_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 + _stepUpHeight = _radius + 0.25f * _halfHeight + 0.1f; + _stepDownHeight = _radius; + + // 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) { + if (_enabled && _dynamicsWorld) { + glm::quat rotation = _avatarData->getOrientation(); + glm::vec3 position = _avatarData->getPosition() + rotation * _shapeLocalOffset; + btVector3 walkVelocity = glmToBullet(_avatarData->getVelocity()); + + _ghostObject->setWorldTransform(btTransform(glmToBullet(rotation), glmToBullet(position))); + setVelocityForTimeInterval(walkVelocity, timeStep); + if (_pendingFlags & PENDING_FLAG_JUMP) { + _pendingFlags &= ~ PENDING_FLAG_JUMP; + if (canJump()) { + _verticalVelocity = _jumpSpeed; + _isJumping = true; + } + } + // remember last position so we can throttle the total motion from the next step + _lastPosition = position; + _stepDt = 0.0f; + } +} + +void CharacterController::postSimulation() { + if (_enabled && _ghostObject) { + const btTransform& avatarTransform = _ghostObject->getWorldTransform(); + glm::quat rotation = bulletToGLM(avatarTransform.getRotation()); + glm::vec3 position = bulletToGLM(avatarTransform.getOrigin()); + + // cap the velocity of the step so that the character doesn't POP! so hard on steps + glm::vec3 finalStep = position - _lastPosition; + btVector3 finalVelocity = _walkDirection; + btVector3 up = quatRotate(_currentRotation, LOCAL_UP_AXIS); + finalVelocity += _verticalVelocity * up; + const btScalar MAX_RESOLUTION_SPEED = 5.0f; // m/sec + btScalar maxStepLength = glm::max(MAX_RESOLUTION_SPEED, 2.0f * finalVelocity.length()) * _stepDt; + btScalar stepLength = glm::length(finalStep); + if (stepLength > maxStepLength) { + position = _lastPosition + (maxStepLength / stepLength) * finalStep; + // NOTE: we don't need to move ghostObject to throttled position unless + // we want to support do async ray-traces/collision-queries against character + } + + _avatarData->setOrientation(rotation); + _avatarData->setPosition(position - rotation * _shapeLocalOffset); + } +} + diff --git a/libraries/physics/src/CharacterController.h b/libraries/physics/src/CharacterController.h index 301253b2bd..eeaa5836dd 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. @@ -19,6 +20,8 @@ subject to the following restrictions: #ifndef hifi_CharacterController_h #define hifi_CharacterController_h +#include + #include #include #include @@ -35,75 +38,79 @@ 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: - btScalar m_halfHeight; + AvatarData* _avatarData = NULL; + btPairCachingGhostObject* _ghostObject; - btPairCachingGhostObject* m_ghostObject; - btConvexShape* m_convexShape;//is also in m_ghostObject, but it needs to be convex, so we store it here to avoid upcast + 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; - btScalar m_fallSpeed; - btScalar m_jumpSpeed; - btScalar m_maxJumpHeight; - btScalar m_maxSlopeRadians; // Slope angle that is set (used for returning the exact value) - btScalar m_maxSlopeCosine; // Cosine equivalent of m_maxSlopeRadians (calculated once when set, for optimization) - btScalar m_gravity; + btScalar _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 _stepUpHeight; // height of stepUp prior to stepForward + btScalar _stepDownHeight; // height of stepDown - btScalar m_stepHeight; - - 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; - btScalar m_currentStepOffset; - btVector3 m_targetPosition; + btVector3 _currentPosition; + btQuaternion _currentRotation; + btVector3 _targetPosition; + glm::vec3 _lastPosition; + btScalar _lastStepUp; ///keep track of the contact manifolds - btManifoldArray m_manifoldArray; + btManifoldArray _manifoldArray; - bool m_touchingContact; - btVector3 m_touchingNormal; + bool _touchingContact; + btVector3 _floorNormal; // points from object to character - bool m_wasOnGround; - bool m_wasJumping; - bool m_useGhostObjectSweepTest; - bool m_useWalkDirection; - btScalar m_velocityTimeInterval; - int m_upAxis; + bool _enabled; + bool _isOnGround; + bool _isJumping; + bool _isHovering; + quint64 _jumpToHoverStart; + btScalar _velocityTimeInterval; + btScalar _stepDt; + 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); btVector3 perpindicularComponent(const btVector3& direction, const btVector3& normal); bool recoverFromPenetration(btCollisionWorld* collisionWorld); + void scanDown(btCollisionWorld* collisionWorld); void stepUp(btCollisionWorld* collisionWorld); void updateTargetPositionBasedOnCollision(const btVector3& hit_normal, btScalar tangentMag = btScalar(0.0), btScalar normalMag = btScalar(1.0)); - void stepForwardAndStrafe(btCollisionWorld* collisionWorld, const btVector3& walkMove); + void stepForward(btCollisionWorld* collisionWorld, const btVector3& walkMove); void stepDown(btCollisionWorld* collisionWorld, btScalar dt); + void createShapeAndGhost(); public: BT_DECLARE_ALIGNED_ALLOCATOR(); - CharacterController( - btPairCachingGhostObject* ghostObject, - btConvexShape* convexShape, - btScalar stepHeight, - int upAxis = 1); + CharacterController(AvatarData* avatarData); ~CharacterController(); @@ -116,14 +123,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 @@ -139,18 +138,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); - void setFallSpeed(btScalar fallSpeed); + 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; @@ -161,12 +161,21 @@ public: btScalar getMaxSlope() const; btPairCachingGhostObject* getGhostObject(); - void setUseGhostSweepTest(bool useGhostObjectSweepTest) { - m_useGhostObjectSweepTest = useGhostObjectSweepTest; - } - bool onGround() const; void setUpInterpolate(bool value); + + bool needsRemoval() const; + bool needsAddition() const; + void setEnabled(bool enabled); + bool isEnabled() const { return _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(); }; #endif // hifi_CharacterController_h diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index cd0769255b..35eb006655 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -169,7 +169,9 @@ void EntityMotionState::updateObjectVelocities() { } void EntityMotionState::computeShapeInfo(ShapeInfo& shapeInfo) { - _entity->computeShapeInfo(shapeInfo); + if (_entity->isReadyToComputeShape()) { + _entity->computeShapeInfo(shapeInfo); + } } float EntityMotionState::computeMass(const ShapeInfo& shapeInfo) const { diff --git a/libraries/physics/src/PhysicsEngine.cpp b/libraries/physics/src/PhysicsEngine.cpp index 90224caa0c..f7dc90e72f 100644 --- a/libraries/physics/src/PhysicsEngine.cpp +++ b/libraries/physics/src/PhysicsEngine.cpp @@ -24,12 +24,17 @@ uint32_t PhysicsEngine::getNumSubsteps() { } PhysicsEngine::PhysicsEngine(const glm::vec3& offset) - : _originOffset(offset), - _avatarShapeLocalOffset(0.0f) { + : _originOffset(offset) { } PhysicsEngine::~PhysicsEngine() { // TODO: delete engine components... if we ever plan to create more than one instance + delete _collisionConfig; + delete _collisionDispatcher; + delete _broadphaseFilter; + delete _constraintSolver; + delete _dynamicsWorld; + // delete _ghostPairCallback; } // begin EntitySimulation overrides @@ -279,17 +284,14 @@ void PhysicsEngine::init(EntityEditPacketSender* packetSender) { } void PhysicsEngine::stepSimulation() { - // expect the engine to have an avatar (and hence: a character controller) - assert(_avatarData); - lock(); // NOTE: the grand order of operations is: - // (1) relay incoming changes + // (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; @@ -298,24 +300,25 @@ void PhysicsEngine::stepSimulation() { _clock.reset(); float timeStep = btMin(dt, MAX_TIMESTEP); - _avatarData->lockForRead(); - if (_avatarData->isPhysicsEnabled()) { - // update character controller - glm::quat rotation = _avatarData->getOrientation(); - glm::vec3 position = _avatarData->getPosition() + rotation * _avatarShapeLocalOffset; - _avatarGhostObject->setWorldTransform(btTransform(glmToBullet(rotation), glmToBullet(position))); - - btVector3 walkVelocity = glmToBullet(_avatarData->getVelocity()); - _characterController->setVelocityForTimeInterval(walkVelocity, timeStep); + // 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); } - _avatarData->unlock(); - // This is step (2). + // 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. // @@ -326,21 +329,11 @@ void PhysicsEngine::stepSimulation() { // // TODO: untangle these lock sequences. _entityTree->lockForWrite(); - lock(); _dynamicsWorld->synchronizeMotionStates(); - _avatarData->lockForRead(); - bool avatarHasPhysicsEnabled = _avatarData->isPhysicsEnabled(); - _avatarData->unlock(); - if (avatarHasPhysicsEnabled) { - const btTransform& avatarTransform = _avatarGhostObject->getWorldTransform(); - glm::quat rotation = bulletToGLM(avatarTransform.getRotation()); - glm::vec3 offset = rotation * _avatarShapeLocalOffset; - _avatarData->lockForWrite(); - _avatarData->setOrientation(rotation); - _avatarData->setPosition(bulletToGLM(avatarTransform.getOrigin()) - offset); - _avatarData->unlock(); + if (_characterController) { + _characterController->postSimulation(); } unlock(); @@ -620,76 +613,11 @@ bool PhysicsEngine::updateObjectHard(btRigidBody* body, ObjectMotionState* motio return true; } - - -void PhysicsEngine::setAvatarData(AvatarData *avatarData) { - assert(avatarData); // don't pass NULL argument - - // compute capsule dimensions - AABox box = avatarData->getLocalAABox(); - const glm::vec3& diagonal = box.getScale(); - float radius = 0.5f * sqrtf(0.5f * (diagonal.x * diagonal.x + diagonal.z * diagonal.z)); - float halfHeight = 0.5f * diagonal.y - radius; - float MIN_HALF_HEIGHT = 0.1f; - if (halfHeight < MIN_HALF_HEIGHT) { - halfHeight = MIN_HALF_HEIGHT; +void PhysicsEngine::setCharacterController(CharacterController* character) { + if (!_characterController) { + lock(); + _characterController = character; + unlock(); } - glm::vec3 offset = box.getCorner() + 0.5f * diagonal; - - if (!_avatarData) { - // _avatarData is being initialized - _avatarData = avatarData; - } else { - // _avatarData is being updated - assert(_avatarData == avatarData); - - // get old dimensions from shape - btCapsuleShape* capsule = static_cast(_avatarGhostObject->getCollisionShape()); - btScalar oldRadius = capsule->getRadius(); - btScalar oldHalfHeight = capsule->getHalfHeight(); - - // compare dimensions (and offset) - float radiusDelta = glm::abs(radius - oldRadius); - float heightDelta = glm::abs(halfHeight - oldHalfHeight); - if (radiusDelta < FLT_EPSILON && heightDelta < FLT_EPSILON) { - // shape hasn't changed --> nothing to do - float offsetDelta = glm::distance(offset, _avatarShapeLocalOffset); - if (offsetDelta > FLT_EPSILON) { - // if only the offset changes then we can update it --> no need to rebuild shape - _avatarShapeLocalOffset = offset; - } - return; - } - - // delete old controller and friends - _dynamicsWorld->removeCollisionObject(_avatarGhostObject); - _dynamicsWorld->removeAction(_characterController); - delete _characterController; - _characterController = NULL; - delete _avatarGhostObject; - _avatarGhostObject = NULL; - delete capsule; - } - - // set offset - _avatarShapeLocalOffset = offset; - - // build ghost, shape, and controller - _avatarGhostObject = new btPairCachingGhostObject(); - _avatarGhostObject->setWorldTransform(btTransform(glmToBullet(_avatarData->getOrientation()), - glmToBullet(_avatarData->getPosition()))); - // ?TODO: use ShapeManager to get avatar's shape? - btCapsuleShape* capsule = new btCapsuleShape(radius, 2.0f * halfHeight); - - _avatarGhostObject->setCollisionShape(capsule); - _avatarGhostObject->setCollisionFlags(btCollisionObject::CF_CHARACTER_OBJECT); - - const float MIN_STEP_HEIGHT = 0.35f; - btScalar stepHeight = glm::max(MIN_STEP_HEIGHT, radius + 0.5f * halfHeight); - _characterController = new CharacterController(_avatarGhostObject, capsule, stepHeight); - - _dynamicsWorld->addCollisionObject(_avatarGhostObject, btBroadphaseProxy::CharacterFilter, - btBroadphaseProxy::StaticFilter | btBroadphaseProxy::DefaultFilter); - _dynamicsWorld->addAction(_characterController); - _characterController->reset(_dynamicsWorld); } + diff --git a/libraries/physics/src/PhysicsEngine.h b/libraries/physics/src/PhysicsEngine.h index acf1617b16..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 @@ -123,9 +121,6 @@ private: /// character collisions CharacterController* _characterController = NULL; - class btPairCachingGhostObject* _avatarGhostObject = NULL; - AvatarData* _avatarData = NULL; - glm::vec3 _avatarShapeLocalOffset; }; #endif // hifi_PhysicsEngine_h diff --git a/libraries/physics/src/ShapeInfoUtil.cpp b/libraries/physics/src/ShapeInfoUtil.cpp index 116be984b9..8900c5a0dc 100644 --- a/libraries/physics/src/ShapeInfoUtil.cpp +++ b/libraries/physics/src/ShapeInfoUtil.cpp @@ -29,6 +29,9 @@ int ShapeInfoUtil::toBulletShapeType(int shapeInfoType) { case SHAPE_TYPE_CONVEX_HULL: bulletShapeType = CONVEX_HULL_SHAPE_PROXYTYPE; break; + case SHAPE_TYPE_COMPOUND: + bulletShapeType = COMPOUND_SHAPE_PROXYTYPE; + break; } return bulletShapeType; } @@ -48,6 +51,9 @@ int ShapeInfoUtil::fromBulletShapeType(int bulletShapeType) { case CONVEX_HULL_SHAPE_PROXYTYPE: shapeInfoType = SHAPE_TYPE_CONVEX_HULL; break; + case COMPOUND_SHAPE_PROXYTYPE: + shapeInfoType = SHAPE_TYPE_COMPOUND; + break; } return shapeInfoType; } @@ -70,12 +76,34 @@ void ShapeInfoUtil::collectInfoFromShape(const btCollisionShape* shape, ShapeInf const btConvexHullShape* convexHullShape = static_cast(shape); const int numPoints = convexHullShape->getNumPoints(); const btVector3* btPoints = convexHullShape->getUnscaledPoints(); - QVector points; + QVector> points; + QVector childPoints; for (int i = 0; i < numPoints; i++) { glm::vec3 point(btPoints->getX(), btPoints->getY(), btPoints->getZ()); - points << point; + childPoints << point; } - info.setConvexHull(points); + points << childPoints; + info.setConvexHulls(points); + } + break; + case SHAPE_TYPE_COMPOUND: { + const btCompoundShape* compoundShape = static_cast(shape); + const int numChildShapes = compoundShape->getNumChildShapes(); + QVector> points; + for (int i = 0; i < numChildShapes; i ++) { + const btCollisionShape* childShape = compoundShape->getChildShape(i); + const btConvexHullShape* convexHullShape = static_cast(childShape); + const int numPoints = convexHullShape->getNumPoints(); + const btVector3* btPoints = convexHullShape->getUnscaledPoints(); + + QVector childPoints; + for (int j = 0; j < numPoints; j++) { + glm::vec3 point(btPoints->getX(), btPoints->getY(), btPoints->getZ()); + childPoints << point; + } + points << childPoints; + } + info.setConvexHulls(points); } break; default: { @@ -108,12 +136,32 @@ btCollisionShape* ShapeInfoUtil::createShapeFromInfo(const ShapeInfo& info) { } break; case SHAPE_TYPE_CONVEX_HULL: { - shape = new btConvexHullShape(); - const QVector& points = info.getPoints(); - foreach (glm::vec3 point, points) { + auto hull = new btConvexHullShape(); + const QVector>& points = info.getPoints(); + foreach (glm::vec3 point, points[0]) { btVector3 btPoint(point[0], point[1], point[2]); - static_cast(shape)->addPoint(btPoint); + hull->addPoint(btPoint, false); } + hull->recalcLocalAabb(); + shape = hull; + } + break; + case SHAPE_TYPE_COMPOUND: { + auto compound = new btCompoundShape(); + const QVector>& points = info.getPoints(); + + btTransform trans; + trans.setIdentity(); + foreach (QVector hullPoints, points) { + auto hull = new btConvexHullShape(); + foreach (glm::vec3 point, hullPoints) { + btVector3 btPoint(point[0], point[1], point[2]); + hull->addPoint(btPoint, false); + } + hull->recalcLocalAabb(); + compound->addChildShape (trans, hull); + } + shape = compound; } break; } diff --git a/libraries/physics/src/ShapeInfoUtil.h b/libraries/physics/src/ShapeInfoUtil.h index fb59f30c69..9585161440 100644 --- a/libraries/physics/src/ShapeInfoUtil.h +++ b/libraries/physics/src/ShapeInfoUtil.h @@ -20,6 +20,8 @@ // translates between ShapeInfo and btShape namespace ShapeInfoUtil { + + // XXX is collectInfoFromShape no longer strictly needed? void collectInfoFromShape(const btCollisionShape* shape, ShapeInfo& info); btCollisionShape* createShapeFromInfo(const ShapeInfo& info); diff --git a/libraries/physics/src/ShapeManager.cpp b/libraries/physics/src/ShapeManager.cpp index 513fbfa7a5..b4d322a4a3 100644 --- a/libraries/physics/src/ShapeManager.cpp +++ b/libraries/physics/src/ShapeManager.cpp @@ -9,6 +9,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include + #include #include "ShapeInfoUtil.h" @@ -35,6 +37,7 @@ btCollisionShape* ShapeManager::getShape(const ShapeInfo& info) { const float MIN_SHAPE_DIAGONAL_SQUARED = 3.0e-4f; // 1 cm cube const float MAX_SHAPE_DIAGONAL_SQUARED = 3.0e4f; // 100 m cube if (diagonal < MIN_SHAPE_DIAGONAL_SQUARED || diagonal > MAX_SHAPE_DIAGONAL_SQUARED) { + // qDebug() << "ShapeManager::getShape -- not making shape due to size" << diagonal; return NULL; } DoubleHashKey key = info.getHash(); @@ -100,6 +103,18 @@ void ShapeManager::collectGarbage() { DoubleHashKey& key = _pendingGarbage[i]; ShapeReference* shapeRef = _shapeMap.find(key); if (shapeRef && shapeRef->refCount == 0) { + // if the shape we're about to delete is compound, delete the children first. + auto shapeType = ShapeInfoUtil::fromBulletShapeType(shapeRef->shape->getShapeType()); + if (shapeType == SHAPE_TYPE_COMPOUND) { + const btCompoundShape* compoundShape = static_cast(shapeRef->shape); + const int numChildShapes = compoundShape->getNumChildShapes(); + QVector> points; + for (int i = 0; i < numChildShapes; i ++) { + const btCollisionShape* childShape = compoundShape->getChildShape(i); + delete childShape; + } + } + delete shapeRef->shape; _shapeMap.remove(key); } diff --git a/libraries/render-utils/src/DeferredLightingEffect.cpp b/libraries/render-utils/src/DeferredLightingEffect.cpp index 96ab790cd6..ffa84adbdf 100644 --- a/libraries/render-utils/src/DeferredLightingEffect.cpp +++ b/libraries/render-utils/src/DeferredLightingEffect.cpp @@ -134,7 +134,7 @@ void DeferredLightingEffect::addPointLight(const glm::vec3& position, float radi void DeferredLightingEffect::addSpotLight(const glm::vec3& position, float radius, const glm::vec3& color, float intensity, const glm::quat& orientation, float exponent, float cutoff) { - int lightID = _pointLights.size() + _spotLights.size() + _globalLights.size(); + unsigned int lightID = _pointLights.size() + _spotLights.size() + _globalLights.size(); if (lightID >= _allocatedLights.size()) { _allocatedLights.push_back(model::LightPointer(new model::Light())); } @@ -494,18 +494,13 @@ void DeferredLightingEffect::loadLightProgram(const char* fragSource, bool limit locations.invViewMat = program.uniformLocation("invViewMat"); GLint loc = -1; -#if defined(Q_OS_MAC) - loc = program.uniformLocation("lightBuffer"); - if (loc >= 0) { - locations.lightBufferUnit = loc; - } else { - locations.lightBufferUnit = -1; - } -#elif defined(Q_OS_WIN) + +#if (GPU_FEATURE_PROFILE == GPU_CORE) + const GLint LIGHT_GPU_SLOT = 3; loc = glGetUniformBlockIndex(program.programId(), "lightBuffer"); if (loc >= 0) { - glUniformBlockBinding(program.programId(), loc, 0); - locations.lightBufferUnit = 0; + glUniformBlockBinding(program.programId(), loc, LIGHT_GPU_SLOT); + locations.lightBufferUnit = LIGHT_GPU_SLOT; } else { locations.lightBufferUnit = -1; } @@ -518,18 +513,12 @@ void DeferredLightingEffect::loadLightProgram(const char* fragSource, bool limit } #endif -#if defined(Q_OS_MAC) - loc = program.uniformLocation("atmosphereBufferUnit"); - if (loc >= 0) { - locations.atmosphereBufferUnit = loc; - } else { - locations.atmosphereBufferUnit = -1; - } -#elif defined(Q_OS_WIN) +#if (GPU_FEATURE_PROFILE == GPU_CORE) + const GLint ATMOSPHERE_GPU_SLOT = 4; loc = glGetUniformBlockIndex(program.programId(), "atmosphereBufferUnit"); if (loc >= 0) { - glUniformBlockBinding(program.programId(), loc, 1); - locations.atmosphereBufferUnit = 1; + glUniformBlockBinding(program.programId(), loc, ATMOSPHERE_GPU_SLOT); + locations.atmosphereBufferUnit = ATMOSPHERE_GPU_SLOT; } else { locations.atmosphereBufferUnit = -1; } diff --git a/libraries/render-utils/src/GeometryCache.cpp b/libraries/render-utils/src/GeometryCache.cpp index 29f76291ea..f8fc4633cc 100644 --- a/libraries/render-utils/src/GeometryCache.cpp +++ b/libraries/render-utils/src/GeometryCache.cpp @@ -21,6 +21,7 @@ #include #include +#include #include #include "TextureCache.h" @@ -1770,8 +1771,8 @@ void GeometryCache::renderLine(const glm::vec2& p1, const glm::vec2& p2, } -QSharedPointer GeometryCache::getGeometry(const QUrl& url, const QUrl& fallback, bool delayLoad, bool block) { - return getResource(url, fallback, delayLoad, NULL, block).staticCast(); +QSharedPointer GeometryCache::getGeometry(const QUrl& url, const QUrl& fallback, bool delayLoad) { + return getResource(url, fallback, delayLoad, NULL).staticCast(); } QSharedPointer GeometryCache::createResource(const QUrl& url, diff --git a/libraries/render-utils/src/GeometryCache.h b/libraries/render-utils/src/GeometryCache.h index a64d041fc1..37156a6c71 100644 --- a/libraries/render-utils/src/GeometryCache.h +++ b/libraries/render-utils/src/GeometryCache.h @@ -203,8 +203,7 @@ public: /// Loads geometry from the specified URL. /// \param fallback a fallback URL to load if the desired one is unavailable /// \param delayLoad if true, don't load the geometry immediately; wait until load is first requested - QSharedPointer getGeometry(const QUrl& url, const QUrl& fallback = QUrl(), - bool delayLoad = false, bool block = true); + QSharedPointer getGeometry(const QUrl& url, const QUrl& fallback = QUrl(), bool delayLoad = false); protected: diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index d5e15ed2c8..81c9f9448c 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -36,13 +36,13 @@ #include "Model.h" #include "model_vert.h" -#include "model_shadow_vert.h" -#include "model_normal_map_vert.h" -#include "model_lightmap_vert.h" -#include "model_lightmap_normal_map_vert.h" -#include "skin_model_vert.h" -#include "skin_model_shadow_vert.h" -#include "skin_model_normal_map_vert.h" +#include "model_shadow_vert.h" +#include "model_normal_map_vert.h" +#include "model_lightmap_vert.h" +#include "model_lightmap_normal_map_vert.h" +#include "skin_model_vert.h" +#include "skin_model_shadow_vert.h" +#include "skin_model_normal_map_vert.h" #include "model_frag.h" #include "model_shadow_frag.h" @@ -94,26 +94,26 @@ Model::~Model() { deleteGeometry(); } -ProgramObject Model::_program; -ProgramObject Model::_normalMapProgram; -ProgramObject Model::_specularMapProgram; -ProgramObject Model::_normalSpecularMapProgram; -ProgramObject Model::_translucentProgram; +gpu::ShaderPointer Model::_program; +gpu::ShaderPointer Model::_normalMapProgram; +gpu::ShaderPointer Model::_specularMapProgram; +gpu::ShaderPointer Model::_normalSpecularMapProgram; +gpu::ShaderPointer Model::_translucentProgram; -ProgramObject Model::_lightmapProgram; -ProgramObject Model::_lightmapNormalMapProgram; -ProgramObject Model::_lightmapSpecularMapProgram; -ProgramObject Model::_lightmapNormalSpecularMapProgram; +gpu::ShaderPointer Model::_lightmapProgram; +gpu::ShaderPointer Model::_lightmapNormalMapProgram; +gpu::ShaderPointer Model::_lightmapSpecularMapProgram; +gpu::ShaderPointer Model::_lightmapNormalSpecularMapProgram; -ProgramObject Model::_shadowProgram; +gpu::ShaderPointer Model::_shadowProgram; -ProgramObject Model::_skinProgram; -ProgramObject Model::_skinNormalMapProgram; -ProgramObject Model::_skinSpecularMapProgram; -ProgramObject Model::_skinNormalSpecularMapProgram; -ProgramObject Model::_skinTranslucentProgram; +gpu::ShaderPointer Model::_skinProgram; +gpu::ShaderPointer Model::_skinNormalMapProgram; +gpu::ShaderPointer Model::_skinSpecularMapProgram; +gpu::ShaderPointer Model::_skinNormalSpecularMapProgram; +gpu::ShaderPointer Model::_skinTranslucentProgram; -ProgramObject Model::_skinShadowProgram; +gpu::ShaderPointer Model::_skinShadowProgram; Model::Locations Model::_locations; Model::Locations Model::_normalMapLocations; @@ -135,6 +135,8 @@ Model::SkinLocations Model::_skinTranslucentLocations; AbstractViewStateInterface* Model::_viewState = NULL; +const GLint MATERIAL_GPU_SLOT = 3; + void Model::setScale(const glm::vec3& scale) { setScaleInternal(scale); // if anyone sets scale manually, then we are no longer scaled to fit @@ -165,104 +167,31 @@ void Model::setOffset(const glm::vec3& offset) { _snappedToRegistrationPoint = false; } -void Model::initProgram(ProgramObject& program, Model::Locations& locations, bool link) { - if (link) { - program.bindAttributeLocation("tangent", gpu::Stream::TANGENT); - program.bindAttributeLocation("texcoord1", gpu::Stream::TEXCOORD1); - program.link(); - } - program.bind(); +void Model::initProgram(gpu::ShaderPointer& program, Model::Locations& locations) { + locations.alphaThreshold = program->getUniforms().findLocation("alphaThreshold"); + locations.texcoordMatrices = program->getUniforms().findLocation("texcoordMatrices"); + locations.emissiveParams = program->getUniforms().findLocation("emissiveParams"); + locations.glowIntensity = program->getUniforms().findLocation("glowIntensity"); - locations.tangent = program.attributeLocation("tangent"); + locations.specularTextureUnit = program->getTextures().findLocation("specularMap"); + locations.emissiveTextureUnit = program->getTextures().findLocation("emissiveMap"); - locations.alphaThreshold = program.uniformLocation("alphaThreshold"); - locations.texcoordMatrices = program.uniformLocation("texcoordMatrices"); - locations.emissiveParams = program.uniformLocation("emissiveParams"); - locations.glowIntensity = program.uniformLocation("glowIntensity"); - program.setUniformValue("diffuseMap", 0); - program.setUniformValue("normalMap", 1); - - int loc = program.uniformLocation("specularMap"); - if (loc >= 0) { - program.setUniformValue("specularMap", 2); - locations.specularTextureUnit = 2; - } else { - locations.specularTextureUnit = -1; - } - - loc = program.uniformLocation("emissiveMap"); - if (loc >= 0) { - program.setUniformValue("emissiveMap", 3); - locations.emissiveTextureUnit = 3; - } else { - locations.emissiveTextureUnit = -1; - } - - // bindable uniform version -#if defined(Q_OS_MAC) - loc = program.uniformLocation("materialBuffer"); - if (loc >= 0) { - locations.materialBufferUnit = loc; - } else { - locations.materialBufferUnit = -1; - } -#elif defined(Q_OS_WIN) - loc = glGetUniformBlockIndex(program.programId(), "materialBuffer"); - if (loc >= 0) { - glUniformBlockBinding(program.programId(), loc, 1); - locations.materialBufferUnit = 1; - } else { - locations.materialBufferUnit = -1; - } +#if (GPU_FEATURE_PROFILE == GPU_CORE) + locations.materialBufferUnit = program->getBuffers().findLocation("materialBuffer"); #else - loc = program.uniformLocation("materialBuffer"); - if (loc >= 0) { - locations.materialBufferUnit = loc; - } else { - locations.materialBufferUnit = -1; - } + locations.materialBufferUnit = program->getUniforms().findLocation("materialBuffer"); #endif -#if defined(Q_OS_WIN) - loc = glGetUniformBlockIndex(program.programId(), "transformObjectBuffer"); - if (loc >= 0) { - glUniformBlockBinding(program.programId(), loc, gpu::TRANSFORM_OBJECT_SLOT); - // locations.materialBufferUnit = 1; - } -#endif - -#if defined(Q_OS_WIN) - loc = glGetUniformBlockIndex(program.programId(), "transformCameraBuffer"); - if (loc >= 0) { - glUniformBlockBinding(program.programId(), loc, gpu::TRANSFORM_CAMERA_SLOT); - // locations.materialBufferUnit = 1; - } -#endif - - //program.link(); - if (!program.isLinked()) { - program.release(); - } - - program.release(); } -void Model::initSkinProgram(ProgramObject& program, Model::SkinLocations& locations) { - program.bindAttributeLocation("tangent", gpu::Stream::TANGENT); - program.bindAttributeLocation("texcoord1", gpu::Stream::TEXCOORD1); - program.bindAttributeLocation("clusterIndices", gpu::Stream::SKIN_CLUSTER_INDEX); - program.bindAttributeLocation("clusterWeights", gpu::Stream::SKIN_CLUSTER_WEIGHT); - program.link(); +void Model::initSkinProgram(gpu::ShaderPointer& program, Model::SkinLocations& locations) { - initProgram(program, locations, false); + initProgram(program, locations); - program.bind(); + locations.clusterMatrices = program->getUniforms().findLocation("clusterMatrices"); - locations.clusterMatrices = program.uniformLocation("clusterMatrices"); - locations.clusterIndices = program.attributeLocation("clusterIndices"); - locations.clusterWeights = program.attributeLocation("clusterWeights"); - - program.release(); + locations.clusterIndices = program->getInputs().findLocation("clusterIndices");; + locations.clusterWeights = program->getInputs().findLocation("clusterWeights");; } QVector Model::createJointStates(const FBXGeometry& geometry) { @@ -298,10 +227,9 @@ void Model::initJointTransforms() { } void Model::init() { - if (!_program.isLinked()) { -/* //Work in progress not used yet + if (_program.isNull()) { gpu::Shader::BindingSet slotBindings; - slotBindings.insert(gpu::Shader::Binding(std::string("materialBuffer"), 1)); + slotBindings.insert(gpu::Shader::Binding(std::string("materialBuffer"), MATERIAL_GPU_SLOT)); slotBindings.insert(gpu::Shader::Binding(std::string("diffuseMap"), 0)); slotBindings.insert(gpu::Shader::Binding(std::string("normalMap"), 1)); slotBindings.insert(gpu::Shader::Binding(std::string("specularMap"), 2)); @@ -333,124 +261,72 @@ void Model::init() { bool makeResult = false; // Programs - auto program = gpu::ShaderPointer(gpu::Shader::createProgram(modelVertex, modelPixel)); - makeResult = gpu::Shader::makeProgram(*program, slotBindings); - - auto normalMapProgram = gpu::ShaderPointer(gpu::Shader::createProgram(modelNormalMapVertex, modelNormalMapPixel)); - makeResult = gpu::Shader::makeProgram(*normalMapProgram, slotBindings); - - auto specularMapProgram = gpu::ShaderPointer(gpu::Shader::createProgram(modelVertex, modelSpecularMapPixel)); - makeResult = gpu::Shader::makeProgram(*specularMapProgram, slotBindings); - - auto normalSpecularMapProgram = gpu::ShaderPointer(gpu::Shader::createProgram(modelNormalMapVertex, modelNormalSpecularMapPixel)); - makeResult = gpu::Shader::makeProgram(*normalSpecularMapProgram, slotBindings); - - auto translucentProgram = gpu::ShaderPointer(gpu::Shader::createProgram(modelVertex, modelTranslucentPixel)); - makeResult = gpu::Shader::makeProgram(*translucentProgram, slotBindings); - - auto shadowProgram = gpu::ShaderPointer(gpu::Shader::createProgram(modelShadowVertex, modelShadowPixel)); - makeResult = gpu::Shader::makeProgram(*shadowProgram, slotBindings); - - auto lightmapProgram = gpu::ShaderPointer(gpu::Shader::createProgram(modelLightmapVertex, modelLightmapPixel)); - makeResult = gpu::Shader::makeProgram(*lightmapProgram, slotBindings); - - auto lightmapNormalMapProgram = gpu::ShaderPointer(gpu::Shader::createProgram(modelLightmapNormalMapVertex, modelLightmapNormalMapPixel)); - makeResult = gpu::Shader::makeProgram(*lightmapNormalMapProgram, slotBindings); - - auto lightmapSpecularMapProgram = gpu::ShaderPointer(gpu::Shader::createProgram(modelLightmapVertex, modelLightmapSpecularMapPixel)); - makeResult = gpu::Shader::makeProgram(*lightmapSpecularMapProgram, slotBindings); - - auto lightmapNormalSpecularMapProgram = gpu::ShaderPointer(gpu::Shader::createProgram(modelLightmapNormalMapVertex, modelLightmapNormalSpecularMapPixel)); - makeResult = gpu::Shader::makeProgram(*lightmapNormalSpecularMapProgram, slotBindings); - - auto skinProgram = gpu::ShaderPointer(gpu::Shader::createProgram(skinModelVertex, modelPixel)); - makeResult = gpu::Shader::makeProgram(*skinProgram, slotBindings); - - auto skinNormalMapProgram = gpu::ShaderPointer(gpu::Shader::createProgram(skinModelNormalMapVertex, modelNormalMapPixel)); - makeResult = gpu::Shader::makeProgram(*skinNormalMapProgram, slotBindings); - - auto skinSpecularMapProgram = gpu::ShaderPointer(gpu::Shader::createProgram(skinModelVertex, modelSpecularMapPixel)); - makeResult = gpu::Shader::makeProgram(*skinSpecularMapProgram, slotBindings); - - auto skinNormalSpecularMapProgram = gpu::ShaderPointer(gpu::Shader::createProgram(skinModelNormalMapVertex, modelNormalSpecularMapPixel)); - makeResult = gpu::Shader::makeProgram(*skinNormalSpecularMapProgram, slotBindings); - - auto skinShadowProgram = gpu::ShaderPointer(gpu::Shader::createProgram(skinModelShadowVertex, modelShadowPixel)); - makeResult = gpu::Shader::makeProgram(*skinShadowProgram, slotBindings); - - auto skinTranslucentProgram = gpu::ShaderPointer(gpu::Shader::createProgram(skinModelVertex, modelTranslucentPixel)); - makeResult = gpu::Shader::makeProgram(*skinTranslucentProgram, slotBindings); -*/ - - _program.addShaderFromSourceCode(QGLShader::Vertex, model_vert); - _program.addShaderFromSourceCode(QGLShader::Fragment, model_frag); + _program = gpu::ShaderPointer(gpu::Shader::createProgram(modelVertex, modelPixel)); + makeResult = gpu::Shader::makeProgram(*_program, slotBindings); initProgram(_program, _locations); - - _normalMapProgram.addShaderFromSourceCode(QGLShader::Vertex, model_normal_map_vert); - _normalMapProgram.addShaderFromSourceCode(QGLShader::Fragment, model_normal_map_frag); - initProgram(_normalMapProgram, _normalMapLocations); - - _specularMapProgram.addShaderFromSourceCode(QGLShader::Vertex, model_vert); - _specularMapProgram.addShaderFromSourceCode(QGLShader::Fragment, model_specular_map_frag); - initProgram(_specularMapProgram, _specularMapLocations); - - _normalSpecularMapProgram.addShaderFromSourceCode(QGLShader::Vertex, model_normal_map_vert); - _normalSpecularMapProgram.addShaderFromSourceCode(QGLShader::Fragment, model_normal_specular_map_frag); - initProgram(_normalSpecularMapProgram, _normalSpecularMapLocations); - - _translucentProgram.addShaderFromSourceCode(QGLShader::Vertex, model_vert); - _translucentProgram.addShaderFromSourceCode(QGLShader::Fragment, model_translucent_frag); - initProgram(_translucentProgram, _translucentLocations); - - // Lightmap - _lightmapProgram.addShaderFromSourceCode(QGLShader::Vertex, model_lightmap_vert); - _lightmapProgram.addShaderFromSourceCode(QGLShader::Fragment, model_lightmap_frag); - initProgram(_lightmapProgram, _lightmapLocations); - - _lightmapNormalMapProgram.addShaderFromSourceCode(QGLShader::Vertex, model_lightmap_normal_map_vert); - _lightmapNormalMapProgram.addShaderFromSourceCode(QGLShader::Fragment, model_lightmap_normal_map_frag); - initProgram(_lightmapNormalMapProgram, _lightmapNormalMapLocations); - - _lightmapSpecularMapProgram.addShaderFromSourceCode(QGLShader::Vertex, model_lightmap_vert); - _lightmapSpecularMapProgram.addShaderFromSourceCode(QGLShader::Fragment, model_lightmap_specular_map_frag); - initProgram(_lightmapSpecularMapProgram, _lightmapSpecularMapLocations); - - _lightmapNormalSpecularMapProgram.addShaderFromSourceCode(QGLShader::Vertex, model_lightmap_normal_map_vert); - _lightmapNormalSpecularMapProgram.addShaderFromSourceCode(QGLShader::Fragment, model_lightmap_normal_specular_map_frag); - initProgram(_lightmapNormalSpecularMapProgram, _lightmapNormalSpecularMapLocations); - // end lightmap - - - _shadowProgram.addShaderFromSourceCode(QGLShader::Vertex, model_shadow_vert); - _shadowProgram.addShaderFromSourceCode(QGLShader::Fragment, model_shadow_frag); - // Shadow program uses the same locations as standard rendering path but we still need to set the bindings - Model::Locations tempLoc; - initProgram(_shadowProgram, tempLoc); - - _skinProgram.addShaderFromSourceCode(QGLShader::Vertex, skin_model_vert); - _skinProgram.addShaderFromSourceCode(QGLShader::Fragment, model_frag); - initSkinProgram(_skinProgram, _skinLocations); - - _skinNormalMapProgram.addShaderFromSourceCode(QGLShader::Vertex, skin_model_normal_map_vert); - _skinNormalMapProgram.addShaderFromSourceCode(QGLShader::Fragment, model_normal_map_frag); - initSkinProgram(_skinNormalMapProgram, _skinNormalMapLocations); - - _skinSpecularMapProgram.addShaderFromSourceCode(QGLShader::Vertex, model_vert); - _skinSpecularMapProgram.addShaderFromSourceCode(QGLShader::Fragment, model_specular_map_frag); - initSkinProgram(_skinSpecularMapProgram, _skinSpecularMapLocations); - - _skinNormalSpecularMapProgram.addShaderFromSourceCode(QGLShader::Vertex, skin_model_normal_map_vert); - _skinNormalSpecularMapProgram.addShaderFromSourceCode(QGLShader::Fragment, model_normal_specular_map_frag); - initSkinProgram(_skinNormalSpecularMapProgram, _skinNormalSpecularMapLocations); - - _skinShadowProgram.addShaderFromSourceCode(QGLShader::Vertex, skin_model_shadow_vert); - _skinShadowProgram.addShaderFromSourceCode(QGLShader::Fragment, model_shadow_frag); - initSkinProgram(_skinShadowProgram, _skinShadowLocations); - - - _skinTranslucentProgram.addShaderFromSourceCode(QGLShader::Vertex, skin_model_vert); - _skinTranslucentProgram.addShaderFromSourceCode(QGLShader::Fragment, model_translucent_frag); + + _normalMapProgram = gpu::ShaderPointer(gpu::Shader::createProgram(modelNormalMapVertex, modelNormalMapPixel)); + makeResult = gpu::Shader::makeProgram(*_normalMapProgram, slotBindings); + initProgram(_normalMapProgram, _normalMapLocations); + + _specularMapProgram = gpu::ShaderPointer(gpu::Shader::createProgram(modelVertex, modelSpecularMapPixel)); + makeResult = gpu::Shader::makeProgram(*_specularMapProgram, slotBindings); + initProgram(_specularMapProgram, _specularMapLocations); + + _normalSpecularMapProgram = gpu::ShaderPointer(gpu::Shader::createProgram(modelNormalMapVertex, modelNormalSpecularMapPixel)); + makeResult = gpu::Shader::makeProgram(*_normalSpecularMapProgram, slotBindings); + initProgram(_normalSpecularMapProgram, _normalSpecularMapLocations); + + _translucentProgram = gpu::ShaderPointer(gpu::Shader::createProgram(modelVertex, modelTranslucentPixel)); + makeResult = gpu::Shader::makeProgram(*_translucentProgram, slotBindings); + initProgram(_translucentProgram, _translucentLocations); + + _shadowProgram = gpu::ShaderPointer(gpu::Shader::createProgram(modelShadowVertex, modelShadowPixel)); + makeResult = gpu::Shader::makeProgram(*_shadowProgram, slotBindings); + Model::Locations tempShadowLoc; + initProgram(_shadowProgram, tempShadowLoc); + + _lightmapProgram = gpu::ShaderPointer(gpu::Shader::createProgram(modelLightmapVertex, modelLightmapPixel)); + makeResult = gpu::Shader::makeProgram(*_lightmapProgram, slotBindings); + initProgram(_lightmapProgram, _lightmapLocations); + + _lightmapNormalMapProgram = gpu::ShaderPointer(gpu::Shader::createProgram(modelLightmapNormalMapVertex, modelLightmapNormalMapPixel)); + makeResult = gpu::Shader::makeProgram(*_lightmapNormalMapProgram, slotBindings); + initProgram(_lightmapNormalMapProgram, _lightmapNormalMapLocations); + + _lightmapSpecularMapProgram = gpu::ShaderPointer(gpu::Shader::createProgram(modelLightmapVertex, modelLightmapSpecularMapPixel)); + makeResult = gpu::Shader::makeProgram(*_lightmapSpecularMapProgram, slotBindings); + initProgram(_lightmapSpecularMapProgram, _lightmapSpecularMapLocations); + + _lightmapNormalSpecularMapProgram = gpu::ShaderPointer(gpu::Shader::createProgram(modelLightmapNormalMapVertex, modelLightmapNormalSpecularMapPixel)); + makeResult = gpu::Shader::makeProgram(*_lightmapNormalSpecularMapProgram, slotBindings); + initProgram(_lightmapNormalSpecularMapProgram, _lightmapNormalSpecularMapLocations); + + _skinProgram = gpu::ShaderPointer(gpu::Shader::createProgram(skinModelVertex, modelPixel)); + makeResult = gpu::Shader::makeProgram(*_skinProgram, slotBindings); + initSkinProgram(_skinProgram, _skinLocations); + + _skinNormalMapProgram = gpu::ShaderPointer(gpu::Shader::createProgram(skinModelNormalMapVertex, modelNormalMapPixel)); + makeResult = gpu::Shader::makeProgram(*_skinNormalMapProgram, slotBindings); + initSkinProgram(_skinNormalMapProgram, _skinNormalMapLocations); + + _skinSpecularMapProgram = gpu::ShaderPointer(gpu::Shader::createProgram(skinModelVertex, modelSpecularMapPixel)); + makeResult = gpu::Shader::makeProgram(*_skinSpecularMapProgram, slotBindings); + initSkinProgram(_skinSpecularMapProgram, _skinSpecularMapLocations); + + _skinNormalSpecularMapProgram = gpu::ShaderPointer(gpu::Shader::createProgram(skinModelNormalMapVertex, modelNormalSpecularMapPixel)); + makeResult = gpu::Shader::makeProgram(*_skinNormalSpecularMapProgram, slotBindings); + initSkinProgram(_skinNormalSpecularMapProgram, _skinNormalSpecularMapLocations); + + _skinShadowProgram = gpu::ShaderPointer(gpu::Shader::createProgram(skinModelShadowVertex, modelShadowPixel)); + makeResult = gpu::Shader::makeProgram(*_skinShadowProgram, slotBindings); + initSkinProgram(_skinShadowProgram, _skinShadowLocations); + + _skinTranslucentProgram = gpu::ShaderPointer(gpu::Shader::createProgram(skinModelVertex, modelTranslucentPixel)); + makeResult = gpu::Shader::makeProgram(*_skinTranslucentProgram, slotBindings); initSkinProgram(_skinTranslucentProgram, _skinTranslucentLocations); + + (void) makeResult; // quiet compiler } } @@ -851,21 +727,21 @@ bool Model::renderCore(float alpha, RenderMode mode, RenderArgs* args) { const float DEFAULT_ALPHA_THRESHOLD = 0.5f; - //renderMeshes(RenderMode mode, bool translucent, float alphaThreshold, bool hasTangents, bool hasSpecular, book isSkinned, args); + //renderMeshes(batch, mode, translucent, alphaThreshold, hasTangents, hasSpecular, isSkinned, args, forceRenderMeshes); int opaqueMeshPartsRendered = 0; - opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, false, false, false, false, args); - opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, false, false, false, true, args); - opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, false, false, true, false, args); - opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, false, false, true, true, args); - opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, false, true, false, false, args); - opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, false, true, false, true, args); - opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, false, true, true, false, args); - opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, false, true, true, true, args); + opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, false, false, false, false, args, true); + opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, false, false, false, true, args, true); + opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, false, false, true, false, args, true); + opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, false, false, true, true, args, true); + opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, false, true, false, false, args, true); + opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, false, true, false, true, args, true); + opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, false, true, true, false, args, true); + opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, false, true, true, true, args, true); - opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, true, false, false, false, args); - opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, true, false, true, false, args); - opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, true, true, false, false, args); - opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, true, true, true, false, args); + opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, true, false, false, false, args, true); + opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, true, false, true, false, args, true); + opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, true, true, false, false, args, true); + opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, true, true, true, false, args, true); // render translucent meshes afterwards //DependencyManager::get()->setPrimaryDrawBuffers(false, true, true); @@ -879,14 +755,14 @@ bool Model::renderCore(float alpha, RenderMode mode, RenderArgs* args) { int translucentMeshPartsRendered = 0; const float MOSTLY_OPAQUE_THRESHOLD = 0.75f; - translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, false, false, false, args); - translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, false, false, true, args); - translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, false, true, false, args); - translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, false, true, true, args); - translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, true, false, false, args); - translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, true, false, true, args); - translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, true, true, false, args); - translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, true, true, true, args); + translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, false, false, false, args, true); + translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, false, false, true, args, true); + translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, false, true, false, args, true); + translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, false, true, true, args, true); + translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, true, false, false, args, true); + translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, true, false, true, args, true); + translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, true, true, false, args, true); + translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, true, true, true, args, true); GLBATCH(glDisable)(GL_ALPHA_TEST); GLBATCH(glEnable)(GL_BLEND); @@ -903,14 +779,14 @@ bool Model::renderCore(float alpha, RenderMode mode, RenderArgs* args) { if (mode == DEFAULT_RENDER_MODE || mode == DIFFUSE_RENDER_MODE) { const float MOSTLY_TRANSPARENT_THRESHOLD = 0.0f; - translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, false, false, false, args); - translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, false, false, true, args); - translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, false, true, false, args); - translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, false, true, true, args); - translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, true, false, false, args); - translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, true, false, true, args); - translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, true, true, false, args); - translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, true, true, true, args); + translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, false, false, false, args, true); + translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, false, false, true, args, true); + translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, false, true, false, args, true); + translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, false, true, true, args, true); + translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, true, false, false, args, true); + translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, true, false, true, args, true); + translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, true, true, false, args, true); + translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, true, true, true, args, true); } GLBATCH(glDepthMask)(true); @@ -968,10 +844,67 @@ bool Model::renderCore(float alpha, RenderMode mode, RenderArgs* args) { args->_translucentMeshPartsRendered = translucentMeshPartsRendered; args->_opaqueMeshPartsRendered = opaqueMeshPartsRendered; } - + + #ifdef WANT_DEBUG_MESHBOXES + renderDebugMeshBoxes(); + #endif + return true; } +void Model::renderDebugMeshBoxes() { + int colorNdx = 0; + foreach(AABox box, _calculatedMeshBoxes) { + if (_debugMeshBoxesID == GeometryCache::UNKNOWN_ID) { + _debugMeshBoxesID = DependencyManager::get()->allocateID(); + } + QVector points; + + glm::vec3 brn = box.getCorner(); + glm::vec3 bln = brn + glm::vec3(box.getDimensions().x, 0, 0); + glm::vec3 brf = brn + glm::vec3(0, 0, box.getDimensions().z); + glm::vec3 blf = brn + glm::vec3(box.getDimensions().x, 0, box.getDimensions().z); + + glm::vec3 trn = brn + glm::vec3(0, box.getDimensions().y, 0); + glm::vec3 tln = bln + glm::vec3(0, box.getDimensions().y, 0); + glm::vec3 trf = brf + glm::vec3(0, box.getDimensions().y, 0); + glm::vec3 tlf = blf + glm::vec3(0, box.getDimensions().y, 0); + + points << brn << bln; + points << brf << blf; + points << brn << brf; + points << bln << blf; + + points << trn << tln; + points << trf << tlf; + points << trn << trf; + points << tln << tlf; + + points << brn << trn; + points << brf << trf; + points << bln << tln; + points << blf << tlf; + + glm::vec4 color[] = { + { 1.0f, 0.0f, 0.0f, 1.0f }, // red + { 0.0f, 1.0f, 0.0f, 1.0f }, // green + { 0.0f, 0.0f, 1.0f, 1.0f }, // blue + { 1.0f, 0.0f, 1.0f, 1.0f }, // purple + { 1.0f, 1.0f, 0.0f, 1.0f }, // yellow + { 0.0f, 1.0f, 1.0f, 1.0f }, // cyan + { 1.0f, 1.0f, 1.0f, 1.0f }, // white + { 0.0f, 0.5f, 0.0f, 1.0f }, + { 0.0f, 0.0f, 0.5f, 1.0f }, + { 0.5f, 0.0f, 0.5f, 1.0f }, + { 0.5f, 0.5f, 0.0f, 1.0f }, + { 0.0f, 0.5f, 0.5f, 1.0f } }; + + DependencyManager::get()->updateVertices(_debugMeshBoxesID, points, color[colorNdx]); + DependencyManager::get()->renderVertices(gpu::LINES, _debugMeshBoxesID); + colorNdx++; + } +} + Extents Model::getBindExtents() const { if (!isActive()) { return Extents(); @@ -1101,12 +1034,22 @@ void Model::setURL(const QUrl& url, const QUrl& fallback, bool retainCurrent, bo } } -void Model::setCollisionModelURL(const QUrl& url, const QUrl& fallback, bool delayLoad) { + +const QSharedPointer Model::getCollisionGeometry(bool delayLoad) +{ + if (_collisionGeometry.isNull() && !_collisionUrl.isEmpty()) { + _collisionGeometry = DependencyManager::get()->getGeometry(_collisionUrl, QUrl(), delayLoad); + } + + return _collisionGeometry; +} + +void Model::setCollisionModelURL(const QUrl& url) { if (_collisionUrl == url) { return; } _collisionUrl = url; - _collisionGeometry = DependencyManager::get()->getGeometry(url, fallback, delayLoad); + _collisionGeometry = DependencyManager::get()->getGeometry(url, QUrl(), true); } bool Model::getJointPositionInWorldFrame(int jointIndex, glm::vec3& position) const { @@ -1432,8 +1375,11 @@ void Model::updateJointState(int index) { glm::mat4 parentTransform = glm::scale(_scale) * glm::translate(_offset) * geometry.offset; state.computeTransform(parentTransform); } else { - const JointState& parentState = _jointStates.at(parentIndex); - state.computeTransform(parentState.getTransform(), parentState.getTransformChanged()); + // guard against out-of-bounds access to _jointStates + if (joint.parentIndex >= 0 && joint.parentIndex < _jointStates.size()) { + const JointState& parentState = _jointStates.at(parentIndex); + state.computeTransform(parentState.getTransform(), parentState.getTransformChanged()); + } } } @@ -2316,67 +2262,66 @@ QVector* Model::pickMeshList(bool translucent, float alphaThreshold, bool h void Model::pickPrograms(gpu::Batch& batch, RenderMode mode, bool translucent, float alphaThreshold, bool hasLightmap, bool hasTangents, bool hasSpecular, bool isSkinned, RenderArgs* args, Locations*& locations, SkinLocations*& skinLocations) { - - ProgramObject* program = &_program; + gpu::ShaderPointer program = _program; locations = &_locations; - ProgramObject* skinProgram = &_skinProgram; + gpu::ShaderPointer skinProgram = _skinProgram; skinLocations = &_skinLocations; if (mode == SHADOW_RENDER_MODE) { - program = &_shadowProgram; - skinProgram = &_skinShadowProgram; + program = _shadowProgram; + skinProgram = _skinShadowProgram; skinLocations = &_skinShadowLocations; } else if (translucent && alphaThreshold == 0.0f) { - program = &_translucentProgram; + program = _translucentProgram; locations = &_translucentLocations; - skinProgram = &_skinTranslucentProgram; + skinProgram = _skinTranslucentProgram; skinLocations = &_skinTranslucentLocations; } else if (hasLightmap) { if (hasTangents) { if (hasSpecular) { - program = &_lightmapNormalSpecularMapProgram; + program = _lightmapNormalSpecularMapProgram; locations = &_lightmapNormalSpecularMapLocations; - skinProgram = NULL; + skinProgram.reset(); skinLocations = NULL; } else { - program = &_lightmapNormalMapProgram; + program = _lightmapNormalMapProgram; locations = &_lightmapNormalMapLocations; - skinProgram = NULL; + skinProgram.reset(); skinLocations = NULL; } } else if (hasSpecular) { - program = &_lightmapSpecularMapProgram; + program = _lightmapSpecularMapProgram; locations = &_lightmapSpecularMapLocations; - skinProgram = NULL; + skinProgram.reset(); skinLocations = NULL; } else { - program = &_lightmapProgram; + program = _lightmapProgram; locations = &_lightmapLocations; - skinProgram = NULL; + skinProgram.reset(); skinLocations = NULL; } } else { if (hasTangents) { if (hasSpecular) { - program = &_normalSpecularMapProgram; + program = _normalSpecularMapProgram; locations = &_normalSpecularMapLocations; - skinProgram = &_skinNormalSpecularMapProgram; + skinProgram = _skinNormalSpecularMapProgram; skinLocations = &_skinNormalSpecularMapLocations; } else { - program = &_normalMapProgram; + program = _normalMapProgram; locations = &_normalMapLocations; - skinProgram = &_skinNormalMapProgram; + skinProgram = _skinNormalMapProgram; skinLocations = &_skinNormalMapLocations; } } else if (hasSpecular) { - program = &_specularMapProgram; + program = _specularMapProgram; locations = &_specularMapLocations; - skinProgram = &_skinSpecularMapProgram; + skinProgram = _skinSpecularMapProgram; skinLocations = &_skinSpecularMapLocations; } } - ProgramObject* activeProgram = program; + gpu::ShaderPointer activeProgram = program; Locations* activeLocations = locations; if (isSkinned) { @@ -2384,12 +2329,10 @@ void Model::pickPrograms(gpu::Batch& batch, RenderMode mode, bool translucent, f activeLocations = skinLocations; locations = skinLocations; } - // This code replace the "bind()" on the QGLProgram - if (!activeProgram->isLinked()) { - activeProgram->link(); - } + + GLuint glprogram = gpu::GLBackend::getShaderID(activeProgram); + GLBATCH(glUseProgram)(glprogram); - GLBATCH(glUseProgram)(activeProgram->programId()); if ((activeLocations->alphaThreshold > -1) && (mode != SHADOW_RENDER_MODE)) { GLBATCH(glUniform1f)(activeLocations->alphaThreshold, alphaThreshold); @@ -2428,7 +2371,8 @@ int Model::renderMeshesForModelsInScene(gpu::Batch& batch, RenderMode mode, bool } int Model::renderMeshes(gpu::Batch& batch, RenderMode mode, bool translucent, float alphaThreshold, - bool hasLightmap, bool hasTangents, bool hasSpecular, bool isSkinned, RenderArgs* args) { + bool hasLightmap, bool hasTangents, bool hasSpecular, bool isSkinned, RenderArgs* args, + bool forceRenderSomeMeshes) { PROFILE_RANGE(__FUNCTION__); int meshPartsRendered = 0; @@ -2448,8 +2392,10 @@ int Model::renderMeshes(gpu::Batch& batch, RenderMode mode, bool translucent, fl Locations* locations; SkinLocations* skinLocations; - pickPrograms(batch, mode, translucent, alphaThreshold, hasLightmap, hasTangents, hasSpecular, isSkinned, args, locations, skinLocations); - meshPartsRendered = renderMeshesFromList(list, batch, mode, translucent, alphaThreshold, args, locations, skinLocations); + pickPrograms(batch, mode, translucent, alphaThreshold, hasLightmap, hasTangents, hasSpecular, isSkinned, + args, locations, skinLocations); + meshPartsRendered = renderMeshesFromList(list, batch, mode, translucent, alphaThreshold, + args, locations, skinLocations, forceRenderSomeMeshes); GLBATCH(glUseProgram)(0); return meshPartsRendered; @@ -2457,7 +2403,7 @@ int Model::renderMeshes(gpu::Batch& batch, RenderMode mode, bool translucent, fl int Model::renderMeshesFromList(QVector& list, gpu::Batch& batch, RenderMode mode, bool translucent, float alphaThreshold, RenderArgs* args, - Locations* locations, SkinLocations* skinLocations) { + Locations* locations, SkinLocations* skinLocations, bool forceRenderSomeMeshes) { PROFILE_RANGE(__FUNCTION__); auto textureCache = DependencyManager::get(); @@ -2493,11 +2439,21 @@ int Model::renderMeshesFromList(QVector& list, gpu::Batch& batch, RenderMod // if we got here, then check to see if this mesh is in view if (args) { bool shouldRender = true; + bool forceRender = false; args->_meshesConsidered++; if (args->_viewFrustum) { - shouldRender = args->_viewFrustum->boxInFrustum(_calculatedMeshBoxes.at(i)) != ViewFrustum::OUTSIDE; - if (shouldRender) { + + // NOTE: This is a hack to address the fact that for avatar meshes, the _calculatedMeshBoxes can be wrong + // for some meshes. Those meshes where the mesh's modelTransform is the identity matrix, and will have + // incorrectly calculated mesh boxes. In this case, we will ignore the box and assume it's visible. + if (forceRenderSomeMeshes && (geometry.meshes.at(i).modelTransform == glm::mat4())) { + forceRender = true; + } + + shouldRender = forceRender || args->_viewFrustum->boxInFrustum(_calculatedMeshBoxes.at(i)) != ViewFrustum::OUTSIDE; + + if (shouldRender && !forceRender) { float distance = args->_viewFrustum->distanceToCamera(_calculatedMeshBoxes.at(i).calcCenter()); shouldRender = !_viewState ? false : _viewState->shouldRenderMesh(_calculatedMeshBoxes.at(i).getLargestDimension(), distance); diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 890ea87d25..3b4cbdd450 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -109,7 +109,7 @@ public: const QUrl& getURL() const { return _url; } // Set the model to use for collisions - Q_INVOKABLE void setCollisionModelURL(const QUrl& url, const QUrl& fallback = QUrl(), bool delayLoad = false); + Q_INVOKABLE void setCollisionModelURL(const QUrl& url); const QUrl& getCollisionURL() const { return _collisionUrl; } /// Sets the distance parameter used for LOD computations. @@ -134,7 +134,7 @@ public: const QSharedPointer& getGeometry() const { return _geometry; } /// Returns a reference to the shared collision geometry. - const QSharedPointer getCollisionGeometry() {return _collisionGeometry; } + const QSharedPointer getCollisionGeometry(bool delayLoad = true); /// Returns the number of joint states in the model. int getJointStateCount() const { return _jointStates.size(); } @@ -318,31 +318,27 @@ private: int _blendNumber; int _appliedBlendNumber; + static gpu::ShaderPointer _program; + static gpu::ShaderPointer _normalMapProgram; + static gpu::ShaderPointer _specularMapProgram; + static gpu::ShaderPointer _normalSpecularMapProgram; + static gpu::ShaderPointer _translucentProgram; - static ProgramObject _program; - static ProgramObject _normalMapProgram; - static ProgramObject _specularMapProgram; - static ProgramObject _normalSpecularMapProgram; - static ProgramObject _translucentProgram; + static gpu::ShaderPointer _lightmapProgram; + static gpu::ShaderPointer _lightmapNormalMapProgram; + static gpu::ShaderPointer _lightmapSpecularMapProgram; + static gpu::ShaderPointer _lightmapNormalSpecularMapProgram; - static ProgramObject _lightmapProgram; - static ProgramObject _lightmapNormalMapProgram; - static ProgramObject _lightmapSpecularMapProgram; - static ProgramObject _lightmapNormalSpecularMapProgram; - - static ProgramObject _shadowProgram; + static gpu::ShaderPointer _shadowProgram; - static ProgramObject _skinProgram; - static ProgramObject _skinNormalMapProgram; - static ProgramObject _skinSpecularMapProgram; - static ProgramObject _skinNormalSpecularMapProgram; - static ProgramObject _skinTranslucentProgram; + static gpu::ShaderPointer _skinProgram; + static gpu::ShaderPointer _skinNormalMapProgram; + static gpu::ShaderPointer _skinSpecularMapProgram; + static gpu::ShaderPointer _skinNormalSpecularMapProgram; + static gpu::ShaderPointer _skinTranslucentProgram; - static ProgramObject _skinShadowProgram; + static gpu::ShaderPointer _skinShadowProgram; - static int _normalMapTangentLocation; - static int _normalSpecularMapTangentLocation; - class Locations { public: int tangent; @@ -365,8 +361,9 @@ private: static Locations _lightmapNormalMapLocations; static Locations _lightmapSpecularMapLocations; static Locations _lightmapNormalSpecularMapLocations; - + static void initProgram(ProgramObject& program, Locations& locations, bool link = true); + static void initProgram(gpu::ShaderPointer& program, Locations& locations); class SkinLocations : public Locations { public: @@ -383,6 +380,7 @@ private: static SkinLocations _skinTranslucentLocations; static void initSkinProgram(ProgramObject& program, SkinLocations& locations); + static void initSkinProgram(gpu::ShaderPointer& program, SkinLocations& locations); QVector _calculatedMeshBoxes; // world coordinate AABoxes for all sub mesh boxes bool _calculatedMeshBoxesValid; @@ -446,6 +444,9 @@ private: QVector _meshesOpaqueLightmapTangentsSpecular; QVector _meshesOpaqueLightmapSpecular; + // debug rendering support + void renderDebugMeshBoxes(); + int _debugMeshBoxesID = GeometryCache::UNKNOWN_ID; // Scene rendering support static QVector _modelsInScene; @@ -458,12 +459,15 @@ private: void renderSetup(RenderArgs* args); bool renderCore(float alpha, RenderMode mode, RenderArgs* args); int renderMeshes(gpu::Batch& batch, RenderMode mode, bool translucent, float alphaThreshold, - bool hasLightmap, bool hasTangents, bool hasSpecular, bool isSkinned, RenderArgs* args = NULL); + bool hasLightmap, bool hasTangents, bool hasSpecular, bool isSkinned, RenderArgs* args = NULL, + bool forceRenderSomeMeshes = false); + void setupBatchTransform(gpu::Batch& batch); QVector* pickMeshList(bool translucent, float alphaThreshold, bool hasLightmap, bool hasTangents, bool hasSpecular, bool isSkinned); int renderMeshesFromList(QVector& list, gpu::Batch& batch, RenderMode mode, bool translucent, float alphaThreshold, - RenderArgs* args, Locations* locations, SkinLocations* skinLocations); + RenderArgs* args, Locations* locations, SkinLocations* skinLocations, + bool forceRenderSomeMeshes = false); static void pickPrograms(gpu::Batch& batch, RenderMode mode, bool translucent, float alphaThreshold, bool hasLightmap, bool hasTangents, bool hasSpecular, bool isSkinned, RenderArgs* args, diff --git a/libraries/render-utils/src/Shadow.slh b/libraries/render-utils/src/Shadow.slh index d4c19915ff..2e75e2764c 100755 --- a/libraries/render-utils/src/Shadow.slh +++ b/libraries/render-utils/src/Shadow.slh @@ -16,10 +16,8 @@ uniform sampler2DShadow shadowMap; // Fetching it float fetchShadow(vec3 texcoord) { -<@if GLPROFILE == PC_GL @> +<@if GPU_FEATURE_PROFILE == GPU_CORE @> return texture(shadowMap, texcoord); -<@elif GLPROFILE == MAC_GL@> - return shadow2D(shadowMap, texcoord).r; <@else@> return shadow2D(shadowMap, texcoord).r; <@endif@> diff --git a/libraries/render-utils/src/model.slv b/libraries/render-utils/src/model.slv index 4f416e8f1f..21c9238273 100755 --- a/libraries/render-utils/src/model.slv +++ b/libraries/render-utils/src/model.slv @@ -12,10 +12,14 @@ // <@include gpu/Transform.slh@> + +<$declareStandardTransform()$> + const int MAX_TEXCOORDS = 2; uniform mat4 texcoordMatrices[MAX_TEXCOORDS]; + // the interpolated normal varying vec4 normal; @@ -27,12 +31,11 @@ void main(void) { // and the texture coordinates gl_TexCoord[0] = texcoordMatrices[0] * vec4(gl_MultiTexCoord0.xy, 0.0, 1.0); - // use standard pipeline transform + // standard transform TransformCamera cam = getTransformCamera(); TransformObject obj = getTransformObject(); - gl_Position = transformModelToClipPos(cam, obj, gl_Vertex); - - // transform and store the normal for interpolation - normal = vec4(normalize(transformModelToEyeDir(cam, obj, gl_Normal)), 0.0); + <$transformModelToClipPos(cam, obj, gl_Vertex, gl_Position)$> + <$transformModelToEyeDir(cam, obj, gl_Normal, normal.xyz)$> + normal = vec4(normalize(normal.xyz), 0.0); } diff --git a/libraries/render-utils/src/model_lightmap.slv b/libraries/render-utils/src/model_lightmap.slv index afe3c73549..f24ba4e53d 100755 --- a/libraries/render-utils/src/model_lightmap.slv +++ b/libraries/render-utils/src/model_lightmap.slv @@ -14,6 +14,8 @@ <@include gpu/Transform.slh@> +<$declareStandardTransform()$> + const int MAX_TEXCOORDS = 2; uniform mat4 texcoordMatrices[MAX_TEXCOORDS]; @@ -35,12 +37,12 @@ void main(void) { // interpolatedTexcoord1 = vec2(texcoordMatrices[1] * vec4(gl_MultiTexCoord0.xy, 0.0, 1.0)).xy; interpolatedTexcoord1 = vec2(texcoordMatrices[1] * vec4(texcoord1.xy, 0.0, 1.0)).xy; - // use standard pipeline transform + // standard transform TransformCamera cam = getTransformCamera(); TransformObject obj = getTransformObject(); - gl_Position = transformModelToClipPos(cam, obj, gl_Vertex); - - // transform and store the normal for interpolation - normal = vec4(normalize(transformModelToEyeDir(cam, obj, gl_Normal)), 0.0); + <$transformModelToClipPos(cam, obj, gl_Vertex, gl_Position)$> + <$transformModelToEyeDir(cam, obj, gl_Normal, normal.xyz)$> + + normal = vec4(normalize(normal.xyz), 0.0); } diff --git a/libraries/render-utils/src/model_lightmap_normal_map.slv b/libraries/render-utils/src/model_lightmap_normal_map.slv index 6e66b28e63..a1413b1530 100755 --- a/libraries/render-utils/src/model_lightmap_normal_map.slv +++ b/libraries/render-utils/src/model_lightmap_normal_map.slv @@ -14,6 +14,8 @@ <@include gpu/Transform.slh@> +<$declareStandardTransform()$> + const int MAX_TEXCOORDS = 2; uniform mat4 texcoordMatrices[MAX_TEXCOORDS]; @@ -44,12 +46,13 @@ void main(void) { gl_TexCoord[0] = texcoordMatrices[0] * vec4(gl_MultiTexCoord0.xy, 0.0, 1.0); interpolatedTexcoord1 = vec2(texcoordMatrices[1] * vec4(texcoord1.xy, 0.0, 1.0)).xy; - // use standard pipeline transform + // standard transform TransformCamera cam = getTransformCamera(); TransformObject obj = getTransformObject(); - gl_Position = transformModelToClipPos(cam, obj, gl_Vertex); - - // transform and store the normal for interpolation - interpolatedNormal = vec4(normalize(transformModelToEyeDir(cam, obj, gl_Normal)), 0.0); - interpolatedTangent = vec4(normalize(transformModelToEyeDir(cam, obj, tangent)), 0.0); + <$transformModelToClipPos(cam, obj, gl_Vertex, gl_Position)$> + <$transformModelToEyeDir(cam, obj, gl_Normal, interpolatedNormal.xyz)$> + <$transformModelToEyeDir(cam, obj, tangent, interpolatedTangent.xyz)$> + + interpolatedNormal = vec4(normalize(interpolatedNormal.xyz), 0.0); + interpolatedTangent = vec4(normalize(interpolatedTangent.xyz), 0.0); } diff --git a/libraries/render-utils/src/model_normal_map.slv b/libraries/render-utils/src/model_normal_map.slv index 4111458464..9983310b52 100755 --- a/libraries/render-utils/src/model_normal_map.slv +++ b/libraries/render-utils/src/model_normal_map.slv @@ -13,6 +13,8 @@ // <@include gpu/Transform.slh@> + +<$declareStandardTransform()$> const int MAX_TEXCOORDS = 2; @@ -38,12 +40,13 @@ void main(void) { // and the texture coordinates gl_TexCoord[0] = texcoordMatrices[0] * vec4(gl_MultiTexCoord0.xy, 0.0, 1.0); - // use standard pipeline transform + // standard transform TransformCamera cam = getTransformCamera(); TransformObject obj = getTransformObject(); - gl_Position = transformModelToClipPos(cam, obj, gl_Vertex); - - // transform and store the normal for interpolation - interpolatedNormal = vec4(normalize(transformModelToEyeDir(cam, obj, gl_Normal)), 0.0); - interpolatedTangent = vec4(normalize(transformModelToEyeDir(cam, obj, tangent)), 0.0); + <$transformModelToClipPos(cam, obj, gl_Vertex, gl_Position)$> + <$transformModelToEyeDir(cam, obj, gl_Normal, interpolatedNormal.xyz)$> + <$transformModelToEyeDir(cam, obj, tangent, interpolatedTangent.xyz)$> + + interpolatedNormal = vec4(normalize(interpolatedNormal.xyz), 0.0); + interpolatedTangent = vec4(normalize(interpolatedTangent.xyz), 0.0); } diff --git a/libraries/render-utils/src/model_shadow.slv b/libraries/render-utils/src/model_shadow.slv index 98f6bf5104..88e597557e 100755 --- a/libraries/render-utils/src/model_shadow.slv +++ b/libraries/render-utils/src/model_shadow.slv @@ -12,10 +12,11 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // <@include gpu/Transform.slh@> +<$declareStandardTransform()$> void main(void) { - // use standard pipeline transform + // standard transform TransformCamera cam = getTransformCamera(); TransformObject obj = getTransformObject(); - gl_Position = transformModelToClipPos(cam, obj, gl_Vertex); + <$transformModelToClipPos(cam, obj, gl_Vertex, gl_Position)$> } diff --git a/libraries/render-utils/src/skin_model.slv b/libraries/render-utils/src/skin_model.slv index 780b72323c..f65d5a8bdc 100755 --- a/libraries/render-utils/src/skin_model.slv +++ b/libraries/render-utils/src/skin_model.slv @@ -13,6 +13,7 @@ // <@include gpu/Transform.slh@> +<$declareStandardTransform()$> const int MAX_TEXCOORDS = 2; const int MAX_CLUSTERS = 128; @@ -43,11 +44,11 @@ void main(void) { // and the texture coordinates gl_TexCoord[0] = texcoordMatrices[0] * vec4(gl_MultiTexCoord0.xy, 0.0, 1.0); - // use standard pipeline transform + // standard transform TransformCamera cam = getTransformCamera(); TransformObject obj = getTransformObject(); - gl_Position = transformModelToClipPos(cam, obj, position); - - // transform and store the normal for interpolation - normal = vec4(normalize(transformModelToEyeDir(cam, obj, normal.xyz)), 0.0); + <$transformModelToClipPos(cam, obj, position, gl_Position)$> + <$transformModelToEyeDir(cam, obj, normal.xyz, normal.xyz)$> + + normal = vec4(normalize(normal.xyz), 0.0); } diff --git a/libraries/render-utils/src/skin_model_normal_map.slv b/libraries/render-utils/src/skin_model_normal_map.slv index 43ec9c6a9d..6a4170152d 100755 --- a/libraries/render-utils/src/skin_model_normal_map.slv +++ b/libraries/render-utils/src/skin_model_normal_map.slv @@ -13,6 +13,7 @@ // <@include gpu/Transform.slh@> +<$declareStandardTransform()$> const int MAX_TEXCOORDS = 2; const int MAX_CLUSTERS = 128; @@ -44,22 +45,20 @@ void main(void) { interpolatedNormal += clusterMatrix * vec4(gl_Normal, 0.0) * clusterWeight; interpolatedTangent += clusterMatrix * vec4(tangent, 0.0) * clusterWeight; } - // interpolatedNormal = gl_ModelViewMatrix * interpolatedNormal; - // interpolatedTangent = gl_ModelViewMatrix * interpolatedTangent; - + // pass along the diffuse color gl_FrontColor = gl_Color; // and the texture coordinates gl_TexCoord[0] = texcoordMatrices[0] * vec4(gl_MultiTexCoord0.xy, 0.0, 1.0); - gl_Position = gl_ModelViewProjectionMatrix * interpolatedPosition; - - // use standard pipeline transform + // standard transform TransformCamera cam = getTransformCamera(); TransformObject obj = getTransformObject(); - gl_Position = transformModelToClipPos(cam, obj, interpolatedPosition); - - interpolatedNormal = vec4(normalize(transformModelToEyeDir(cam, obj, interpolatedNormal.xyz)), 0.0); - interpolatedTangent = vec4(normalize(transformModelToEyeDir(cam, obj, interpolatedTangent.xyz)), 0.0); + <$transformModelToClipPos(cam, obj, interpolatedPosition, gl_Position)$> + <$transformModelToEyeDir(cam, obj, gl_Normal, interpolatedNormal.xyz)$> + <$transformModelToEyeDir(cam, obj, tangent, interpolatedTangent.xyz)$> + + interpolatedNormal = vec4(normalize(interpolatedNormal.xyz), 0.0); + interpolatedTangent = vec4(normalize(interpolatedTangent.xyz), 0.0); } diff --git a/libraries/render-utils/src/skin_model_shadow.slv b/libraries/render-utils/src/skin_model_shadow.slv index de7f8f4e9f..03b912a981 100755 --- a/libraries/render-utils/src/skin_model_shadow.slv +++ b/libraries/render-utils/src/skin_model_shadow.slv @@ -13,6 +13,7 @@ // <@include gpu/Transform.slh@> +<$declareStandardTransform()$> const int MAX_CLUSTERS = 128; const int INDICES_PER_VERTEX = 4; @@ -30,8 +31,8 @@ void main(void) { position += clusterMatrix * gl_Vertex * clusterWeight; } - // use standard pipeline transform + // standard transform TransformCamera cam = getTransformCamera(); TransformObject obj = getTransformObject(); - gl_Position = transformModelToClipPos(cam, obj, position); + <$transformModelToClipPos(cam, obj, position, gl_Position)$> } diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 716af1d188..831db73a0a 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -360,10 +360,6 @@ void ScriptEngine::init() { globalObject().setProperty("TREE_SCALE", newVariant(QVariant(TREE_SCALE))); globalObject().setProperty("COLLISION_GROUP_ENVIRONMENT", newVariant(QVariant(COLLISION_GROUP_ENVIRONMENT))); globalObject().setProperty("COLLISION_GROUP_AVATARS", newVariant(QVariant(COLLISION_GROUP_AVATARS))); - - globalObject().setProperty("AVATAR_MOTION_OBEY_LOCAL_GRAVITY", newVariant(QVariant(AVATAR_MOTION_OBEY_LOCAL_GRAVITY))); - globalObject().setProperty("AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY", newVariant(QVariant(AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY))); - } QScriptValue ScriptEngine::registerGlobalObject(const QString& name, QObject* object) { diff --git a/libraries/script-engine/src/XMLHttpRequestClass.cpp b/libraries/script-engine/src/XMLHttpRequestClass.cpp index 116548db61..3054472a3c 100644 --- a/libraries/script-engine/src/XMLHttpRequestClass.cpp +++ b/libraries/script-engine/src/XMLHttpRequestClass.cpp @@ -22,6 +22,8 @@ #include "XMLHttpRequestClass.h" #include "ScriptEngine.h" +const QString METAVERSE_API_URL = "https://metaverse.highfidelity.com/api/"; + Q_DECLARE_METATYPE(QByteArray*) XMLHttpRequestClass::XMLHttpRequestClass(QScriptEngine* engine) : @@ -207,7 +209,7 @@ void XMLHttpRequestClass::open(const QString& method, const QString& url, bool a notImplemented(); } } else { - if (url.toLower().left(33) == "https://metaverse.highfidelity.io/api/") { + if (url.toLower().left(METAVERSE_API_URL.length()) == METAVERSE_API_URL) { AccountManager& accountManager = AccountManager::getInstance(); if (accountManager.hasValidAccessToken()) { diff --git a/libraries/shared/src/ShapeInfo.cpp b/libraries/shared/src/ShapeInfo.cpp index 61432830e7..5fe1fc230d 100644 --- a/libraries/shared/src/ShapeInfo.cpp +++ b/libraries/shared/src/ShapeInfo.cpp @@ -23,6 +23,7 @@ void ShapeInfo::clear() { void ShapeInfo::setParams(ShapeType type, const glm::vec3& halfExtents, QString url) { _type = type; + _points.clear(); switch(type) { case SHAPE_TYPE_NONE: _halfExtents = glm::vec3(0.0f); @@ -37,6 +38,12 @@ void ShapeInfo::setParams(ShapeType type, const glm::vec3& halfExtents, QString break; } case SHAPE_TYPE_CONVEX_HULL: + _url = QUrl(url); + // halfExtents aren't used by convex-hull or compound convex-hull except as part of + // the generation of the key for the ShapeManager. + _halfExtents = halfExtents; + break; + case SHAPE_TYPE_COMPOUND: _url = QUrl(url); _halfExtents = halfExtents; break; @@ -47,31 +54,44 @@ void ShapeInfo::setParams(ShapeType type, const glm::vec3& halfExtents, QString } void ShapeInfo::setBox(const glm::vec3& halfExtents) { + _url = ""; _type = SHAPE_TYPE_BOX; _halfExtents = halfExtents; + _points.clear(); _doubleHashKey.clear(); } void ShapeInfo::setSphere(float radius) { + _url = ""; _type = SHAPE_TYPE_SPHERE; _halfExtents = glm::vec3(radius, radius, radius); + _points.clear(); _doubleHashKey.clear(); } void ShapeInfo::setEllipsoid(const glm::vec3& halfExtents) { + _url = ""; _type = SHAPE_TYPE_ELLIPSOID; _halfExtents = halfExtents; + _points.clear(); _doubleHashKey.clear(); } -void ShapeInfo::setConvexHull(const QVector& points) { - _type = SHAPE_TYPE_CONVEX_HULL; +void ShapeInfo::setConvexHulls(const QVector>& points) { + if (points.size() == 1) { + _type = SHAPE_TYPE_CONVEX_HULL; + } else { + _type = SHAPE_TYPE_COMPOUND; + } _points = points; + _doubleHashKey.clear(); } void ShapeInfo::setCapsuleY(float radius, float halfHeight) { + _url = ""; _type = SHAPE_TYPE_CAPSULE_Y; _halfExtents = glm::vec3(radius, halfHeight, radius); + _points.clear(); _doubleHashKey.clear(); } diff --git a/libraries/shared/src/ShapeInfo.h b/libraries/shared/src/ShapeInfo.h index 0a55f7c51d..4dce121d64 100644 --- a/libraries/shared/src/ShapeInfo.h +++ b/libraries/shared/src/ShapeInfo.h @@ -44,14 +44,14 @@ public: void setBox(const glm::vec3& halfExtents); void setSphere(float radius); void setEllipsoid(const glm::vec3& halfExtents); - void setConvexHull(const QVector& points); + void setConvexHulls(const QVector>& points); void setCapsuleY(float radius, float halfHeight); const int getType() const { return _type; } const glm::vec3& getHalfExtents() const { return _halfExtents; } - const QVector& getPoints() const { return _points; } + const QVector>& getPoints() const { return _points; } void clearPoints () { _points.clear(); } void appendToPoints (const QVector& newPoints) { _points << newPoints; } @@ -64,8 +64,8 @@ protected: ShapeType _type = SHAPE_TYPE_NONE; glm::vec3 _halfExtents = glm::vec3(0.0f); DoubleHashKey _doubleHashKey; - QVector _points; // points for convex collision hull - QUrl _url; // url for model of convex collision hull + QVector> _points; // points for convex collision hulls + QUrl _url; // url for model of convex collision hulls }; #endif // hifi_ShapeInfo_h diff --git a/libraries/shared/src/SimpleMovingAverage.cpp b/libraries/shared/src/SimpleMovingAverage.cpp index 64198d2a06..90a9509c91 100644 --- a/libraries/shared/src/SimpleMovingAverage.cpp +++ b/libraries/shared/src/SimpleMovingAverage.cpp @@ -14,6 +14,7 @@ SimpleMovingAverage::SimpleMovingAverage(int numSamplesToAverage) : _numSamples(0), + _lastEventTimestamp(0), _average(0.0f), _eventDeltaAverage(0.0f), WEIGHTING(1.0f / numSamplesToAverage), diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index 5c7c306a62..ba2938aaa6 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -8,5 +8,5 @@ set_target_properties(scribe PROPERTIES FOLDER "Tools") find_package(VHACD) if(VHACD_FOUND) add_subdirectory(vhacd) -set_target_properties(vhacd PROPERTIES FOLDER "Tools") +# set_target_properties(vhacd PROPERTIES FOLDER "Tools") endif() diff --git a/tools/refresh-tags.sh b/tools/refresh-tags.sh index d3157fa179..e8040dac81 100755 --- a/tools/refresh-tags.sh +++ b/tools/refresh-tags.sh @@ -2,13 +2,13 @@ rm -f TAGS -find . -name *.h -print | while read I +find . -name *.h -print | grep -v build-ext |while read I do etags --append "$I" done -find . -name *.cpp -print | grep -v 'moc_' | while read I +find . -name *.cpp -print | grep -v 'moc_' | grep -v build-ext | while read I do etags --append "$I" done