diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index 2734cdf01f..740f456595 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -397,7 +397,7 @@ void Agent::processAgentAvatarAndAudio(float deltaTime) { if (_numAvatarSoundSentBytes == soundByteArray.size()) { // we're done with this sound object - so set our pointer back to NULL // and our sent bytes back to zero - _avatarSound = NULL; + _avatarSound.clear(); _numAvatarSoundSentBytes = 0; } } diff --git a/assignment-client/src/Agent.h b/assignment-client/src/Agent.h index fbaec7efe6..d86b99db33 100644 --- a/assignment-client/src/Agent.h +++ b/assignment-client/src/Agent.h @@ -56,7 +56,7 @@ public: public slots: void run(); - void playAvatarSound(Sound* avatarSound) { setAvatarSound(avatarSound); } + void playAvatarSound(SharedSoundPointer avatarSound) { setAvatarSound(avatarSound); } private slots: void requestScript(); @@ -77,7 +77,7 @@ private: MixedAudioStream _receivedAudioStream; float _lastReceivedAudioLoudness; - void setAvatarSound(Sound* avatarSound) { _avatarSound = avatarSound; } + void setAvatarSound(SharedSoundPointer avatarSound) { _avatarSound = avatarSound; } void sendAvatarIdentityPacket(); void sendAvatarBillboardPacket(); @@ -85,7 +85,7 @@ private: QString _scriptContents; QTimer* _scriptRequestTimeout { nullptr }; bool _isListeningToAudioStream = false; - Sound* _avatarSound = nullptr; + SharedSoundPointer _avatarSound; int _numAvatarSoundSentBytes = 0; bool _isAvatar = false; QTimer* _avatarIdentityTimer = nullptr; diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index 44a1796a8d..53d062d4bd 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -1,5 +1,5 @@ { - "version": 1.1, + "version": 1.2, "settings": [ { "name": "metaverse", @@ -249,7 +249,7 @@ "label": "X end", "can_set": true, "placeholder": "16384.0" - }, + }, { "name": "y_min", "label": "Y start", diff --git a/domain-server/resources/web/settings/index.shtml b/domain-server/resources/web/settings/index.shtml index 35eab080b9..3f969ef913 100644 --- a/domain-server/resources/web/settings/index.shtml +++ b/domain-server/resources/web/settings/index.shtml @@ -103,4 +103,5 @@ + diff --git a/domain-server/resources/web/settings/js/settings.js b/domain-server/resources/web/settings/js/settings.js index fae07ace45..dfdfeded04 100644 --- a/domain-server/resources/web/settings/js/settings.js +++ b/domain-server/resources/web/settings/js/settings.js @@ -867,6 +867,14 @@ function saveSettings() { // 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 + if (formJSON["security"]) { + var password = formJSON["security"]["http_password"]; + if (password.length > 0) { + formJSON["security"]["http_password"] = sha256_digest(password); + } + } + console.log(formJSON); // re-enable all inputs diff --git a/domain-server/resources/web/settings/js/sha256.js b/domain-server/resources/web/settings/js/sha256.js new file mode 100644 index 0000000000..6f2160018f --- /dev/null +++ b/domain-server/resources/web/settings/js/sha256.js @@ -0,0 +1,247 @@ +/* +* A JavaScript implementation of the SHA256 hash function. +* +* FILE: sha256.js +* VERSION: 0.8 +* AUTHOR: Christoph Bichlmeier +* +* NOTE: This version is not tested thoroughly! +* +* Copyright (c) 2003, Christoph Bichlmeier +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions +* are met: +* 1. Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* 2. Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer in the +* documentation and/or other materials provided with the distribution. +* 3. Neither the name of the copyright holder nor the names of contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* ====================================================================== +* +* THIS SOFTWARE IS PROVIDED BY THE AUTHORS ''AS IS'' AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE +* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR +* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/* SHA256 logical functions */ +function rotateRight(n,x) { + return ((x >>> n) | (x << (32 - n))); +} +function choice(x,y,z) { + return ((x & y) ^ (~x & z)); +} +function majority(x,y,z) { + return ((x & y) ^ (x & z) ^ (y & z)); +} +function sha256_Sigma0(x) { + return (rotateRight(2, x) ^ rotateRight(13, x) ^ rotateRight(22, x)); +} +function sha256_Sigma1(x) { + return (rotateRight(6, x) ^ rotateRight(11, x) ^ rotateRight(25, x)); +} +function sha256_sigma0(x) { + return (rotateRight(7, x) ^ rotateRight(18, x) ^ (x >>> 3)); +} +function sha256_sigma1(x) { + return (rotateRight(17, x) ^ rotateRight(19, x) ^ (x >>> 10)); +} +function sha256_expand(W, j) { + return (W[j&0x0f] += sha256_sigma1(W[(j+14)&0x0f]) + W[(j+9)&0x0f] + +sha256_sigma0(W[(j+1)&0x0f])); +} + +/* Hash constant words K: */ +var K256 = new Array( + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, + 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, + 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, + 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, + 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, + 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, + 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, + 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, + 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 +); + +/* global arrays */ +var ihash, count, buffer; +var sha256_hex_digits = "0123456789abcdef"; + +/* Add 32-bit integers with 16-bit operations (bug in some JS-interpreters: +overflow) */ +function safe_add(x, y) +{ + var lsw = (x & 0xffff) + (y & 0xffff); + var msw = (x >> 16) + (y >> 16) + (lsw >> 16); + return (msw << 16) | (lsw & 0xffff); +} + +/* Initialise the SHA256 computation */ +function sha256_init() { + ihash = new Array(8); + count = new Array(2); + buffer = new Array(64); + count[0] = count[1] = 0; + ihash[0] = 0x6a09e667; + ihash[1] = 0xbb67ae85; + ihash[2] = 0x3c6ef372; + ihash[3] = 0xa54ff53a; + ihash[4] = 0x510e527f; + ihash[5] = 0x9b05688c; + ihash[6] = 0x1f83d9ab; + ihash[7] = 0x5be0cd19; +} + +/* Transform a 512-bit message block */ +function sha256_transform() { + var a, b, c, d, e, f, g, h, T1, T2; + var W = new Array(16); + + /* Initialize registers with the previous intermediate value */ + a = ihash[0]; + b = ihash[1]; + c = ihash[2]; + d = ihash[3]; + e = ihash[4]; + f = ihash[5]; + g = ihash[6]; + h = ihash[7]; + + /* make 32-bit words */ + for(var i=0; i<16; i++) + W[i] = ((buffer[(i<<2)+3]) | (buffer[(i<<2)+2] << 8) | (buffer[(i<<2)+1] +<< 16) | (buffer[i<<2] << 24)); + + for(var j=0; j<64; j++) { + T1 = h + sha256_Sigma1(e) + choice(e, f, g) + K256[j]; + if(j < 16) T1 += W[j]; + else T1 += sha256_expand(W, j); + T2 = sha256_Sigma0(a) + majority(a, b, c); + h = g; + g = f; + f = e; + e = safe_add(d, T1); + d = c; + c = b; + b = a; + a = safe_add(T1, T2); + } + + /* Compute the current intermediate hash value */ + ihash[0] += a; + ihash[1] += b; + ihash[2] += c; + ihash[3] += d; + ihash[4] += e; + ihash[5] += f; + ihash[6] += g; + ihash[7] += h; +} + +/* Read the next chunk of data and update the SHA256 computation */ +function sha256_update(data, inputLen) { + var i, index, curpos = 0; + /* Compute number of bytes mod 64 */ + index = ((count[0] >> 3) & 0x3f); + var remainder = (inputLen & 0x3f); + + /* Update number of bits */ + if ((count[0] += (inputLen << 3)) < (inputLen << 3)) count[1]++; + count[1] += (inputLen >> 29); + + /* Transform as many times as possible */ + for(i=0; i+63> 3) & 0x3f); + buffer[index++] = 0x80; + if(index <= 56) { + for(var i=index; i<56; i++) + buffer[i] = 0; + } else { + for(var i=index; i<64; i++) + buffer[i] = 0; + sha256_transform(); + for(var i=0; i<56; i++) + buffer[i] = 0; + } + buffer[56] = (count[1] >>> 24) & 0xff; + buffer[57] = (count[1] >>> 16) & 0xff; + buffer[58] = (count[1] >>> 8) & 0xff; + buffer[59] = count[1] & 0xff; + buffer[60] = (count[0] >>> 24) & 0xff; + buffer[61] = (count[0] >>> 16) & 0xff; + buffer[62] = (count[0] >>> 8) & 0xff; + buffer[63] = count[0] & 0xff; + sha256_transform(); +} + +/* Split the internal hash values into an array of bytes */ +function sha256_encode_bytes() { + var j=0; + var output = new Array(32); + for(var i=0; i<8; i++) { + output[j++] = ((ihash[i] >>> 24) & 0xff); + output[j++] = ((ihash[i] >>> 16) & 0xff); + output[j++] = ((ihash[i] >>> 8) & 0xff); + output[j++] = (ihash[i] & 0xff); + } + return output; +} + +/* Get the internal hash as a hex string */ +function sha256_encode_hex() { + var output = new String(); + for(var i=0; i<8; i++) { + for(var j=28; j>=0; j-=4) + output += sha256_hex_digits.charAt((ihash[i] >>> j) & 0x0f); + } + return output; +} + +/* Main function: returns a hex string representing the SHA256 value of the +given data */ +function sha256_digest(data) { + sha256_init(); + sha256_update(data, data.length); + sha256_final(); + return sha256_encode_hex(); +} + +/* test if the JS-interpreter is working properly */ +function sha256_self_test() { + return sha256_digest("message digest") == +"f7846f55cf23e14eebeab5b4e1550cad5b509e3348fbc4efa3a1413d393cb650"; +} diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index d80196e9fa..4ff3564e72 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -1679,8 +1679,9 @@ bool DomainServer::isAuthenticatedRequest(HTTPConnection* connection, const QUrl QString settingsUsername = valueForKeyPath(settingsMap, BASIC_AUTH_USERNAME_KEY_PATH)->toString(); const QVariant* settingsPasswordVariant = valueForKeyPath(settingsMap, BASIC_AUTH_PASSWORD_KEY_PATH); QString settingsPassword = settingsPasswordVariant ? settingsPasswordVariant->toString() : ""; + QString hexHeaderPassword = QCryptographicHash::hash(headerPassword.toUtf8(), QCryptographicHash::Sha256).toHex(); - if (settingsUsername == headerUsername && headerPassword == settingsPassword) { + if (settingsUsername == headerUsername && hexHeaderPassword == settingsPassword) { return true; } } diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index 49ae03ccd4..0ca0cf8232 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -129,7 +129,9 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList // reload the master and user config so that the merged config is right _configMap.loadMasterAndUserConfig(argumentList); } - } else if (oldVersion < 1.1) { + } + + if (oldVersion < 1.1) { static const QString ENTITY_SERVER_SETTINGS_KEY = "entity_server_settings"; static const QString ENTITY_FILE_NAME_KEY = "persistFilename"; static const QString ENTITY_FILE_PATH_KEYPATH = ENTITY_SERVER_SETTINGS_KEY + ".persistFilePath"; @@ -165,6 +167,28 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList } } + + if (oldVersion < 1.2) { + // This was prior to the base64 encoding of password for HTTP Basic Authentication. + // If we have a password in the previous settings file, make it base 64 + static const QString BASIC_AUTH_PASSWORD_KEY_PATH { "security.http_password" }; + + QVariant* passwordVariant = valueForKeyPath(_configMap.getUserConfig(), BASIC_AUTH_PASSWORD_KEY_PATH); + + if (passwordVariant && passwordVariant->canConvert(QMetaType::QString)) { + QString plaintextPassword = passwordVariant->toString(); + + qDebug() << "Migrating plaintext password to SHA256 hash in domain-server settings."; + + *passwordVariant = QCryptographicHash::hash(plaintextPassword.toUtf8(), QCryptographicHash::Sha256).toHex(); + + // write the new settings to file + persistToFile(); + + // reload the master and user config so the merged config is correct + _configMap.loadMasterAndUserConfig(argumentList); + } + } } // write the current description version to our settings diff --git a/examples/controllers/handControllerGrab.js b/examples/controllers/handControllerGrab.js index 2e9129f3ac..4b20651899 100644 --- a/examples/controllers/handControllerGrab.js +++ b/examples/controllers/handControllerGrab.js @@ -12,7 +12,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html /*global print, MyAvatar, Entities, AnimationCache, SoundCache, Scene, Camera, Overlays, Audio, HMD, AvatarList, AvatarManager, Controller, UndoStack, Window, Account, GlobalServices, Script, ScriptDiscoveryService, LODManager, Menu, Vec3, Quat, AudioDevice, Paths, Clipboard, Settings, XMLHttpRequest, randFloat, randInt, pointInExtents, vec3equal, setEntityCustomData, getEntityCustomData */ -Script.include("../libraries/utils.js"); +Script.include("/~/libraries/utils.js"); // @@ -79,6 +79,7 @@ var NEAR_PICK_MAX_DISTANCE = 0.3; // max length of pick-ray for close grabbing t var PICK_BACKOFF_DISTANCE = 0.2; // helps when hand is intersecting the grabble object var NEAR_GRABBING_KINEMATIC = true; // force objects to be kinematic when near-grabbed var SHOW_GRAB_SPHERE = false; // draw a green sphere to show the grab search position and size +var CHECK_TOO_FAR_UNEQUIP_TIME = 1.0; // seconds // // equip @@ -290,11 +291,13 @@ function MyController(hand) { this.intersectionDistance = 0.0; this.searchSphereDistance = DEFAULT_SEARCH_SPHERE_DISTANCE; - this.ignoreIK = false; this.offsetPosition = Vec3.ZERO; this.offsetRotation = Quat.IDENTITY; + this.lastPickTime = 0; + this.lastUnequipCheckTime = 0; + var _this = this; this.update = function() { @@ -1523,18 +1526,30 @@ function MyController(hand) { return; } - if (props.parentID == MyAvatar.sessionUUID && - Vec3.length(props.localPosition) > NEAR_PICK_MAX_DISTANCE * 2.0) { - // for whatever reason, the held/equipped entity has been pulled away. ungrab or unequip. - print("handControllerGrab -- autoreleasing held or equipped item because it is far from hand." + - props.parentID + " " + vec3toStr(props.position)); - this.setState(STATE_RELEASE); - if (this.state == STATE_CONTINUE_NEAR_GRABBING) { - this.callEntityMethodOnGrabbed("releaseGrab"); - } else { // (this.state == STATE_CONTINUE_EQUIP || this.state == STATE_CONTINUE_HOLD) - this.callEntityMethodOnGrabbed("releaseEquip"); + + var now = Date.now(); + if (now - this.lastUnequipCheckTime > MSECS_PER_SEC * CHECK_TOO_FAR_UNEQUIP_TIME) { + this.lastUnequipCheckTime = now; + + if (props.parentID == MyAvatar.sessionUUID && + Vec3.length(props.localPosition) > NEAR_PICK_MAX_DISTANCE * 2.0) { + var handPosition = this.getHandPosition(); + // the center of the equipped object being far from the hand isn't enough to autoequip -- we also + // need to fail the findEntities test. + nearPickedCandidateEntities = Entities.findEntities(handPosition, GRAB_RADIUS); + if (nearPickedCandidateEntities.indexOf(this.grabbedEntity) == -1) { + // for whatever reason, the held/equipped entity has been pulled away. ungrab or unequip. + print("handControllerGrab -- autoreleasing held or equipped item because it is far from hand." + + props.parentID + " " + vec3toStr(props.position)); + this.setState(STATE_RELEASE); + if (this.state == STATE_CONTINUE_NEAR_GRABBING) { + this.callEntityMethodOnGrabbed("releaseGrab"); + } else { // (this.state == STATE_CONTINUE_EQUIP || this.state == STATE_CONTINUE_HOLD) + this.callEntityMethodOnGrabbed("releaseEquip"); + } + return; + } } - return; } // Keep track of the fingertip velocity to impart when we release the object. diff --git a/examples/depthReticle.js b/examples/depthReticle.js index 90e5db0f1a..10d604f707 100644 --- a/examples/depthReticle.js +++ b/examples/depthReticle.js @@ -98,10 +98,21 @@ function seekToLookAt() { } function autoHideReticle() { + var now = Date.now(); + + // sometimes we don't actually get mouse move messages (for example, if the focus has been set + // to an overlay or web page 'overlay') in but the mouse can still be moving, and we don't want + // to autohide in these cases, so we will take this opportunity to also check if the reticle + // position has changed. + if (lastMouseX != Reticle.position.x || lastMouseY != Reticle.position.y) { + lastMouseMoveOrClick = now; + lastMouseX = Reticle.position.x; + lastMouseY = Reticle.position.y; + } + // if we haven't moved in a long period of time, and we're not pointing at some // system overlay (like a window), then hide the reticle if (Reticle.visible && !Reticle.pointingAtSystemOverlay) { - var now = Date.now(); var timeSinceLastMouseMove = now - lastMouseMoveOrClick; if (timeSinceLastMouseMove > HIDE_STATIC_MOUSE_AFTER) { Reticle.visible = false; diff --git a/examples/example/audio/largeHall.js b/examples/example/audio/largeHall.js new file mode 100644 index 0000000000..3c710a8eaa --- /dev/null +++ b/examples/example/audio/largeHall.js @@ -0,0 +1,78 @@ +// +// largeHall.js +// examples +// +// Created by Freidrica on 4/1/16. +// Copyright 2016 High Fidelity, Inc. +// +// This entity script invokes reverb upon entering an entity acting as a trigger zone + +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +(function() { + var _this = this; + print("EBL PRELOADING NEW VERSION ") + var audioOptions = new AudioEffectOptions({ + bandwidth: 7000, + preDelay: 80, + lateDelay: 0, + reverbTime: 3, + earlyDiffusion: 100, + lateDiffusion: 100, + roomSize: 50, + density: 100, + bassMult: 1.5, + bassFreq: 250, + highGain: -12, + highFreq: 3000, + modRate: 2.3, + modDepth: 50, + earlyGain: -12, + lateGain: -12, + earlyMixLeft: 20, + earlyMixRight: 20, + lateMixLeft: 90, + lateMixRight: 90, + wetDryMix: 90, + }); + + function setter(name) { + return function(value) { + audioOptions[name] = value; + AudioDevice.setReverbOptions(audioOptions); + } + } + + function getter(name) { + return function() { + return audioOptions[name]; + } + } + + function displayer(units) { + return function(value) { + return (value).toFixed(1) + units; + } + } + + function scriptEnding() { + AudioDevice.setReverb(false); + print("Reverb is OFF."); + } + _this.enterEntity = function(entityID) { + print('EBL I am insiude'); + + AudioDevice.setReverbOptions(audioOptions); + AudioDevice.setReverb(true); + print("Reverb is ON."); + }; + + _this.leaveEntity = function(entityID) { + print('EBL I am outsidee'); + AudioDevice.setReverb(false); + print("Reverb is OFF."); + // Messages.sendMessage('PlayBackOnAssignment', 'BowShootingGameWelcome'); + }; +}); \ No newline at end of file diff --git a/examples/example/audio/smallRoom.js b/examples/example/audio/smallRoom.js new file mode 100644 index 0000000000..e692c8810d --- /dev/null +++ b/examples/example/audio/smallRoom.js @@ -0,0 +1,78 @@ +// +// smallRoom.js +// examples +// +// Created by Freidrica on 4/1/16. +// Copyright 2016 High Fidelity, Inc. +// +// This entity script invokes reverb upon entering an entity acting as a trigger zone + +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +(function() { + var _this = this; + print("EBL PRELOADING NEW VERSION ") + var audioOptions = new AudioEffectOptions({ + bandwidth: 7000, + preDelay: 20, + lateDelay: 0, + reverbTime: 1.5, + earlyDiffusion: 100, + lateDiffusion: 100, + roomSize: 50, + density: 100, + bassMult: 1.5, + bassFreq: 250, + highGain: -12, + highFreq: 3000, + modRate: 2.3, + modDepth: 50, + earlyGain: -24, + lateGain: -24, + earlyMixLeft: 20, + earlyMixRight: 20, + lateMixLeft: 90, + lateMixRight: 90, + wetDryMix: 70, + }); + + function setter(name) { + return function(value) { + audioOptions[name] = value; + AudioDevice.setReverbOptions(audioOptions); + } + } + + function getter(name) { + return function() { + return audioOptions[name]; + } + } + + function displayer(units) { + return function(value) { + return (value).toFixed(1) + units; + } + } + + function scriptEnding() { + AudioDevice.setReverb(false); + print("Reverb is OFF."); + } + _this.enterEntity = function(entityID) { + print('EBL I am insiude'); + // create a slider for each parameter + AudioDevice.setReverbOptions(audioOptions); + AudioDevice.setReverb(true); + print("Reverb is ON."); + }; + + _this.leaveEntity = function(entityID) { + print('EBL I am outside'); + AudioDevice.setReverb(false); + print("Reverb is OFF."); + }; +}); \ No newline at end of file diff --git a/examples/html/edit-style.css b/examples/html/edit-style.css index d4f7f7b16a..0ab9ad406e 100644 --- a/examples/html/edit-style.css +++ b/examples/html/edit-style.css @@ -10,43 +10,50 @@ @font-face { font-family: Raleway-Regular; - src: url(../../resources/fonts/Raleway-Regular.ttf), /* Production */ - url(../../interface/resources/fonts/Raleway-Regular.ttf); /* Development */ + src: url(../../resources/fonts/Raleway-Regular.ttf), /* Windows production */ + url(../../fonts/Raleway-Regular.ttf), /* OSX production */ + url(../../interface/resources/fonts/Raleway-Regular.ttf); /* Development, running script in /HiFi/examples */ } @font-face { font-family: Raleway-Light; src: url(../../resources/fonts/Raleway-Light.ttf), + url(../../fonts/Raleway-Light.ttf), url(../../interface/resources/fonts/Raleway-Light.ttf); } @font-face { font-family: Raleway-Bold; src: url(../../resources/fonts/Raleway-Bold.ttf), + url(../../fonts/Raleway-Bold.ttf), url(../../interface/resources/fonts/Raleway-Bold.ttf); } @font-face { font-family: Raleway-SemiBold; src: url(../../resources/fonts/Raleway-SemiBold.ttf), + url(../../fonts/Raleway-SemiBold.ttf), url(../../interface/resources/fonts/Raleway-SemiBold.ttf); } @font-face { font-family: FiraSans-SemiBold; src: url(../../resources/fonts/FiraSans-SemiBold.ttf), + url(../../fonts/FiraSans-SemiBold.ttf), url(../../interface/resources/fonts/FiraSans-SemiBold.ttf); } @font-face { font-family: AnonymousPro-Regular; - src: url(../../resources/fonts/AnonymousPro-Regular.ttf), + src: url(../../resources/fonts/AnonymousPro-Regular.ttf), + url(../../fonts/AnonymousPro-Regular.ttf), url(../../interface/resources/fonts/AnonymousPro-Regular.ttf); } @font-face { font-family: HiFi-Glyphs; - src: url(../../resources/fonts/hifi-glyphs.ttf), + src: url(../../resources/fonts/hifi-glyphs.ttf), + url(../../fonts/hifi-glyphs.ttf), url(../../interface/resources/fonts/hifi-glyphs.ttf); } @@ -69,8 +76,10 @@ body { -moz-user-select: none; -ms-user-select: none; user-select: none; -} + overflow-x: hidden; + overflow-y: auto; +} table { font-family: FiraSans-SemiBold; @@ -237,11 +246,11 @@ input[type="text"] { input[type="number"] { position: relative; height: 28px; - width: 120px; + width: 124px; } input[type=number] { - padding-right: 6px; + padding-right: 3px; } input[type=number]::-webkit-inner-spin-button { -webkit-appearance: none; @@ -249,6 +258,7 @@ input[type=number]::-webkit-inner-spin-button { display: block; position: relative; width: 10px; + height: 100%; overflow: hidden; font-family: hifi-glyphs; font-size: 50px; @@ -474,6 +484,10 @@ input[type=checkbox]:checked + label:hover { min-height: 29px; } +.property.checkbox { + width: auto; +} + .property label, .number label { display: table-cell; vertical-align: middle; @@ -678,7 +692,7 @@ div.refresh input[type="button"] { padding-left: 25px; } .pyr .tuple input { - padding-left: 45px; + padding-left: 40px; } .tuple div > label:first-child { @@ -802,6 +816,7 @@ textarea:enabled[scrolling="true"]::-webkit-resizer { width: 100%; overflow-x: hidden; overflow-y: auto; + box-sizing: border-box; padding-top: 28px; /* Space for header and footer outside of scroll region. */ margin-top: 28px; border-left: 2px solid #575757; @@ -820,6 +835,7 @@ textarea:enabled[scrolling="true"]::-webkit-resizer { } #entity-table thead { + box-sizing: border-box; border: 2px solid #575757; border-top-left-radius: 7px; border-top-right-radius: 7px; @@ -827,6 +843,7 @@ textarea:enabled[scrolling="true"]::-webkit-resizer { } #entity-table tfoot { + box-sizing: border-box; border: 2px solid #575757; border-bottom-left-radius: 7px; border-bottom-right-radius: 7px; diff --git a/examples/html/entityList.html b/examples/html/entityList.html index 04ba309cb0..45f91767dc 100644 --- a/examples/html/entityList.html +++ b/examples/html/entityList.html @@ -254,7 +254,7 @@ function resize() { // Take up available window space - elEntityTableScroll.style.height = window.innerHeight - 232; + elEntityTableScroll.style.height = window.innerHeight - 200; // Update the widths of the header cells to match the body var tds = document.querySelectorAll("#entity-table-body tr:first-child td"); diff --git a/interface/resources/controllers/oculus_remote.json b/interface/resources/controllers/oculus_remote.json new file mode 100644 index 0000000000..580d7b1184 --- /dev/null +++ b/interface/resources/controllers/oculus_remote.json @@ -0,0 +1,13 @@ +{ + "name": "Oculus Remote to Standard", + "channels": [ + { "from": "OculusRemote.Start", "to": "Actions.UiNavSelect" }, + { "from": "OculusRemote.Back", "to": "Actions.UiNavBack" }, + { "from": "OculusRemote.DU", "to": "Standard.DU" }, + { "from": "OculusRemote.DD", "to": "Standard.DD" }, + { "from": "OculusRemote.DL", "to": "Standard.DL" }, + { "from": "OculusRemote.DR", "to": "Standard.DR" } + ] +} + + diff --git a/interface/resources/controllers/oculus_touch.json b/interface/resources/controllers/oculus_touch.json new file mode 100644 index 0000000000..cc8c2f8bdc --- /dev/null +++ b/interface/resources/controllers/oculus_touch.json @@ -0,0 +1,23 @@ +{ + "name": "Oculus Touch to Standard", + "channels": [ + { "from": "OculusTouch.LY", "filters": "invert", "to": "Standard.LY" }, + { "from": "OculusTouch.LX", "to": "Standard.LX" }, + { "from": "OculusTouch.LT", "to": "Standard.LT" }, + + { "from": "OculusTouch.RY", "filters": "invert", "to": "Standard.RY" }, + { "from": "OculusTouch.RX", "to": "Standard.RX" }, + + { "from": "OculusTouch.RT", "to": "Standard.RT" }, + { "from": "OculusTouch.RB", "to": "Standard.RB" }, + { "from": "OculusTouch.RS", "to": "Standard.RS" }, + + { "from": "OculusTouch.LeftApplicationMenu", "to": "Standard.Back" }, + { "from": "OculusTouch.RightApplicationMenu", "to": "Standard.Start" }, + + { "from": "OculusTouch.LeftHand", "to": "Standard.LeftHand" }, + { "from": "OculusTouch.RightHand", "to": "Standard.RightHand" } + ] +} + + diff --git a/interface/resources/controllers/standard_navigation.json b/interface/resources/controllers/standard_navigation.json index 62c0883142..c9dc90cbe6 100644 --- a/interface/resources/controllers/standard_navigation.json +++ b/interface/resources/controllers/standard_navigation.json @@ -2,9 +2,6 @@ "name": "Standard to Action", "when": "Application.NavigationFocused", "channels": [ - { "disabled_from": { "makeAxis" : [ "Standard.DD", "Standard.DU" ] }, "to": "Actions.UiNavVertical" }, - { "disabled_from": { "makeAxis" : [ "Standard.DL", "Standard.DR" ] }, "to": "Actions.UiNavLateral" }, - { "disabled_from": { "makeAxis" : [ "Standard.LB", "Standard.RB" ] }, "to": "Actions.UiNavGroup" }, { "from": "Standard.DU", "to": "Actions.UiNavVertical" }, { "from": "Standard.DD", "to": "Actions.UiNavVertical", "filters": "invert" }, { "from": "Standard.DL", "to": "Actions.UiNavLateral", "filters": "invert" }, diff --git a/interface/resources/qml/ToolWindow.qml b/interface/resources/qml/ToolWindow.qml index b6d70cb245..ddd314d9ff 100644 --- a/interface/resources/qml/ToolWindow.qml +++ b/interface/resources/qml/ToolWindow.qml @@ -30,7 +30,7 @@ Window { title: "Edit" property alias tabView: tabView implicitWidth: 520; implicitHeight: 695 - minSize: Qt.vector2d(400, 500) + minSize: Qt.vector2d(412, 500) HifiConstants { id: hifi } diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index adcd43952c..a6555352dd 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -241,6 +241,8 @@ public: *crashTrigger = 0xDEAD10CC; } + static void setSuppressStatus(bool suppress) { _suppressStatus = suppress; } + void run() override { while (!_quit) { QThread::sleep(HEARTBEAT_UPDATE_INTERVAL_SECS); @@ -252,35 +254,45 @@ public: auto elapsedMovingAverage = _movingAverage.getAverage(); if (elapsedMovingAverage > _maxElapsedAverage) { - qDebug() << "DEADLOCK WATCHDOG NEW maxElapsedAverage:" + qDebug() << "DEADLOCK WATCHDOG WARNING:" << "lastHeartbeatAge:" << lastHeartbeatAge << "elapsedMovingAverage:" << elapsedMovingAverage << "maxElapsed:" << _maxElapsed << "PREVIOUS maxElapsedAverage:" << _maxElapsedAverage - << "NEW maxElapsedAverage:" << elapsedMovingAverage + << "NEW maxElapsedAverage:" << elapsedMovingAverage << "** NEW MAX ELAPSED AVERAGE **" << "samples:" << _movingAverage.getSamples(); _maxElapsedAverage = elapsedMovingAverage; } if (lastHeartbeatAge > _maxElapsed) { - qDebug() << "DEADLOCK WATCHDOG NEW maxElapsed:" + qDebug() << "DEADLOCK WATCHDOG WARNING:" << "lastHeartbeatAge:" << lastHeartbeatAge << "elapsedMovingAverage:" << elapsedMovingAverage << "PREVIOUS maxElapsed:" << _maxElapsed - << "NEW maxElapsed:" << lastHeartbeatAge + << "NEW maxElapsed:" << lastHeartbeatAge << "** NEW MAX ELAPSED **" << "maxElapsedAverage:" << _maxElapsedAverage << "samples:" << _movingAverage.getSamples(); _maxElapsed = lastHeartbeatAge; } - if ((sinceLastReport > HEARTBEAT_REPORT_INTERVAL_USECS) || (elapsedMovingAverage > WARNING_ELAPSED_HEARTBEAT)) { - qDebug() << "DEADLOCK WATCHDOG STATUS -- lastHeartbeatAge:" << lastHeartbeatAge - << "elapsedMovingAverage:" << elapsedMovingAverage - << "maxElapsed:" << _maxElapsed - << "maxElapsedAverage:" << _maxElapsedAverage - << "samples:" << _movingAverage.getSamples(); + if (elapsedMovingAverage > WARNING_ELAPSED_HEARTBEAT) { + qDebug() << "DEADLOCK WATCHDOG WARNING:" + << "lastHeartbeatAge:" << lastHeartbeatAge + << "elapsedMovingAverage:" << elapsedMovingAverage << "** OVER EXPECTED VALUE**" + << "maxElapsed:" << _maxElapsed + << "maxElapsedAverage:" << _maxElapsedAverage + << "samples:" << _movingAverage.getSamples(); + _lastReport = now; + } + + if (!_suppressStatus && sinceLastReport > HEARTBEAT_REPORT_INTERVAL_USECS) { + qDebug() << "DEADLOCK WATCHDOG STATUS:" + << "lastHeartbeatAge:" << lastHeartbeatAge + << "elapsedMovingAverage:" << elapsedMovingAverage + << "maxElapsed:" << _maxElapsed + << "maxElapsedAverage:" << _maxElapsedAverage + << "samples:" << _movingAverage.getSamples(); _lastReport = now; } -#ifdef NDEBUG if (lastHeartbeatAge > MAX_HEARTBEAT_AGE_USECS) { qDebug() << "DEADLOCK DETECTED -- " << "lastHeartbeatAge:" << lastHeartbeatAge @@ -290,12 +302,17 @@ public: << "maxElapsed:" << _maxElapsed << "maxElapsedAverage:" << _maxElapsedAverage << "samples:" << _movingAverage.getSamples(); - deadlockDetectionCrash(); + + // Don't actually crash in debug builds, in case this apparent deadlock is simply from + // the developer actively debugging code + #ifdef NDEBUG + deadlockDetectionCrash(); + #endif } -#endif } } + static std::atomic _suppressStatus; static std::atomic _heartbeat; static std::atomic _lastReport; static std::atomic _maxElapsed; @@ -305,12 +322,17 @@ public: bool _quit { false }; }; +std::atomic DeadlockWatchdogThread::_suppressStatus; std::atomic DeadlockWatchdogThread::_heartbeat; std::atomic DeadlockWatchdogThread::_lastReport; std::atomic DeadlockWatchdogThread::_maxElapsed; std::atomic DeadlockWatchdogThread::_maxElapsedAverage; ThreadSafeMovingAverage DeadlockWatchdogThread::_movingAverage; +void Application::toggleSuppressDeadlockWatchdogStatus(bool checked) { + DeadlockWatchdogThread::setSuppressStatus(checked); +} + #ifdef Q_OS_WIN class MyNativeEventFilter : public QAbstractNativeEventFilter { public: @@ -380,6 +402,11 @@ void messageHandler(QtMsgType type, const QMessageLogContext& context, const QSt } } +static const QString STATE_IN_HMD = "InHMD"; +static const QString STATE_SNAP_TURN = "SnapTurn"; +static const QString STATE_GROUNDED = "Grounded"; +static const QString STATE_NAV_FOCUSED = "NavigationFocused"; + bool setupEssentials(int& argc, char** argv) { unsigned int listenPort = 0; // bind to an ephemeral port by default const char** constArgv = const_cast(argv); @@ -449,6 +476,7 @@ bool setupEssentials(int& argc, char** argv) { DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); + controller::StateController::setStateVariables({ { STATE_IN_HMD, STATE_SNAP_TURN, STATE_GROUNDED, STATE_NAV_FOCUSED } }); DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); @@ -895,9 +923,19 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : DependencyManager::get()->toggleMute(); } else if (action == controller::toInt(controller::Action::CYCLE_CAMERA)) { cycleCamera(); + } else if (action == controller::toInt(controller::Action::UI_NAV_SELECT)) { + if (!offscreenUi->navigationFocused()) { + auto reticlePosition = getApplicationCompositor().getReticlePosition(); + offscreenUi->toggleMenu(_glWidget->mapFromGlobal(QPoint(reticlePosition.x, reticlePosition.y))); + } } else if (action == controller::toInt(controller::Action::CONTEXT_MENU)) { auto reticlePosition = getApplicationCompositor().getReticlePosition(); offscreenUi->toggleMenu(_glWidget->mapFromGlobal(QPoint(reticlePosition.x, reticlePosition.y))); + } else if (action == controller::toInt(controller::Action::UI_NAV_SELECT)) { + if (!offscreenUi->navigationFocused()) { + auto reticlePosition = getApplicationCompositor().getReticlePosition(); + offscreenUi->toggleMenu(_glWidget->mapFromGlobal(QPoint(reticlePosition.x, reticlePosition.y))); + } } else if (action == controller::toInt(controller::Action::RETICLE_X)) { auto oldPos = getApplicationCompositor().getReticlePosition(); getApplicationCompositor().setReticlePosition({ oldPos.x + state, oldPos.y }); @@ -910,28 +948,23 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : } }); - // A new controllerInput device used to reflect current values from the application state - _applicationStateDevice = std::make_shared(); + _applicationStateDevice = userInputMapper->getStateDevice(); - _applicationStateDevice->addInputVariant(QString("InHMD"), controller::StateController::ReadLambda([]() -> float { - return (float)qApp->isHMDMode(); - })); - _applicationStateDevice->addInputVariant(QString("SnapTurn"), controller::StateController::ReadLambda([]() -> float { - return (float)qApp->getMyAvatar()->getSnapTurn(); - })); - _applicationStateDevice->addInputVariant(QString("Grounded"), controller::StateController::ReadLambda([]() -> float { - return (float)qApp->getMyAvatar()->getCharacterController()->onGround(); - })); - _applicationStateDevice->addInputVariant(QString("NavigationFocused"), controller::StateController::ReadLambda([]() -> float { - auto offscreenUi = DependencyManager::get(); - return offscreenUi->navigationFocused() ? 1.0 : 0.0; - })); - - userInputMapper->registerDevice(_applicationStateDevice); + _applicationStateDevice->setInputVariant(STATE_IN_HMD, []() -> float { + return qApp->isHMDMode() ? 1 : 0; + }); + _applicationStateDevice->setInputVariant(STATE_SNAP_TURN, []() -> float { + return qApp->getMyAvatar()->getSnapTurn() ? 1 : 0; + }); + _applicationStateDevice->setInputVariant(STATE_GROUNDED, []() -> float { + return qApp->getMyAvatar()->getCharacterController()->onGround() ? 1 : 0; + }); + _applicationStateDevice->setInputVariant(STATE_NAV_FOCUSED, []() -> float { + return DependencyManager::get()->navigationFocused() ? 1 : 0; + }); // Setup the keyboardMouseDevice and the user input mapper with the default bindings userInputMapper->registerDevice(_keyboardMouseDevice->getInputDevice()); - userInputMapper->loadDefaultMapping(userInputMapper->getStandardDeviceID()); // force the model the look at the correct directory (weird order of operations issue) scriptEngines->setScriptsLocation(scriptEngines->getScriptsLocation()); @@ -2152,10 +2185,43 @@ void Application::keyPressEvent(QKeyEvent* event) { if (isShifted) { Menu::getInstance()->triggerOption(MenuOption::MiniMirror); } else { + // whenever switching to/from full screen mirror from the keyboard, remember + // the state you were in before full screen mirror, and return to that. + auto previousMode = _myCamera.getMode(); + if (previousMode != CAMERA_MODE_MIRROR) { + switch (previousMode) { + case CAMERA_MODE_FIRST_PERSON: + _returnFromFullScreenMirrorTo = MenuOption::FirstPerson; + break; + case CAMERA_MODE_THIRD_PERSON: + _returnFromFullScreenMirrorTo = MenuOption::ThirdPerson; + break; + + // FIXME - it's not clear that these modes make sense to return to... + case CAMERA_MODE_INDEPENDENT: + _returnFromFullScreenMirrorTo = MenuOption::IndependentMode; + break; + case CAMERA_MODE_ENTITY: + _returnFromFullScreenMirrorTo = MenuOption::CameraEntityMode; + break; + + default: + _returnFromFullScreenMirrorTo = MenuOption::ThirdPerson; + break; + } + } + bool isMirrorChecked = Menu::getInstance()->isOptionChecked(MenuOption::FullscreenMirror); Menu::getInstance()->setIsOptionChecked(MenuOption::FullscreenMirror, !isMirrorChecked); if (isMirrorChecked) { - Menu::getInstance()->setIsOptionChecked(MenuOption::ThirdPerson, true); + + // if we got here without coming in from a non-Full Screen mirror case, then our + // _returnFromFullScreenMirrorTo is unknown. In that case we'll go to the old + // behavior of returning to ThirdPerson + if (_returnFromFullScreenMirrorTo.isEmpty()) { + _returnFromFullScreenMirrorTo = MenuOption::ThirdPerson; + } + Menu::getInstance()->setIsOptionChecked(_returnFromFullScreenMirrorTo, true); } cameraMenuChanged(); } @@ -3462,11 +3528,9 @@ void Application::update(float deltaTime) { const quint64 TOO_LONG_SINCE_LAST_QUERY = 3 * USECS_PER_SECOND; bool queryIsDue = sinceLastQuery > TOO_LONG_SINCE_LAST_QUERY; bool viewIsDifferentEnough = !_lastQueriedViewFrustum.isVerySimilar(_viewFrustum); - // if it's been a while since our last query or the view has significantly changed then send a query, otherwise suppress it if (queryIsDue || viewIsDifferentEnough) { _lastQueriedTime = now; - if (DependencyManager::get()->shouldRenderEntities()) { queryOctree(NodeType::EntityServer, PacketType::EntityQuery, _entityServerJurisdictions); } @@ -3561,7 +3625,7 @@ int Application::sendNackPackets() { return packetsSent; } -void Application::queryOctree(NodeType_t serverType, PacketType packetType, NodeToJurisdictionMap& jurisdictions) { +void Application::queryOctree(NodeType_t serverType, PacketType packetType, NodeToJurisdictionMap& jurisdictions, bool forceResend) { if (!_settingsLoaded) { return; // bail early if settings are not loaded @@ -3648,7 +3712,7 @@ void Application::queryOctree(NodeType_t serverType, PacketType packetType, Node auto queryPacket = NLPacket::create(packetType); - nodeList->eachNode([&](const SharedNodePointer& node){ + nodeList->eachNode([&](const SharedNodePointer& node) { // only send to the NodeTypes that are serverType if (node->getActiveSocket() && node->getType() == serverType) { @@ -3717,6 +3781,16 @@ void Application::queryOctree(NodeType_t serverType, PacketType packetType, Node _octreeQuery.setMaxQueryPacketsPerSecond(0); } + // if asked to forceResend, then set the query's position/orientation to be degenerate in a manner + // that will cause our next query to be guarenteed to be different and the server will resend to us + if (forceResend) { + _octreeQuery.setCameraPosition(glm::vec3(-0.1, -0.1, -0.1)); + const glm::quat OFF_IN_NEGATIVE_SPACE = glm::quat(-0.5, 0, -0.5, 1.0); + _octreeQuery.setCameraOrientation(OFF_IN_NEGATIVE_SPACE); + _octreeQuery.setCameraNearClip(0.1f); + _octreeQuery.setCameraFarClip(0.1f); + } + // encode the query data int packetSize = _octreeQuery.getBroadcastData(reinterpret_cast(queryPacket->getPayload())); queryPacket->setPayloadSize(packetSize); @@ -4133,6 +4207,7 @@ void Application::clearDomainOctreeDetails() { auto skyStage = DependencyManager::get()->getSkyStage(); skyStage->setBackgroundMode(model::SunSkyStage::SKY_DOME); + _recentlyClearedDomain = true; } void Application::domainChanged(const QString& domainHostname) { @@ -4154,7 +4229,7 @@ void Application::nodeAdded(SharedNodePointer node) const { } } -void Application::nodeActivated(SharedNodePointer node) const { +void Application::nodeActivated(SharedNodePointer node) { if (node->getType() == NodeType::AssetServer) { // asset server just connected - check if we have the asset browser showing @@ -4173,10 +4248,20 @@ void Application::nodeActivated(SharedNodePointer node) const { } } } + + // If we get a new EntityServer activated, do a "forceRedraw" query. This will send a degenerate + // query so that the server will think our next non-degenerate query is "different enough" to send + // us a full scene + if (_recentlyClearedDomain && node->getType() == NodeType::EntityServer) { + _recentlyClearedDomain = false; + if (DependencyManager::get()->shouldRenderEntities()) { + queryOctree(NodeType::EntityServer, PacketType::EntityQuery, _entityServerJurisdictions, true); + } + } + } void Application::nodeKilled(SharedNodePointer node) { - // These are here because connecting NodeList::nodeKilled to OctreePacketProcessor::nodeKilled doesn't work: // OctreePacketProcessor::nodeKilled is not being called when NodeList::nodeKilled is emitted. // This may have to do with GenericThread::threadRoutine() blocking the QThread event loop diff --git a/interface/src/Application.h b/interface/src/Application.h index f2b6edeea4..ebe2e53584 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -255,6 +255,7 @@ public slots: void resetSensors(bool andReload = false); void setActiveFaceTracker() const; + void toggleSuppressDeadlockWatchdogStatus(bool checked); #ifdef HAVE_IVIEWHMD void setActiveEyeTracker(); @@ -312,7 +313,7 @@ private slots: void domainChanged(const QString& domainHostname); void updateWindowTitle() const; void nodeAdded(SharedNodePointer node) const; - void nodeActivated(SharedNodePointer node) const; + void nodeActivated(SharedNodePointer node); void nodeKilled(SharedNodePointer node); static void packetSent(quint64 length); void updateDisplayMode(); @@ -331,7 +332,7 @@ private: void updateThreads(float deltaTime); void updateDialogs(float deltaTime) const; - void queryOctree(NodeType_t serverType, PacketType packetType, NodeToJurisdictionMap& jurisdictions); + void queryOctree(NodeType_t serverType, PacketType packetType, NodeToJurisdictionMap& jurisdictions, bool forceResend = false); static void loadViewFrustum(Camera& camera, ViewFrustum& viewFrustum); glm::vec3 getSunDirection() const; @@ -520,6 +521,10 @@ private: std::atomic _processOctreeStatsCounter { 0 }; bool _keyboardDeviceHasFocus { true }; + + bool _recentlyClearedDomain { false }; + + QString _returnFromFullScreenMirrorTo; }; #endif // hifi_Application_h diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 12cec7b383..f6386ba72d 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -219,12 +219,12 @@ Menu::Menu() { // View > First Person cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::FirstPerson, 0, // QML Qt:: Key_P - false, qApp, SLOT(cameraMenuChanged()))); + true, qApp, SLOT(cameraMenuChanged()))); // View > Third Person cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::ThirdPerson, 0, - true, qApp, SLOT(cameraMenuChanged()))); + false, qApp, SLOT(cameraMenuChanged()))); // View > Mirror cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(viewMenu, @@ -530,6 +530,9 @@ Menu::Menu() { addCheckableActionToQMenuAndActionHash(timingMenu, MenuOption::PipelineWarnings); addCheckableActionToQMenuAndActionHash(timingMenu, MenuOption::LogExtraTimings); addCheckableActionToQMenuAndActionHash(timingMenu, MenuOption::SuppressShortTimings); + addCheckableActionToQMenuAndActionHash(timingMenu, MenuOption::SupressDeadlockWatchdogStatus, 0, false, + qApp, SLOT(toggleSuppressDeadlockWatchdogStatus(bool))); + // Developer > Audio >>> MenuWrapper* audioDebugMenu = developerMenu->addMenu("Audio"); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 3488f03b0e..48bda01076 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -162,6 +162,7 @@ namespace MenuOption { const QString Stats = "Stats"; const QString StopAllScripts = "Stop All Scripts"; const QString SuppressShortTimings = "Suppress Timings Less than 10ms"; + const QString SupressDeadlockWatchdogStatus = "Supress Deadlock Watchdog Status"; const QString ThirdPerson = "Third Person"; const QString ThreePointCalibration = "3 Point Calibration"; const QString ThrottleFPSIfNotFocus = "Throttle FPS If Not Focus"; // FIXME - this value duplicated in Basic2DWindowOpenGLDisplayPlugin.cpp diff --git a/libraries/audio/src/AudioInjector.cpp b/libraries/audio/src/AudioInjector.cpp index ad6b436d02..a6515f5f65 100644 --- a/libraries/audio/src/AudioInjector.cpp +++ b/libraries/audio/src/AudioInjector.cpp @@ -32,8 +32,8 @@ AudioInjector::AudioInjector(QObject* parent) : } -AudioInjector::AudioInjector(Sound* sound, const AudioInjectorOptions& injectorOptions) : - _audioData(sound->getByteArray()), +AudioInjector::AudioInjector(const Sound& sound, const AudioInjectorOptions& injectorOptions) : + _audioData(sound.getByteArray()), _options(injectorOptions) { diff --git a/libraries/audio/src/AudioInjector.h b/libraries/audio/src/AudioInjector.h index 79e2e645dd..2dad2856b9 100644 --- a/libraries/audio/src/AudioInjector.h +++ b/libraries/audio/src/AudioInjector.h @@ -45,7 +45,7 @@ public: }; AudioInjector(QObject* parent); - AudioInjector(Sound* sound, const AudioInjectorOptions& injectorOptions); + AudioInjector(const Sound& sound, const AudioInjectorOptions& injectorOptions); AudioInjector(const QByteArray& audioData, const AudioInjectorOptions& injectorOptions); bool isFinished() const { return _state == State::Finished; } diff --git a/libraries/audio/src/AudioRingBuffer.cpp b/libraries/audio/src/AudioRingBuffer.cpp index ae69efee92..96f1bbb9dd 100644 --- a/libraries/audio/src/AudioRingBuffer.cpp +++ b/libraries/audio/src/AudioRingBuffer.cpp @@ -23,13 +23,15 @@ #include "AudioRingBuffer.h" +static const QString RING_BUFFER_OVERFLOW_DEBUG { "AudioRingBuffer::writeData has overflown the buffer. Overwriting old data." }; + AudioRingBuffer::AudioRingBuffer(int numFrameSamples, bool randomAccessMode, int numFramesCapacity) : -_frameCapacity(numFramesCapacity), -_sampleCapacity(numFrameSamples * numFramesCapacity), -_bufferLength(numFrameSamples * (numFramesCapacity + 1)), -_numFrameSamples(numFrameSamples), -_randomAccessMode(randomAccessMode), -_overflowCount(0) + _frameCapacity(numFramesCapacity), + _sampleCapacity(numFrameSamples * numFramesCapacity), + _bufferLength(numFrameSamples * (numFramesCapacity + 1)), + _numFrameSamples(numFrameSamples), + _randomAccessMode(randomAccessMode), + _overflowCount(0) { if (numFrameSamples) { _buffer = new int16_t[_bufferLength]; @@ -41,6 +43,8 @@ _overflowCount(0) _nextOutput = NULL; _endOfLastWrite = NULL; } + + static QString repeatedMessage = LogHandler::getInstance().addRepeatedMessageRegex(RING_BUFFER_OVERFLOW_DEBUG); }; AudioRingBuffer::~AudioRingBuffer() { @@ -131,8 +135,6 @@ int AudioRingBuffer::writeData(const char* data, int maxSize) { _nextOutput = shiftedPositionAccomodatingWrap(_nextOutput, samplesToDelete); _overflowCount++; - const QString RING_BUFFER_OVERFLOW_DEBUG { "AudioRingBuffer::writeData has overflown the buffer. Overwriting old data." }; - static QString repeatedMessage = LogHandler::getInstance().addRepeatedMessageRegex(RING_BUFFER_OVERFLOW_DEBUG); qCDebug(audio) << qPrintable(RING_BUFFER_OVERFLOW_DEBUG); } @@ -179,7 +181,12 @@ int AudioRingBuffer::addSilentSamples(int silentSamples) { if (silentSamples > samplesRoomFor) { // there's not enough room for this write. write as many silent samples as we have room for silentSamples = samplesRoomFor; - qCDebug(audio) << "Dropping some silent samples to prevent ring buffer overflow"; + + static const QString DROPPED_SILENT_DEBUG { + "AudioRingBuffer::addSilentSamples dropping silent samples to prevent overflow." + }; + static QString repeatedMessage = LogHandler::getInstance().addRepeatedMessageRegex(DROPPED_SILENT_DEBUG); + qCDebug(audio) << qPrintable(DROPPED_SILENT_DEBUG); } // memset zeroes into the buffer, accomodate a wrap around the end @@ -243,7 +250,7 @@ int AudioRingBuffer::writeSamples(ConstIterator source, int maxSamples) { int samplesToDelete = samplesToCopy - samplesRoomFor; _nextOutput = shiftedPositionAccomodatingWrap(_nextOutput, samplesToDelete); _overflowCount++; - qCDebug(audio) << "Overflowed ring buffer! Overwriting old data"; + qCDebug(audio) << qPrintable(RING_BUFFER_OVERFLOW_DEBUG); } int16_t* bufferLast = _buffer + _bufferLength - 1; @@ -264,7 +271,7 @@ int AudioRingBuffer::writeSamplesWithFade(ConstIterator source, int maxSamples, int samplesToDelete = samplesToCopy - samplesRoomFor; _nextOutput = shiftedPositionAccomodatingWrap(_nextOutput, samplesToDelete); _overflowCount++; - qCDebug(audio) << "Overflowed ring buffer! Overwriting old data"; + qCDebug(audio) << qPrintable(RING_BUFFER_OVERFLOW_DEBUG); } int16_t* bufferLast = _buffer + _bufferLength - 1; diff --git a/libraries/audio/src/Sound.cpp b/libraries/audio/src/Sound.cpp index 9816b1b61d..806e33819e 100644 --- a/libraries/audio/src/Sound.cpp +++ b/libraries/audio/src/Sound.cpp @@ -27,22 +27,18 @@ #include "AudioLogging.h" #include "Sound.h" -static int soundMetaTypeId = qRegisterMetaType(); - -QScriptValue soundSharedPointerToScriptValue(QScriptEngine* engine, SharedSoundPointer const& in) { - return engine->newQObject(in.data()); +QScriptValue soundSharedPointerToScriptValue(QScriptEngine* engine, const SharedSoundPointer& in) { + return engine->newQObject(new SoundScriptingInterface(in), QScriptEngine::ScriptOwnership); } -void soundSharedPointerFromScriptValue(const QScriptValue& object, SharedSoundPointer &out) { - out = SharedSoundPointer(qobject_cast(object.toQObject())); +void soundSharedPointerFromScriptValue(const QScriptValue& object, SharedSoundPointer& out) { + if (auto soundInterface = qobject_cast(object.toQObject())) { + out = soundInterface->getSound(); + } } -QScriptValue soundPointerToScriptValue(QScriptEngine* engine, Sound* const& in) { - return engine->newQObject(in); -} - -void soundPointerFromScriptValue(const QScriptValue &object, Sound* &out) { - out = qobject_cast(object.toQObject()); +SoundScriptingInterface::SoundScriptingInterface(SharedSoundPointer sound) : _sound(sound) { + QObject::connect(sound.data(), &Sound::ready, this, &SoundScriptingInterface::ready); } Sound::Sound(const QUrl& url, bool isStereo) : diff --git a/libraries/audio/src/Sound.h b/libraries/audio/src/Sound.h index 91456f2fff..23ef92aa6a 100644 --- a/libraries/audio/src/Sound.h +++ b/libraries/audio/src/Sound.h @@ -20,18 +20,16 @@ class Sound : public Resource { Q_OBJECT - - Q_PROPERTY(bool downloaded READ isReady) - Q_PROPERTY(float duration READ getDuration) + public: Sound(const QUrl& url, bool isStereo = false); bool isStereo() const { return _isStereo; } bool isReady() const { return _isReady; } - float getDuration() { return _duration; } + float getDuration() const { return _duration; } - const QByteArray& getByteArray() { return _byteArray; } + const QByteArray& getByteArray() const { return _byteArray; } signals: void ready(); @@ -50,13 +48,28 @@ private: typedef QSharedPointer SharedSoundPointer; +class SoundScriptingInterface : public QObject { + Q_OBJECT + + Q_PROPERTY(bool downloaded READ isReady) + Q_PROPERTY(float duration READ getDuration) + +public: + SoundScriptingInterface(SharedSoundPointer sound); + SharedSoundPointer getSound() { return _sound; } + + bool isReady() const { return _sound->isReady(); } + float getDuration() { return _sound->getDuration(); } + +signals: + void ready(); + +private: + SharedSoundPointer _sound; +}; + Q_DECLARE_METATYPE(SharedSoundPointer) -QScriptValue soundSharedPointerToScriptValue(QScriptEngine* engine, SharedSoundPointer const& in); -void soundSharedPointerFromScriptValue(const QScriptValue& object, SharedSoundPointer &out); - -Q_DECLARE_METATYPE(Sound*) -QScriptValue soundPointerToScriptValue(QScriptEngine* engine, Sound* const& in); -void soundPointerFromScriptValue(const QScriptValue& object, Sound* &out); - +QScriptValue soundSharedPointerToScriptValue(QScriptEngine* engine, const SharedSoundPointer& in); +void soundSharedPointerFromScriptValue(const QScriptValue& object, SharedSoundPointer& out); #endif // hifi_Sound_h diff --git a/libraries/controllers/src/controllers/ScriptingInterface.cpp b/libraries/controllers/src/controllers/ScriptingInterface.cpp index 275aeedb6b..c2e64ca19e 100644 --- a/libraries/controllers/src/controllers/ScriptingInterface.cpp +++ b/libraries/controllers/src/controllers/ScriptingInterface.cpp @@ -33,8 +33,10 @@ static QVariantMap createDeviceMap(const controller::InputDevice::Pointer device for (const auto& inputMapping : userInputMapper->getAvailableInputs(device->getDeviceID())) { const auto& input = inputMapping.first; const auto inputName = QString(inputMapping.second).remove(SANITIZE_NAME_EXPRESSION); +#ifdef DEBUG qCDebug(controllers) << "\tInput " << input.getChannel() << (int)input.getType() << QString::number(input.getID(), 16) << ": " << inputName; +#endif deviceMap.insert(inputName, input.getID()); } return deviceMap; diff --git a/libraries/controllers/src/controllers/StateController.cpp b/libraries/controllers/src/controllers/StateController.cpp index 1d39449e4d..9b0301eb14 100644 --- a/libraries/controllers/src/controllers/StateController.cpp +++ b/libraries/controllers/src/controllers/StateController.cpp @@ -19,32 +19,39 @@ namespace controller { +static QStringList stateVariables; + +void StateController::setStateVariables(const QStringList& newStateVariables) { + stateVariables = newStateVariables; +} + StateController::StateController() : InputDevice("Application") { + _deviceID = UserInputMapper::STATE_DEVICE; + for (const auto& variable : stateVariables) { + _namedReadLambdas[variable] = []()->float{ return 0; }; + } } -StateController::~StateController() { -} - -void StateController::update(float deltaTime, const InputCalibrationData& inputCalibrationData, bool jointsCaptured) {} - -void StateController::focusOutEvent() {} - -void StateController::addInputVariant(QString name, ReadLambda lambda) { - _namedReadLambdas.push_back(NamedReadLambda(name, lambda)); +void StateController::setInputVariant(const QString& name, ReadLambda lambda) { + // All state variables must be predeclared; + Q_ASSERT(_namedReadLambdas.contains(name)); + _namedReadLambdas[name] = lambda; } Input::NamedVector StateController::getAvailableInputs() const { Input::NamedVector availableInputs; int i = 0; - for (auto& pair : _namedReadLambdas) { - availableInputs.push_back(Input::NamedPair(Input(_deviceID, i, ChannelType::BUTTON), pair.first)); + for (const auto& name : stateVariables) { + availableInputs.push_back(Input::NamedPair(Input(_deviceID, i, ChannelType::BUTTON), name)); i++; } return availableInputs; } EndpointPointer StateController::createEndpoint(const Input& input) const { - return std::make_shared(_namedReadLambdas[input.getChannel()].second); + auto name = stateVariables[input.getChannel()]; + ReadLambda& readLambda = const_cast&>(_namedReadLambdas)[name]; + return std::make_shared(readLambda); } } \ No newline at end of file diff --git a/libraries/controllers/src/controllers/StateController.h b/libraries/controllers/src/controllers/StateController.h index ed36e4f838..57414c3ae8 100644 --- a/libraries/controllers/src/controllers/StateController.h +++ b/libraries/controllers/src/controllers/StateController.h @@ -24,26 +24,28 @@ class StateController : public QObject, public InputDevice { Q_PROPERTY(QString name READ getName) public: + using Pointer = std::shared_ptr; + using ReadLambda = std::function; + using NamedReadLambda = QPair; + + static void setStateVariables(const QStringList& stateVariables); + + StateController(); + const QString& getName() const { return _name; } // Device functions virtual Input::NamedVector getAvailableInputs() const override; - virtual void update(float deltaTime, const InputCalibrationData& inputCalibrationData, bool jointsCaptured) override; - virtual void focusOutEvent() override; - StateController(); - virtual ~StateController(); + void update(float deltaTime, const InputCalibrationData& inputCalibrationData, bool jointsCaptured) override {} + void focusOutEvent() override {} - using ReadLambda = std::function; - using NamedReadLambda = QPair; - - void addInputVariant(QString name, ReadLambda lambda); - - virtual EndpointPointer createEndpoint(const Input& input) const override; + void setInputVariant(const QString& name, ReadLambda lambda); + EndpointPointer createEndpoint(const Input& input) const override; protected: - QVector _namedReadLambdas; + QHash _namedReadLambdas; }; } diff --git a/libraries/controllers/src/controllers/UserInputMapper.cpp b/libraries/controllers/src/controllers/UserInputMapper.cpp index fe64566b29..2b7d837aa5 100755 --- a/libraries/controllers/src/controllers/UserInputMapper.cpp +++ b/libraries/controllers/src/controllers/UserInputMapper.cpp @@ -44,13 +44,15 @@ namespace controller { - const uint16_t UserInputMapper::ACTIONS_DEVICE = Input::INVALID_DEVICE - 0xFF; const uint16_t UserInputMapper::STANDARD_DEVICE = 0; + const uint16_t UserInputMapper::ACTIONS_DEVICE = Input::INVALID_DEVICE - 0x00FF; + const uint16_t UserInputMapper::STATE_DEVICE = Input::INVALID_DEVICE - 0x0100; } // Default contruct allocate the poutput size with the current hardcoded action channels controller::UserInputMapper::UserInputMapper() { registerDevice(std::make_shared()); + registerDevice(_stateDevice = std::make_shared()); registerDevice(std::make_shared()); } @@ -138,7 +140,6 @@ void UserInputMapper::loadDefaultMapping(uint16 deviceID) { return; } - auto mapping = loadMappings(proxyEntry->second->getDefaultMappingConfigs()); if (mapping) { auto prevMapping = _mappingsByDevice[deviceID]; @@ -235,6 +236,10 @@ void fixBisectedAxis(float& full, float& negative, float& positive) { void UserInputMapper::update(float deltaTime) { Locker locker(_lock); + + static uint64_t updateCount = 0; + ++updateCount; + // Reset the axis state for next loop for (auto& channel : _actionStates) { channel = 0.0f; @@ -694,11 +699,17 @@ Pose UserInputMapper::getPose(const Input& input) const { return getPose(endpoint); } -Mapping::Pointer UserInputMapper::loadMapping(const QString& jsonFile) { +Mapping::Pointer UserInputMapper::loadMapping(const QString& jsonFile, bool enable) { Locker locker(_lock); if (jsonFile.isEmpty()) { return Mapping::Pointer(); } + // Each mapping only needs to be loaded once + static QSet loaded; + if (loaded.contains(jsonFile)) { + return Mapping::Pointer(); + } + loaded.insert(jsonFile); QString json; { QFile file(jsonFile); @@ -707,7 +718,11 @@ Mapping::Pointer UserInputMapper::loadMapping(const QString& jsonFile) { } file.close(); } - return parseMapping(json); + auto result = parseMapping(json); + if (enable) { + enableMapping(result->name); + } + return result; } MappingPointer UserInputMapper::loadMappings(const QStringList& jsonFiles) { @@ -961,7 +976,7 @@ Route::Pointer UserInputMapper::parseRoute(const QJsonValue& value) { result->json = QString(QJsonDocument(obj).toJson()); result->source = parseSource(obj[JSON_CHANNEL_FROM]); result->debug = obj[JSON_CHANNEL_DEBUG].toBool(); - result->debug = obj[JSON_CHANNEL_PEEK].toBool(); + result->peek = obj[JSON_CHANNEL_PEEK].toBool(); if (!result->source) { qWarning() << "Invalid route source " << obj[JSON_CHANNEL_FROM]; return Route::Pointer(); @@ -1033,7 +1048,7 @@ Mapping::Pointer UserInputMapper::parseMapping(const QJsonValue& json) { Route::Pointer route = parseRoute(channelIt); if (!route) { - qWarning() << "Couldn't parse route"; + qWarning() << "Couldn't parse route:" << mapping->name << QString(QJsonDocument(channelIt.toObject()).toJson()); continue; } diff --git a/libraries/controllers/src/controllers/UserInputMapper.h b/libraries/controllers/src/controllers/UserInputMapper.h index 1021032b40..95cc629c73 100644 --- a/libraries/controllers/src/controllers/UserInputMapper.h +++ b/libraries/controllers/src/controllers/UserInputMapper.h @@ -30,6 +30,7 @@ #include "DeviceProxy.h" #include "StandardControls.h" #include "Actions.h" +#include "StateController.h" namespace controller { @@ -55,8 +56,9 @@ namespace controller { using uint16 = uint16_t; using uint32 = uint32_t; - static const uint16_t ACTIONS_DEVICE; static const uint16_t STANDARD_DEVICE; + static const uint16_t ACTIONS_DEVICE; + static const uint16_t STATE_DEVICE; UserInputMapper(); virtual ~UserInputMapper(); @@ -100,10 +102,11 @@ namespace controller { const DevicesMap& getDevices() { return _registeredDevices; } uint16 getStandardDeviceID() const { return STANDARD_DEVICE; } InputDevice::Pointer getStandardDevice() { return _registeredDevices[getStandardDeviceID()]; } + StateController::Pointer getStateDevice() { return _stateDevice; } MappingPointer newMapping(const QString& mappingName); MappingPointer parseMapping(const QString& json); - MappingPointer loadMapping(const QString& jsonFile); + MappingPointer loadMapping(const QString& jsonFile, bool enable = false); MappingPointer loadMappings(const QStringList& jsonFiles); void loadDefaultMapping(uint16 deviceID); @@ -120,6 +123,7 @@ namespace controller { // GetFreeDeviceID should be called before registering a device to use an ID not used by a different device. uint16 getFreeDeviceID() { return _nextFreeDeviceID++; } DevicesMap _registeredDevices; + StateController::Pointer _stateDevice; uint16 _nextFreeDeviceID = STANDARD_DEVICE + 1; std::vector _actionStates = std::vector(toInt(Action::NUM_ACTIONS), 0.0f); diff --git a/libraries/controllers/src/controllers/impl/Endpoint.cpp b/libraries/controllers/src/controllers/impl/Endpoint.cpp index e771b1916f..289f83b228 100644 --- a/libraries/controllers/src/controllers/impl/Endpoint.cpp +++ b/libraries/controllers/src/controllers/impl/Endpoint.cpp @@ -12,5 +12,8 @@ // warning LNK4221: This object file does not define any previously undefined public symbols, // so it will not be used by any link operation that consumes this library // -//#include "Endpoint.h" +#include "Endpoint.h" +namespace controller { + Endpoint::WriteLambda DEFAULT_WRITE_LAMBDA = [](float) {}; +} diff --git a/libraries/controllers/src/controllers/impl/Endpoint.h b/libraries/controllers/src/controllers/impl/Endpoint.h index 475dc035bb..a938dd30b6 100644 --- a/libraries/controllers/src/controllers/impl/Endpoint.h +++ b/libraries/controllers/src/controllers/impl/Endpoint.h @@ -67,6 +67,23 @@ namespace controller { WriteLambda _writeLambda; }; + extern Endpoint::WriteLambda DEFAULT_WRITE_LAMBDA; + + class LambdaRefEndpoint : public Endpoint { + public: + using Endpoint::apply; + LambdaRefEndpoint(const ReadLambda& readLambda, const WriteLambda& writeLambda = DEFAULT_WRITE_LAMBDA) + : Endpoint(Input::INVALID_INPUT), _readLambda(readLambda), _writeLambda(writeLambda) { + } + + virtual float peek() const override { return _readLambda(); } + virtual void apply(float value, const Pointer& source) override { _writeLambda(value); } + + private: + const ReadLambda& _readLambda; + const WriteLambda& _writeLambda; + }; + class VirtualEndpoint : public Endpoint { public: diff --git a/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp b/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp index 3e09e96704..b39fd8861d 100644 --- a/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp +++ b/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp @@ -467,8 +467,6 @@ void CompositorHelper::toggle() { } } - - glm::mat4 CompositorHelper::getReticleTransform(const glm::mat4& eyePose, const glm::vec3& headPosition) const { glm::mat4 result; if (isHMD()) { @@ -487,7 +485,7 @@ glm::mat4 CompositorHelper::getReticleTransform(const glm::mat4& eyePose, const pointerTransform[3] = vec4(cursorRay + headPosition, 1); // Scale up the cursor because of distance reticleScale *= reticleDepth; - } + } glm::mat4 overlayXfm; _modelTransform.getMatrix(overlayXfm); pointerTransform = overlayXfm * pointerTransform; @@ -503,7 +501,7 @@ glm::mat4 CompositorHelper::getReticleTransform(const glm::mat4& eyePose, const mousePosition.y *= -1.0f; vec2 mouseSize = CURSOR_PIXEL_SIZE / canvasSize; - return glm::scale(glm::translate(glm::mat4(), vec3(mousePosition, 0.0f)), vec3(mouseSize, 1.0f)); + result = glm::scale(glm::translate(glm::mat4(), vec3(mousePosition, 0.0f)), vec3(mouseSize, 1.0f)); } return result; } diff --git a/libraries/display-plugins/src/display-plugins/CompositorHelper.h b/libraries/display-plugins/src/display-plugins/CompositorHelper.h index 47433bf419..062e5c1319 100644 --- a/libraries/display-plugins/src/display-plugins/CompositorHelper.h +++ b/libraries/display-plugins/src/display-plugins/CompositorHelper.h @@ -176,7 +176,7 @@ private: bool _reticleOverQml { false }; - bool _allowMouseCapture { true }; + std::atomic _allowMouseCapture { true }; bool _fakeMouseEvent { false }; diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp index e5d98d18f7..3d997b0109 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp @@ -245,8 +245,10 @@ bool OpenGLDisplayPlugin::activate() { #if THREADED_PRESENT // Start the present thread if necessary - auto presentThread = DependencyManager::get(); - if (!presentThread) { + QSharedPointer presentThread; + if (DependencyManager::isSet()) { + presentThread = DependencyManager::get(); + } else { auto widget = _container->getPrimaryWidget(); DependencyManager::set(); presentThread = DependencyManager::get(); diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index b87329d5b6..eab28f500a 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -71,8 +71,8 @@ EntityTreeRenderer::EntityTreeRenderer(bool wantScripts, AbstractViewStateInterf } EntityTreeRenderer::~EntityTreeRenderer() { - // NOTE: we don't need to delete _entitiesScriptEngine because it is registered with the application and has a - // signal tied to call it's deleteLater on doneRunning + // NOTE: We don't need to delete _entitiesScriptEngine because + // it is registered with ScriptEngines, which will call deleteLater for us. } void EntityTreeRenderer::clear() { diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 431d638063..4cd8a34248 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -888,6 +888,9 @@ void EntityItem::simulateKinematicMotion(float timeElapsed, bool setFlags) { if (hasActions()) { return; } + if (!_parentID.isNull()) { + return; + } if (hasLocalAngularVelocity()) { glm::vec3 localAngularVelocity = getLocalAngularVelocity(); @@ -1975,6 +1978,11 @@ QList EntityItem::getActionsOfType(EntityActionType typeToG void EntityItem::locationChanged() { requiresRecalcBoxes(); + _dirtyFlags |= Simulation::DIRTY_TRANSFORM; + EntityTreePointer tree = getTree(); + if (tree) { + tree->entityChanged(getThisPointer()); + } SpatiallyNestable::locationChanged(); // tell all the children, also } @@ -1984,6 +1992,7 @@ void EntityItem::dimensionsChanged() { } void EntityItem::globalizeProperties(EntityItemProperties& properties, const QString& messageTemplate, const glm::vec3& offset) const { + // TODO -- combine this with convertLocationToScriptSemantics bool success; auto globalPosition = getPosition(success); if (success) { diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index bca27211a8..540cc68689 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -475,13 +475,20 @@ QVector EntityScriptingInterface::findEntitiesInBox(const glm::vec3& corn return result; } -RayToEntityIntersectionResult EntityScriptingInterface::findRayIntersection(const PickRay& ray, bool precisionPicking, const QScriptValue& entityIdsToInclude, const QScriptValue& entityIdsToDiscard) { +RayToEntityIntersectionResult EntityScriptingInterface::findRayIntersection(const PickRay& ray, bool precisionPicking, + const QScriptValue& entityIdsToInclude, const QScriptValue& entityIdsToDiscard) { + QVector entitiesToInclude = qVectorEntityItemIDFromScriptValue(entityIdsToInclude); QVector entitiesToDiscard = qVectorEntityItemIDFromScriptValue(entityIdsToDiscard); - return findRayIntersectionWorker(ray, Octree::TryLock, precisionPicking, entitiesToInclude, entitiesToDiscard); + return findRayIntersectionWorker(ray, Octree::Lock, precisionPicking, entitiesToInclude, entitiesToDiscard); } -RayToEntityIntersectionResult EntityScriptingInterface::findRayIntersectionBlocking(const PickRay& ray, bool precisionPicking, const QScriptValue& entityIdsToInclude, const QScriptValue& entityIdsToDiscard) { +// FIXME - we should remove this API and encourage all users to use findRayIntersection() instead. We've changed +// findRayIntersection() to be blocking because it never makes sense for a script to get back a non-answer +RayToEntityIntersectionResult EntityScriptingInterface::findRayIntersectionBlocking(const PickRay& ray, bool precisionPicking, + const QScriptValue& entityIdsToInclude, const QScriptValue& entityIdsToDiscard) { + + qWarning() << "Entities.findRayIntersectionBlocking() is obsolete, use Entities.findRayIntersection() instead."; const QVector& entitiesToInclude = qVectorEntityItemIDFromScriptValue(entityIdsToInclude); const QVector entitiesToDiscard = qVectorEntityItemIDFromScriptValue(entityIdsToDiscard); return findRayIntersectionWorker(ray, Octree::Lock, precisionPicking, entitiesToInclude, entitiesToDiscard); diff --git a/libraries/gpu/src/gpu/GLBackendTextureTransfer.cpp b/libraries/gpu/src/gpu/GLBackendTextureTransfer.cpp index 2610d9bc4a..907bc6a538 100644 --- a/libraries/gpu/src/gpu/GLBackendTextureTransfer.cpp +++ b/libraries/gpu/src/gpu/GLBackendTextureTransfer.cpp @@ -77,6 +77,12 @@ void GLTextureTransferHelper::setup() { #endif } +void GLTextureTransferHelper::shutdown() { + _canvas->doneCurrent(); + _canvas->moveToThreadWithContext(qApp->thread()); +} + + bool GLTextureTransferHelper::processQueueItems(const Queue& messages) { for (auto package : messages) { glWaitSync(package.fence, 0, GL_TIMEOUT_IGNORED); diff --git a/libraries/gpu/src/gpu/GLBackendTextureTransfer.h b/libraries/gpu/src/gpu/GLBackendTextureTransfer.h index 3a147defdf..1e850586d4 100644 --- a/libraries/gpu/src/gpu/GLBackendTextureTransfer.h +++ b/libraries/gpu/src/gpu/GLBackendTextureTransfer.h @@ -28,6 +28,7 @@ public: protected: void setup() override; + void shutdown() override; bool processQueueItems(const Queue& messages) override; void transferTextureSynchronous(const gpu::Texture& texture); diff --git a/libraries/networking/src/ResourceCache.cpp b/libraries/networking/src/ResourceCache.cpp index c661c2f32a..2273902263 100644 --- a/libraries/networking/src/ResourceCache.cpp +++ b/libraries/networking/src/ResourceCache.cpp @@ -380,7 +380,7 @@ void Resource::allReferencesCleared() { _cache->addUnusedResource(self); } else { - delete this; + deleteLater(); } } diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index e3952ba1d6..700160514c 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -161,7 +161,12 @@ PhysicsMotionType EntityMotionState::computePhysicsMotionType() const { } return MOTION_TYPE_DYNAMIC; } - return (_entity->isMovingRelativeToParent() || _entity->hasActions()) ? MOTION_TYPE_KINEMATIC : MOTION_TYPE_STATIC; + if (_entity->isMovingRelativeToParent() || + _entity->hasActions() || + _entity->hasAncestorOfType(NestableType::Avatar)) { + return MOTION_TYPE_KINEMATIC; + } + return MOTION_TYPE_STATIC; } bool EntityMotionState::isMoving() const { diff --git a/libraries/render-utils/src/sdf_text3D_overlay.slf b/libraries/render-utils/src/sdf_text3D_overlay.slf new file mode 100644 index 0000000000..d357b05e14 --- /dev/null +++ b/libraries/render-utils/src/sdf_text3D_overlay.slf @@ -0,0 +1,52 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// sdf_text.frag +// fragment shader +// +// Created by Bradley Austin Davis on 2015-02-04 +// Based on fragment shader code from +// https://github.com/paulhoux/Cinder-Samples/blob/master/TextRendering/include/text/Text.cpp +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + +uniform sampler2D Font; +uniform bool Outline; +uniform vec4 Color; + +// the interpolated normal +in vec3 _normal; +in vec2 _texCoord0; + +layout(location = 0) out vec4 _fragColor0; + +const float gamma = 2.2; +const float smoothing = 32.0; +const float interiorCutoff = 0.8; +const float outlineExpansion = 0.2; + +void main() { + // retrieve signed distance + float sdf = texture(Font, _texCoord0).g; + if (Outline) { + if (sdf > interiorCutoff) { + sdf = 1.0 - sdf; + } else { + sdf += outlineExpansion; + } + } + // perform adaptive anti-aliasing of the edges + // The larger we're rendering, the less anti-aliasing we need + float s = smoothing * length(fwidth(_texCoord0)); + float w = clamp( s, 0.0, 0.5); + float a = smoothstep(0.5 - w, 0.5 + w, sdf); + + // gamma correction for linear attenuation + a = pow(a, 1.0 / gamma); + + // discard if unvisible + if (a < 0.01) { + discard; + } + _fragColor0 = vec4(Color.rgb, a); +} \ No newline at end of file diff --git a/libraries/render-utils/src/text/Font.cpp b/libraries/render-utils/src/text/Font.cpp index 762a7fb723..3c460fdd99 100644 --- a/libraries/render-utils/src/text/Font.cpp +++ b/libraries/render-utils/src/text/Font.cpp @@ -9,6 +9,7 @@ #include "sdf_text3D_vert.h" #include "sdf_text3D_frag.h" +#include "sdf_text3D_overlay_frag.h" #include "../RenderUtilsLogging.h" #include "FontFamilies.h" @@ -220,10 +221,13 @@ void Font::setupGPU() { { auto vertexShader = gpu::Shader::createVertex(std::string(sdf_text3D_vert)); auto pixelShader = gpu::Shader::createPixel(std::string(sdf_text3D_frag)); + auto pixelShaderOverlay = gpu::Shader::createPixel(std::string(sdf_text3D_overlay_frag)); gpu::ShaderPointer program = gpu::Shader::createProgram(vertexShader, pixelShader); + gpu::ShaderPointer programOverlay = gpu::Shader::createProgram(vertexShader, pixelShaderOverlay); gpu::Shader::BindingSet slotBindings; gpu::Shader::makeProgram(*program, slotBindings); + gpu::Shader::makeProgram(*programOverlay, slotBindings); _fontLoc = program->getTextures().findLocation("Font"); _outlineLoc = program->getUniforms().findLocation("Outline"); @@ -237,9 +241,10 @@ void Font::setupGPU() { gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); _pipeline = gpu::Pipeline::create(program, state); - auto layeredState = std::make_shared(state->getValues()); - layeredState->setDepthTest(false); - _layeredPipeline = gpu::Pipeline::create(program, layeredState); + auto layeredState = std::make_shared(); + layeredState->setCullMode(gpu::State::CULL_BACK); + layeredState->setDepthTest(true, true, gpu::LESS_EQUAL); + _layeredPipeline = gpu::Pipeline::create(programOverlay, layeredState); } // Sanity checks diff --git a/libraries/script-engine/src/AudioScriptingInterface.cpp b/libraries/script-engine/src/AudioScriptingInterface.cpp index 7e7ca77b88..f7cc04b209 100644 --- a/libraries/script-engine/src/AudioScriptingInterface.cpp +++ b/libraries/script-engine/src/AudioScriptingInterface.cpp @@ -17,7 +17,6 @@ void registerAudioMetaTypes(QScriptEngine* engine) { qScriptRegisterMetaType(engine, injectorOptionsToScriptValue, injectorOptionsFromScriptValue); qScriptRegisterMetaType(engine, soundSharedPointerToScriptValue, soundSharedPointerFromScriptValue); - qScriptRegisterMetaType(engine, soundPointerToScriptValue, soundPointerFromScriptValue); } AudioScriptingInterface& AudioScriptingInterface::getInstance() { @@ -31,13 +30,14 @@ AudioScriptingInterface::AudioScriptingInterface() : } -ScriptAudioInjector* AudioScriptingInterface::playSound(Sound* sound, const AudioInjectorOptions& injectorOptions) { +ScriptAudioInjector* AudioScriptingInterface::playSound(SharedSoundPointer sound, const AudioInjectorOptions& injectorOptions) { if (QThread::currentThread() != thread()) { ScriptAudioInjector* injector = NULL; QMetaObject::invokeMethod(this, "playSound", Qt::BlockingQueuedConnection, Q_RETURN_ARG(ScriptAudioInjector*, injector), - Q_ARG(Sound*, sound), Q_ARG(const AudioInjectorOptions&, injectorOptions)); + Q_ARG(SharedSoundPointer, sound), + Q_ARG(const AudioInjectorOptions&, injectorOptions)); return injector; } diff --git a/libraries/script-engine/src/AudioScriptingInterface.h b/libraries/script-engine/src/AudioScriptingInterface.h index 0b0b6587a2..07a6b171f4 100644 --- a/libraries/script-engine/src/AudioScriptingInterface.h +++ b/libraries/script-engine/src/AudioScriptingInterface.h @@ -27,7 +27,7 @@ public: protected: // this method is protected to stop C++ callers from calling, but invokable from script - Q_INVOKABLE ScriptAudioInjector* playSound(Sound* sound, const AudioInjectorOptions& injectorOptions = AudioInjectorOptions()); + Q_INVOKABLE ScriptAudioInjector* playSound(SharedSoundPointer sound, const AudioInjectorOptions& injectorOptions = AudioInjectorOptions()); Q_INVOKABLE void setStereoInput(bool stereo); diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 31047dd423..a3e0744b46 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -154,7 +154,11 @@ ScriptEngine::~ScriptEngine() { void ScriptEngine::disconnectNonEssentialSignals() { disconnect(); - connect(this, &ScriptEngine::doneRunning, thread(), &QThread::quit); + QThread* receiver; + // Ensure the thread should be running, and does exist + if (_isRunning && _isThreaded && (receiver = thread())) { + connect(this, &ScriptEngine::doneRunning, receiver, &QThread::quit); + } } void ScriptEngine::runInThread() { @@ -578,7 +582,7 @@ void ScriptEngine::addEventHandler(const EntityItemID& entityID, const QString& _registeredHandlers[entityID] = RegisteredEventHandlers(); } CallbackList& handlersForEvent = _registeredHandlers[entityID][eventName]; - CallbackData handlerData = {handler, currentEntityIdentifier}; + CallbackData handlerData = {handler, currentEntityIdentifier, currentSandboxURL}; handlersForEvent << handlerData; // Note that the same handler can be added many times. See removeEntityEventHandler(). } @@ -795,7 +799,7 @@ void ScriptEngine::timerFired() { // call the associated JS function, if it exists if (timerData.function.isValid()) { - callWithEnvironment(timerData.definingEntityIdentifier, timerData.function, timerData.function, QScriptValueList()); + callWithEnvironment(timerData.definingEntityIdentifier, timerData.definingSandboxURL, timerData.function, timerData.function, QScriptValueList()); } } @@ -810,7 +814,7 @@ QObject* ScriptEngine::setupTimerWithInterval(const QScriptValue& function, int // make sure the timer stops when the script does connect(this, &ScriptEngine::scriptEnding, newTimer, &QTimer::stop); - CallbackData timerData = {function, currentEntityIdentifier}; + CallbackData timerData = {function, currentEntityIdentifier, currentSandboxURL}; _timerFunctionMap.insert(newTimer, timerData); newTimer->start(intervalMS); @@ -885,19 +889,49 @@ void ScriptEngine::include(const QStringList& includeFiles, QScriptValue callbac return; // bail early } QList urls; + bool knowsSensitivity = false; + Qt::CaseSensitivity sensitivity; + auto getSensitivity = [&]() { + if (!knowsSensitivity) { + QString path = currentSandboxURL.path(); + QFileInfo upperFI(path.toUpper()); + QFileInfo lowerFI(path.toLower()); + sensitivity = (upperFI == lowerFI) ? Qt::CaseInsensitive : Qt::CaseSensitive; + knowsSensitivity = true; + } + return sensitivity; + }; + + // Guard against meaningless query and fragment parts. + // Do NOT use PreferLocalFile as its behavior is unpredictable (e.g., on defaultScriptsLocation()) + const auto strippingFlags = QUrl::RemoveFilename | QUrl::RemoveQuery | QUrl::RemoveFragment; for (QString file : includeFiles) { QUrl thisURL { resolvePath(file) }; if (!_includedURLs.contains(thisURL)) { - urls.append(thisURL); - _includedURLs << thisURL; - } - else { + if (!currentSandboxURL.isEmpty() && (thisURL.scheme() == "file") && + ( + (currentSandboxURL.scheme() != "file") || + ( + !thisURL.toString(strippingFlags).startsWith(defaultScriptsLocation().toString(), getSensitivity()) && + !thisURL.toString(strippingFlags).startsWith(currentSandboxURL.toString(strippingFlags), getSensitivity()) + ) + ) + ) { + qCWarning(scriptengine) << "Script.include() ignoring file path" << thisURL << "outside of original entity script" << currentSandboxURL; + } else { + // We could also check here for CORS, but we don't yet. + // It turns out that QUrl.resolve will not change hosts and copy authority, so we don't need to check that here. + urls.append(thisURL); + _includedURLs << thisURL; + } + } else { qCDebug(scriptengine) << "Script.include() ignoring previously included url:" << thisURL; } } BatchLoader* loader = new BatchLoader(urls); EntityItemID capturedEntityIdentifier = currentEntityIdentifier; + QUrl capturedSandboxURL = currentSandboxURL; auto evaluateScripts = [=](const QMap& data) { auto parentURL = _parentURL; @@ -912,13 +946,13 @@ void ScriptEngine::include(const QStringList& includeFiles, QScriptValue callbac auto operation = [&]() { evaluate(contents, url.toString()); }; - doWithEnvironment(capturedEntityIdentifier, operation); + doWithEnvironment(capturedEntityIdentifier, capturedSandboxURL, operation); } } _parentURL = parentURL; if (callback.isFunction()) { - callWithEnvironment(capturedEntityIdentifier, QScriptValue(callback), QScriptValue(), QScriptValueList()); + callWithEnvironment(capturedEntityIdentifier, capturedSandboxURL, QScriptValue(callback), QScriptValue(), QScriptValueList()); } loader->deleteLater(); @@ -996,10 +1030,11 @@ void ScriptEngine::forwardHandlerCall(const EntityItemID& entityID, const QStrin CallbackList handlersForEvent = handlersOnEntity[eventName]; if (!handlersForEvent.isEmpty()) { for (int i = 0; i < handlersForEvent.count(); ++i) { - // handlersForEvent[i] can tonain many handlers that may have each been added by different interface or entity scripts, + // handlersForEvent[i] can contain many handlers that may have each been added by different interface or entity scripts, // and the entity scripts may be for entities other than the one this is a handler for. // Fortunately, the definingEntityIdentifier captured the entity script id (if any) when the handler was added. - callWithEnvironment(handlersForEvent[i].definingEntityIdentifier, handlersForEvent[i].function, QScriptValue(), eventHandlerArgs); + CallbackData& handler = handlersForEvent[i]; + callWithEnvironment(handler.definingEntityIdentifier, handler.definingSandboxURL, handler.function, QScriptValue(), eventHandlerArgs); } } } @@ -1089,9 +1124,19 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co } if (!testConstructor.isFunction()) { - qCDebug(scriptengine) << "ScriptEngine::loadEntityScript() entity:" << entityID << "\n" - " NOT CONSTRUCTOR\n" - " SCRIPT:" << scriptOrURL; + QString testConstructorType = QString(testConstructor.toVariant().typeName()); + if (testConstructorType == "") { + testConstructorType = "empty"; + } + QString testConstructorValue = testConstructor.toString(); + const int maxTestConstructorValueSize = 80; + if (testConstructorValue.size() > maxTestConstructorValueSize) { + testConstructorValue = testConstructorValue.mid(0, maxTestConstructorValueSize) + "..."; + } + qCDebug(scriptengine) << "Error -- ScriptEngine::loadEntityScript() entity:" << entityID + << "failed to load entity script -- expected a function, got " + testConstructorType + << "," << testConstructorValue + << "," << scriptOrURL; if (!isFileUrl) { scriptCache->addScriptToBadScriptList(scriptOrURL); @@ -1106,13 +1151,14 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co lastModified = (quint64)QFileInfo(file).lastModified().toMSecsSinceEpoch(); } QScriptValue entityScriptConstructor, entityScriptObject; + QUrl sandboxURL = currentSandboxURL.isEmpty() ? scriptOrURL : currentSandboxURL; auto initialization = [&]{ entityScriptConstructor = evaluate(contents, fileName); entityScriptObject = entityScriptConstructor.construct(); }; - doWithEnvironment(entityID, initialization); + doWithEnvironment(entityID, sandboxURL, initialization); - EntityScriptDetails newDetails = { scriptOrURL, entityScriptObject, lastModified }; + EntityScriptDetails newDetails = { scriptOrURL, entityScriptObject, lastModified, sandboxURL }; _entityScripts[entityID] = newDetails; if (isURL) { setParentURL(""); @@ -1201,9 +1247,11 @@ void ScriptEngine::refreshFileScript(const EntityItemID& entityID) { // Even if entityID is supplied as currentEntityIdentifier, this still documents the source // of the code being executed (e.g., if we ever sandbox different entity scripts, or provide different // global values for different entity scripts). -void ScriptEngine::doWithEnvironment(const EntityItemID& entityID, std::function operation) { +void ScriptEngine::doWithEnvironment(const EntityItemID& entityID, const QUrl& sandboxURL, std::function operation) { EntityItemID oldIdentifier = currentEntityIdentifier; + QUrl oldSandboxURL = currentSandboxURL; currentEntityIdentifier = entityID; + currentSandboxURL = sandboxURL; #if DEBUG_CURRENT_ENTITY QScriptValue oldData = this->globalObject().property("debugEntityID"); @@ -1215,12 +1263,13 @@ void ScriptEngine::doWithEnvironment(const EntityItemID& entityID, std::function #endif currentEntityIdentifier = oldIdentifier; + currentSandboxURL = oldSandboxURL; } -void ScriptEngine::callWithEnvironment(const EntityItemID& entityID, QScriptValue function, QScriptValue thisObject, QScriptValueList args) { +void ScriptEngine::callWithEnvironment(const EntityItemID& entityID, const QUrl& sandboxURL, QScriptValue function, QScriptValue thisObject, QScriptValueList args) { auto operation = [&]() { function.call(thisObject, args); }; - doWithEnvironment(entityID, operation); + doWithEnvironment(entityID, sandboxURL, operation); } void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const QStringList& params) { @@ -1249,7 +1298,7 @@ void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QS QScriptValueList args; args << entityID.toScriptValue(this); args << qScriptValueFromSequence(this, params); - callWithEnvironment(entityID, entityScript.property(methodName), entityScript, args); + callWithEnvironment(entityID, details.definingSandboxURL, entityScript.property(methodName), entityScript, args); } } @@ -1281,7 +1330,7 @@ void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QS QScriptValueList args; args << entityID.toScriptValue(this); args << event.toScriptValue(this); - callWithEnvironment(entityID, entityScript.property(methodName), entityScript, args); + callWithEnvironment(entityID, details.definingSandboxURL, entityScript.property(methodName), entityScript, args); } } } @@ -1315,7 +1364,7 @@ void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QS args << entityID.toScriptValue(this); args << otherID.toScriptValue(this); args << collisionToScriptValue(this, collision); - callWithEnvironment(entityID, entityScript.property(methodName), entityScript, args); + callWithEnvironment(entityID, details.definingSandboxURL, entityScript.property(methodName), entityScript, args); } } } diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index 8dfc9c9abe..a6a623e751 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -47,6 +47,7 @@ class CallbackData { public: QScriptValue function; EntityItemID definingEntityIdentifier; + QUrl definingSandboxURL; }; typedef QList CallbackList; @@ -57,6 +58,7 @@ public: QString scriptText; QScriptValue scriptObject; int64_t lastModified; + QUrl definingSandboxURL; }; class ScriptEngine : public QScriptEngine, public ScriptUser, public EntitiesScriptEngineProvider { @@ -214,8 +216,9 @@ protected: Q_INVOKABLE void entityScriptContentAvailable(const EntityItemID& entityID, const QString& scriptOrURL, const QString& contents, bool isURL, bool success); EntityItemID currentEntityIdentifier {}; // Contains the defining entity script entity id during execution, if any. Empty for interface script execution. - void doWithEnvironment(const EntityItemID& entityID, std::function operation); - void callWithEnvironment(const EntityItemID& entityID, QScriptValue function, QScriptValue thisObject, QScriptValueList args); + QUrl currentSandboxURL {}; // The toplevel url string for the entity script that loaded the code being executed, else empty. + void doWithEnvironment(const EntityItemID& entityID, const QUrl& sandboxURL, std::function operation); + void callWithEnvironment(const EntityItemID& entityID, const QUrl& sandboxURL, QScriptValue function, QScriptValue thisObject, QScriptValueList args); friend class ScriptEngines; static std::atomic _stoppingAllScripts; diff --git a/libraries/shared/src/DependencyManager.h b/libraries/shared/src/DependencyManager.h index c0568bc752..719763f706 100644 --- a/libraries/shared/src/DependencyManager.h +++ b/libraries/shared/src/DependencyManager.h @@ -49,6 +49,9 @@ public: template static QSharedPointer get(); + template + static bool isSet(); + template static QSharedPointer set(Args&&... args); @@ -89,6 +92,14 @@ QSharedPointer DependencyManager::get() { return instance.toStrongRef(); } +template +bool DependencyManager::isSet() { + static size_t hashCode = manager().getHashCode(); + + QSharedPointer& instance = manager().safeGet(hashCode); + return !instance.isNull(); +} + template QSharedPointer DependencyManager::set(Args&&... args) { static size_t hashCode = manager().getHashCode(); diff --git a/libraries/shared/src/SpatiallyNestable.cpp b/libraries/shared/src/SpatiallyNestable.cpp index 13bf5d9054..96a5a1d8ae 100644 --- a/libraries/shared/src/SpatiallyNestable.cpp +++ b/libraries/shared/src/SpatiallyNestable.cpp @@ -849,3 +849,17 @@ AACube SpatiallyNestable::getQueryAACube() const { } return result; } + +bool SpatiallyNestable::hasAncestorOfType(NestableType nestableType) { + bool success; + SpatiallyNestablePointer parent = getParentPointer(success); + if (!success || !parent) { + return false; + } + + if (parent->_nestableType == nestableType) { + return true; + } + + return parent->hasAncestorOfType(nestableType); +} diff --git a/libraries/shared/src/SpatiallyNestable.h b/libraries/shared/src/SpatiallyNestable.h index 379f2facd7..58a141b1fa 100644 --- a/libraries/shared/src/SpatiallyNestable.h +++ b/libraries/shared/src/SpatiallyNestable.h @@ -142,6 +142,8 @@ public: bool isParentIDValid() const { bool success = false; getParentPointer(success); return success; } virtual SpatialParentTree* getParentTree() const { return nullptr; } + bool hasAncestorOfType(NestableType nestableType); + protected: const NestableType _nestableType; // EntityItem or an AvatarData QUuid _id; diff --git a/libraries/ui/src/VrMenu.cpp b/libraries/ui/src/VrMenu.cpp index 5acf81c319..e6f0e92067 100644 --- a/libraries/ui/src/VrMenu.cpp +++ b/libraries/ui/src/VrMenu.cpp @@ -43,6 +43,11 @@ public: auto result = static_cast(object->userData(USER_DATA_ID)); if (!result) { qWarning() << "Unable to find MenuUserData for object " << object; + if (auto action = dynamic_cast(object)) { + qWarning() << action->text(); + } else if (auto menu = dynamic_cast(object)) { + qWarning() << menu->title(); + } return nullptr; } return result; diff --git a/plugins/oculus/src/OculusControllerManager.cpp b/plugins/oculus/src/OculusControllerManager.cpp index f11ec18b10..50ef6b09a1 100644 --- a/plugins/oculus/src/OculusControllerManager.cpp +++ b/plugins/oculus/src/OculusControllerManager.cpp @@ -44,12 +44,17 @@ bool OculusControllerManager::activate() { // register with UserInputMapper auto userInputMapper = DependencyManager::get(); - if (_remote) { + + if (OVR_SUCCESS(ovr_GetInputState(_session, ovrControllerType_Remote, &_inputState))) { + _remote = std::make_shared(*this); userInputMapper->registerDevice(_remote); } - if (_touch) { + + if (OVR_SUCCESS(ovr_GetInputState(_session, ovrControllerType_Touch, &_inputState))) { + _touch = std::make_shared(*this); userInputMapper->registerDevice(_touch); } + return true; } @@ -74,16 +79,20 @@ void OculusControllerManager::deactivate() { void OculusControllerManager::pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, bool jointsCaptured) { PerformanceTimer perfTimer("OculusControllerManager::TouchDevice::update"); - if (!OVR_SUCCESS(ovr_GetInputState(_session, ovrControllerType_Touch, &_inputState))) { - qCWarning(oculus) << "Unable to read oculus input state"; - return; + if (_touch) { + if (OVR_SUCCESS(ovr_GetInputState(_session, ovrControllerType_Touch, &_inputState))) { + _touch->update(deltaTime, inputCalibrationData, jointsCaptured); + } else { + qCWarning(oculus) << "Unable to read Oculus touch input state"; + } } - if (_touch) { - _touch->update(deltaTime, inputCalibrationData, jointsCaptured); - } if (_remote) { - _remote->update(deltaTime, inputCalibrationData, jointsCaptured); + if (OVR_SUCCESS(ovr_GetInputState(_session, ovrControllerType_Remote, &_inputState))) { + _remote->update(deltaTime, inputCalibrationData, jointsCaptured); + } else { + qCWarning(oculus) << "Unable to read Oculus remote input state"; + } } } @@ -99,6 +108,12 @@ void OculusControllerManager::pluginFocusOutEvent() { using namespace controller; static const std::vector> BUTTON_MAP { { + { ovrButton_Up, DU }, + { ovrButton_Down, DD }, + { ovrButton_Left, DL }, + { ovrButton_Right, DR }, + { ovrButton_Enter, START }, + { ovrButton_Back, BACK }, { ovrButton_X, X }, { ovrButton_Y, Y }, { ovrButton_A, A }, @@ -124,6 +139,39 @@ static const std::vector> TOUCH_MAP { { ovrTouch_RIndexPointing, RIGHT_INDEX_POINT }, } }; + +controller::Input::NamedVector OculusControllerManager::RemoteDevice::getAvailableInputs() const { + using namespace controller; + QVector availableInputs { + makePair(DU, "DU"), + makePair(DD, "DD"), + makePair(DL, "DL"), + makePair(DR, "DR"), + makePair(START, "Start"), + makePair(BACK, "Back"), + }; + return availableInputs; +} + +QString OculusControllerManager::RemoteDevice::getDefaultMappingConfig() const { + static const QString MAPPING_JSON = PathUtils::resourcesPath() + "/controllers/oculus_remote.json"; + return MAPPING_JSON; +} + +void OculusControllerManager::RemoteDevice::update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, bool jointsCaptured) { + _buttonPressedMap.clear(); + const auto& inputState = _parent._inputState; + for (const auto& pair : BUTTON_MAP) { + if (inputState.Buttons & pair.first) { + _buttonPressedMap.insert(pair.second); + } + } +} + +void OculusControllerManager::RemoteDevice::focusOutEvent() { + _buttonPressedMap.clear(); +} + void OculusControllerManager::TouchDevice::update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, bool jointsCaptured) { _poseStateMap.clear(); _buttonPressedMap.clear(); @@ -211,7 +259,7 @@ controller::Input::NamedVector OculusControllerManager::TouchDevice::getAvailabl } QString OculusControllerManager::TouchDevice::getDefaultMappingConfig() const { - static const QString MAPPING_JSON = PathUtils::resourcesPath() + "/controllers/touch.json"; + static const QString MAPPING_JSON = PathUtils::resourcesPath() + "/controllers/oculus_touch.json"; return MAPPING_JSON; } diff --git a/plugins/oculus/src/OculusControllerManager.h b/plugins/oculus/src/OculusControllerManager.h index fc10dcc73d..60969097f8 100644 --- a/plugins/oculus/src/OculusControllerManager.h +++ b/plugins/oculus/src/OculusControllerManager.h @@ -45,7 +45,7 @@ private: class RemoteDevice : public OculusInputDevice { public: using Pointer = std::shared_ptr; - RemoteDevice(OculusControllerManager& parent) : OculusInputDevice(parent, "Oculus Remote") {} + RemoteDevice(OculusControllerManager& parent) : OculusInputDevice(parent, "OculusRemote") {} controller::Input::NamedVector getAvailableInputs() const override; QString getDefaultMappingConfig() const override; @@ -58,7 +58,7 @@ private: class TouchDevice : public OculusInputDevice { public: using Pointer = std::shared_ptr; - TouchDevice(OculusControllerManager& parent) : OculusInputDevice(parent, "Oculus Touch") {} + TouchDevice(OculusControllerManager& parent) : OculusInputDevice(parent, "OculusTouch") {} controller::Input::NamedVector getAvailableInputs() const override; QString getDefaultMappingConfig() const override;