From f88433b7bb25c1ad3862d1e302f4507dc32e9e45 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Fri, 27 Jan 2017 11:23:17 -0800 Subject: [PATCH 01/17] Add tealight.js entity server script --- .../marketplace/teaLight/teaLight.js | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 unpublishedScripts/marketplace/teaLight/teaLight.js diff --git a/unpublishedScripts/marketplace/teaLight/teaLight.js b/unpublishedScripts/marketplace/teaLight/teaLight.js new file mode 100644 index 0000000000..85b0a3c310 --- /dev/null +++ b/unpublishedScripts/marketplace/teaLight/teaLight.js @@ -0,0 +1,21 @@ +(function() { + var MINIMUM_LIGHT_INTENSITY = 100.0; + var MAXIMUM_LIGHT_INTENSITY = 125.0; + + // Return a random number between `low` (inclusive) and `high` (exclusive) + function randFloat(low, high) { + return low + Math.random() * (high - low); + } + + var self = this; + this.preload = function(entityID) { + self.intervalID = Script.setInterval(function() { + Entities.editEntity(entityID, { + intensity: randFloat(MINIMUM_LIGHT_INTENSITY, MAXIMUM_LIGHT_INTENSITY) + }); + }, 100); + }; + this.unload = function() { + Script.clearInterval(self.intervalID); + } +}); From 8f4586b9b8e66eb1317885456ed52464db50d854 Mon Sep 17 00:00:00 2001 From: humbletim Date: Sat, 28 Jan 2017 10:46:50 -0500 Subject: [PATCH 02/17] hide webview context menu when clicked --- interface/resources/QtWebEngine/UIDelegates/Menu.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/interface/resources/QtWebEngine/UIDelegates/Menu.qml b/interface/resources/QtWebEngine/UIDelegates/Menu.qml index 21c5e71394..5176d9d11e 100644 --- a/interface/resources/QtWebEngine/UIDelegates/Menu.qml +++ b/interface/resources/QtWebEngine/UIDelegates/Menu.qml @@ -33,6 +33,7 @@ Item { propagateComposedEvents: true acceptedButtons: "AllButtons" onClicked: { + menu.visible = false; menu.done(); mouse.accepted = false; } From 8c0d7f9e28fce4215c8f2e1ec378684640104764 Mon Sep 17 00:00:00 2001 From: humbletim Date: Sat, 28 Jan 2017 14:37:49 -0500 Subject: [PATCH 03/17] hide context menu on item click --- interface/resources/QtWebEngine/UIDelegates/MenuItem.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/interface/resources/QtWebEngine/UIDelegates/MenuItem.qml b/interface/resources/QtWebEngine/UIDelegates/MenuItem.qml index 561a8926e1..1890fcb81d 100644 --- a/interface/resources/QtWebEngine/UIDelegates/MenuItem.qml +++ b/interface/resources/QtWebEngine/UIDelegates/MenuItem.qml @@ -32,6 +32,7 @@ Item { MouseArea { anchors.fill: parent onClicked: { + menu.visible = false; root.triggered(); menu.done(); } From c3f9663ab0addb14ed9e3c6d64fe249d7f255b9f Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Tue, 31 Jan 2017 01:25:59 +0100 Subject: [PATCH 04/17] - Fix for simulation owned entities moving to 0,0,0 after cache reload - Fix for simulation priority, use SCRIPT_GRAB_SIMULATION_PRIORITY in EntityItem::grabSimulationOwnership() --- libraries/entities/src/EntityItem.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 64bc9fbd5a..52dad5e976 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -688,6 +688,14 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef somethingChanged = true; _simulationOwner.clearCurrentOwner(); } + } else if (newSimOwner.matchesValidID(myNodeID) && !(_dirtyFlags & Simulation::DIRTY_SIMULATION_OWNERSHIP_FOR_POKE) + && !(_dirtyFlags & Simulation::DIRTY_SIMULATION_OWNERSHIP_FOR_GRAB)) { + // entity-server tells us that we have simulation ownership while we never requested this for this EntityItem, + // this could happen when the user reloads the cache and entity tree. + _dirtyFlags |= Simulation::DIRTY_SIMULATOR_ID; + somethingChanged = true; + _simulationOwner.clearCurrentOwner(); + weOwnSimulation = false; } else if (_simulationOwner.set(newSimOwner)) { _dirtyFlags |= Simulation::DIRTY_SIMULATOR_ID; somethingChanged = true; @@ -1278,7 +1286,7 @@ void EntityItem::grabSimulationOwnership() { auto nodeList = DependencyManager::get(); if (_simulationOwner.matchesValidID(nodeList->getSessionUUID())) { // we already own it - _simulationOwner.promotePriority(SCRIPT_POKE_SIMULATION_PRIORITY); + _simulationOwner.promotePriority(SCRIPT_GRAB_SIMULATION_PRIORITY); } else { // we don't own it yet _simulationOwner.setPendingPriority(SCRIPT_GRAB_SIMULATION_PRIORITY, usecTimestampNow()); From 015aafe0fbe8de86f5d72dd7e45b2c02e16d4ade Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Mon, 30 Jan 2017 13:57:13 -0800 Subject: [PATCH 05/17] make table additions in DS settings clearer --- domain-server/resources/web/css/style.css | 4 + .../resources/web/settings/js/settings.js | 249 +++++++++++------- 2 files changed, 162 insertions(+), 91 deletions(-) diff --git a/domain-server/resources/web/css/style.css b/domain-server/resources/web/css/style.css index ad426671a4..553f408e15 100644 --- a/domain-server/resources/web/css/style.css +++ b/domain-server/resources/web/css/style.css @@ -125,6 +125,10 @@ tr.new-row { background-color: #dff0d8; } +tr.invalid-input { + background-color: #f2dede; +} + .graphable-stat { text-align: center; color: #5286BC; diff --git a/domain-server/resources/web/settings/js/settings.js b/domain-server/resources/web/settings/js/settings.js index 659372267c..fbc2aefceb 100644 --- a/domain-server/resources/web/settings/js/settings.js +++ b/domain-server/resources/web/settings/js/settings.js @@ -38,14 +38,15 @@ var Settings = { DOMAIN_ID_SELECTOR: '[name="metaverse.id"]', ACCESS_TOKEN_SELECTOR: '[name="metaverse.access_token"]', PLACES_TABLE_ID: 'places-table', - FORM_ID: 'settings-form' + FORM_ID: 'settings-form', + INVALID_ROW_CLASS: 'invalid-input' }; var viewHelpers = { getFormGroup: function(keypath, setting, values, isAdvanced) { form_group = "
"; setting_value = _(values).valueForKeyPath(keypath); @@ -891,23 +892,105 @@ function reloadSettings(callback) { }); } +function validateInputs() { + // check if any new values are bad + var tables = $('table'); + + var inputsValid = true; + + var tables = $('table'); + + // clear any current invalid rows + $('tr.' + Settings.INVALID_ROW_CLASS).removeClass(Settings.INVALID_ROW_CLASS); + + function markParentRowInvalid(rowChild) { + $(rowChild).closest('tr').addClass(Settings.INVALID_ROW_CLASS); + } + + _.each(tables, function(table) { + var inputs = $(table).find('tr.' + Settings.NEW_ROW_CLASS + ' input[data-changed="true"]'); + + var empty = false; + + _.each(inputs, function(input){ + var inputVal = $(input).val(); + + if (inputVal.length === 0) { + empty = true + markParentRowInvalid(input); + return; + } + }); + + if (empty) { + showErrorMessage("Error", "Empty field(s)"); + inputsValid = false; + return + } + + // validate keys specificially for spaces and equality to an existing key + var newKeys = $(table).find('tr.' + Settings.NEW_ROW_CLASS + ' td.key'); + + var keyWithSpaces = false; + var duplicateKey = false; + + _.each(newKeys, function(keyCell) { + var keyVal = $(keyCell).children('input').val(); + + if (keyVal.indexOf(' ') !== -1) { + keyWithSpaces = true; + markParentRowInvalid(keyCell); + return; + } + + // make sure we don't have duplicate keys in the table + var otherKeys = $(table).find('td.key').not(keyCell); + _.each(otherKeys, function(otherKeyCell) { + var keyInput = $(otherKeyCell).children('input'); + + if (keyInput.length) { + if ($(keyInput).val() == keyVal) { + duplicateKey = true; + } + } else if ($(otherKeyCell).html() == keyVal) { + duplicateKey = true; + } + + if (duplicateKey) { + markParentRowInvalid(keyCell); + return; + } + }); + + }); + + if (keyWithSpaces) { + showErrorMessage("Error", "Key contains spaces"); + inputsValid = false; + return + } + + if (duplicateKey) { + showErrorMessage("Error", "Two keys cannot be identical"); + inputsValid = false; + return; + } + }); + + return inputsValid; +} var SETTINGS_ERROR_MESSAGE = "There was a problem saving domain settings. Please try again!"; function saveSettings() { - // disable any inputs not changed - $("input:not([data-changed])").each(function(){ - $(this).prop('disabled', true); - }); - // grab a JSON representation of the form via form2js - var formJSON = form2js('settings-form', ".", false, cleanupFormValues, true); - - // check if we've set the basic http password - if so convert it to base64 + // verify that the password and confirmation match before saving var canPost = true; + if (formJSON["security"]) { var password = formJSON["security"]["http_password"]; var verify_password = formJSON["security"]["verify_http_password"]; + if (password && password.length > 0) { if (password != verify_password) { bootbox.alert({"message": "Passwords must match!", "title":"Password Error"}); @@ -919,23 +1002,46 @@ function saveSettings() { } } - console.log("----- SAVING ------"); - console.log(formJSON); + if (canPost && validateInputs()) { + // POST the form JSON to the domain-server settings.json endpoint so the settings are saved - // re-enable all inputs - $("input").each(function(){ - $(this).prop('disabled', false); - }); + // disable any inputs not changed + $("input:not([data-changed])").each(function(){ + $(this).prop('disabled', true); + }); - // remove focus from the button - $(this).blur(); + // grab a JSON representation of the form via form2js + var formJSON = form2js('settings-form', ".", false, cleanupFormValues, true); - // POST the form JSON to the domain-server settings.json endpoint so the settings are saved - if (canPost) { + // check if we've set the basic http password - if so convert it to base64 + if (formJSON["security"]) { + var password = formJSON["security"]["http_password"]; + if (password && password.length > 0) { + formJSON["security"]["http_password"] = sha256_digest(password); + } + } + + console.log("----- SAVING ------"); + console.log(formJSON); + + // re-enable all inputs + $("input").each(function(){ + $(this).prop('disabled', false); + }); + + // remove focus from the button + $(this).blur(); + + // POST the form JSON to the domain-server settings.json endpoint so the settings are saved postSettings(formJSON); } } +// disable any inputs not changed +$("input:not([data-changed])").each(function(){ + $(this).prop('disabled', true); +}); + $('body').on('click', '.save-button', function(e){ saveSettings(); return false; @@ -1110,8 +1216,9 @@ function makeTable(setting, keypath, setting_value) { if (setting.can_add_new_categories) { html += makeTableCategoryInput(setting, numVisibleColumns); } + if (setting.can_add_new_rows || setting.can_add_new_categories) { - html += makeTableInputs(setting, {}, ""); + html += makeTableHiddenInputs(setting, {}, ""); } } html += "" @@ -1137,7 +1244,7 @@ function makeTableCategoryHeader(categoryKey, categoryValue, numVisibleColumns, return html; } -function makeTableInputs(setting, initialValues, categoryValue) { +function makeTableHiddenInputs(setting, initialValues, categoryValue) { var html = ""; @@ -1148,7 +1255,7 @@ function makeTableInputs(setting, initialValues, categoryValue) { if (setting.key) { html += "\ - \ + \ " } @@ -1157,14 +1264,14 @@ function makeTableInputs(setting, initialValues, categoryValue) { if (col.type === "checkbox") { html += "" + - "" + ""; } else { html += "" + - "" + ""; @@ -1244,49 +1351,17 @@ function addTableRow(row) { var columns = row.parent().children('.' + Settings.DATA_ROW_CLASS); + var input_clone = row.clone(); + if (!isArray) { - // Check key spaces - var key = row.children(".key").children("input").val() - if (key.indexOf(' ') !== -1) { - showErrorMessage("Error", "Key contains spaces") - return - } - // Check keys with the same name - var equals = false; - _.each(columns.children(".key"), function(element) { - if ($(element).text() === key) { - equals = true - return - } - }) - if (equals) { - showErrorMessage("Error", "Two keys cannot be identical") - return - } + // show the key input + var keyInput = row.children(".key").children("input"); } - // Check empty fields - var empty = false; - _.each(row.children('.' + Settings.DATA_COL_CLASS + ' input'), function(element) { - if ($(element).val().length === 0) { - empty = true - return - } - }) - - if (empty) { - showErrorMessage("Error", "Empty field(s)") - return - } - - var input_clone = row.clone() - // Change input row to data row - var table = row.parents("table") - var setting_name = table.attr("name") - var full_name = setting_name + "." + key - row.addClass(Settings.DATA_ROW_CLASS + " " + Settings.NEW_ROW_CLASS) - row.removeClass("inputs") + var table = row.parents("table"); + var setting_name = table.attr("name"); + row.addClass(Settings.DATA_ROW_CLASS + " " + Settings.NEW_ROW_CLASS); _.each(row.children(), function(element) { if ($(element).hasClass("numbered")) { @@ -1308,34 +1383,17 @@ function addTableRow(row) { anchor.addClass(Settings.DEL_ROW_SPAN_CLASSES) } else if ($(element).hasClass("key")) { var input = $(element).children("input") - $(element).html(input.val()) - input.remove() + input.show(); } else if ($(element).hasClass(Settings.DATA_COL_CLASS)) { - // Hide inputs - var input = $(element).find("input") - var isCheckbox = false; - var isTime = false; - if (input.hasClass("table-checkbox")) { - input = $(input).parent(); - isCheckbox = true; - } else if (input.hasClass("table-time")) { - input = $(input).parent(); - isTime = true; - } + // show inputs + var input = $(element).find("input"); + input.show(); - var val = input.val(); - if (isCheckbox) { - // don't hide the checkbox - val = $(input).find("input").is(':checked'); - } else if (isTime) { - // don't hide the time - } else { - input.attr("type", "hidden") - } + var isCheckbox = input.hasClass("table-checkbox"); if (isArray) { var row_index = row.siblings('.' + Settings.DATA_ROW_CLASS).length - var key = $(element).attr('name') + var key = $(element).attr('name'); // are there multiple columns or just one? // with multiple we have an array of Objects, with one we have an array of whatever the value type is @@ -1347,17 +1405,21 @@ function addTableRow(row) { input.attr("name", setting_name + "[" + row_index + "]" + (num_columns > 1 ? "." + key : "")) } } else { - input.attr("name", full_name + "." + $(element).attr("name")) + // because the name of the setting in question requires the key + // setup a hook to change the HTML name of the element whenever the key changes + var colName = $(element).attr("name"); + keyInput.on('change', function(){ + input.attr("name", setting_name + "." + $(this).val() + "." + colName); + }); } if (isCheckbox) { $(input).find("input").attr("data-changed", "true"); } else { input.attr("data-changed", "true"); - $(element).append(val); } } else { - console.log("Unknown table element") + console.log("Unknown table element"); } }); @@ -1387,7 +1449,12 @@ function deleteTableRow($row) { $row.empty(); if (!isArray) { - $row.html(""); + if ($row.attr('name')) { + $row.html(""); + } else { + // for rows that didn't have a key, simply remove the row + $row.remove(); + } } else { if ($table.find('.' + Settings.DATA_ROW_CLASS + "[data-category='" + categoryName + "']").length <= 1) { // This is the last row of the category, so delete the header From 1474f22fd72e31966088d166917a1c83e170eb60 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 31 Jan 2017 10:54:46 -0800 Subject: [PATCH 06/17] don't validate category inputs for empty --- .../resources/web/settings/js/settings.js | 52 +++++++++---------- 1 file changed, 25 insertions(+), 27 deletions(-) diff --git a/domain-server/resources/web/settings/js/settings.js b/domain-server/resources/web/settings/js/settings.js index fbc2aefceb..d483e8171f 100644 --- a/domain-server/resources/web/settings/js/settings.js +++ b/domain-server/resources/web/settings/js/settings.js @@ -908,7 +908,7 @@ function validateInputs() { } _.each(tables, function(table) { - var inputs = $(table).find('tr.' + Settings.NEW_ROW_CLASS + ' input[data-changed="true"]'); + var inputs = $(table).find('tr.' + Settings.NEW_ROW_CLASS + ':not([data-category]) input[data-changed="true"]'); var empty = false; @@ -917,6 +917,7 @@ function validateInputs() { if (inputVal.length === 0) { empty = true + markParentRowInvalid(input); return; } @@ -984,25 +985,7 @@ var SETTINGS_ERROR_MESSAGE = "There was a problem saving domain settings. Please function saveSettings() { - // verify that the password and confirmation match before saving - var canPost = true; - - if (formJSON["security"]) { - var password = formJSON["security"]["http_password"]; - var verify_password = formJSON["security"]["verify_http_password"]; - - if (password && password.length > 0) { - if (password != verify_password) { - bootbox.alert({"message": "Passwords must match!", "title":"Password Error"}); - canPost = false; - } else { - formJSON["security"]["http_password"] = sha256_digest(password); - delete formJSON["security"]["verify_http_password"]; - } - } - } - - if (canPost && validateInputs()) { + if (validateInputs()) { // POST the form JSON to the domain-server settings.json endpoint so the settings are saved // disable any inputs not changed @@ -1021,6 +1004,24 @@ function saveSettings() { } } + // verify that the password and confirmation match before saving + var canPost = true; + + if (formJSON["security"]) { + var password = formJSON["security"]["http_password"]; + var verify_password = formJSON["security"]["verify_http_password"]; + + if (password && password.length > 0) { + if (password != verify_password) { + bootbox.alert({"message": "Passwords must match!", "title":"Password Error"}); + canPost = false; + } else { + formJSON["security"]["http_password"] = sha256_digest(password); + delete formJSON["security"]["verify_http_password"]; + } + } + } + console.log("----- SAVING ------"); console.log(formJSON); @@ -1032,16 +1033,13 @@ function saveSettings() { // remove focus from the button $(this).blur(); - // POST the form JSON to the domain-server settings.json endpoint so the settings are saved - postSettings(formJSON); + if (canPost) { + // POST the form JSON to the domain-server settings.json endpoint so the settings are saved + postSettings(formJSON); + } } } -// disable any inputs not changed -$("input:not([data-changed])").each(function(){ - $(this).prop('disabled', true); -}); - $('body').on('click', '.save-button', function(e){ saveSettings(); return false; From 79cb0ba074787907827664242440ea87657a7ba3 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 31 Jan 2017 11:05:57 -0800 Subject: [PATCH 07/17] fix keyboard behaviour for category tables --- domain-server/resources/web/settings/js/settings.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/domain-server/resources/web/settings/js/settings.js b/domain-server/resources/web/settings/js/settings.js index d483e8171f..22ce5b3170 100644 --- a/domain-server/resources/web/settings/js/settings.js +++ b/domain-server/resources/web/settings/js/settings.js @@ -216,8 +216,8 @@ $(document).ready(function(){ sibling = sibling.next(); } - if (sibling.hasClass(Settings.ADD_DEL_BUTTONS_CLASS)) { - sibling.find('.' + Settings.ADD_ROW_BUTTON_CLASS).click(); + // for tables with categories we add the entry and setup the new row on enter + if (sibling.find("." + Settings.ADD_CATEGORY_BUTTON_CLASS).length) { sibling.find("." + Settings.ADD_CATEGORY_BUTTON_CLASS).click(); // set focus to the first input in the new row From 360899887775ae01049d57ab552f4cc83e6f479a Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Tue, 31 Jan 2017 21:01:03 +0100 Subject: [PATCH 08/17] use dedicated bool rather than unreliable dirtyFlags to determine if the entityItem had ever bid for simulation ownership --- libraries/entities/src/EntityItem.cpp | 15 +++++++++++++-- libraries/entities/src/EntityItem.h | 7 ++++++- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index d77fac131d..61f082c9b6 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -688,8 +688,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef somethingChanged = true; _simulationOwner.clearCurrentOwner(); } - } else if (newSimOwner.matchesValidID(myNodeID) && !(_dirtyFlags & Simulation::DIRTY_SIMULATION_OWNERSHIP_FOR_POKE) - && !(_dirtyFlags & Simulation::DIRTY_SIMULATION_OWNERSHIP_FOR_GRAB)) { + } else if (newSimOwner.matchesValidID(myNodeID) && !_hasBidOnSimulation) { // entity-server tells us that we have simulation ownership while we never requested this for this EntityItem, // this could happen when the user reloads the cache and entity tree. _dirtyFlags |= Simulation::DIRTY_SIMULATOR_ID; @@ -1279,6 +1278,7 @@ void EntityItem::pokeSimulationOwnership() { // we don't own it yet _simulationOwner.setPendingPriority(SCRIPT_POKE_SIMULATION_PRIORITY, usecTimestampNow()); } + checkForFirstSimulationBid(_simulationOwner); } void EntityItem::grabSimulationOwnership() { @@ -1291,6 +1291,7 @@ void EntityItem::grabSimulationOwnership() { // we don't own it yet _simulationOwner.setPendingPriority(SCRIPT_GRAB_SIMULATION_PRIORITY, usecTimestampNow()); } + checkForFirstSimulationBid(_simulationOwner); } bool EntityItem::setProperties(const EntityItemProperties& properties) { @@ -1861,6 +1862,7 @@ void EntityItem::setSimulationOwner(const QUuid& id, quint8 priority) { qCDebug(entities) << "sim ownership for" << getDebugName() << "is now" << id << priority; } _simulationOwner.set(id, priority); + checkForFirstSimulationBid(_simulationOwner); } void EntityItem::setSimulationOwner(const SimulationOwner& owner) { @@ -1869,6 +1871,7 @@ void EntityItem::setSimulationOwner(const SimulationOwner& owner) { } _simulationOwner.set(owner); + checkForFirstSimulationBid(_simulationOwner); } void EntityItem::updateSimulationOwner(const SimulationOwner& owner) { @@ -1879,6 +1882,7 @@ void EntityItem::updateSimulationOwner(const SimulationOwner& owner) { if (_simulationOwner.set(owner)) { _dirtyFlags |= Simulation::DIRTY_SIMULATOR_ID; } + checkForFirstSimulationBid(_simulationOwner); } void EntityItem::clearSimulationOwnership() { @@ -1895,6 +1899,7 @@ void EntityItem::clearSimulationOwnership() { void EntityItem::setPendingOwnershipPriority(quint8 priority, const quint64& timestamp) { _simulationOwner.setPendingPriority(priority, timestamp); + checkForFirstSimulationBid(_simulationOwner); } QString EntityItem::actionsToDebugString() { @@ -2152,6 +2157,12 @@ void EntityItem::setActionDataInternal(QByteArray actionData) { checkWaitingToRemove(); } +void EntityItem::checkForFirstSimulationBid(const SimulationOwner& simulationOwner) const { + if (!_hasBidOnSimulation && simulationOwner.matchesValidID(DependencyManager::get()->getSessionUUID())) { + _hasBidOnSimulation = true; + } +} + void EntityItem::serializeActions(bool& success, QByteArray& result) const { if (_objectActions.size() == 0) { success = true; diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index b203de203b..98a2a1e268 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -478,6 +478,8 @@ protected: const QByteArray getActionDataInternal() const; void setActionDataInternal(QByteArray actionData); + void checkForFirstSimulationBid(const SimulationOwner& simulationOwner) const; + virtual void locationChanged(bool tellPhysics = true) override; virtual void dimensionsChanged() override; @@ -586,6 +588,9 @@ protected: static quint64 _rememberDeletedActionTime; mutable QHash _previouslyDeletedActions; + // per entity keep state if it ever bid on simulation, so that we can ignore false simulation ownership + mutable bool _hasBidOnSimulation = false; + QUuid _sourceUUID; /// the server node UUID we came from bool _clientOnly { false }; @@ -594,7 +599,7 @@ protected: // physics related changes from the network to suppress any duplicates and make // sure redundant applications are idempotent glm::vec3 _lastUpdatedPositionValue; - glm::quat _lastUpdatedRotationValue; + glm::quat _lastUpdatedRotationValue; glm::vec3 _lastUpdatedVelocityValue; glm::vec3 _lastUpdatedAngularVelocityValue; glm::vec3 _lastUpdatedAccelerationValue; From 174a7ad5bdadc72f14875281889b26fb7706ac2b Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Tue, 31 Jan 2017 22:54:58 +0100 Subject: [PATCH 09/17] Optimizations and style fixes from code review --- libraries/entities/src/EntityItem.cpp | 16 +++-------- libraries/entities/src/EntityItem.h | 27 +++++++++---------- .../entities/src/EntityScriptingInterface.cpp | 2 ++ libraries/physics/src/EntityMotionState.cpp | 2 ++ 4 files changed, 21 insertions(+), 26 deletions(-) diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 61f082c9b6..3c10d0382c 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -1278,7 +1278,6 @@ void EntityItem::pokeSimulationOwnership() { // we don't own it yet _simulationOwner.setPendingPriority(SCRIPT_POKE_SIMULATION_PRIORITY, usecTimestampNow()); } - checkForFirstSimulationBid(_simulationOwner); } void EntityItem::grabSimulationOwnership() { @@ -1291,7 +1290,6 @@ void EntityItem::grabSimulationOwnership() { // we don't own it yet _simulationOwner.setPendingPriority(SCRIPT_GRAB_SIMULATION_PRIORITY, usecTimestampNow()); } - checkForFirstSimulationBid(_simulationOwner); } bool EntityItem::setProperties(const EntityItemProperties& properties) { @@ -1862,7 +1860,6 @@ void EntityItem::setSimulationOwner(const QUuid& id, quint8 priority) { qCDebug(entities) << "sim ownership for" << getDebugName() << "is now" << id << priority; } _simulationOwner.set(id, priority); - checkForFirstSimulationBid(_simulationOwner); } void EntityItem::setSimulationOwner(const SimulationOwner& owner) { @@ -1871,7 +1868,6 @@ void EntityItem::setSimulationOwner(const SimulationOwner& owner) { } _simulationOwner.set(owner); - checkForFirstSimulationBid(_simulationOwner); } void EntityItem::updateSimulationOwner(const SimulationOwner& owner) { @@ -1882,7 +1878,6 @@ void EntityItem::updateSimulationOwner(const SimulationOwner& owner) { if (_simulationOwner.set(owner)) { _dirtyFlags |= Simulation::DIRTY_SIMULATOR_ID; } - checkForFirstSimulationBid(_simulationOwner); } void EntityItem::clearSimulationOwnership() { @@ -1899,7 +1894,10 @@ void EntityItem::clearSimulationOwnership() { void EntityItem::setPendingOwnershipPriority(quint8 priority, const quint64& timestamp) { _simulationOwner.setPendingPriority(priority, timestamp); - checkForFirstSimulationBid(_simulationOwner); +} + +void EntityItem::rememberHasSimulationOwnershipBid() const { + _hasBidOnSimulation = true; } QString EntityItem::actionsToDebugString() { @@ -2157,12 +2155,6 @@ void EntityItem::setActionDataInternal(QByteArray actionData) { checkWaitingToRemove(); } -void EntityItem::checkForFirstSimulationBid(const SimulationOwner& simulationOwner) const { - if (!_hasBidOnSimulation && simulationOwner.matchesValidID(DependencyManager::get()->getSessionUUID())) { - _hasBidOnSimulation = true; - } -} - void EntityItem::serializeActions(bool& success, QByteArray& result) const { if (_objectActions.size() == 0) { success = true; diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 98a2a1e268..e69195d53d 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -321,6 +321,7 @@ public: void updateSimulationOwner(const SimulationOwner& owner); void clearSimulationOwnership(); void setPendingOwnershipPriority(quint8 priority, const quint64& timestamp); + void rememberHasSimulationOwnershipBid() const; const QString& getMarketplaceID() const { return _marketplaceID; } void setMarketplaceID(const QString& value) { _marketplaceID = value; } @@ -478,8 +479,6 @@ protected: const QByteArray getActionDataInternal() const; void setActionDataInternal(QByteArray actionData); - void checkForFirstSimulationBid(const SimulationOwner& simulationOwner) const; - virtual void locationChanged(bool tellPhysics = true) override; virtual void dimensionsChanged() override; @@ -499,16 +498,16 @@ protected: mutable AABox _cachedAABox; mutable AACube _maxAACube; mutable AACube _minAACube; - mutable bool _recalcAABox = true; - mutable bool _recalcMinAACube = true; - mutable bool _recalcMaxAACube = true; + mutable bool _recalcAABox { true }; + mutable bool _recalcMinAACube { true }; + mutable bool _recalcMaxAACube { true }; float _localRenderAlpha; - float _density = ENTITY_ITEM_DEFAULT_DENSITY; // kg/m^3 + float _density { ENTITY_ITEM_DEFAULT_DENSITY }; // kg/m^3 // NOTE: _volumeMultiplier is used to allow some mass properties code exist in the EntityItem base class // rather than in all of the derived classes. If we ever collapse these classes to one we could do it a // different way. - float _volumeMultiplier = 1.0f; + float _volumeMultiplier { 1.0f }; glm::vec3 _gravity; glm::vec3 _acceleration; float _damping; @@ -518,7 +517,7 @@ protected: QString _script; /// the value of the script property QString _loadedScript; /// the value of _script when the last preload signal was sent - quint64 _scriptTimestamp{ ENTITY_ITEM_DEFAULT_SCRIPT_TIMESTAMP }; /// the script loaded property used for forced reload + quint64 _scriptTimestamp { ENTITY_ITEM_DEFAULT_SCRIPT_TIMESTAMP }; /// the script loaded property used for forced reload QString _serverScripts; /// keep track of time when _serverScripts property was last changed @@ -526,7 +525,7 @@ protected: /// the value of _scriptTimestamp when the last preload signal was sent // NOTE: on construction we want this to be different from _scriptTimestamp so we intentionally bump it - quint64 _loadedScriptTimestamp{ ENTITY_ITEM_DEFAULT_SCRIPT_TIMESTAMP + 1 }; + quint64 _loadedScriptTimestamp { ENTITY_ITEM_DEFAULT_SCRIPT_TIMESTAMP + 1 }; QString _collisionSoundURL; SharedSoundPointer _collisionSound; @@ -564,8 +563,8 @@ protected: uint32_t _dirtyFlags; // things that have changed from EXTERNAL changes (via script or packet) but NOT from simulation // these backpointers are only ever set/cleared by friends: - EntityTreeElementPointer _element = nullptr; // set by EntityTreeElement - void* _physicsInfo = nullptr; // set by EntitySimulation + EntityTreeElementPointer _element { nullptr }; // set by EntityTreeElement + void* _physicsInfo { nullptr }; // set by EntitySimulation bool _simulated; // set by EntitySimulation bool addActionInternal(EntitySimulationPointer simulation, EntityActionPointer action); @@ -582,14 +581,14 @@ protected: // are used to keep track of and work around this situation. void checkWaitingToRemove(EntitySimulationPointer simulation = nullptr); mutable QSet _actionsToRemove; - mutable bool _actionDataDirty = false; - mutable bool _actionDataNeedsTransmit = false; + mutable bool _actionDataDirty { false }; + mutable bool _actionDataNeedsTransmit { false }; // _previouslyDeletedActions is used to avoid an action being re-added due to server round-trip lag static quint64 _rememberDeletedActionTime; mutable QHash _previouslyDeletedActions; // per entity keep state if it ever bid on simulation, so that we can ignore false simulation ownership - mutable bool _hasBidOnSimulation = false; + mutable bool _hasBidOnSimulation { false }; QUuid _sourceUUID; /// the server node UUID we came from diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index 6fb5d14329..85c3fc74f6 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -231,6 +231,7 @@ QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties // and make note of it now, so we can act on it right away. propertiesWithSimID.setSimulationOwner(myNodeID, SCRIPT_POKE_SIMULATION_PRIORITY); entity->setSimulationOwner(myNodeID, SCRIPT_POKE_SIMULATION_PRIORITY); + entity->rememberHasSimulationOwnershipBid(); } entity->setLastBroadcast(usecTimestampNow()); @@ -444,6 +445,7 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties& // we make a bid for simulation ownership properties.setSimulationOwner(myNodeID, SCRIPT_POKE_SIMULATION_PRIORITY); entity->pokeSimulationOwnership(); + entity->rememberHasSimulationOwnershipBid(); } } if (properties.parentRelatedPropertyChanged() && entity->computePuffedQueryAACube()) { diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index b0bdc34b52..02cee9a03a 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -582,6 +582,8 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_ _nextOwnershipBid = now + USECS_BETWEEN_OWNERSHIP_BIDS; // copy _outgoingPriority into pendingPriority... _entity->setPendingOwnershipPriority(_outgoingPriority, now); + // don't forget to remember that we have made a bid + _entity->rememberHasSimulationOwnershipBid(); // ...then reset _outgoingPriority in preparation for the next frame _outgoingPriority = 0; } else if (_outgoingPriority != _entity->getSimulationPriority()) { From d4a161041ee5e14c6f816296176057dad3b9eccb Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 31 Jan 2017 15:32:47 -0800 Subject: [PATCH 10/17] drop empty parts when splitting script whitelist --- libraries/entities/src/EntityTree.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 5fa86f6745..bf935a7dbe 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -64,7 +64,7 @@ EntityTree::~EntityTree() { } void EntityTree::setEntityScriptSourceWhitelist(const QString& entityScriptSourceWhitelist) { - _entityScriptSourceWhitelist = entityScriptSourceWhitelist.split(','); + _entityScriptSourceWhitelist = entityScriptSourceWhitelist.split(',', QString::SkipEmptyParts); } From e42853a96c0237a95866c248b81c850956893e25 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Tue, 31 Jan 2017 16:05:38 -0800 Subject: [PATCH 11/17] cleanly log failed entity adds, and tell the client to delete his copy --- libraries/entities/src/EntityTree.cpp | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 5fa86f6745..e2a6d8beb0 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -1111,7 +1111,15 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c endUpdate = usecTimestampNow(); _totalUpdates++; } else if (message.getType() == PacketType::EntityAdd) { - if (senderNode->getCanRez() || senderNode->getCanRezTmp()) { + bool failedAdd = !allowed; + if (!allowed) { + qCDebug(entities) << "Filtered entity add. ID:" << entityItemID; + } else if (!senderNode->getCanRez() && !senderNode->getCanRezTmp()) { + failedAdd = true; + qCDebug(entities) << "User without 'rez rights' [" << senderNode->getUUID() + << "] attempted to add an entity ID:" << entityItemID; + + } else { // this is a new entity... assign a new entityID properties.setCreated(properties.getLastEdited()); properties.setLastEditedBy(senderNode->getUUID()); @@ -1126,7 +1134,7 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c startLogging = usecTimestampNow(); if (wantEditLogging()) { qCDebug(entities) << "User [" << senderNode->getUUID() << "] added entity. ID:" - << newEntity->getEntityItemID(); + << newEntity->getEntityItemID(); qCDebug(entities) << " properties:" << properties; } if (wantTerseEditLogging()) { @@ -1136,10 +1144,14 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c } endLogging = usecTimestampNow(); + } else { + failedAdd = true; + qCDebug(entities) << "Add entity failed ID:" << entityItemID; } - } else { - qCDebug(entities) << "User without 'rez rights' [" << senderNode->getUUID() - << "] attempted to add an entity."; + } + if (failedAdd) { // Let client know it failed, so that they don't have an entity that no one else sees. + QWriteLocker locker(&_recentlyDeletedEntitiesLock); + _recentlyDeletedEntityItemIDs.insert(usecTimestampNow(), entityItemID); } } else { static QString repeatedMessage = From 8f362861eb19a8fd636c1c6d52b252615877f77d Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 31 Jan 2017 16:53:25 -0800 Subject: [PATCH 12/17] tablet-ui: button icon images now work with http urls. --- interface/resources/qml/hifi/tablet/TabletButton.qml | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/interface/resources/qml/hifi/tablet/TabletButton.qml b/interface/resources/qml/hifi/tablet/TabletButton.qml index 9ad8d1476c..c6c810d25e 100644 --- a/interface/resources/qml/hifi/tablet/TabletButton.qml +++ b/interface/resources/qml/hifi/tablet/TabletButton.qml @@ -75,6 +75,14 @@ Item { source: buttonOutline } + function urlHelper(src) { + if (src.match(/\bhttp/)) { + return src; + } else { + return "../../../" + src; + } + } + Image { id: icon width: 50 @@ -84,7 +92,7 @@ Item { anchors.bottomMargin: 5 anchors.horizontalCenter: parent.horizontalCenter fillMode: Image.Stretch - source: "../../../" + tabletButton.icon + source: tabletButton.urlHelper(tabletButton.icon) } ColorOverlay { @@ -185,7 +193,7 @@ Item { PropertyChanges { target: icon - source: "../../../" + tabletButton.activeIcon + source: tabletButton.urlHelper(tabletButton.activeIcon) } }, State { From cf855391af34d47a44e0ca5e46a7b9da534701c2 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 31 Jan 2017 16:54:15 -0800 Subject: [PATCH 13/17] Added jsdocs for all button properties --- .../script-engine/src/TabletScriptingInterface.h | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/libraries/script-engine/src/TabletScriptingInterface.h b/libraries/script-engine/src/TabletScriptingInterface.h index b0b2d00e0f..0b7829c7fb 100644 --- a/libraries/script-engine/src/TabletScriptingInterface.h +++ b/libraries/script-engine/src/TabletScriptingInterface.h @@ -119,7 +119,7 @@ public: * @param msg {object|string} */ Q_INVOKABLE void emitScriptEvent(QVariant msg); - + Q_INVOKABLE bool onHomeScreen(); QObject* getTabletSurface(); @@ -170,14 +170,14 @@ public: /**jsdoc * Returns the current value of this button's properties * @function TabletButtonProxy#getProperties - * @returns {object} + * @returns {ButtonProperties} */ Q_INVOKABLE QVariantMap getProperties() const; /**jsdoc * Replace the values of some of this button's properties * @function TabletButtonProxy#editProperties - * @param properties {object} set of properties to change + * @param {ButtonProperties} properties - set of properties to change */ Q_INVOKABLE void editProperties(QVariantMap properties); @@ -199,4 +199,13 @@ protected: QVariantMap _properties; }; +/**jsdoc + * @typedef TabletButtonProxy.ButtonProperties + * @property {string} text - button caption + * @property {string} icon - url to button icon. (50 x 50) + * @property {string} activeText - button caption when button is active + * @property {string} activeIcon - url to button icon used when button is active. (50 x 50) + * @property {string} isActive - true when button is active. + */ + #endif // hifi_TabletScriptingInterface_h From f03d2988c1ad8f49407f912d6df1ceaaa809d57c Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 31 Jan 2017 17:30:45 -0800 Subject: [PATCH 14/17] fix for checkbox name setting --- domain-server/resources/web/settings/js/settings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/domain-server/resources/web/settings/js/settings.js b/domain-server/resources/web/settings/js/settings.js index 22ce5b3170..b04d55b9eb 100644 --- a/domain-server/resources/web/settings/js/settings.js +++ b/domain-server/resources/web/settings/js/settings.js @@ -1398,7 +1398,7 @@ function addTableRow(row) { var num_columns = row.children('.' + Settings.DATA_COL_CLASS).length if (isCheckbox) { - $(input).find("input").attr("name", setting_name + "[" + row_index + "]" + (num_columns > 1 ? "." + key : "")) + input.attr("name", setting_name + "[" + row_index + "]" + (num_columns > 1 ? "." + key : "")) } else { input.attr("name", setting_name + "[" + row_index + "]" + (num_columns > 1 ? "." + key : "")) } From c147b9bec0672aa9253c406ab0a2f75005906adc Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 31 Jan 2017 18:00:18 -0800 Subject: [PATCH 15/17] Fix for tablets in third-person HMD mode. --- scripts/system/libraries/WebTablet.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/scripts/system/libraries/WebTablet.js b/scripts/system/libraries/WebTablet.js index 65551b2140..edb637d9a2 100644 --- a/scripts/system/libraries/WebTablet.js +++ b/scripts/system/libraries/WebTablet.js @@ -198,6 +198,11 @@ WebTablet = function (url, width, dpi, hand, clientOnly) { _this.geometryChanged(geometry); }; Window.geometryChanged.connect(this.myGeometryChanged); + + this.myCameraModeChanged = function(newMode) { + _this.cameraModeChanged(newMode); + }; + Camera.modeUpdated.connect(this.myCameraModeChanged); }; WebTablet.prototype.setHomeButtonTexture = function() { @@ -228,6 +233,7 @@ WebTablet.prototype.destroy = function () { Controller.mouseReleaseEvent.disconnect(this.myMouseReleaseEvent); Window.geometryChanged.disconnect(this.myGeometryChanged); + Camera.modeUpdated.disconnect(this.myCameraModeChanged); }; WebTablet.prototype.geometryChanged = function (geometry) { @@ -370,6 +376,19 @@ WebTablet.prototype.mousePressEvent = function (event) { } }; +WebTablet.prototype.cameraModeChanged = function (newMode) { + // reposition the tablet, after a small delay. + // This allows HMD.position to reflect the new camera mode. + var self = this; + Script.setTimeout(function () { + var NO_HANDS = -1; + var tabletProperties = {}; + // compute position, rotation & parentJointIndex of the tablet + self.calculateTabletAttachmentProperties(NO_HANDS, tabletProperties); + Entities.editEntity(self.tabletEntityID, tabletProperties); + }, 10); +}; + function rayIntersectPlane(planePosition, planeNormal, rayStart, rayDirection) { var rayDirectionDotPlaneNormal = Vec3.dot(rayDirection, planeNormal); if (rayDirectionDotPlaneNormal > 0.00001 || rayDirectionDotPlaneNormal < -0.00001) { From 008a58f9d708e00ce4d8de9de1d9ff70805bb903 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 31 Jan 2017 18:03:48 -0800 Subject: [PATCH 16/17] Only reposition the tablet when camera mode changes, in HMD mode. --- scripts/system/libraries/WebTablet.js | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/scripts/system/libraries/WebTablet.js b/scripts/system/libraries/WebTablet.js index edb637d9a2..52912337a1 100644 --- a/scripts/system/libraries/WebTablet.js +++ b/scripts/system/libraries/WebTablet.js @@ -379,14 +379,16 @@ WebTablet.prototype.mousePressEvent = function (event) { WebTablet.prototype.cameraModeChanged = function (newMode) { // reposition the tablet, after a small delay. // This allows HMD.position to reflect the new camera mode. - var self = this; - Script.setTimeout(function () { - var NO_HANDS = -1; - var tabletProperties = {}; - // compute position, rotation & parentJointIndex of the tablet - self.calculateTabletAttachmentProperties(NO_HANDS, tabletProperties); - Entities.editEntity(self.tabletEntityID, tabletProperties); - }, 10); + if (HMD.active) { + var self = this; + Script.setTimeout(function () { + var NO_HANDS = -1; + var tabletProperties = {}; + // compute position, rotation & parentJointIndex of the tablet + self.calculateTabletAttachmentProperties(NO_HANDS, tabletProperties); + Entities.editEntity(self.tabletEntityID, tabletProperties); + }, 10); + } }; function rayIntersectPlane(planePosition, planeNormal, rayStart, rayDirection) { From 3c04f54e7509b08c36eff7311f16a3f6b636c391 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 31 Jan 2017 18:22:38 -0800 Subject: [PATCH 17/17] more fixes to third person tablet usage. --- scripts/system/libraries/WebTablet.js | 31 +++++++++++++-------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/scripts/system/libraries/WebTablet.js b/scripts/system/libraries/WebTablet.js index 52912337a1..75ca2e514f 100644 --- a/scripts/system/libraries/WebTablet.js +++ b/scripts/system/libraries/WebTablet.js @@ -24,6 +24,7 @@ var CAMERA_MATRIX = -7; var ROT_Y_180 = {x: 0, y: 1, z: 0, w: 0}; var TABLET_TEXTURE_RESOLUTION = { x: 480, y: 706 }; var INCHES_TO_METERS = 1 / 39.3701; +var NO_HANDS = -1; var TABLET_URL = Script.resourcesPath() + "meshes/tablet-with-home-button.fbx"; @@ -35,18 +36,21 @@ var TABLET_MODEL_PATH = "http://hifi-content.s3.amazonaws.com/alan/dev/tablet-wi // * position - position in front of the user // * rotation - rotation of entity so it faces the user. function calcSpawnInfo(hand, height) { - var noHands = -1; var finalPosition; - if (HMD.active && hand !== noHands) { + + var headPos = (HMD.active && Camera.mode === "first person") ? HMD.position : Camera.position; + var headRot = (HMD.active && Camera.mode === "first person") ? HMD.orientation : Camera.orientation; + + if (HMD.active && hand !== NO_HANDS) { var handController = getControllerWorldLocation(hand, true); var controllerPosition = handController.position; // compute the angle of the chord with length (height / 2) - var theta = Math.asin(height / (2 * Vec3.distance(HMD.position, controllerPosition))); + var theta = Math.asin(height / (2 * Vec3.distance(headPos, controllerPosition))); // then we can use this angle to rotate the vector between the HMD position and the center of the tablet. // this vector, u, will become our new look at direction. - var d = Vec3.normalize(Vec3.subtract(HMD.position, controllerPosition)); + var d = Vec3.normalize(Vec3.subtract(headPos, controllerPosition)); var w = Vec3.normalize(Vec3.cross(Y_AXIS, d)); var q = Quat.angleAxis(theta * (180 / Math.PI), w); var u = Vec3.multiplyQbyV(q, d); @@ -64,8 +68,8 @@ function calcSpawnInfo(hand, height) { rotation: lookAtRot }; } else { - var front = Quat.getFront(Camera.orientation); - finalPosition = Vec3.sum(Camera.position, Vec3.multiply(0.6, front)); + var front = Quat.getFront(headRot); + finalPosition = Vec3.sum(headPos, Vec3.multiply(0.6, front)); var orientation = Quat.lookAt({x: 0, y: 0, z: 0}, front, {x: 0, y: 1, z: 0}); return { position: finalPosition, @@ -238,7 +242,6 @@ WebTablet.prototype.destroy = function () { WebTablet.prototype.geometryChanged = function (geometry) { if (!HMD.active) { - var NO_HANDS = -1; var tabletProperties = {}; // compute position, rotation & parentJointIndex of the tablet this.calculateTabletAttachmentProperties(NO_HANDS, tabletProperties); @@ -294,7 +297,6 @@ WebTablet.prototype.onHmdChanged = function () { Controller.mouseReleaseEvent.connect(this.myMouseReleaseEvent); } - var NO_HANDS = -1; var tabletProperties = {}; // compute position, rotation & parentJointIndex of the tablet this.calculateTabletAttachmentProperties(NO_HANDS, tabletProperties); @@ -377,17 +379,14 @@ WebTablet.prototype.mousePressEvent = function (event) { }; WebTablet.prototype.cameraModeChanged = function (newMode) { - // reposition the tablet, after a small delay. + // reposition the tablet. // This allows HMD.position to reflect the new camera mode. if (HMD.active) { var self = this; - Script.setTimeout(function () { - var NO_HANDS = -1; - var tabletProperties = {}; - // compute position, rotation & parentJointIndex of the tablet - self.calculateTabletAttachmentProperties(NO_HANDS, tabletProperties); - Entities.editEntity(self.tabletEntityID, tabletProperties); - }, 10); + var tabletProperties = {}; + // compute position, rotation & parentJointIndex of the tablet + self.calculateTabletAttachmentProperties(NO_HANDS, tabletProperties); + Entities.editEntity(self.tabletEntityID, tabletProperties); } };