From 6302ae6bd5d204b91a5a2773a3cb1d8d738c20b7 Mon Sep 17 00:00:00 2001 From: Menithal Date: Wed, 15 Feb 2017 19:48:29 +0200 Subject: [PATCH 01/64] Changed AudioScope Hotkeys, Allowing User Hotkeys AudioScope Open Hotkey changed from Ctrl + P to Ctrl + Alt + P and Audioscope Pause Hotkey changed from Ctrl + Shift + P to Ctrl + Shift + Alt + P --- interface/src/Menu.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index acf97ad5f7..431fe0638a 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -577,7 +577,7 @@ Menu::Menu() { nodeList.data(), SLOT(toggleSendNewerDSConnectVersion(bool))); #endif - + // Developer >> Tests >>> MenuWrapper* testMenu = developerMenu->addMenu("Tests"); addActionToQMenuAndActionHash(testMenu, MenuOption::RunClientScriptTests, 0, dialogsManager.data(), SLOT(showTestingResults())); @@ -628,9 +628,9 @@ Menu::Menu() { auto scope = DependencyManager::get(); MenuWrapper* audioScopeMenu = audioDebugMenu->addMenu("Audio Scope"); - addCheckableActionToQMenuAndActionHash(audioScopeMenu, MenuOption::AudioScope, Qt::CTRL | Qt::Key_P, false, + addCheckableActionToQMenuAndActionHash(audioScopeMenu, MenuOption::AudioScope, Qt::CTRL | Qt::ALT | Qt::Key_P, false, scope.data(), SLOT(toggle())); - addCheckableActionToQMenuAndActionHash(audioScopeMenu, MenuOption::AudioScopePause, Qt::CTRL | Qt::SHIFT | Qt::Key_P, false, + addCheckableActionToQMenuAndActionHash(audioScopeMenu, MenuOption::AudioScopePause, Qt::CTRL | Qt::ALT | Qt::SHIFT | Qt::Key_P, false, scope.data(), SLOT(togglePause())); addDisabledActionAndSeparator(audioScopeMenu, "Display Frames"); From 7b02d1073a08cca23b904f2c1848b38fe7a9ca5a Mon Sep 17 00:00:00 2001 From: Menithal Date: Wed, 15 Feb 2017 19:49:31 +0200 Subject: [PATCH 02/64] Added a generic Window Notification for Feedback This allows to use the existing Pop-up notifications for generic notification messages --- scripts/system/notifications.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/system/notifications.js b/scripts/system/notifications.js index 3ae071c7e3..b2ebb1fd46 100644 --- a/scripts/system/notifications.js +++ b/scripts/system/notifications.js @@ -521,6 +521,9 @@ function onEditError(msg) { createNotification(wordWrap(msg), NotificationType.EDIT_ERROR); } +function onNotify(msg) { + createNotification(wordWrap(msg), NotificationType.UNKNOWN); // Needs a generic notification system for user feedback, thus using this +} function onSnapshotTaken(pathStillSnapshot, pathAnimatedSnapshot, notify) { if (notify) { @@ -637,6 +640,7 @@ Window.domainConnectionRefused.connect(onDomainConnectionRefused); Window.snapshotTaken.connect(onSnapshotTaken); Window.processingGif.connect(processingGif); Window.notifyEditError = onEditError; +Window.notify = onNotify; setup(); From 655c49d9c00f68d8a6f6814d819ceffd1032ce7a Mon Sep 17 00:00:00 2001 From: Menithal Date: Wed, 15 Feb 2017 19:50:15 +0200 Subject: [PATCH 03/64] edit.js: started to work on Parenting via hotkey --- scripts/system/edit.js | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/scripts/system/edit.js b/scripts/system/edit.js index da39edf8ba..5476e1208f 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -1096,7 +1096,30 @@ function recursiveDelete(entities, childrenList) { Entities.deleteEntity(entityID); } } +function parentSelectedEntities() { + if (SelectionManager.hasSelection()) { + SelectionManager.saveProperties(); + var selectedEntities = selectionManager.selections; + if (selectedEntities.length <= 1) { + Window.notifyEditError("You must have multiple objects selected in order to parent them"); + return; + } + var lastEntityId = selectedEntities[selectedEntities.length] + selectedEntities.some(function (id, index) { + if (lastId === id) { + return false; + } + Entities.editProperties(id, {parentID: lastId}) + return true; + }); + SelectionManager.clearSelections(); + + Window.notify("Entities Parented"); + } else { + Window.notifyEditError("You have nothing selected") + } +} function deleteSelectedEntities() { if (SelectionManager.hasSelection()) { selectedParticleEntity = 0; @@ -1324,6 +1347,8 @@ Controller.keyReleaseEvent.connect(function (event) { }); grid.setPosition(newPosition); } + } else if (event.text === 'p' && event.isCtrl) { + parentSelectedEntities(); } }); From c3021ae60d917bb7c50df1757d07f6e5f46aa25b Mon Sep 17 00:00:00 2001 From: Menithal Date: Wed, 15 Feb 2017 22:21:29 +0200 Subject: [PATCH 04/64] Fixing Menu Items for Parenting / Unparenting --- scripts/system/edit.js | 78 ++++++++++++++++++++++++++++++++---------- 1 file changed, 59 insertions(+), 19 deletions(-) diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 5476e1208f..afb171e936 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -838,7 +838,6 @@ function setupModelMenus() { }); modelMenuAddedDelete = true; } - Menu.addMenuItem({ menuName: "Edit", menuItemName: "Entity List...", @@ -846,6 +845,23 @@ function setupModelMenus() { afterItem: "Entities", grouping: "Advanced" }); + + Menu.addMenuItem({ + menuName: "Edit", + menuItemName: "Parent Entity to Last", + shortcutKey: "CTRL+P", + afterItem: "Entity List...", + grouping: "Advanced" + }); + + Menu.addMenuItem({ + menuName: "Edit", + menuItemName: "Unparent Entity", + afterItem: "Entity List...", + shortcutKey: "CTRL+SHIFT+P", + afterItem: "Entity List...", + grouping: "Advanced" + }); Menu.addMenuItem({ menuName: "Edit", menuItemName: "Allow Selecting of Large Models", @@ -953,6 +969,8 @@ function cleanupModelMenus() { Menu.removeMenuItem("Edit", "Delete"); } + Menu.removeMenuItem("Edit", "Parent"); + Menu.removeMenuItem("Edit", "Unparent"); Menu.removeMenuItem("Edit", "Entity List..."); Menu.removeMenuItem("Edit", "Allow Selecting of Large Models"); Menu.removeMenuItem("Edit", "Allow Selecting of Small Models"); @@ -1096,28 +1114,42 @@ function recursiveDelete(entities, childrenList) { Entities.deleteEntity(entityID); } } -function parentSelectedEntities() { +function unparentSelectedEntities() { + print("unparenting2 Selected Entities"); if (SelectionManager.hasSelection()) { - SelectionManager.saveProperties(); var selectedEntities = selectionManager.selections; - if (selectedEntities.length <= 1) { - Window.notifyEditError("You must have multiple objects selected in order to parent them"); - return; - } - - var lastEntityId = selectedEntities[selectedEntities.length] - selectedEntities.some(function (id, index) { - if (lastId === id) { - return false; - } - Entities.editProperties(id, {parentID: lastId}) + selectedEntities.forEach(function (id, index) { + Entities.editEntity(id, {parentID: null}) return true; }); - SelectionManager.clearSelections(); - Window.notify("Entities Parented"); + Window.notify("Entities Unparented"); + } + +} +function parentSelectedEntities() { + print("parenting selected Entities"); + if (SelectionManager.hasSelection()) { + var selectedEntities = selectionManager.selections; + if (selectedEntities.length <= 1) { + Window.notifyEditError("You must have multiple objects selected in order to parent them"); + return; + } + var lastEntityId = selectedEntities[selectedEntities.length-1]; + + print("last " + lastEntityId); + selectedEntities.forEach(function (id, index) { + if (lastEntityId !== id) { + print("iterating2 " + id); + + // OK time to check why this breaks! + Entities.editEntity(id, {parentID: lastEntityId}) + } + }); + + Window.notify("Entities Parented"); } else { - Window.notifyEditError("You have nothing selected") + Window.notifyEditError("You have nothing selected") } } function deleteSelectedEntities() { @@ -1182,6 +1214,10 @@ function handeMenuEvent(menuItem) { Entities.setLightsArePickable(Menu.isOptionChecked("Allow Selecting of Lights")); } else if (menuItem === "Delete") { deleteSelectedEntities(); + } else if (menuItem === "Parent") { + parentSelectedEntities(); + } else if (menuItem === "Unparent") { + unparentSelectedEntities(); } else if (menuItem === "Export Entities") { if (!selectionManager.hasSelection()) { Window.notifyEditError("No entities have been selected."); @@ -1347,8 +1383,12 @@ Controller.keyReleaseEvent.connect(function (event) { }); grid.setPosition(newPosition); } - } else if (event.text === 'p' && event.isCtrl) { - parentSelectedEntities(); + } else if (event.text === 'p' && event.isControl ) { + if (event.isShifted) { + unparentSelectedEntities(); + } else { + parentSelectedEntities(); + } } }); From ff2c344eaaab3e7e6d121415fb8530277ea46428 Mon Sep 17 00:00:00 2001 From: Menithal Date: Thu, 16 Feb 2017 09:04:58 +0200 Subject: [PATCH 05/64] Migrated Audioscope Hotkeys to use F2 instead of P Apparently ctrl + shift + P which was planned to be used in the client for unparenting entities did not work out, as the command went out of the main view. Migrating to ctrl + F2 and ctrl + alt + F2 --- interface/src/Menu.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 431fe0638a..3ff5e00c06 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -628,9 +628,9 @@ Menu::Menu() { auto scope = DependencyManager::get(); MenuWrapper* audioScopeMenu = audioDebugMenu->addMenu("Audio Scope"); - addCheckableActionToQMenuAndActionHash(audioScopeMenu, MenuOption::AudioScope, Qt::CTRL | Qt::ALT | Qt::Key_P, false, + addCheckableActionToQMenuAndActionHash(audioScopeMenu, MenuOption::AudioScope, Qt::CTRL | Qt::Key_F2, false, scope.data(), SLOT(toggle())); - addCheckableActionToQMenuAndActionHash(audioScopeMenu, MenuOption::AudioScopePause, Qt::CTRL | Qt::ALT | Qt::SHIFT | Qt::Key_P, false, + addCheckableActionToQMenuAndActionHash(audioScopeMenu, MenuOption::AudioScopePause, Qt::CTRL | Qt::ALT | Qt::Key_F2, false, scope.data(), SLOT(togglePause())); addDisabledActionAndSeparator(audioScopeMenu, "Display Frames"); From d96f95a7e9b52765137c2c8d1d27eb93f9eba776 Mon Sep 17 00:00:00 2001 From: Menithal Date: Thu, 16 Feb 2017 10:05:59 +0200 Subject: [PATCH 06/64] Updated edit.js and selection tool Last selected entity will now be yellow instead of orange --- scripts/system/edit.js | 8 +------ .../system/libraries/entitySelectionTool.js | 22 +++++++++++-------- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/scripts/system/edit.js b/scripts/system/edit.js index afb171e936..1c76e03efd 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -1115,7 +1115,6 @@ function recursiveDelete(entities, childrenList) { } } function unparentSelectedEntities() { - print("unparenting2 Selected Entities"); if (SelectionManager.hasSelection()) { var selectedEntities = selectionManager.selections; selectedEntities.forEach(function (id, index) { @@ -1128,7 +1127,6 @@ function unparentSelectedEntities() { } function parentSelectedEntities() { - print("parenting selected Entities"); if (SelectionManager.hasSelection()) { var selectedEntities = selectionManager.selections; if (selectedEntities.length <= 1) { @@ -1137,16 +1135,12 @@ function parentSelectedEntities() { } var lastEntityId = selectedEntities[selectedEntities.length-1]; - print("last " + lastEntityId); selectedEntities.forEach(function (id, index) { if (lastEntityId !== id) { - print("iterating2 " + id); - // OK time to check why this breaks! Entities.editEntity(id, {parentID: lastEntityId}) } }); - Window.notify("Entities Parented"); } else { Window.notifyEditError("You have nothing selected") @@ -1384,7 +1378,7 @@ Controller.keyReleaseEvent.connect(function (event) { grid.setPosition(newPosition); } } else if (event.text === 'p' && event.isControl ) { - if (event.isShifted) { + if (event.isAlt) { unparentSelectedEntities(); } else { parentSelectedEntities(); diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index b9bae72d14..146dc1894b 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -1170,14 +1170,14 @@ SelectionDisplay = (function() { // determine which bottom corner we are closest to /*------------------------------ example: - + BRF +--------+ BLF | | | | BRN +--------+ BLN - + * - + ------------------------------*/ var cameraPosition = Camera.getPosition(); @@ -2189,8 +2189,12 @@ SelectionDisplay = (function() { offset = Vec3.multiplyQbyV(properties.rotation, offset); var boxPosition = Vec3.sum(properties.position, offset); + var color = {red: 255, green: 153, blue: 0}; + if (i >= selectionManager.selections.length - 1) color = {red: 255, green: 255, blue: 0}; + Overlays.editOverlay(selectionBoxes[i], { position: boxPosition, + color: color, rotation: properties.rotation, dimensions: properties.dimensions, visible: true, @@ -2395,7 +2399,7 @@ SelectionDisplay = (function() { if (wantDebug) { print("Start Elevation: " + translateXZTool.startingElevation + ", elevation: " + elevation); } - if ((translateXZTool.startingElevation > 0.0 && elevation < MIN_ELEVATION) || + if ((translateXZTool.startingElevation > 0.0 && elevation < MIN_ELEVATION) || (translateXZTool.startingElevation < 0.0 && elevation > -MIN_ELEVATION)) { if (wantDebug) { print("too close to horizon!"); @@ -3857,7 +3861,7 @@ SelectionDisplay = (function() { }; that.mousePressEvent = function(event) { - var wantDebug = false; + var wantDebug = false; if (!event.isLeftButton && !that.triggered) { // if another mouse button than left is pressed ignore it return false; @@ -3883,7 +3887,7 @@ SelectionDisplay = (function() { if (result.intersects) { - + if (wantDebug) { print("something intersects... "); print(" result.overlayID:" + result.overlayID + "[" + overlayNames[result.overlayID] + "]"); @@ -3983,7 +3987,7 @@ SelectionDisplay = (function() { if (wantDebug) { print("rotate handle case..."); } - + // After testing our stretch handles, then check out rotate handles Overlays.editOverlay(yawHandle, { @@ -4205,7 +4209,7 @@ SelectionDisplay = (function() { case selectionBox: activeTool = translateXZTool; translateXZTool.pickPlanePosition = result.intersection; - translateXZTool.greatestDimension = Math.max(Math.max(SelectionManager.worldDimensions.x, SelectionManager.worldDimensions.y), + translateXZTool.greatestDimension = Math.max(Math.max(SelectionManager.worldDimensions.x, SelectionManager.worldDimensions.y), SelectionManager.worldDimensions.z); if (wantDebug) { print("longest dimension: " + translateXZTool.greatestDimension); @@ -4214,7 +4218,7 @@ SelectionDisplay = (function() { translateXZTool.startingElevation = translateXZTool.elevation(pickRay.origin, translateXZTool.pickPlanePosition); print(" starting elevation: " + translateXZTool.startingElevation); } - + mode = translateXZTool.mode; activeTool.onBegin(event); somethingClicked = 'selectionBox'; From 7a5665ff4f2370f1cae6b018cb2568cd07a3aa59 Mon Sep 17 00:00:00 2001 From: Menithal Date: Thu, 16 Feb 2017 10:09:19 +0200 Subject: [PATCH 07/64] Hotkey change to make AudioScope similar to earlier --- interface/src/Menu.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 3ff5e00c06..c131367aee 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -630,7 +630,7 @@ Menu::Menu() { MenuWrapper* audioScopeMenu = audioDebugMenu->addMenu("Audio Scope"); addCheckableActionToQMenuAndActionHash(audioScopeMenu, MenuOption::AudioScope, Qt::CTRL | Qt::Key_F2, false, scope.data(), SLOT(toggle())); - addCheckableActionToQMenuAndActionHash(audioScopeMenu, MenuOption::AudioScopePause, Qt::CTRL | Qt::ALT | Qt::Key_F2, false, + addCheckableActionToQMenuAndActionHash(audioScopeMenu, MenuOption::AudioScopePause, Qt::CTRL | Qt::SHIFT | Qt::Key_F2, false, scope.data(), SLOT(togglePause())); addDisabledActionAndSeparator(audioScopeMenu, "Display Frames"); From afbf4bc30f53cd63efc2df1ab5ebfa14de7ec65d Mon Sep 17 00:00:00 2001 From: Menithal Date: Thu, 16 Feb 2017 10:52:42 +0200 Subject: [PATCH 08/64] Fixed Parenting/Unparenting Menu Removal and Binding --- scripts/system/edit.js | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 1c76e03efd..b83ca9a896 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -969,8 +969,8 @@ function cleanupModelMenus() { Menu.removeMenuItem("Edit", "Delete"); } - Menu.removeMenuItem("Edit", "Parent"); - Menu.removeMenuItem("Edit", "Unparent"); + Menu.removeMenuItem("Edit", "Parent Entity to Last"); + Menu.removeMenuItem("Edit", "Unparent Entity"); Menu.removeMenuItem("Edit", "Entity List..."); Menu.removeMenuItem("Edit", "Allow Selecting of Large Models"); Menu.removeMenuItem("Edit", "Allow Selecting of Small Models"); @@ -1134,10 +1134,8 @@ function parentSelectedEntities() { return; } var lastEntityId = selectedEntities[selectedEntities.length-1]; - selectedEntities.forEach(function (id, index) { if (lastEntityId !== id) { - // OK time to check why this breaks! Entities.editEntity(id, {parentID: lastEntityId}) } }); @@ -1208,9 +1206,9 @@ function handeMenuEvent(menuItem) { Entities.setLightsArePickable(Menu.isOptionChecked("Allow Selecting of Lights")); } else if (menuItem === "Delete") { deleteSelectedEntities(); - } else if (menuItem === "Parent") { + } else if (menuItem === "Parent Entity to Last") { parentSelectedEntities(); - } else if (menuItem === "Unparent") { + } else if (menuItem === "Unparent Entity") { unparentSelectedEntities(); } else if (menuItem === "Export Entities") { if (!selectionManager.hasSelection()) { @@ -1378,7 +1376,7 @@ Controller.keyReleaseEvent.connect(function (event) { grid.setPosition(newPosition); } } else if (event.text === 'p' && event.isControl ) { - if (event.isAlt) { + if (event.isShifted) { unparentSelectedEntities(); } else { parentSelectedEntities(); From 51246a2e45ece2e60e11b97f388101de55a38800 Mon Sep 17 00:00:00 2001 From: David Kelly Date: Fri, 17 Feb 2017 10:24:01 -0700 Subject: [PATCH 09/64] first cut, pretty ugly --- interface/resources/qml/hifi/NameCard.qml | 5 +- interface/resources/qml/hifi/Pal.qml | 42 +++++++++++---- scripts/system/pal.js | 66 +++++++++++++---------- 3 files changed, 73 insertions(+), 40 deletions(-) diff --git a/interface/resources/qml/hifi/NameCard.qml b/interface/resources/qml/hifi/NameCard.qml index b55b9c517d..5eb6e6b8b1 100644 --- a/interface/resources/qml/hifi/NameCard.qml +++ b/interface/resources/qml/hifi/NameCard.qml @@ -31,6 +31,7 @@ Item { property real displayNameTextPixelSize: 18 property int usernameTextHeight: 12 property real audioLevel: 0.0 + property real avgAudioLevel: 0.0 property bool isMyCard: false property bool selected: false property bool isAdmin: false @@ -335,7 +336,7 @@ Item { } } - // Per-Avatar Gain Slider + // Per-Avatar Gain Slider Slider { id: gainSlider // Size @@ -369,7 +370,7 @@ Item { mouse.accepted = false } onReleased: { - // the above mouse.accepted seems to make this + // the above mouse.accepted seems to make this // never get called, nonetheless... mouse.accepted = false } diff --git a/interface/resources/qml/hifi/Pal.qml b/interface/resources/qml/hifi/Pal.qml index c1fea7c09b..0d1da1eed5 100644 --- a/interface/resources/qml/hifi/Pal.qml +++ b/interface/resources/qml/hifi/Pal.qml @@ -2,7 +2,7 @@ // Pal.qml // qml/hifi // -// People Action List +// People Action List // // Created by Howard Stearns on 12/12/2016 // Copyright 2016 High Fidelity, Inc. @@ -29,8 +29,8 @@ Rectangle { property int myCardHeight: 90 property int rowHeight: 70 property int actionButtonWidth: 55 - property int nameCardWidth: palContainer.width - actionButtonWidth*(iAmAdmin ? 4 : 2) - 4 - hifi.dimensions.scrollbarBackgroundWidth - property var myData: ({displayName: "", userName: "", audioLevel: 0.0, admin: true}) // valid dummy until set + property int nameCardWidth: palContainer.width - actionButtonWidth*(iAmAdmin ? 5 : 3) - 4 - hifi.dimensions.scrollbarBackgroundWidth + property var myData: ({displayName: "", userName: "", audioLevel: 0.0, avgAudioLevel: 0.0, admin: true}) // valid dummy until set property var ignored: ({}); // Keep a local list of ignored avatars & their data. Necessary because HashMap is slow to respond after ignoring. property var userModelData: [] // This simple list is essentially a mirror of the userModel listModel without all the extra complexities. property bool iAmAdmin: false @@ -86,6 +86,7 @@ Rectangle { displayName: myData.displayName userName: myData.userName audioLevel: myData.audioLevel + avgAudioLevel: myData.avgAudioLevel isMyCard: true // Size width: nameCardWidth @@ -184,6 +185,13 @@ Rectangle { movable: false resizable: false } + TableViewColumn { + role: "avgAudioLevel" + title: "VOL" + width: actionButtonWidth + movable: false + resizable: false + } TableViewColumn { visible: iAmAdmin role: "mute" @@ -218,6 +226,7 @@ Rectangle { id: itemCell property bool isCheckBox: styleData.role === "personalMute" || styleData.role === "ignore" property bool isButton: styleData.role === "mute" || styleData.role === "kick" + property bool isText: styleData.role == "avgAudioLevel" // This NameCard refers to the cell that contains an avatar's // DisplayName and UserName NameCard { @@ -226,7 +235,8 @@ Rectangle { displayName: styleData.value userName: model ? model.userName : "" audioLevel: model ? model.audioLevel : 0.0 - visible: !isCheckBox && !isButton + avgAudioLevel: model ? model.avgAudioLevel : 0.0 + visible: !isCheckBox && !isButton && !isText uuid: model ? model.sessionId : "" selected: styleData.selected isAdmin: model && model.admin @@ -236,7 +246,16 @@ Rectangle { // Anchors anchors.left: parent.left } - + Text { + id: avgAudioVolume + text: model ? model.avgAudioLevel : 0.0 + visible: isText + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + width: parent.width + height: parent.height + } + // This CheckBox belongs in the columns that contain the stateful action buttons ("Mute" & "Ignore" for now) // KNOWN BUG with the Checkboxes: When clicking in the center of the sorting header, the checkbox // will appear in the "hovered" state. Hovering over the checkbox will fix it. @@ -272,7 +291,7 @@ Rectangle { checked = Qt.binding(function() { return (model[styleData.role])}) } } - + // This Button belongs in the columns that contain the stateless action buttons ("Silence" & "Ban" for now) HifiControls.Button { id: actionButton @@ -542,23 +561,28 @@ Rectangle { } } break; - case 'updateAudioLevel': + case 'updateAudioLevel': for (var userId in message.params) { - var audioLevel = message.params[userId]; + var audioLevel = message.params[userId][0]; + var avgAudioLevel = message.params[userId][1]; // If the userId is 0, we're updating "myData". if (userId == 0) { myData.audioLevel = audioLevel; myCard.audioLevel = audioLevel; // Defensive programming + myData.avgAudioLevel = avgAudioLevel; + myCard.avgAudioLevel = avgAudioLevel; } else { var userIndex = findSessionIndex(userId); if (userIndex != -1) { userModel.setProperty(userIndex, "audioLevel", audioLevel); userModelData[userIndex].audioLevel = audioLevel; // Defensive programming + userModel.setProperty(userIndex, "avgAudioLevel", avgAudioLevel); + userModelData[userIndex].avgAudioLevel = avgAudioLevel; } } } break; - case 'clearLocalQMLData': + case 'clearLocalQMLData': ignored = {}; gainSliderValueDB = {}; break; diff --git a/scripts/system/pal.js b/scripts/system/pal.js index 57648da79a..b5d7c3885d 100644 --- a/scripts/system/pal.js +++ b/scripts/system/pal.js @@ -13,7 +13,7 @@ (function() { // BEGIN LOCAL_SCOPE -// hardcoding these as it appears we cannot traverse the originalTextures in overlays??? Maybe I've missed +// hardcoding these as it appears we cannot traverse the originalTextures in overlays??? Maybe I've missed // something, will revisit as this is sorta horrible. const UNSELECTED_TEXTURES = {"idle-D": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-idle.png"), "idle-E": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-idle.png") @@ -87,7 +87,7 @@ ExtendedOverlay.prototype.hover = function (hovering) { } else { lastHoveringId = 0; } - } + } this.editOverlay({color: color(this.selected, hovering, this.audioLevel)}); if (this.model) { this.model.editOverlay({textures: textures(this.selected, hovering)}); @@ -104,7 +104,7 @@ ExtendedOverlay.prototype.select = function (selected) { if (this.selected === selected) { return; } - + UserActivityLogger.palAction(selected ? "avatar_selected" : "avatar_deselected", this.key); this.editOverlay({color: color(selected, this.hovering, this.audioLevel)}); @@ -273,7 +273,7 @@ function sendToQml(message) { // function addAvatarNode(id) { var selected = ExtendedOverlay.isSelected(id); - return new ExtendedOverlay(id, "sphere", { + return new ExtendedOverlay(id, "sphere", { drawInFront: true, solid: true, alpha: 0.8, @@ -290,6 +290,7 @@ function populateUserList(selectData) { userName: '', sessionId: id || '', audioLevel: 0.0, + avgAudioLevel: 0.0, admin: false, personalMute: !!id && Users.getPersonalMuteStatus(id), // expects proper boolean, not null ignore: !!id && Users.getIgnoreStatus(id) // ditto @@ -341,7 +342,7 @@ function updateOverlays() { var target = avatar.position; var distance = Vec3.distance(target, eye); var offset = 0.2; - + // base offset on 1/2 distance from hips to head if we can var headIndex = avatar.getJointIndex("Head"); if (headIndex > 0) { @@ -350,7 +351,7 @@ function updateOverlays() { // get diff between target and eye (a vector pointing to the eye from avatar position) var diff = Vec3.subtract(target, eye); - + // move a bit in front, towards the camera target = Vec3.subtract(target, Vec3.multiply(Vec3.normalize(diff), offset)); @@ -361,12 +362,12 @@ function updateOverlays() { overlay.editOverlay({ color: color(ExtendedOverlay.isSelected(id), overlay.hovering, overlay.audioLevel), position: target, - dimensions: 0.032 * distance + dimensions: 0.032 * distance }); if (overlay.model) { overlay.model.ping = pingPong; overlay.model.editOverlay({ - position: target, + position: target, scale: 0.2 * distance, // constant apparent size rotation: Camera.orientation }); @@ -433,7 +434,7 @@ function handleMouseMoveEvent(event) { // find out which overlay (if any) is ove handleMouseMove(pickRay); } function handleTriggerPressed(hand, value) { - // The idea is if you press one trigger, it is the one + // The idea is if you press one trigger, it is the one // we will consider the mouse. Even if the other is pressed, // we ignore it until this one is no longer pressed. isPressed = value > TRIGGER_PRESS_THRESHOLD; @@ -441,10 +442,10 @@ function handleTriggerPressed(hand, value) { currentHandPressed = isPressed ? hand : 0; return; } - if (currentHandPressed == hand) { + if (currentHandPressed == hand) { currentHandPressed = isPressed ? hand : 0; return; - } + } // otherwise, the other hand is still triggered // so do nothing. } @@ -574,41 +575,48 @@ function receiveMessage(channel, messageString, senderID) { Messages.subscribe(CHANNEL); Messages.messageReceived.connect(receiveMessage); - var AVERAGING_RATIO = 0.05; +var LONG_AVERAGING_RATIO = 0.75; var LOUDNESS_FLOOR = 11.0; var LOUDNESS_SCALE = 2.8 / 5.0; var LOG2 = Math.log(2.0); var myData = {}; // we're not includied in ExtendedOverlay.get. +function scaleAudio(val) { + var audioLevel = 0.0; + if (val <= LOUDNESS_FLOOR) { + audioLevel = val / LOUDNESS_FLOOR * LOUDNESS_SCALE; + } else { + audioLevel = (val -(LOUDNESS_FLOOR -1 )) * LOUDNESS_SCALE; + } + if (audioLevel > 1.0) { + audioLevel = 1; + } + return audioLevel; +} function getAudioLevel(id) { // the VU meter should work similarly to the one in AvatarInputs: log scale, exponentially averaged // But of course it gets the data at a different rate, so we tweak the averaging ratio and frequency // of updating (the latter for efficiency too). var avatar = AvatarList.getAvatar(id); var audioLevel = 0.0; + var avgAudioLevel = 0.0; var data = id ? ExtendedOverlay.get(id) : myData; - if (!data) { - return audioLevel; - } + if (data) { - // we will do exponential moving average by taking some the last loudness and averaging - data.accumulatedLevel = AVERAGING_RATIO * (data.accumulatedLevel || 0) + (1 - AVERAGING_RATIO) * (avatar.audioLoudness); + // we will do exponential moving average by taking some the last loudness and averaging + data.accumulatedLevel = AVERAGING_RATIO * (data.accumulatedLevel || 0) + (1 - AVERAGING_RATIO) * (avatar.audioLoudness); + data.longAccumulatedLevel = LONG_AVERAGING_RATIO * (data.longAccumulatedLevel || 0) + (1 - LONG_AVERAGING_RATIO) * (avatar.audioLoudness); - // add 1 to insure we don't go log() and hit -infinity. Math.log is - // natural log, so to get log base 2, just divide by ln(2). - var logLevel = Math.log(data.accumulatedLevel + 1) / LOG2; + // add 1 to insure we don't go log() and hit -infinity. Math.log is + // natural log, so to get log base 2, just divide by ln(2). + audioLevel = scaleAudio(Math.log(data.accumulatedLevel + 1) / LOG2); + avgAudioLevel = scaleAudio(Math.log(data.longAccumulatedLevel + 1) / LOG2); - if (logLevel <= LOUDNESS_FLOOR) { - audioLevel = logLevel / LOUDNESS_FLOOR * LOUDNESS_SCALE; - } else { - audioLevel = (logLevel - (LOUDNESS_FLOOR - 1.0)) * LOUDNESS_SCALE; + data.audioLevel = audioLevel; + data.averageAudioLevel = avgAudioLevel; } - if (audioLevel > 1.0) { - audioLevel = 1; - } - data.audioLevel = audioLevel; - return audioLevel; + return [audioLevel, avgAudioLevel]; } function createAudioInterval(interval) { From d938c70b486c26e9bff3c7dd78a1c9a86586098a Mon Sep 17 00:00:00 2001 From: David Kelly Date: Fri, 17 Feb 2017 11:11:08 -0700 Subject: [PATCH 10/64] volume goes 0-9 now --- interface/resources/qml/hifi/Pal.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/resources/qml/hifi/Pal.qml b/interface/resources/qml/hifi/Pal.qml index 0d1da1eed5..e0f2df76ce 100644 --- a/interface/resources/qml/hifi/Pal.qml +++ b/interface/resources/qml/hifi/Pal.qml @@ -248,7 +248,7 @@ Rectangle { } Text { id: avgAudioVolume - text: model ? model.avgAudioLevel : 0.0 + text: model ? (10 * model.avgAudioLevel).toFixed(0) : 0.0 visible: isText horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter From f5d266a56253703e35a8baf78159479ccac22ac7 Mon Sep 17 00:00:00 2001 From: Menithal Date: Fri, 17 Feb 2017 23:28:28 +0200 Subject: [PATCH 11/64] Updated --- scripts/system/edit.js | 26 ++++++++++++------- scripts/system/html/entityList.html | 3 +++ scripts/system/html/entityProperties.html | 3 ++- scripts/system/html/js/parentingHotkey.js | 12 +++++++++ .../system/libraries/entitySelectionTool.js | 4 +-- 5 files changed, 36 insertions(+), 12 deletions(-) create mode 100644 scripts/system/html/js/parentingHotkey.js diff --git a/scripts/system/edit.js b/scripts/system/edit.js index b83ca9a896..d759760893 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -859,7 +859,6 @@ function setupModelMenus() { menuItemName: "Unparent Entity", afterItem: "Entity List...", shortcutKey: "CTRL+SHIFT+P", - afterItem: "Entity List...", grouping: "Advanced" }); Menu.addMenuItem({ @@ -1003,6 +1002,9 @@ Script.scriptEnding.connect(function () { Overlays.deleteOverlay(importingSVOImageOverlay); Overlays.deleteOverlay(importingSVOTextOverlay); + + Controller.keyReleaseEvent.disconnect(keyReleaseEvent); + Controller.keyPressEvent.disconnect(keyPressEvent); }); var lastOrientation = null; @@ -1121,10 +1123,8 @@ function unparentSelectedEntities() { Entities.editEntity(id, {parentID: null}) return true; }); - Window.notify("Entities Unparented"); } - } function parentSelectedEntities() { if (SelectionManager.hasSelection()) { @@ -1335,13 +1335,12 @@ Window.svoImportRequested.connect(importSVO); Menu.menuItemEvent.connect(handeMenuEvent); -Controller.keyPressEvent.connect(function (event) { +var keyPressEvent = function (event) { if (isActive) { cameraManager.keyPressEvent(event); } -}); - -Controller.keyReleaseEvent.connect(function (event) { +}; +var keyReleaseEvent = function (event) { if (isActive) { cameraManager.keyReleaseEvent(event); } @@ -1375,14 +1374,16 @@ Controller.keyReleaseEvent.connect(function (event) { }); grid.setPosition(newPosition); } - } else if (event.text === 'p' && event.isControl ) { + } else if (event.text === 'p' && event.isControl && !event.isAutoRepeat ) { if (event.isShifted) { unparentSelectedEntities(); } else { parentSelectedEntities(); } } -}); +}; +Controller.keyReleaseEvent.connect(keyReleaseEvent); +Controller.keyPressEvent.connect(keyPressEvent); function recursiveAdd(newParentID, parentData) { var children = parentData.children; @@ -1632,6 +1633,10 @@ var PropertiesTool = function (opts) { } pushCommandForSelections(); selectionManager._update(); + } else if(data.type === 'parent') { + parentSelectedEntities(); + } else if(data.type === 'unparent') { + unparentSelectedEntities(); } else if(data.type === 'saveUserData'){ //the event bridge and json parsing handle our avatar id string differently. var actualID = data.id.split('"')[1]; @@ -1889,6 +1894,9 @@ var PopupMenu = function () { for (var i = 0; i < overlays.length; i++) { Overlays.deleteOverlay(overlays[i]); } + Controller.mousePressEvent.disconnect(self.mousePressEvent); + Controller.mouseMoveEvent.disconnect(self.mouseMoveEvent); + Controller.mouseReleaseEvent.disconnect(self.mouseReleaseEvent); } Controller.mousePressEvent.connect(self.mousePressEvent); diff --git a/scripts/system/html/entityList.html b/scripts/system/html/entityList.html index 197d8f550a..67d1973fa7 100644 --- a/scripts/system/html/entityList.html +++ b/scripts/system/html/entityList.html @@ -16,6 +16,7 @@ +
@@ -89,6 +90,8 @@ + +
No entities found in view within a 100 meter radius. Try moving to a different location and refreshing.
diff --git a/scripts/system/html/entityProperties.html b/scripts/system/html/entityProperties.html index b11127b26c..84c8a15dd3 100644 --- a/scripts/system/html/entityProperties.html +++ b/scripts/system/html/entityProperties.html @@ -22,6 +22,7 @@ +
@@ -61,7 +62,7 @@
- +

diff --git a/scripts/system/html/js/parentingHotkey.js b/scripts/system/html/js/parentingHotkey.js new file mode 100644 index 0000000000..c17dae01fe --- /dev/null +++ b/scripts/system/html/js/parentingHotkey.js @@ -0,0 +1,12 @@ +var keyReleaseEvent = function (event) { + if (event.text === 'p' && event.isControl && !event.isAutoRepeat ) { + if (event.isShifted) { + EventBridge.emitWebEvent(JSON.stringify({ type: 'unparent' })); + } else { + EventBridge.emitWebEvent(JSON.stringify({ type: 'parent' })); + } + } +}; + +window.onkeypress = keyReleaseEvent; +Controller.keyReleaseEvent.connect(keyReleaseEvent); diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index 146dc1894b..13f61dfd80 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -2189,8 +2189,8 @@ SelectionDisplay = (function() { offset = Vec3.multiplyQbyV(properties.rotation, offset); var boxPosition = Vec3.sum(properties.position, offset); - var color = {red: 255, green: 153, blue: 0}; - if (i >= selectionManager.selections.length - 1) color = {red: 255, green: 255, blue: 0}; + var color = {red: 255, green: 128, blue: 0}; + if (i >= selectionManager.selections.length - 1) color = {red: 255, green: 255, blue: 64}; Overlays.editOverlay(selectionBoxes[i], { position: boxPosition, From dcbe3c622db92d2e5729813392a9c483c5ae1c22 Mon Sep 17 00:00:00 2001 From: Menithal Date: Sat, 18 Feb 2017 11:51:19 +0200 Subject: [PATCH 12/64] Added Hotkey Listening to the webview Ctrl + P and Ctrl + Shift + P are now available even if the edit window has been selected --- scripts/system/edit.js | 13 +++---- scripts/system/html/entityList.html | 2 - scripts/system/html/entityProperties.html | 1 - scripts/system/html/js/entityList.js | 44 ++++++++++++---------- scripts/system/html/js/entityProperties.js | 16 ++++++-- scripts/system/html/js/gridControls.js | 11 +++++- scripts/system/html/js/parentingHotkey.js | 12 ------ 7 files changed, 52 insertions(+), 47 deletions(-) delete mode 100644 scripts/system/html/js/parentingHotkey.js diff --git a/scripts/system/edit.js b/scripts/system/edit.js index d759760893..d0236ce993 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -1374,12 +1374,6 @@ var keyReleaseEvent = function (event) { }); grid.setPosition(newPosition); } - } else if (event.text === 'p' && event.isControl && !event.isAutoRepeat ) { - if (event.isShifted) { - unparentSelectedEntities(); - } else { - parentSelectedEntities(); - } } }; Controller.keyReleaseEvent.connect(keyReleaseEvent); @@ -1586,6 +1580,7 @@ var PropertiesTool = function (opts) { print('Edit.js received web event that was not valid json.') return; } + print(JSON.stringify(data)) var i, properties, dY, diff, newPosition; if (data.type === "print") { if (data.message) { @@ -1924,7 +1919,11 @@ var particleExplorerTool = new ParticleExplorerTool(); var selectedParticleEntity = 0; entityListTool.webView.webEventReceived.connect(function (data) { data = JSON.parse(data); - if (data.type === "selectionUpdate") { + if(data.type === 'parent') { + parentSelectedEntities(); + } else if(data.type === 'unparent') { + unparentSelectedEntities(); + } else if (data.type === "selectionUpdate") { var ids = data.entityIds; if (ids.length === 1) { if (Entities.getEntityProperties(ids[0], "type").type === "ParticleEffect") { diff --git a/scripts/system/html/entityList.html b/scripts/system/html/entityList.html index 67d1973fa7..3cb79353f9 100644 --- a/scripts/system/html/entityList.html +++ b/scripts/system/html/entityList.html @@ -16,7 +16,6 @@ -
@@ -91,7 +90,6 @@ -
No entities found in view within a 100 meter radius. Try moving to a different location and refreshing.
diff --git a/scripts/system/html/entityProperties.html b/scripts/system/html/entityProperties.html index 84c8a15dd3..cc73d71517 100644 --- a/scripts/system/html/entityProperties.html +++ b/scripts/system/html/entityProperties.html @@ -22,7 +22,6 @@ -
diff --git a/scripts/system/html/js/entityList.js b/scripts/system/html/js/entityList.js index 1af9c1e1d6..53d2d9030b 100644 --- a/scripts/system/html/js/entityList.js +++ b/scripts/system/html/js/entityList.js @@ -26,7 +26,7 @@ debugPrint = function (message) { }; function loaded() { - openEventBridge(function() { + openEventBridge(function() { entityList = new List('entity-list', { valueNames: ['name', 'type', 'url', 'locked', 'visible'], page: MAX_ITEMS}); entityList.clear(); elEntityTable = document.getElementById("entity-table"); @@ -48,7 +48,7 @@ function loaded() { elNoEntitiesInView = document.getElementById("no-entities-in-view"); elNoEntitiesRadius = document.getElementById("no-entities-radius"); elEntityTableScroll = document.getElementById("entity-table-scroll"); - + document.getElementById("entity-name").onclick = function() { setSortColumn('name'); }; @@ -90,7 +90,7 @@ function loaded() { selection = selection.concat(selectedEntities); } else if (clickEvent.shiftKey && selectedEntities.length > 0) { var previousItemFound = -1; - var clickedItemFound = -1; + var clickedItemFound = -1; for (var entity in entityList.visibleItems) { if (clickedItemFound === -1 && this.dataset.entityId == entityList.visibleItems[entity].values().id) { clickedItemFound = entity; @@ -113,11 +113,11 @@ function loaded() { selection = selection.concat(betweenItems, selectedEntities); } } - + selectedEntities = selection; - + this.className = 'selected'; - + EventBridge.emitWebEvent(JSON.stringify({ type: "selectionUpdate", focus: false, @@ -126,7 +126,7 @@ function loaded() { refreshFooter(); } - + function onRowDoubleClicked() { EventBridge.emitWebEvent(JSON.stringify({ type: "selectionUpdate", @@ -134,7 +134,7 @@ function loaded() { entityIds: [this.dataset.entityId], })); } - + const BYTES_PER_MEGABYTE = 1024 * 1024; function decimalMegabytes(number) { @@ -173,7 +173,7 @@ function loaded() { currentElement.onclick = onRowClicked; currentElement.ondblclick = onRowDoubleClicked; }); - + if (refreshEntityListTimer) { clearTimeout(refreshEntityListTimer); } @@ -183,13 +183,13 @@ function loaded() { item.values({ name: name, url: filename, locked: locked, visible: visible }); } } - + function clearEntities() { entities = {}; entityList.clear(); refreshFooter(); } - + var elSortOrder = { name: document.querySelector('#entity-name .sort-order'), type: document.querySelector('#entity-type .sort-order'), @@ -215,12 +215,12 @@ function loaded() { entityList.sort(currentSortColumn, { order: currentSortOrder }); } setSortColumn('type'); - + function refreshEntities() { clearEntities(); EventBridge.emitWebEvent(JSON.stringify({ type: 'refresh' })); } - + function refreshFooter() { if (selectedEntities.length > 1) { elFooter.firstChild.nodeValue = selectedEntities.length + " entities selected"; @@ -239,7 +239,7 @@ function loaded() { entityList.search(elFilter.value); refreshFooter(); } - + function updateSelectedEntities(selectedIDs) { var notFound = false; for (var id in entities) { @@ -262,7 +262,7 @@ function loaded() { return notFound; } - + elRefresh.onclick = function() { refreshEntities(); } @@ -282,7 +282,7 @@ function loaded() { EventBridge.emitWebEvent(JSON.stringify({ type: 'delete' })); refreshEntities(); } - + document.addEventListener("keydown", function (keyDownEvent) { if (keyDownEvent.target.nodeName === "INPUT") { return; @@ -292,8 +292,15 @@ function loaded() { EventBridge.emitWebEvent(JSON.stringify({ type: 'delete' })); refreshEntities(); } + if (keyDownEvent.keyCode === 80 && keyDownEvent.ctrlKey) { + if (keyDownEvent.shiftKey) { + EventBridge.emitWebEvent(JSON.stringify({ type: 'unparent' })); + } else { + EventBridge.emitWebEvent(JSON.stringify({ type: 'parent' })); + } + } }, false); - + var isFilterInView = false; var FILTER_IN_VIEW_ATTRIBUTE = "pressed"; elNoEntitiesInView.style.display = "none"; @@ -320,7 +327,7 @@ function loaded() { if (window.EventBridge !== undefined) { EventBridge.scriptEventReceived.connect(function(data) { data = JSON.parse(data); - + if (data.type === "clearEntityList") { clearEntities(); } else if (data.type == "selectionUpdate") { @@ -426,4 +433,3 @@ function loaded() { event.preventDefault(); }, false); } - diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index 8879c0f34e..ab8a3f7c85 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -273,7 +273,7 @@ function updateCheckedSubProperty(propertyName, propertyValue, subPropertyElemen propertyValue += subPropertyString + ','; } } else { - // We've unchecked, so remove + // We've unchecked, so remove propertyValue = propertyValue.replace(subPropertyString + ",", ""); } @@ -780,7 +780,7 @@ function loaded() { if (lastEntityID !== '"' + properties.id + '"' && lastEntityID !== null && editor !== null) { saveJSONUserData(true); } - //the event bridge and json parsing handle our avatar id string differently. + //the event bridge and json parsing handle our avatar id string differently. lastEntityID = '"' + properties.id + '"'; elID.innerHTML = properties.id; @@ -1390,7 +1390,7 @@ function loaded() { elZoneFlyingAllowed.addEventListener('change', createEmitCheckedPropertyUpdateFunction('flyingAllowed')); elZoneGhostingAllowed.addEventListener('change', createEmitCheckedPropertyUpdateFunction('ghostingAllowed')); elZoneFilterURL.addEventListener('change', createEmitTextPropertyUpdateFunction('filterURL')); - + var voxelVolumeSizeChangeFunction = createEmitVec3PropertyUpdateFunction( 'voxelVolumeSize', elVoxelVolumeSizeX, elVoxelVolumeSizeY, elVoxelVolumeSizeZ); elVoxelVolumeSizeX.addEventListener('change', voxelVolumeSizeChangeFunction); @@ -1441,7 +1441,15 @@ function loaded() { })); }); - + document.addEventListener("keydown", function (keyDown) { + if (keyDown.keyCode === 80 && keyDown.ctrlKey) { + if (keyDown.shiftKey) { + EventBridge.emitWebEvent(JSON.stringify({ type: 'unparent' })); + } else { + EventBridge.emitWebEvent(JSON.stringify({ type: 'parent' })); + } + } + }); window.onblur = function() { // Fake a change event var ev = document.createEvent("HTMLEvents"); diff --git a/scripts/system/html/js/gridControls.js b/scripts/system/html/js/gridControls.js index a245ed4cda..75052414ee 100644 --- a/scripts/system/html/js/gridControls.js +++ b/scripts/system/html/js/gridControls.js @@ -131,10 +131,17 @@ function loaded() { EventBridge.emitWebEvent(JSON.stringify({ type: 'init' })); }); - + document.addEventListener("keydown", function (keyDown) { + if (keyDown.keyCode === 80 && keyDown.ctrlKey) { + if (keyDown.shiftKey) { + EventBridge.emitWebEvent(JSON.stringify({ type: 'unparent' })); + } else { + EventBridge.emitWebEvent(JSON.stringify({ type: 'parent' })); + } + } + }) // Disable right-click context menu which is not visible in the HMD and makes it seem like the app has locked document.addEventListener("contextmenu", function (event) { event.preventDefault(); }, false); } - diff --git a/scripts/system/html/js/parentingHotkey.js b/scripts/system/html/js/parentingHotkey.js deleted file mode 100644 index c17dae01fe..0000000000 --- a/scripts/system/html/js/parentingHotkey.js +++ /dev/null @@ -1,12 +0,0 @@ -var keyReleaseEvent = function (event) { - if (event.text === 'p' && event.isControl && !event.isAutoRepeat ) { - if (event.isShifted) { - EventBridge.emitWebEvent(JSON.stringify({ type: 'unparent' })); - } else { - EventBridge.emitWebEvent(JSON.stringify({ type: 'parent' })); - } - } -}; - -window.onkeypress = keyReleaseEvent; -Controller.keyReleaseEvent.connect(keyReleaseEvent); From 0df6305f901e24ab1b3f4d3028f4c169059cb8e9 Mon Sep 17 00:00:00 2001 From: Menithal Date: Sat, 18 Feb 2017 18:22:01 +0200 Subject: [PATCH 13/64] Revert removal of hotkey from js event --- scripts/system/edit.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/scripts/system/edit.js b/scripts/system/edit.js index d0236ce993..688fabab8b 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -849,7 +849,6 @@ function setupModelMenus() { Menu.addMenuItem({ menuName: "Edit", menuItemName: "Parent Entity to Last", - shortcutKey: "CTRL+P", afterItem: "Entity List...", grouping: "Advanced" }); @@ -858,7 +857,6 @@ function setupModelMenus() { menuName: "Edit", menuItemName: "Unparent Entity", afterItem: "Entity List...", - shortcutKey: "CTRL+SHIFT+P", grouping: "Advanced" }); Menu.addMenuItem({ @@ -1374,6 +1372,12 @@ var keyReleaseEvent = function (event) { }); grid.setPosition(newPosition); } + } else if (event.text === 'p' && event.isControl && !event.isAutoRepeat ) { + if (event.isShifted) { + unparentSelectedEntities(); + } else { + parentSelectedEntities(); + } } }; Controller.keyReleaseEvent.connect(keyReleaseEvent); From f3c806e91090c720e4f3841600f7933d44258a7e Mon Sep 17 00:00:00 2001 From: Menithal Date: Sat, 18 Feb 2017 18:33:00 +0200 Subject: [PATCH 14/64] Revert of hotkey detection, removed menu hotkeys - Holding hotkeys will no longer spam parent events rapidly, detects only the release --- scripts/system/edit.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 688fabab8b..bc76489c41 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -1372,7 +1372,7 @@ var keyReleaseEvent = function (event) { }); grid.setPosition(newPosition); } - } else if (event.text === 'p' && event.isControl && !event.isAutoRepeat ) { + } else if (event.key === 80 && event.isControl && !event.isAutoRepeat ) { if (event.isShifted) { unparentSelectedEntities(); } else { From 9b8503eba5ce4d359c30c681e4765e8dfa242227 Mon Sep 17 00:00:00 2001 From: Menithal Date: Sun, 19 Feb 2017 18:18:38 +0200 Subject: [PATCH 15/64] Initial Grab Clone --- scripts/system/html/entityProperties.html | 15 ++++++++++++++- scripts/system/html/js/entityProperties.js | 13 ++++++++++--- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/scripts/system/html/entityProperties.html b/scripts/system/html/entityProperties.html index b11127b26c..4c3723a2ef 100644 --- a/scripts/system/html/entityProperties.html +++ b/scripts/system/html/entityProperties.html @@ -61,7 +61,7 @@
- +

@@ -295,12 +295,25 @@
+
+ + +
+
+
+ Cloneable Settings +
+
+
+
+
+

diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index 8879c0f34e..63cb037ada 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -273,7 +273,7 @@ function updateCheckedSubProperty(propertyName, propertyValue, subPropertyElemen propertyValue += subPropertyString + ','; } } else { - // We've unchecked, so remove + // We've unchecked, so remove propertyValue = propertyValue.replace(subPropertyString + ",", ""); } @@ -584,6 +584,7 @@ function loaded() { var elCollisionSoundURL = document.getElementById("property-collision-sound-url"); var elGrabbable = document.getElementById("property-grabbable"); + var elCloneable = document.getElementById("property-cloneable"); var elWantsTrigger = document.getElementById("property-wants-trigger"); var elIgnoreIK = document.getElementById("property-ignore-ik"); @@ -780,7 +781,7 @@ function loaded() { if (lastEntityID !== '"' + properties.id + '"' && lastEntityID !== null && editor !== null) { saveJSONUserData(true); } - //the event bridge and json parsing handle our avatar id string differently. + //the event bridge and json parsing handle our avatar id string differently. lastEntityID = '"' + properties.id + '"'; elID.innerHTML = properties.id; @@ -1156,6 +1157,12 @@ function loaded() { elGrabbable.addEventListener('change', function() { userDataChanger("grabbableKey", "grabbable", elGrabbable, elUserData, properties.dynamic); }); + elCloneable.addEventListener('change', function () { + userDataChanger("grabbableKey", "cloneable", elCloneable, elUserData, false); + if (elCloneable.checked) { + + } + }); elWantsTrigger.addEventListener('change', function() { userDataChanger("grabbableKey", "wantsTrigger", elWantsTrigger, elUserData, false); }); @@ -1390,7 +1397,7 @@ function loaded() { elZoneFlyingAllowed.addEventListener('change', createEmitCheckedPropertyUpdateFunction('flyingAllowed')); elZoneGhostingAllowed.addEventListener('change', createEmitCheckedPropertyUpdateFunction('ghostingAllowed')); elZoneFilterURL.addEventListener('change', createEmitTextPropertyUpdateFunction('filterURL')); - + var voxelVolumeSizeChangeFunction = createEmitVec3PropertyUpdateFunction( 'voxelVolumeSize', elVoxelVolumeSizeX, elVoxelVolumeSizeY, elVoxelVolumeSizeZ); elVoxelVolumeSizeX.addEventListener('change', voxelVolumeSizeChangeFunction); From 2a4b493de560049bf34f2e179cca4970a830a7c7 Mon Sep 17 00:00:00 2001 From: Menithal Date: Sun, 19 Feb 2017 18:24:56 +0200 Subject: [PATCH 16/64] Changed order on Menu --- scripts/system/edit.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/system/edit.js b/scripts/system/edit.js index bc76489c41..61bbd23180 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -856,14 +856,14 @@ function setupModelMenus() { Menu.addMenuItem({ menuName: "Edit", menuItemName: "Unparent Entity", - afterItem: "Entity List...", + afterItem: "Parent Entity to Last", grouping: "Advanced" }); Menu.addMenuItem({ menuName: "Edit", menuItemName: "Allow Selecting of Large Models", shortcutKey: "CTRL+META+L", - afterItem: "Entity List...", + afterItem: "Unparent Entity", isCheckable: true, isChecked: true, grouping: "Advanced" From d9f86612de72cb10004d70ae68db4e06669650fb Mon Sep 17 00:00:00 2001 From: Menithal Date: Sun, 19 Feb 2017 20:18:11 +0200 Subject: [PATCH 17/64] Fixed view toggle, Added new Notifications - View Toggle now only toggles if no other modifiers are pressed - More notification, and cases for notification - Parenting to existing parent - Unparenting when there is no parent - Unparenting single entities --- interface/src/Application.cpp | 10 ++++--- scripts/system/edit.js | 54 ++++++++++++++++++++++++++++------- 2 files changed, 50 insertions(+), 14 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 488e97b5e6..bfc5be29e7 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2886,10 +2886,12 @@ void Application::keyPressEvent(QKeyEvent* event) { } break; case Qt::Key_P: { - bool isFirstPersonChecked = Menu::getInstance()->isOptionChecked(MenuOption::FirstPerson); - Menu::getInstance()->setIsOptionChecked(MenuOption::FirstPerson, !isFirstPersonChecked); - Menu::getInstance()->setIsOptionChecked(MenuOption::ThirdPerson, isFirstPersonChecked); - cameraMenuChanged(); + if (!(isShifted || isMeta || isOption)) { + bool isFirstPersonChecked = Menu::getInstance()->isOptionChecked(MenuOption::FirstPerson); + Menu::getInstance()->setIsOptionChecked(MenuOption::FirstPerson, !isFirstPersonChecked); + Menu::getInstance()->setIsOptionChecked(MenuOption::ThirdPerson, isFirstPersonChecked); + cameraMenuChanged(); + } break; } diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 61bbd23180..7731edd622 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -1116,30 +1116,64 @@ function recursiveDelete(entities, childrenList) { } function unparentSelectedEntities() { if (SelectionManager.hasSelection()) { - var selectedEntities = selectionManager.selections; - selectedEntities.forEach(function (id, index) { - Entities.editEntity(id, {parentID: null}) - return true; - }); - Window.notify("Entities Unparented"); + var selectedEntities = selectionManager.selections; + var parentCheck = false; + + if (selectedEntities.length < 1) { + Window.notifyEditError("You must have an entity selected inorder to unparent it."); + return; + } + selectedEntities.forEach(function (id, index) { + var parentId = Entities.getEntityProperties(id, ["parentID"]).parentID; + if (parentId !== null && parentId.length > 0 && parentId !== "{00000000-0000-0000-0000-000000000000}") { + parentCheck = true; + } + Entities.editEntity(id, {parentID: null}) + return true; + }); + if (parentCheck) { + if (selectedEntities.length > 1) { + Window.notify("Entities unparented"); + } else { + Window.notify("Entity unparented"); + } + } else { + if (selectedEntities.length > 1) { + Window.notify("Selected Entities have no parents"); + } else { + Window.notify("Selected Entity does not have a parent"); + } + } + } else { + Window.notifyEditError("You have nothing selected to unparent"); } } function parentSelectedEntities() { if (SelectionManager.hasSelection()) { var selectedEntities = selectionManager.selections; if (selectedEntities.length <= 1) { - Window.notifyEditError("You must have multiple objects selected in order to parent them"); - return; + Window.notifyEditError("You must have multiple entities selected in order to parent them"); + return; } + var parentCheck = false; var lastEntityId = selectedEntities[selectedEntities.length-1]; selectedEntities.forEach(function (id, index) { if (lastEntityId !== id) { + var parentId = Entities.getEntityProperties(id, ["parentID"]).parentID; + if (parentId !== lastEntityId) { + parentCheck = true; + } Entities.editEntity(id, {parentID: lastEntityId}) } }); - Window.notify("Entities Parented"); + + if(parentCheck) { + Window.notify("Entities parented"); + }else { + Window.notify("Entities are already parented to last"); + } } else { - Window.notifyEditError("You have nothing selected") + Window.notifyEditError("You have nothing selected to parent"); } } function deleteSelectedEntities() { From e95e50ad6ebade12a4c07e0bfcd524803fbc9d73 Mon Sep 17 00:00:00 2001 From: Menithal Date: Thu, 23 Feb 2017 10:34:04 +0200 Subject: [PATCH 18/64] Preliminary work on edit.js to allow for new param --- scripts/system/html/entityProperties.html | 6 ++-- scripts/system/html/js/entityProperties.js | 38 +++++++++++++++++----- 2 files changed, 32 insertions(+), 12 deletions(-) diff --git a/scripts/system/html/entityProperties.html b/scripts/system/html/entityProperties.html index 4c3723a2ef..25cd4417d5 100644 --- a/scripts/system/html/entityProperties.html +++ b/scripts/system/html/entityProperties.html @@ -305,13 +305,13 @@
-
+
Cloneable Settings
-
-
+
+
diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index 63cb037ada..7212fd9b9e 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -323,13 +323,9 @@ function setUserDataFromEditor(noUpdate) { }) ); } - } - - } - -function userDataChanger(groupName, keyName, checkBoxElement, userDataElement, defaultValue) { +function userDataChanger(groupName, keyName, values, userDataElement, defaultValue) { var properties = {}; var parsedData = {}; try { @@ -339,15 +335,19 @@ function userDataChanger(groupName, keyName, checkBoxElement, userDataElement, d } else { parsedData = JSON.parse(userDataElement.value); } - } catch (e) {} if (!(groupName in parsedData)) { parsedData[groupName] = {} } + delete parsedData[groupName][keyName]; - if (checkBoxElement.checked !== defaultValue) { - parsedData[groupName][keyName] = checkBoxElement.checked; + if (values instanceof Element) { + if (values.checked !== defaultValue) { + parsedData[groupName][keyName] = values.checked; + } + } else { + parsedData[groupName][keyName] = values; } if (Object.keys(parsedData[groupName]).length == 0) { @@ -584,7 +584,12 @@ function loaded() { var elCollisionSoundURL = document.getElementById("property-collision-sound-url"); var elGrabbable = document.getElementById("property-grabbable"); + var elCloneable = document.getElementById("property-cloneable"); + var elCloneableGroup = document.getElementById("group-cloneable-group"); + var elCloneableLifetime = document.getElementById("property-cloneable-lifetime"); + var elCloneableLimit = document.getElementById("property-cloneable-limit") + var elWantsTrigger = document.getElementById("property-wants-trigger"); var elIgnoreIK = document.getElementById("property-ignore-ik"); @@ -1160,9 +1165,24 @@ function loaded() { elCloneable.addEventListener('change', function () { userDataChanger("grabbableKey", "cloneable", elCloneable, elUserData, false); if (elCloneable.checked) { - + var cloneProperties = { + lifetime: 300, + limit: 10 + }; + userDataChanger("grabbableKey", "cloneable-properties", cloneProperties, elUserData, false); + elCloneableGroup.style.display = "block"; + } else { + userDataChanger("grabbableKey", "cloneable-properties", {}, elUserData, false); + elCloneableGroup.style.display = "none"; } }); + + var numberListener = function (event) { + userDataChanger("grabbableKey", event.target.getAttribute("data-user-data-type"), event.target.value, elUserData, false}); + }; + elCloneableLifetime.addEventListener('change', numberListener); + elCloneableLimit.addEventListener('change', numberListener); + elWantsTrigger.addEventListener('change', function() { userDataChanger("grabbableKey", "wantsTrigger", elWantsTrigger, elUserData, false); }); From 4d98cc436b63af354f05c0a84afa481ba4769e88 Mon Sep 17 00:00:00 2001 From: Menithal Date: Thu, 23 Feb 2017 23:20:21 +0200 Subject: [PATCH 19/64] Finished up Edit.js --- scripts/system/html/entityProperties.html | 6 +-- scripts/system/html/js/entityProperties.js | 43 +++++++++++++++------- 2 files changed, 33 insertions(+), 16 deletions(-) diff --git a/scripts/system/html/entityProperties.html b/scripts/system/html/entityProperties.html index 25cd4417d5..e775be9e3a 100644 --- a/scripts/system/html/entityProperties.html +++ b/scripts/system/html/entityProperties.html @@ -297,7 +297,7 @@
- +
@@ -310,8 +310,8 @@ Cloneable Settings
-
-
+
+
diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index 7212fd9b9e..629b9dc4e9 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -586,9 +586,10 @@ function loaded() { var elGrabbable = document.getElementById("property-grabbable"); var elCloneable = document.getElementById("property-cloneable"); + var elCloneableGroup = document.getElementById("group-cloneable-group"); var elCloneableLifetime = document.getElementById("property-cloneable-lifetime"); - var elCloneableLimit = document.getElementById("property-cloneable-limit") + var elCloneableLimit = document.getElementById("property-cloneable-limit"); var elWantsTrigger = document.getElementById("property-wants-trigger"); var elIgnoreIK = document.getElementById("property-ignore-ik"); @@ -853,6 +854,7 @@ function loaded() { elCollideOtherAvatar.checked = properties.collidesWith.indexOf("otherAvatar") > -1; elGrabbable.checked = properties.dynamic; + elWantsTrigger.checked = false; elIgnoreIK.checked = true; var parsedUserData = {} @@ -869,8 +871,27 @@ function loaded() { if ("ignoreIK" in parsedUserData["grabbableKey"]) { elIgnoreIK.checked = parsedUserData["grabbableKey"].ignoreIK; } + if ("cloneable" in parsedUserData["grabbableKey"]) { + elCloneable.checked = parsedUserData["grabbableKey"].cloneable; + + elCloneableGroup.style.display = elCloneable.checked ? "block": "none"; + elCloneableLimit.value = elCloneable.checked ? 10: 0; + elCloneableLifetime.value = elCloneable.checked ? 300: 0; + } else { + elCloneable.checked = false; + elCloneableGroup.style.display = elCloneable.checked ? "block": "none"; + elCloneableLimit.value = 0; + elCloneableLifetime.value = 0; + } + if ("cloneLifetime" in parsedUserData["grabbableKey"]) { + elCloneableLifetime.value = parsedUserData["grabbableKey"].cloneLifetime; + } + if ("cloneLimit" in parsedUserData["grabbableKey"]) { + elCloneableLimit.value = parsedUserData["grabbableKey"].cloneLimit; + } } - } catch (e) {} + } catch (e) { + } elCollisionSoundURL.value = properties.collisionSoundURL; elLifetime.value = properties.lifetime; @@ -1162,23 +1183,19 @@ function loaded() { elGrabbable.addEventListener('change', function() { userDataChanger("grabbableKey", "grabbable", elGrabbable, elUserData, properties.dynamic); }); - elCloneable.addEventListener('change', function () { - userDataChanger("grabbableKey", "cloneable", elCloneable, elUserData, false); - if (elCloneable.checked) { - var cloneProperties = { - lifetime: 300, - limit: 10 - }; - userDataChanger("grabbableKey", "cloneable-properties", cloneProperties, elUserData, false); + elCloneable.addEventListener('change', function (event) { + if (event.target.checked) { + userDataChanger("grabbableKey", "cloneLifetime", 300, elUserData, -1); + userDataChanger("grabbableKey", "cloneLimit", 10, elUserData, -1); elCloneableGroup.style.display = "block"; } else { - userDataChanger("grabbableKey", "cloneable-properties", {}, elUserData, false); elCloneableGroup.style.display = "none"; } + userDataChanger("grabbableKey", "cloneable", event.target, elUserData, null); }); - + var numberListener = function (event) { - userDataChanger("grabbableKey", event.target.getAttribute("data-user-data-type"), event.target.value, elUserData, false}); + userDataChanger("grabbableKey", event.target.getAttribute("data-user-data-type"), parseInt(event.target.value), elUserData, false); }; elCloneableLifetime.addEventListener('change', numberListener); elCloneableLimit.addEventListener('change', numberListener); From 4a4c914ca8df6b1e7d7d66885789deac94263d31 Mon Sep 17 00:00:00 2001 From: David Kelly Date: Thu, 23 Feb 2017 18:53:22 -0800 Subject: [PATCH 20/64] First cut at new styling --- .../images/Audio-Loudness-Icons/vol-0.svg | 12 ++++++ .../images/Audio-Loudness-Icons/vol-1.svg | 13 ++++++ .../images/Audio-Loudness-Icons/vol-2.svg | 16 ++++++++ .../images/Audio-Loudness-Icons/vol-3.svg | 19 +++++++++ .../images/Audio-Loudness-Icons/vol-4.svg | 21 ++++++++++ interface/resources/qml/hifi/Pal.qml | 40 +++++++++++-------- scripts/system/pal.js | 19 ++++++++- 7 files changed, 122 insertions(+), 18 deletions(-) create mode 100755 interface/resources/images/Audio-Loudness-Icons/vol-0.svg create mode 100755 interface/resources/images/Audio-Loudness-Icons/vol-1.svg create mode 100755 interface/resources/images/Audio-Loudness-Icons/vol-2.svg create mode 100755 interface/resources/images/Audio-Loudness-Icons/vol-3.svg create mode 100755 interface/resources/images/Audio-Loudness-Icons/vol-4.svg diff --git a/interface/resources/images/Audio-Loudness-Icons/vol-0.svg b/interface/resources/images/Audio-Loudness-Icons/vol-0.svg new file mode 100755 index 0000000000..72f247c238 --- /dev/null +++ b/interface/resources/images/Audio-Loudness-Icons/vol-0.svg @@ -0,0 +1,12 @@ + + + + + diff --git a/interface/resources/images/Audio-Loudness-Icons/vol-1.svg b/interface/resources/images/Audio-Loudness-Icons/vol-1.svg new file mode 100755 index 0000000000..9570b9ae6c --- /dev/null +++ b/interface/resources/images/Audio-Loudness-Icons/vol-1.svg @@ -0,0 +1,13 @@ + + + + + + diff --git a/interface/resources/images/Audio-Loudness-Icons/vol-2.svg b/interface/resources/images/Audio-Loudness-Icons/vol-2.svg new file mode 100755 index 0000000000..a2175be39e --- /dev/null +++ b/interface/resources/images/Audio-Loudness-Icons/vol-2.svg @@ -0,0 +1,16 @@ + + + + + + + diff --git a/interface/resources/images/Audio-Loudness-Icons/vol-3.svg b/interface/resources/images/Audio-Loudness-Icons/vol-3.svg new file mode 100755 index 0000000000..21b1095941 --- /dev/null +++ b/interface/resources/images/Audio-Loudness-Icons/vol-3.svg @@ -0,0 +1,19 @@ + + + + + + + + diff --git a/interface/resources/images/Audio-Loudness-Icons/vol-4.svg b/interface/resources/images/Audio-Loudness-Icons/vol-4.svg new file mode 100755 index 0000000000..de0b4027eb --- /dev/null +++ b/interface/resources/images/Audio-Loudness-Icons/vol-4.svg @@ -0,0 +1,21 @@ + + + + + + + + + diff --git a/interface/resources/qml/hifi/Pal.qml b/interface/resources/qml/hifi/Pal.qml index e0f2df76ce..4735ae9312 100644 --- a/interface/resources/qml/hifi/Pal.qml +++ b/interface/resources/qml/hifi/Pal.qml @@ -13,6 +13,7 @@ import QtQuick 2.5 import QtQuick.Controls 1.4 +import QtGraphicalEffects 1.0 import "../styles-uit" import "../controls-uit" as HifiControls @@ -163,6 +164,14 @@ Rectangle { onSortIndicatorColumnChanged: sortModel() onSortIndicatorOrderChanged: sortModel() + TableViewColumn { + role: "avgAudioLevel" + title: "VOL" + width: actionButtonWidth + movable: false + resizable: false + } + TableViewColumn { id: displayNameHeader role: "displayName" @@ -185,13 +194,6 @@ Rectangle { movable: false resizable: false } - TableViewColumn { - role: "avgAudioLevel" - title: "VOL" - width: actionButtonWidth - movable: false - resizable: false - } TableViewColumn { visible: iAmAdmin role: "mute" @@ -226,7 +228,8 @@ Rectangle { id: itemCell property bool isCheckBox: styleData.role === "personalMute" || styleData.role === "ignore" property bool isButton: styleData.role === "mute" || styleData.role === "kick" - property bool isText: styleData.role == "avgAudioLevel" + property bool isAvgAudio: styleData.role === "avgAudioLevel" + // This NameCard refers to the cell that contains an avatar's // DisplayName and UserName NameCard { @@ -236,7 +239,7 @@ Rectangle { userName: model ? model.userName : "" audioLevel: model ? model.audioLevel : 0.0 avgAudioLevel: model ? model.avgAudioLevel : 0.0 - visible: !isCheckBox && !isButton && !isText + visible: !isCheckBox && !isButton && !isAvgAudio uuid: model ? model.sessionId : "" selected: styleData.selected isAdmin: model && model.admin @@ -246,14 +249,19 @@ Rectangle { // Anchors anchors.left: parent.left } - Text { + Image { + visible: isAvgAudio + function getImage() { + var fileName = "../../../images/Audio-Loudness-Icons/vol-"; + fileName += (4.0*(model ? model.avgAudioLevel : 0.0)).toFixed(0); + fileName += ".svg"; + return fileName; + } id: avgAudioVolume - text: model ? (10 * model.avgAudioLevel).toFixed(0) : 0.0 - visible: isText - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - width: parent.width - height: parent.height + width: hifi.dimensions.tableHeaderHeight + fillMode: Image.PreserveAspectFit + source: getImage() + anchors.verticalCenter: parent.verticalCenter } // This CheckBox belongs in the columns that contain the stateful action buttons ("Mute" & "Ignore" for now) diff --git a/scripts/system/pal.js b/scripts/system/pal.js index e9f5ba0a67..fe4eea48a4 100644 --- a/scripts/system/pal.js +++ b/scripts/system/pal.js @@ -197,6 +197,17 @@ HighlightedEntity.updateOverlays = function updateHighlightedEntities() { }); }; +/* this contains current gain for a given node (by session id). More efficient than + * querying it, plus there isn't a getGain function so why write one */ +var sessionGains = {}; +function convertDbToLinear(decibels) { + // +20db = 10x, 0dB = 1x, -10dB = 0.1x, etc... + // but, your perception is that something 2x as loud is +10db + // so we go from -60 to +20 or 1/64x to 4x. For now, we can + // maybe scale the signal this way?? + return Math.pow(2, decibels/10.0); +} + function fromQml(message) { // messages are {method, params}, like json-rpc. See also sendToQml. var data; switch (message.method) { @@ -242,6 +253,8 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See UserActivityLogger.palAction("avatar_gain_changed", data['sessionId']); } else { Users.setAvatarGain(data['sessionId'], data['gain']); + sessionGains[data['sessionId']] = convertDbToLinear(data['gain']); + print("set " + data['sessionId'] + " to " + sessionGains[data['sessionId']]); } break; case 'displayNameUpdate': @@ -576,6 +589,7 @@ function scaleAudio(val) { } return audioLevel; } + function getAudioLevel(id) { // the VU meter should work similarly to the one in AvatarInputs: log scale, exponentially averaged // But of course it gets the data at a different rate, so we tweak the averaging ratio and frequency @@ -594,9 +608,10 @@ function getAudioLevel(id) { // natural log, so to get log base 2, just divide by ln(2). audioLevel = scaleAudio(Math.log(data.accumulatedLevel + 1) / LOG2); avgAudioLevel = scaleAudio(Math.log(data.longAccumulatedLevel + 1) / LOG2); - + // scale avgAudioLevel given that there can be a gain (4x to 1/64x) + avgAudioLevel = Math.min(1.0, avgAudioLevel *(sessionGains[id] || 0.75)); + data.avgAudioLevel = avgAudioLevel; data.audioLevel = audioLevel; - data.averageAudioLevel = avgAudioLevel; } return [audioLevel, avgAudioLevel]; } From 10151c3f5b23f5a9302f9aadd2db13cfdc23f1a7 Mon Sep 17 00:00:00 2001 From: Menithal Date: Fri, 24 Feb 2017 08:04:03 +0200 Subject: [PATCH 21/64] Added Multi-userdata field updater --- scripts/system/html/js/entityProperties.js | 112 +++++++++++++-------- 1 file changed, 71 insertions(+), 41 deletions(-) diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index 629b9dc4e9..abffdaee96 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -27,6 +27,7 @@ var EDITOR_TIMEOUT_DURATION = 1500; var colorPickers = []; var lastEntityID = null; + debugPrint = function(message) { EventBridge.emitWebEvent( JSON.stringify({ @@ -325,49 +326,65 @@ function setUserDataFromEditor(noUpdate) { } } } -function userDataChanger(groupName, keyName, values, userDataElement, defaultValue) { - var properties = {}; - var parsedData = {}; - try { - if ($('#userdata-editor').css('height') !== "0px") { - //if there is an expanded, we want to use its json. - parsedData = getEditorJSON(); - } else { - parsedData = JSON.parse(userDataElement.value); +function multiUserDataChanger(groupName, keyPair, userDataElement, defaults) { + var properties = {}; + var parsedData = {}; + try { + if ($('#userdata-editor').css('height') !== "0px") { + //if there is an expanded, we want to use its json. + parsedData = getEditorJSON(); + } else { + parsedData = JSON.parse(userDataElement.value); + } + } catch (e) {} + + if (!(groupName in parsedData)) { + parsedData[groupName] = {} + } + var keys = Object.keys(keyPair); + keys.forEach(function (key) { + delete parsedData[groupName][key]; + if (keyPair[key] instanceof Element) { + if(keyPair[key].type === "checkbox") { + if (keyPair[key].checked !== defaults[key]) { + parsedData[groupName][key] = keyPair[key].checked; + } + } else { + var val = isNaN(keyPair[key].value) ? keyPair[key].value : parseInt(keyPair[key].value); + if (val !== defaults[key]) { + parsedData[groupName][key] = val; } - } catch (e) {} - - if (!(groupName in parsedData)) { - parsedData[groupName] = {} - } - - delete parsedData[groupName][keyName]; - if (values instanceof Element) { - if (values.checked !== defaultValue) { - parsedData[groupName][keyName] = values.checked; } } else { - parsedData[groupName][keyName] = values; + parsedData[groupName][key] = keyPair[key]; } + }); - if (Object.keys(parsedData[groupName]).length == 0) { - delete parsedData[groupName]; - } - if (Object.keys(parsedData).length > 0) { - properties['userData'] = JSON.stringify(parsedData); - } else { - properties['userData'] = ''; - } + if (Object.keys(parsedData[groupName]).length == 0) { + delete parsedData[groupName]; + } + if (Object.keys(parsedData).length > 0) { + properties['userData'] = JSON.stringify(parsedData); + } else { + properties['userData'] = ''; + } - userDataElement.value = properties['userData']; + userDataElement.value = properties['userData']; - EventBridge.emitWebEvent( - JSON.stringify({ - id: lastEntityID, - type: "update", - properties: properties, - }) - ); + EventBridge.emitWebEvent( + JSON.stringify({ + id: lastEntityID, + type: "update", + properties: properties, + }) + ); + +} +function userDataChanger(groupName, keyName, values, userDataElement, defaultValue) { + var val = {}, def = {}; + val[keyName] = values; + def[keyName] = defaultValue; + multiUserDataChanger(groupName, val, userDataElement, def); }; function setTextareaScrolling(element) { @@ -873,15 +890,18 @@ function loaded() { } if ("cloneable" in parsedUserData["grabbableKey"]) { elCloneable.checked = parsedUserData["grabbableKey"].cloneable; - elCloneableGroup.style.display = elCloneable.checked ? "block": "none"; elCloneableLimit.value = elCloneable.checked ? 10: 0; elCloneableLifetime.value = elCloneable.checked ? 300: 0; + elDynamic.checked = elCloneable.checked ? false: properties.dynamic; + } else { elCloneable.checked = false; + elCloneableGroup.style.display = elCloneable.checked ? "block": "none"; elCloneableLimit.value = 0; elCloneableLifetime.value = 0; + } if ("cloneLifetime" in parsedUserData["grabbableKey"]) { elCloneableLifetime.value = parsedUserData["grabbableKey"].cloneLifetime; @@ -1184,14 +1204,24 @@ function loaded() { userDataChanger("grabbableKey", "grabbable", elGrabbable, elUserData, properties.dynamic); }); elCloneable.addEventListener('change', function (event) { - if (event.target.checked) { - userDataChanger("grabbableKey", "cloneLifetime", 300, elUserData, -1); - userDataChanger("grabbableKey", "cloneLimit", 10, elUserData, -1); + var checked = event.target.checked; + if (checked) { + multiUserDataChanger("grabbableKey", + {cloneLifetime: elCloneableLifetime, cloneLimit: elCloneableLimit, cloneable: event.target}, + elUserData, + {cloneLifetime: 300, cloneLimit: 10, cloneable: false}); elCloneableGroup.style.display = "block"; + EventBridge.emitWebEvent( + '{"id":' + lastEntityID + ', "type":"update", "properties":{"dynamic":false}}' + ); + EventBridge.emitWebEvent( + '{"id":' + lastEntityID + ', "type":"update", "properties":{"grabbable":true}}' + ); } else { elCloneableGroup.style.display = "none"; } - userDataChanger("grabbableKey", "cloneable", event.target, elUserData, null); + + userDataChanger("grabbableKey", "cloneable", checked, elUserData, null); }); var numberListener = function (event) { From 2458f95c42398d904fa7d6e7e0829d864169bca0 Mon Sep 17 00:00:00 2001 From: David Kelly Date: Fri, 24 Feb 2017 10:31:55 -0800 Subject: [PATCH 22/64] more of a peak loudness with decay, now --- scripts/system/pal.js | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/scripts/system/pal.js b/scripts/system/pal.js index fe4eea48a4..bd8a2f5b53 100644 --- a/scripts/system/pal.js +++ b/scripts/system/pal.js @@ -197,7 +197,7 @@ HighlightedEntity.updateOverlays = function updateHighlightedEntities() { }); }; -/* this contains current gain for a given node (by session id). More efficient than +/* this contains current gain for a given node (by session id). More efficient than * querying it, plus there isn't a getGain function so why write one */ var sessionGains = {}; function convertDbToLinear(decibels) { @@ -205,7 +205,7 @@ function convertDbToLinear(decibels) { // but, your perception is that something 2x as loud is +10db // so we go from -60 to +20 or 1/64x to 4x. For now, we can // maybe scale the signal this way?? - return Math.pow(2, decibels/10.0); + return Math.pow(2, decibels/10.0); } function fromQml(message) { // messages are {method, params}, like json-rpc. See also sendToQml. @@ -571,10 +571,10 @@ function receiveMessage(channel, messageString, senderID) { } var AVERAGING_RATIO = 0.05; -var LONG_AVERAGING_RATIO = 0.75; var LOUDNESS_FLOOR = 11.0; var LOUDNESS_SCALE = 2.8 / 5.0; var LOG2 = Math.log(2.0); +var AUDIO_PEAK_DECAY = 0.03; var myData = {}; // we're not includied in ExtendedOverlay.get. function scaleAudio(val) { @@ -602,16 +602,19 @@ function getAudioLevel(id) { // we will do exponential moving average by taking some the last loudness and averaging data.accumulatedLevel = AVERAGING_RATIO * (data.accumulatedLevel || 0) + (1 - AVERAGING_RATIO) * (avatar.audioLoudness); - data.longAccumulatedLevel = LONG_AVERAGING_RATIO * (data.longAccumulatedLevel || 0) + (1 - LONG_AVERAGING_RATIO) * (avatar.audioLoudness); // add 1 to insure we don't go log() and hit -infinity. Math.log is // natural log, so to get log base 2, just divide by ln(2). audioLevel = scaleAudio(Math.log(data.accumulatedLevel + 1) / LOG2); - avgAudioLevel = scaleAudio(Math.log(data.longAccumulatedLevel + 1) / LOG2); - // scale avgAudioLevel given that there can be a gain (4x to 1/64x) - avgAudioLevel = Math.min(1.0, avgAudioLevel *(sessionGains[id] || 0.75)); + + // decay avgAudioLevel + avgAudioLevel = Math.max((1-AUDIO_PEAK_DECAY) * (data.avgAudioLevel || 0), audioLevel); + data.avgAudioLevel = avgAudioLevel; data.audioLevel = audioLevel; + + // now scale for the gain + avgAudioLevel = Math.min(1.0, avgAudioLevel *(sessionGains[id] || 0.75)); } return [audioLevel, avgAudioLevel]; } From d6f0d8c96051179741f61b8f5bfae65cce4122be Mon Sep 17 00:00:00 2001 From: David Kelly Date: Fri, 24 Feb 2017 11:45:03 -0800 Subject: [PATCH 23/64] styling changes, first pass --- interface/resources/qml/hifi/Pal.qml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/interface/resources/qml/hifi/Pal.qml b/interface/resources/qml/hifi/Pal.qml index 4735ae9312..91889fcf91 100644 --- a/interface/resources/qml/hifi/Pal.qml +++ b/interface/resources/qml/hifi/Pal.qml @@ -30,7 +30,7 @@ Rectangle { property int myCardHeight: 90 property int rowHeight: 70 property int actionButtonWidth: 55 - property int nameCardWidth: palContainer.width - actionButtonWidth*(iAmAdmin ? 5 : 3) - 4 - hifi.dimensions.scrollbarBackgroundWidth + property int nameCardWidth: palContainer.width - actionButtonWidth*(iAmAdmin ? 4.5 : 2.5) - 4 - hifi.dimensions.scrollbarBackgroundWidth property var myData: ({displayName: "", userName: "", audioLevel: 0.0, avgAudioLevel: 0.0, admin: true}) // valid dummy until set property var ignored: ({}); // Keep a local list of ignored avatars & their data. Necessary because HashMap is slow to respond after ignoring. property var userModelData: [] // This simple list is essentially a mirror of the userModel listModel without all the extra complexities. @@ -167,7 +167,7 @@ Rectangle { TableViewColumn { role: "avgAudioLevel" title: "VOL" - width: actionButtonWidth + width: actionButtonWidth/2 movable: false resizable: false } @@ -397,7 +397,7 @@ Rectangle { anchors.left: table.left anchors.top: table.top anchors.topMargin: 1 - anchors.leftMargin: nameCardWidth/2 + displayNameHeaderMetrics.width/2 + 6 + anchors.leftMargin: actionButtonWidth/2 + nameCardWidth/2 + displayNameHeaderMetrics.width/2 + 6 RalewayRegular { id: helpText text: "[?]" From 69949cd6b56c18bd173b9a59a209038c8e41d24f Mon Sep 17 00:00:00 2001 From: Menithal Date: Sun, 26 Feb 2017 18:49:13 +0200 Subject: [PATCH 24/64] Finalized Grab-Clone action --- .../system/controllers/handControllerGrab.js | 83 ++++++++++++++++++- 1 file changed, 82 insertions(+), 1 deletion(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index ea76490b7b..7f67c11a7c 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -266,6 +266,27 @@ CONTROLLER_STATE_MACHINE[STATE_OVERLAY_STYLUS_TOUCHING] = { }; CONTROLLER_STATE_MACHINE[STATE_OVERLAY_LASER_TOUCHING] = CONTROLLER_STATE_MACHINE[STATE_OVERLAY_STYLUS_TOUCHING]; +// Object assign polyfill +if (typeof Object.assign != 'function') { + Object.assign = function(target, varArgs) { + 'use strict'; + if (target == null) { + throw new TypeError('Cannot convert undefined or null to object'); + } + var to = Object(target); + for (var index = 1; index < arguments.length; index++) { + var nextSource = arguments[index]; + if (nextSource != null) { + for (var nextKey in nextSource) { + if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) { + to[nextKey] = nextSource[nextKey]; + } + } + } + } + return to; + }; +} function distanceBetweenPointAndEntityBoundingBox(point, entityProps) { var entityXform = new Xform(entityProps.rotation, entityProps.position); @@ -1437,7 +1458,18 @@ function MyController(hand) { return true; }; + this.entityIsCloneable = function(entityID) { + var entityProps = entityPropertiesCache.getGrabbableProps(entityID); + var props = entityPropertiesCache.getProps(entityID); + if (!props) { + return false; + } + if (entityProps.hasOwnProperty("cloneable")/*&& props.locked*/) { + return entityProps.cloneable; + } + return false; + } this.entityIsGrabbable = function(entityID) { var grabbableProps = entityPropertiesCache.getGrabbableProps(entityID); var props = entityPropertiesCache.getProps(entityID); @@ -1517,7 +1549,7 @@ function MyController(hand) { this.entityIsNearGrabbable = function(entityID, handPosition, maxDistance) { - if (!this.entityIsGrabbable(entityID)) { + if (!this.entityIsCloneable(entityID) && !this.entityIsGrabbable(entityID)) { return false; } @@ -2359,6 +2391,55 @@ function MyController(hand) { reparentProps.localPosition = this.offsetPosition; reparentProps.localRotation = this.offsetRotation; } + + if (grabbedProperties.userData.length > 0) { + try{ + var userData = JSON.parse(grabbedProperties.userData); + var grabInfo = userData.grabbableKey; + if (grabInfo && grabInfo.cloneable) { + + var worldEntities = Entities.findEntitiesInBox(Vec3.subtract(MyAvatar.position, {x:25,y:25, z:25}), {x:50, y: 50, z: 50}) + var count = 0; + worldEntities.forEach(function(item) { + var item = Entities.getEntityProperties(item, ["name"]); + if (item.name === grabbedProperties.name) { + count++; + } + }) + var cloneableProps = Entities.getEntityProperties(grabbedProperties.id); + var lifetime = grabInfo.cloneLifetime ? grabInfo.cloneLifetime : 300; + var limit = grabInfo.cloneLimit ? grabInfo.cloneLimit : 10; + var cUserData = Object.assign({}, userData); + var cProperties = Object.assign({}, cloneableProps); + + if (count > limit) { + delete cloneableProps; + delete lifetime; + delete cUserData; + delete cProperties; + return; + } + + delete cUserData.grabbableKey.cloneLifetime; + delete cUserData.grabbableKey.cloneable; + delete cUserData.grabbableKey.cloneLimit; + delete cProperties.id + cUserData.grabbableKey.triggerable = true; + cUserData.grabbableKey.grabbable = true; + cProperties.lifetime = lifetime; + cProperties.userData = JSON.stringify(cUserData); + this.grabbedEntity = Entities.addEntity(cProperties); + grabbedProperties = Entities.getEntityProperties(this.grabbedEntity); + var _this = this; + Script.setTimeout(function () { + // This is needed to wait for the grabbed entity to have been instanciated. + _this.callEntityMethodOnGrabbed("startEquip"); + },400); + } + }catch(e) { + print("ERROR: " + e); + } + } Entities.editEntity(this.grabbedEntity, reparentProps); if (this.thisHandIsParent(grabbedProperties)) { From 1d8be2aeaa682c3fa33b65077f6eb3543c09a995 Mon Sep 17 00:00:00 2001 From: Menithal Date: Sun, 26 Feb 2017 19:17:46 +0200 Subject: [PATCH 25/64] Added missing dynamic --- scripts/system/controllers/handControllerGrab.js | 5 +++++ scripts/system/html/entityProperties.html | 4 ++++ scripts/system/html/js/entityProperties.js | 12 +++++++----- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 7f67c11a7c..4b469798ab 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -2409,6 +2409,7 @@ function MyController(hand) { var cloneableProps = Entities.getEntityProperties(grabbedProperties.id); var lifetime = grabInfo.cloneLifetime ? grabInfo.cloneLifetime : 300; var limit = grabInfo.cloneLimit ? grabInfo.cloneLimit : 10; + var dynamic = grabInfo.cloneDynamic ? grabInfo.cloneDynamic : false; var cUserData = Object.assign({}, userData); var cProperties = Object.assign({}, cloneableProps); @@ -2422,8 +2423,12 @@ function MyController(hand) { delete cUserData.grabbableKey.cloneLifetime; delete cUserData.grabbableKey.cloneable; + delete cUserData.grabbableKey.cloneDynamic; delete cUserData.grabbableKey.cloneLimit; delete cProperties.id + + cProperties.dynamic = dynamic; + cProperties.locked = false; cUserData.grabbableKey.triggerable = true; cUserData.grabbableKey.grabbable = true; cProperties.lifetime = lifetime; diff --git a/scripts/system/html/entityProperties.html b/scripts/system/html/entityProperties.html index e775be9e3a..a0497d6a92 100644 --- a/scripts/system/html/entityProperties.html +++ b/scripts/system/html/entityProperties.html @@ -312,6 +312,10 @@
+
+ + +
diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index abffdaee96..2c77d54e85 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -603,7 +603,7 @@ function loaded() { var elGrabbable = document.getElementById("property-grabbable"); var elCloneable = document.getElementById("property-cloneable"); - + var elCloneableDynamic = document.getElementById("property-cloneable-dynamic"); var elCloneableGroup = document.getElementById("group-cloneable-group"); var elCloneableLifetime = document.getElementById("property-cloneable-lifetime"); var elCloneableLimit = document.getElementById("property-cloneable-limit"); @@ -893,15 +893,14 @@ function loaded() { elCloneableGroup.style.display = elCloneable.checked ? "block": "none"; elCloneableLimit.value = elCloneable.checked ? 10: 0; elCloneableLifetime.value = elCloneable.checked ? 300: 0; + elCloneableDynamic.checked = parsedUserData["grabbableKey"].cloneDynamic ? parsedUserData["grabbableKey"].cloneDynamic : properties.dynamic; elDynamic.checked = elCloneable.checked ? false: properties.dynamic; - } else { elCloneable.checked = false; - + elCloneableDynamic.checked = false; elCloneableGroup.style.display = elCloneable.checked ? "block": "none"; elCloneableLimit.value = 0; elCloneableLifetime.value = 0; - } if ("cloneLifetime" in parsedUserData["grabbableKey"]) { elCloneableLifetime.value = parsedUserData["grabbableKey"].cloneLifetime; @@ -1203,11 +1202,14 @@ function loaded() { elGrabbable.addEventListener('change', function() { userDataChanger("grabbableKey", "grabbable", elGrabbable, elUserData, properties.dynamic); }); + elCloneableDynamic.addEventListener('change', function (event){ + userDataChanger("grabbableKey", "cloneDynamic", event.target, elUserData, -1); + }); elCloneable.addEventListener('change', function (event) { var checked = event.target.checked; if (checked) { multiUserDataChanger("grabbableKey", - {cloneLifetime: elCloneableLifetime, cloneLimit: elCloneableLimit, cloneable: event.target}, + {cloneLifetime: elCloneableLifetime, cloneLimit: elCloneableLimit, cloneDynamic: elCloneableDynamic, cloneable: event.target}, elUserData, {cloneLifetime: 300, cloneLimit: 10, cloneable: false}); elCloneableGroup.style.display = "block"; From 7472efc6376f0abf87aa8536ff79b9eff4b2b75d Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Thu, 23 Feb 2017 18:52:10 -0800 Subject: [PATCH 26/64] don't count NoData avatars as having been broadcast, fix avatars slightly out of view from freezing --- .../src/avatars/AvatarMixerSlave.cpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index 8d9a5e6951..dd25aa4c4b 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -384,18 +384,20 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) { if (includeThisAvatar) { numAvatarDataBytes += avatarPacketList->write(otherNode->getUUID().toRfc4122()); numAvatarDataBytes += avatarPacketList->write(bytes); - _stats.numOthersIncluded++; - // increment the number of avatars sent to this reciever - nodeData->incrementNumAvatarsSentLastFrame(); + if (detail != AvatarData::NoData) { + _stats.numOthersIncluded++; - // set the last sent sequence number for this sender on the receiver - nodeData->setLastBroadcastSequenceNumber(otherNode->getUUID(), - otherNodeData->getLastReceivedSequenceNumber()); + // increment the number of avatars sent to this reciever + nodeData->incrementNumAvatarsSentLastFrame(); - // remember the last time we sent details about this other node to the receiver - nodeData->setLastBroadcastTime(otherNode->getUUID(), start); + // set the last sent sequence number for this sender on the receiver + nodeData->setLastBroadcastSequenceNumber(otherNode->getUUID(), + otherNodeData->getLastReceivedSequenceNumber()); + // remember the last time we sent details about this other node to the receiver + nodeData->setLastBroadcastTime(otherNode->getUUID(), start); + } } avatarPacketList->endSegment(); From 2ceb3d85bd6481179f3c14270169f6013fdf13c4 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 24 Feb 2017 16:03:22 -0800 Subject: [PATCH 27/64] increase max renderable web entity FPS to 30 --- libraries/entities-renderer/src/RenderableWebEntityItem.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index 972c23d534..8c30c01e50 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -113,7 +113,7 @@ bool RenderableWebEntityItem::buildWebSurface(QSharedPointer // FIXME, the max FPS could be better managed by being dynamic (based on the number of current surfaces // and the current rendering load) - _webSurface->setMaxFps(10); + _webSurface->setMaxFps(30); // The lifetime of the QML surface MUST be managed by the main thread // Additionally, we MUST use local variables copied by value, rather than From d18936ea1864063ede40cb8cae4c9a66caa8b038 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Mon, 27 Feb 2017 09:43:02 -0800 Subject: [PATCH 28/64] only force youtube web entities to 30 FPS --- .../entities-renderer/src/RenderableWebEntityItem.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index 8c30c01e50..7cd1b62e3b 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -113,7 +113,14 @@ bool RenderableWebEntityItem::buildWebSurface(QSharedPointer // FIXME, the max FPS could be better managed by being dynamic (based on the number of current surfaces // and the current rendering load) - _webSurface->setMaxFps(30); + + // We special case YouTube URLs since we know they are videos that we should play with at least 30 FPS. + if (QUrl(_sourceUrl).host().endsWith("youtube.com", Qt::CaseInsensitive)) { + _webSurface->setMaxFps(30); + } else { + _webSurface->setMaxFps(10); + } + // The lifetime of the QML surface MUST be managed by the main thread // Additionally, we MUST use local variables copied by value, rather than From 80af749b6949cbd66ba9309cce51019d611457d7 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Mon, 27 Feb 2017 11:09:17 -0800 Subject: [PATCH 29/64] change max FPS for youtube on URL change --- .../src/RenderableWebEntityItem.cpp | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index 7cd1b62e3b..6c44794c3e 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -37,6 +37,8 @@ static uint64_t MAX_NO_RENDER_INTERVAL = 30 * USECS_PER_SECOND; static int MAX_WINDOW_SIZE = 4096; static float OPAQUE_ALPHA_THRESHOLD = 0.99f; +static int DEFAULT_MAX_FPS = 10; +static int YOUTUBE_MAX_FPS = 30; EntityItemPointer RenderableWebEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { EntityItemPointer entity{ new RenderableWebEntityItem(entityID) }; @@ -113,14 +115,7 @@ bool RenderableWebEntityItem::buildWebSurface(QSharedPointer // FIXME, the max FPS could be better managed by being dynamic (based on the number of current surfaces // and the current rendering load) - - // We special case YouTube URLs since we know they are videos that we should play with at least 30 FPS. - if (QUrl(_sourceUrl).host().endsWith("youtube.com", Qt::CaseInsensitive)) { - _webSurface->setMaxFps(30); - } else { - _webSurface->setMaxFps(10); - } - + _webSurface->setMaxFps(DEFAULT_MAX_FPS); // The lifetime of the QML surface MUST be managed by the main thread // Additionally, we MUST use local variables copied by value, rather than @@ -263,12 +258,22 @@ void RenderableWebEntityItem::loadSourceURL() { _sourceUrl.toLower().endsWith(".htm") || _sourceUrl.toLower().endsWith(".html")) { _contentType = htmlContent; _webSurface->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath() + "qml/controls/")); + + // We special case YouTube URLs since we know they are videos that we should play with at least 30 FPS. + if (sourceUrl.host().endsWith("youtube.com", Qt::CaseInsensitive)) { + _webSurface->setMaxFps(YOUTUBE_MAX_FPS); + } else { + _webSurface->setMaxFps(DEFAULT_MAX_FPS); + } + _webSurface->load("WebView.qml", [&](QQmlContext* context, QObject* obj) { context->setContextProperty("eventBridgeJavaScriptToInject", QVariant(_javaScriptToInject)); }); _webSurface->getRootItem()->setProperty("url", _sourceUrl); _webSurface->getRootContext()->setContextProperty("desktop", QVariant()); + + } else { _contentType = qmlContent; _webSurface->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath())); From e7c8085bc8ff5c796a73502289a8e4d7473a4817 Mon Sep 17 00:00:00 2001 From: David Kelly Date: Mon, 27 Feb 2017 12:44:23 -0700 Subject: [PATCH 30/64] switch to using glyph button --- .../images/Audio-Loudness-Icons/vol-0.svg | 12 ------- .../images/Audio-Loudness-Icons/vol-1.svg | 13 ------- .../images/Audio-Loudness-Icons/vol-2.svg | 16 --------- .../images/Audio-Loudness-Icons/vol-3.svg | 19 ---------- .../images/Audio-Loudness-Icons/vol-4.svg | 21 ----------- interface/resources/qml/hifi/Pal.qml | 36 ++++++------------- .../qml/styles-uit/HifiConstants.qml | 10 ++++++ 7 files changed, 20 insertions(+), 107 deletions(-) delete mode 100755 interface/resources/images/Audio-Loudness-Icons/vol-0.svg delete mode 100755 interface/resources/images/Audio-Loudness-Icons/vol-1.svg delete mode 100755 interface/resources/images/Audio-Loudness-Icons/vol-2.svg delete mode 100755 interface/resources/images/Audio-Loudness-Icons/vol-3.svg delete mode 100755 interface/resources/images/Audio-Loudness-Icons/vol-4.svg diff --git a/interface/resources/images/Audio-Loudness-Icons/vol-0.svg b/interface/resources/images/Audio-Loudness-Icons/vol-0.svg deleted file mode 100755 index 72f247c238..0000000000 --- a/interface/resources/images/Audio-Loudness-Icons/vol-0.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - diff --git a/interface/resources/images/Audio-Loudness-Icons/vol-1.svg b/interface/resources/images/Audio-Loudness-Icons/vol-1.svg deleted file mode 100755 index 9570b9ae6c..0000000000 --- a/interface/resources/images/Audio-Loudness-Icons/vol-1.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - diff --git a/interface/resources/images/Audio-Loudness-Icons/vol-2.svg b/interface/resources/images/Audio-Loudness-Icons/vol-2.svg deleted file mode 100755 index a2175be39e..0000000000 --- a/interface/resources/images/Audio-Loudness-Icons/vol-2.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - diff --git a/interface/resources/images/Audio-Loudness-Icons/vol-3.svg b/interface/resources/images/Audio-Loudness-Icons/vol-3.svg deleted file mode 100755 index 21b1095941..0000000000 --- a/interface/resources/images/Audio-Loudness-Icons/vol-3.svg +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - diff --git a/interface/resources/images/Audio-Loudness-Icons/vol-4.svg b/interface/resources/images/Audio-Loudness-Icons/vol-4.svg deleted file mode 100755 index de0b4027eb..0000000000 --- a/interface/resources/images/Audio-Loudness-Icons/vol-4.svg +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - diff --git a/interface/resources/qml/hifi/Pal.qml b/interface/resources/qml/hifi/Pal.qml index a7651e24d9..1db83a0223 100644 --- a/interface/resources/qml/hifi/Pal.qml +++ b/interface/resources/qml/hifi/Pal.qml @@ -214,13 +214,6 @@ Rectangle { movable: false resizable: false } - //TableViewColumn { - // role: "personalMute" - // title: "MUTE" - // width: actionButtonWidth - // movable: false - // resizable: false - //} TableViewColumn { role: "ignore" title: "IGNORE" @@ -283,12 +276,19 @@ Rectangle { // Anchors anchors.left: parent.left } - HifiControls.Button { + HifiControls.GlyphButton { + function getGlyph() { + var fileName = "vol_"; + if (model["personalMute"]) { + fileName += "x_"; + } + fileName += (4.0*(model ? model.avgAudioLevel : 0.0)).toFixed(0); + return hifi.glyphs[fileName]; + } id: avgAudioVolume visible: isAvgAudio + glyph: getGlyph() width: 32 - height: 32 - iconSource: getImage() anchors.verticalCenter: parent.verticalCenter anchors.horizontalCenter: parent.horizontalCenter onClicked: { @@ -298,22 +298,6 @@ Rectangle { Users["personalMute"](model.sessionId, newValue) UserActivityLogger["palAction"](newValue ? "personalMute" : "un-personalMute", model.sessionId) } - HiFiGlyphs { - function getGlyph() { - var fileName = "vol-"; - if (model["personalMute"]) { - fileName += "x-"; - } - fileName += (4.0*(model ? model.avgAudioLevel : 0.0)).toFixed(0); - return hifi.glyphs[fileName]; - } - text: getGlyph() - size: parent.height*1.3 - anchors.fill: parent - horizontalAlignment: Text.AlignHCenter - color: enabled ? hifi.buttons.textColor[actionButton.color] - : hifi.buttons.disabledTextColor[actionButton.colorScheme] - } } // This CheckBox belongs in the columns that contain the stateful action buttons ("Mute" & "Ignore" for now) // KNOWN BUG with the Checkboxes: When clicking in the center of the sorting header, the checkbox diff --git a/interface/resources/qml/styles-uit/HifiConstants.qml b/interface/resources/qml/styles-uit/HifiConstants.qml index e261e2198f..031e80283e 100644 --- a/interface/resources/qml/styles-uit/HifiConstants.qml +++ b/interface/resources/qml/styles-uit/HifiConstants.qml @@ -318,5 +318,15 @@ Item { readonly property string deg: "\\" readonly property string px: "|" readonly property string editPencil: "\ue00d" + readonly property string vol_0: "\ue00e" + readonly property string vol_1: "\ue00f" + readonly property string vol_2: "\ue010" + readonly property string vol_3: "\ue011" + readonly property string vol_4: "\ue012" + readonly property string vol_x_0: "\ue013" + readonly property string vol_x_1: "\ue014" + readonly property string vol_x_2: "\ue015" + readonly property string vol_x_3: "\ue016" + readonly property string vol_x_4: "\ue017" } } From 3bf3f16a6ab53576d2d11d6dae901a8d0918e144 Mon Sep 17 00:00:00 2001 From: David Kelly Date: Mon, 27 Feb 2017 12:56:03 -0700 Subject: [PATCH 31/64] whitespace --- interface/resources/qml/hifi/Pal.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/resources/qml/hifi/Pal.qml b/interface/resources/qml/hifi/Pal.qml index 1db83a0223..f8b2f6ed25 100644 --- a/interface/resources/qml/hifi/Pal.qml +++ b/interface/resources/qml/hifi/Pal.qml @@ -32,7 +32,7 @@ Rectangle { property int rowHeight: 70 property int actionButtonWidth: 55 property int actionButtonAllowance: actionButtonWidth * 2 - property int minNameCardWidth: palContainer.width - (actionButtonAllowance * 2 ) - 4 - hifi.dimensions.scrollbarBackgroundWidth + property int minNameCardWidth: palContainer.width - (actionButtonAllowance * 2) - 4 - hifi.dimensions.scrollbarBackgroundWidth property int nameCardWidth: minNameCardWidth + (iAmAdmin ? 0 : actionButtonAllowance) property var myData: ({displayName: "", userName: "", audioLevel: 0.0, avgAudioLevel: 0.0, admin: true}) // valid dummy until set property var ignored: ({}); // Keep a local list of ignored avatars & their data. Necessary because HashMap is slow to respond after ignoring. From 9fbde41dc45508324ac1feef09f647cc51b39c44 Mon Sep 17 00:00:00 2001 From: Menithal Date: Mon, 27 Feb 2017 23:27:48 +0200 Subject: [PATCH 32/64] Styling formating and Rebuild attempt --- .../system/controllers/handControllerGrab.js | 99 ++++++++++--------- 1 file changed, 50 insertions(+), 49 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 4b469798ab..ad6adf7722 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -1465,7 +1465,7 @@ function MyController(hand) { return false; } - if (entityProps.hasOwnProperty("cloneable")/*&& props.locked*/) { + if (entityProps.hasOwnProperty("cloneable")) { return entityProps.cloneable; } return false; @@ -2393,57 +2393,58 @@ function MyController(hand) { } if (grabbedProperties.userData.length > 0) { - try{ - var userData = JSON.parse(grabbedProperties.userData); - var grabInfo = userData.grabbableKey; - if (grabInfo && grabInfo.cloneable) { + try{ + var userData = JSON.parse(grabbedProperties.userData); + var grabInfo = userData.grabbableKey; + if (grabInfo && grabInfo.cloneable) { + // Check if + var worldEntities = Entities.findEntitiesInBox(Vec3.subtract(MyAvatar.position, {x:25,y:25, z:25}), {x:50, y: 50, z: 50}) + var count = 0; + worldEntities.forEach(function(item) { + var item = Entities.getEntityProperties(item, ["name"]); + if (item.name === grabbedProperties.name) { + count++; + } + }) + var cloneableProps = Entities.getEntityProperties(grabbedProperties.id); + var lifetime = grabInfo.cloneLifetime ? grabInfo.cloneLifetime : 300; + var limit = grabInfo.cloneLimit ? grabInfo.cloneLimit : 10; + var dynamic = grabInfo.cloneDynamic ? grabInfo.cloneDynamic : false; + var cUserData = Object.assign({}, userData); + var cProperties = Object.assign({}, cloneableProps); - var worldEntities = Entities.findEntitiesInBox(Vec3.subtract(MyAvatar.position, {x:25,y:25, z:25}), {x:50, y: 50, z: 50}) - var count = 0; - worldEntities.forEach(function(item) { - var item = Entities.getEntityProperties(item, ["name"]); - if (item.name === grabbedProperties.name) { - count++; + if (count > limit) { + delete cloneableProps; + delete lifetime; + delete cUserData; + delete cProperties; + return; + } + + delete cUserData.grabbableKey.cloneLifetime; + delete cUserData.grabbableKey.cloneable; + delete cUserData.grabbableKey.cloneDynamic; + delete cUserData.grabbableKey.cloneLimit; + delete cProperties.id + + cProperties.dynamic = dynamic; + cProperties.locked = false; + cUserData.grabbableKey.triggerable = true; + cUserData.grabbableKey.grabbable = true; + cProperties.lifetime = lifetime; + cProperties.userData = JSON.stringify(cUserData); + this.grabbedEntity = Entities.addEntity(cProperties); + grabbedProperties = Entities.getEntityProperties(this.grabbedEntity); + var _this = this; + + Script.setTimeout(function () { + // This is needed to wait for the grabbed entity to have been instanciated. + _this.callEntityMethodOnGrabbed("startEquip"); + },400); } - }) - var cloneableProps = Entities.getEntityProperties(grabbedProperties.id); - var lifetime = grabInfo.cloneLifetime ? grabInfo.cloneLifetime : 300; - var limit = grabInfo.cloneLimit ? grabInfo.cloneLimit : 10; - var dynamic = grabInfo.cloneDynamic ? grabInfo.cloneDynamic : false; - var cUserData = Object.assign({}, userData); - var cProperties = Object.assign({}, cloneableProps); - - if (count > limit) { - delete cloneableProps; - delete lifetime; - delete cUserData; - delete cProperties; - return; - } - - delete cUserData.grabbableKey.cloneLifetime; - delete cUserData.grabbableKey.cloneable; - delete cUserData.grabbableKey.cloneDynamic; - delete cUserData.grabbableKey.cloneLimit; - delete cProperties.id - - cProperties.dynamic = dynamic; - cProperties.locked = false; - cUserData.grabbableKey.triggerable = true; - cUserData.grabbableKey.grabbable = true; - cProperties.lifetime = lifetime; - cProperties.userData = JSON.stringify(cUserData); - this.grabbedEntity = Entities.addEntity(cProperties); - grabbedProperties = Entities.getEntityProperties(this.grabbedEntity); - var _this = this; - Script.setTimeout(function () { - // This is needed to wait for the grabbed entity to have been instanciated. - _this.callEntityMethodOnGrabbed("startEquip"); - },400); + }catch(e) { + print("ERROR: " + e); } - }catch(e) { - print("ERROR: " + e); - } } Entities.editEntity(this.grabbedEntity, reparentProps); From b1639ee3aafb07c96e1e516c74446bbe99737a9e Mon Sep 17 00:00:00 2001 From: Matti Lahtinen Date: Mon, 27 Feb 2017 23:40:06 +0200 Subject: [PATCH 33/64] Removed a Stray Print json that would spam logs. --- scripts/system/edit.js | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 7731edd622..bfa5ee478b 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -1618,7 +1618,6 @@ var PropertiesTool = function (opts) { print('Edit.js received web event that was not valid json.') return; } - print(JSON.stringify(data)) var i, properties, dY, diff, newPosition; if (data.type === "print") { if (data.message) { From c04fd0ec66d31d6a0476be57d81cdc8fbc59e358 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Mon, 27 Feb 2017 16:07:05 -0800 Subject: [PATCH 34/64] smaller glyph size --- interface/resources/qml/hifi/Pal.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/interface/resources/qml/hifi/Pal.qml b/interface/resources/qml/hifi/Pal.qml index f8b2f6ed25..99700bc0f2 100644 --- a/interface/resources/qml/hifi/Pal.qml +++ b/interface/resources/qml/hifi/Pal.qml @@ -289,6 +289,7 @@ Rectangle { visible: isAvgAudio glyph: getGlyph() width: 32 + size: height anchors.verticalCenter: parent.verticalCenter anchors.horizontalCenter: parent.horizontalCenter onClicked: { From 9527c51ff1d944b26e8b2e79db346e5cae0919c0 Mon Sep 17 00:00:00 2001 From: samcake Date: Mon, 27 Feb 2017 13:32:24 -0800 Subject: [PATCH 35/64] Backing up from the rgba format to go back to a rgb 888 just to repair the broken normals on Seth's Avatar --- libraries/model/src/model/TextureMap.cpp | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/libraries/model/src/model/TextureMap.cpp b/libraries/model/src/model/TextureMap.cpp index d1fbaf767a..7ac8083d9c 100755 --- a/libraries/model/src/model/TextureMap.cpp +++ b/libraries/model/src/model/TextureMap.cpp @@ -336,8 +336,7 @@ gpu::Texture* TextureUsage::createNormalTextureFromBumpImage(const QImage& srcIm const double pStrength = 2.0; int width = image.width(); int height = image.height(); - // THe end result image for normal map is RGBA32 even though the A is not used - QImage result(width, height, QImage::Format_RGBA8888); + QImage result(width, height, QImage::Format_RGB888); for (int i = 0; i < width; i++) { const int iNextClamped = clampPixelCoordinate(i + 1, width - 1); @@ -377,21 +376,19 @@ gpu::Texture* TextureUsage::createNormalTextureFromBumpImage(const QImage& srcIm glm::normalize(v); // convert to rgb from the value obtained computing the filter - QRgb qRgbValue = qRgb(mapComponent(v.x), mapComponent(v.y), mapComponent(v.z)); + QRgb qRgbValue = qRgba(mapComponent(v.x), mapComponent(v.y), mapComponent(v.z), 1.0); result.setPixel(i, j, qRgbValue); } } gpu::Texture* theTexture = nullptr; if ((image.width() > 0) && (image.height() > 0)) { - - gpu::Element formatGPU = gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA); - gpu::Element formatMip = gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA); + gpu::Element formatGPU = gpu::Element(gpu::VEC3, gpu::NUINT8, gpu::RGB); + gpu::Element formatMip = gpu::Element(gpu::VEC3, gpu::NUINT8, gpu::RGB); theTexture = (gpu::Texture::create2D(formatGPU, image.width(), image.height(), gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR))); theTexture->setSource(srcImageName); theTexture->assignStoredMip(0, formatMip, image.byteCount(), image.constBits()); - generateMips(theTexture, image, formatMip, true); } return theTexture; From 5b6f953ac270d60aab93ae08cfa2302dafee1161 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 28 Feb 2017 17:35:13 +1300 Subject: [PATCH 36/64] Add hand controller state of distance-rotating for second grab hand --- .../system/controllers/handControllerGrab.js | 138 ++++++++++++++---- 1 file changed, 109 insertions(+), 29 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index d313d1cfa1..0bc0600c81 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -205,14 +205,15 @@ var HARDWARE_MOUSE_ID = 0; // Value reserved for hardware mouse. var STATE_OFF = 0; var STATE_SEARCHING = 1; var STATE_DISTANCE_HOLDING = 2; -var STATE_NEAR_GRABBING = 3; -var STATE_NEAR_TRIGGER = 4; -var STATE_FAR_TRIGGER = 5; -var STATE_HOLD = 6; -var STATE_ENTITY_STYLUS_TOUCHING = 7; -var STATE_ENTITY_LASER_TOUCHING = 8; -var STATE_OVERLAY_STYLUS_TOUCHING = 9; -var STATE_OVERLAY_LASER_TOUCHING = 10; +var STATE_DISTANCE_ROTATING = 3; +var STATE_NEAR_GRABBING = 4; +var STATE_NEAR_TRIGGER = 5; +var STATE_FAR_TRIGGER = 6; +var STATE_HOLD = 7; +var STATE_ENTITY_STYLUS_TOUCHING = 8; +var STATE_ENTITY_LASER_TOUCHING = 9; +var STATE_OVERLAY_STYLUS_TOUCHING = 10; +var STATE_OVERLAY_LASER_TOUCHING = 11; var CONTROLLER_STATE_MACHINE = {}; @@ -231,6 +232,11 @@ CONTROLLER_STATE_MACHINE[STATE_DISTANCE_HOLDING] = { enterMethod: "distanceHoldingEnter", updateMethod: "distanceHolding" }; +CONTROLLER_STATE_MACHINE[STATE_DISTANCE_ROTATING] = { + name: "distance_rotating", + enterMethod: "distanceRotatingEnter", + updateMethod: "distanceRotating" +}; CONTROLLER_STATE_MACHINE[STATE_NEAR_GRABBING] = { name: "near_grabbing", enterMethod: "nearGrabbingEnter", @@ -869,7 +875,8 @@ function MyController(hand) { newState !== STATE_OVERLAY_LASER_TOUCHING)) { return; } - setGrabCommunications((newState === STATE_DISTANCE_HOLDING) || (newState === STATE_NEAR_GRABBING)); + setGrabCommunications((newState === STATE_DISTANCE_HOLDING) || (newState === STATE_DISTANCE_ROTATING) + || (newState === STATE_NEAR_GRABBING)); if (WANT_DEBUG || WANT_DEBUG_STATE) { var oldStateName = stateToName(this.state); var newStateName = stateToName(newState); @@ -1439,9 +1446,10 @@ function MyController(hand) { var props = entityPropertiesCache.getProps(hotspot.entityID); var debug = (WANT_DEBUG_SEARCH_NAME && props.name === WANT_DEBUG_SEARCH_NAME); - var okToEquipFromOtherHand = ((this.getOtherHandController().state == STATE_NEAR_GRABBING || - this.getOtherHandController().state == STATE_DISTANCE_HOLDING) && - this.getOtherHandController().grabbedThingID == hotspot.entityID); + var otherHandControllerState = this.getOtherHandController().state; + var okToEquipFromOtherHand = ((otherHandControllerState === STATE_NEAR_GRABBING + || otherHandControllerState === STATE_DISTANCE_HOLDING || otherHandControllerState === STATE_DISTANCE_ROTATING) + && this.getOtherHandController().grabbedThingID === hotspot.entityID); var hasParent = true; if (props.parentID === NULL_UUID) { hasParent = false; @@ -1731,7 +1739,11 @@ function MyController(hand) { this.grabbedThingID = entity; this.grabbedIsOverlay = false; this.grabbedDistance = rayPickInfo.distance; - this.setState(STATE_DISTANCE_HOLDING, "distance hold '" + name + "'"); + if (this.getOtherHandController().state === STATE_DISTANCE_HOLDING) { + this.setState(STATE_DISTANCE_ROTATING, "distance rotate '" + name + "'"); + } else { + this.setState(STATE_DISTANCE_HOLDING, "distance hold '" + name + "'"); + } return; } else { // potentialFarGrabEntity = entity; @@ -2036,6 +2048,19 @@ function MyController(hand) { return (dimensions.x * dimensions.y * dimensions.z) * density; }; + this.ensureDynamic = function () { + // if we distance hold something and keep it very still before releasing it, it ends up + // non-dynamic in bullet. If it's too still, give it a little bounce so it will fall. + var props = Entities.getEntityProperties(this.grabbedThingID, ["velocity", "dynamic", "parentID"]); + if (props.dynamic && props.parentID == NULL_UUID) { + var velocity = props.velocity; + if (Vec3.length(velocity) < 0.05) { // see EntityMotionState.cpp DYNAMIC_LINEAR_VELOCITY_THRESHOLD + velocity = { x: 0.0, y: 0.2, z: 0.0 }; + Entities.editEntity(this.grabbedThingID, { velocity: velocity }); + } + } + }; + this.distanceHoldingEnter = function() { this.clearEquipHaptics(); this.grabPointSphereOff(); @@ -2102,25 +2127,20 @@ function MyController(hand) { this.previousRoomControllerPosition = roomControllerPosition; }; - this.ensureDynamic = function() { - // if we distance hold something and keep it very still before releasing it, it ends up - // non-dynamic in bullet. If it's too still, give it a little bounce so it will fall. - var props = Entities.getEntityProperties(this.grabbedThingID, ["velocity", "dynamic", "parentID"]); - if (props.dynamic && props.parentID == NULL_UUID) { - var velocity = props.velocity; - if (Vec3.length(velocity) < 0.05) { // see EntityMotionState.cpp DYNAMIC_LINEAR_VELOCITY_THRESHOLD - velocity = { x: 0.0, y: 0.2, z:0.0 }; - Entities.editEntity(this.grabbedThingID, { velocity: velocity }); - } - } - }; - this.distanceHolding = function(deltaTime, timestamp) { if (!this.triggerClicked) { this.callEntityMethodOnGrabbed("releaseGrab"); this.ensureDynamic(); this.setState(STATE_OFF, "trigger released"); + if (this.getOtherHandController().state === STATE_DISTANCE_ROTATING) { + this.getOtherHandController().setState(STATE_SEARCHING, "trigger released on holding controller"); + // Can't set state of other controller to STATE_DISTANCE_HOLDING because then either: + // (a) The entity would jump to line up with the formerly rotating controller's orientation, or + // (b) The grab beam would need an orientation offset to the controller's true orientation. + // Neither of these options is good, so instead set STATE_SEARCHING and subsequently let the formerly distance + // rotating controller start distance holding the entity if it happens to be pointing at the entity. + } return; } @@ -2209,11 +2229,11 @@ function MyController(hand) { } this.maybeScale(grabbedProperties); + // visualizations - var rayPickInfo = this.calcRayPickInfo(this.hand); - - this.overlayLineOn(rayPickInfo.searchRay.origin, Vec3.subtract(grabbedProperties.position, this.offsetPosition), COLORS_GRAB_DISTANCE_HOLD); + this.overlayLineOn(rayPickInfo.searchRay.origin, Vec3.subtract(grabbedProperties.position, this.offsetPosition), + COLORS_GRAB_DISTANCE_HOLD); var distanceToObject = Vec3.length(Vec3.subtract(MyAvatar.position, this.currentObjectPosition)); var success = Entities.updateAction(this.grabbedThingID, this.actionID, { @@ -2232,6 +2252,66 @@ function MyController(hand) { this.previousRoomControllerPosition = roomControllerPosition; }; + this.distanceRotatingEnter = function() { + this.clearEquipHaptics(); + this.grabPointSphereOff(); + + this.shouldScale = false; + + var controllerLocation = getControllerWorldLocation(this.handToController(), true); + var worldControllerPosition = controllerLocation.position; + var worldControllerRotation = controllerLocation.orientation; + + // transform the position into room space + var worldToSensorMat = Mat4.inverse(MyAvatar.getSensorToWorldMatrix()); + var roomControllerPosition = Mat4.transformPoint(worldToSensorMat, worldControllerPosition); + + var grabbedProperties = Entities.getEntityProperties(this.grabbedThingID, GRABBABLE_PROPERTIES); + var now = Date.now(); + + // add the action and initialize some variables + this.currentObjectPosition = grabbedProperties.position; + this.currentObjectRotation = grabbedProperties.rotation; + this.currentObjectTime = now; + this.currentCameraOrientation = Camera.orientation; + + this.grabRadius = this.grabbedDistance; + this.grabRadialVelocity = 0.0; + + // offset between controller vector at the grab radius and the entity position + var targetPosition = Vec3.multiply(this.grabRadius, Quat.getUp(worldControllerRotation)); + targetPosition = Vec3.sum(targetPosition, worldControllerPosition); + this.offsetPosition = Vec3.subtract(this.currentObjectPosition, targetPosition); + + // TODO + + Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand); + this.turnOffVisualizations(); + this.previousRoomControllerPosition = roomControllerPosition; + }; + + this.distanceRotating = function(deltaTime, timestamp) { + + if (!this.triggerClicked) { + this.callEntityMethodOnGrabbed("releaseGrab"); + this.ensureDynamic(); + this.setState(STATE_OFF, "trigger released"); + return; + } + + // TODO + + var grabbedProperties = Entities.getEntityProperties(this.grabbedThingID, GRABBABLE_PROPERTIES); + + // TODO + + var rayPickInfo = this.calcRayPickInfo(this.hand); + this.overlayLineOn(rayPickInfo.searchRay.origin, Vec3.subtract(grabbedProperties.position, this.offsetPosition), + COLORS_GRAB_DISTANCE_HOLD); + + // TODO + } + this.setupHoldAction = function() { this.actionID = Entities.addAction("hold", this.grabbedThingID, { hand: this.hand === RIGHT_HAND ? "right" : "left", From 28768d4a781a267e15bc51ecca84c13be7748702 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 28 Feb 2017 22:22:34 +1300 Subject: [PATCH 37/64] Rotate the entity by twice the rotating controller's rotation --- .../system/controllers/handControllerGrab.js | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 0bc0600c81..2910092293 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -2283,7 +2283,8 @@ function MyController(hand) { targetPosition = Vec3.sum(targetPosition, worldControllerPosition); this.offsetPosition = Vec3.subtract(this.currentObjectPosition, targetPosition); - // TODO + // Initial controller rotation. + this.previousWorldControllerRotation = worldControllerRotation; Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand); this.turnOffVisualizations(); @@ -2299,17 +2300,24 @@ function MyController(hand) { return; } - // TODO - var grabbedProperties = Entities.getEntityProperties(this.grabbedThingID, GRABBABLE_PROPERTIES); - // TODO + // Delta rotation of grabbing controller since last update. + var worldControllerRotation = getControllerWorldLocation(this.handToController(), true).orientation; + var controllerRotationDelta = Quat.multiply(worldControllerRotation, Quat.inverse(this.previousWorldControllerRotation)); + + // Rotate entity by twice the delta rotation. + controllerRotationDelta = Quat.multiply(controllerRotationDelta, controllerRotationDelta); + + // Perform the rotation in the translation controller's action update. + this.getOtherHandController().currentObjectRotation = Quat.multiply(controllerRotationDelta, + this.getOtherHandController().currentObjectRotation); var rayPickInfo = this.calcRayPickInfo(this.hand); this.overlayLineOn(rayPickInfo.searchRay.origin, Vec3.subtract(grabbedProperties.position, this.offsetPosition), COLORS_GRAB_DISTANCE_HOLD); - // TODO + this.previousWorldControllerRotation = worldControllerRotation; } this.setupHoldAction = function() { From e3f2f3c5bc830064d6aef54f8259bffca4f6cd2d Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 28 Feb 2017 23:04:23 +1300 Subject: [PATCH 38/64] Tidying --- .../system/controllers/handControllerGrab.js | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 2910092293..2dfa7e6948 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -2256,29 +2256,15 @@ function MyController(hand) { this.clearEquipHaptics(); this.grabPointSphereOff(); - this.shouldScale = false; - var controllerLocation = getControllerWorldLocation(this.handToController(), true); var worldControllerPosition = controllerLocation.position; var worldControllerRotation = controllerLocation.orientation; - // transform the position into room space - var worldToSensorMat = Mat4.inverse(MyAvatar.getSensorToWorldMatrix()); - var roomControllerPosition = Mat4.transformPoint(worldToSensorMat, worldControllerPosition); - var grabbedProperties = Entities.getEntityProperties(this.grabbedThingID, GRABBABLE_PROPERTIES); - var now = Date.now(); - - // add the action and initialize some variables this.currentObjectPosition = grabbedProperties.position; - this.currentObjectRotation = grabbedProperties.rotation; - this.currentObjectTime = now; - this.currentCameraOrientation = Camera.orientation; - this.grabRadius = this.grabbedDistance; - this.grabRadialVelocity = 0.0; - // offset between controller vector at the grab radius and the entity position + // Offset between controller vector at the grab radius and the entity position. var targetPosition = Vec3.multiply(this.grabRadius, Quat.getUp(worldControllerRotation)); targetPosition = Vec3.sum(targetPosition, worldControllerPosition); this.offsetPosition = Vec3.subtract(this.currentObjectPosition, targetPosition); @@ -2288,7 +2274,6 @@ function MyController(hand) { Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand); this.turnOffVisualizations(); - this.previousRoomControllerPosition = roomControllerPosition; }; this.distanceRotating = function(deltaTime, timestamp) { From d8d17be0e06206ce94839765d747259feba505a3 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 28 Feb 2017 23:22:07 +1300 Subject: [PATCH 39/64] Rotate about translation grab position --- scripts/system/controllers/handControllerGrab.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 2dfa7e6948..08877e2b10 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -2298,6 +2298,11 @@ function MyController(hand) { this.getOtherHandController().currentObjectRotation = Quat.multiply(controllerRotationDelta, this.getOtherHandController().currentObjectRotation); + // Rotate about the translation controller's target position. + this.offsetPosition = Vec3.multiplyQbyV(controllerRotationDelta, this.offsetPosition); + this.getOtherHandController().offsetPosition = Vec3.multiplyQbyV(controllerRotationDelta, + this.getOtherHandController().offsetPosition); + var rayPickInfo = this.calcRayPickInfo(this.hand); this.overlayLineOn(rayPickInfo.searchRay.origin, Vec3.subtract(grabbedProperties.position, this.offsetPosition), COLORS_GRAB_DISTANCE_HOLD); From d0c2c26a8e77209d6cba4c4a5c97538a4678d268 Mon Sep 17 00:00:00 2001 From: Matti Lahtinen Date: Tue, 28 Feb 2017 16:37:08 +0200 Subject: [PATCH 40/64] One last gotcha from the conflict resolution. --- scripts/system/controllers/handControllerGrab.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 14d737de55..fd13b74f7d 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -2497,7 +2497,7 @@ function MyController(hand) { cProperties.lifetime = lifetime; cProperties.userData = JSON.stringify(cUserData); this.grabbedThingID = Entities.addEntity(cProperties); - grabbedProperties = Entities.getEntityProperties(this.grabbedEntity); + grabbedProperties = Entities.getEntityProperties(this.grabbedThingID); var _this = this; Script.setTimeout(function () { From 1fc57ce9a68a40de030890c7e2aefb73b3de5375 Mon Sep 17 00:00:00 2001 From: David Kelly Date: Tue, 28 Feb 2017 11:51:40 -0700 Subject: [PATCH 41/64] alan's feedback --- scripts/system/pal.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/scripts/system/pal.js b/scripts/system/pal.js index a609de43a1..67aa94a2f7 100644 --- a/scripts/system/pal.js +++ b/scripts/system/pal.js @@ -634,7 +634,7 @@ var AVERAGING_RATIO = 0.05; var LOUDNESS_FLOOR = 11.0; var LOUDNESS_SCALE = 2.8 / 5.0; var LOG2 = Math.log(2.0); -var AUDIO_PEAK_DECAY = 0.03; +var AUDIO_PEAK_DECAY = 0.02; var myData = {}; // we're not includied in ExtendedOverlay.get. function scaleAudio(val) { @@ -673,8 +673,9 @@ function getAudioLevel(id) { data.avgAudioLevel = avgAudioLevel; data.audioLevel = audioLevel; - // now scale for the gain - avgAudioLevel = Math.min(1.0, avgAudioLevel *(sessionGains[id] || 0.75)); + // now scale for the gain. Also, asked to boost the low end, so one simple way is + // to take sqrt of the value. Lets try that, see how it feels. + avgAudioLevel = Math.min(1.0, Math.sqrt(avgAudioLevel *(sessionGains[id] || 0.75))); } return [audioLevel, avgAudioLevel]; } From 8562a294fa0d666f13c26e56b9520924b0782cee Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 28 Feb 2017 13:24:41 -0800 Subject: [PATCH 42/64] cleanup some extra spacing --- libraries/entities-renderer/src/RenderableWebEntityItem.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index 6c44794c3e..d7d7013f59 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -269,11 +269,10 @@ void RenderableWebEntityItem::loadSourceURL() { _webSurface->load("WebView.qml", [&](QQmlContext* context, QObject* obj) { context->setContextProperty("eventBridgeJavaScriptToInject", QVariant(_javaScriptToInject)); }); + _webSurface->getRootItem()->setProperty("url", _sourceUrl); _webSurface->getRootContext()->setContextProperty("desktop", QVariant()); - - } else { _contentType = qmlContent; _webSurface->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath())); From 148100b26f7b1b359ab4792660ae9de7b1caa4c9 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 28 Feb 2017 15:10:20 -0800 Subject: [PATCH 43/64] fix bug drawing avatar above ground --- interface/src/avatar/SkeletonModel.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index 476abf8d4b..88590a6f69 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -179,9 +179,7 @@ void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { _rig->updateFromEyeParameters(eyeParams); } else { - // no need to call Model::updateRig() because otherAvatars get their joint state - // copied directly from AvtarData::_jointData (there are no Rig animations to blend) - _needsUpdateClusterMatrices = true; + Model::updateRig(deltaTime, parentTransform); // This is a little more work than we really want. // From 1c3840dc788f4a828d748c9ae3b011bf5f1e37d2 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 1 Mar 2017 10:05:24 -0800 Subject: [PATCH 44/64] Add Hifi-Hand-Drop so scripts can cause grabbed items to be dropped --- scripts/system/controllers/handControllerGrab.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index d313d1cfa1..d982a032cc 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -3287,6 +3287,7 @@ Messages.subscribe('Hifi-Hand-Disabler'); Messages.subscribe('Hifi-Hand-Grab'); Messages.subscribe('Hifi-Hand-RayPick-Blacklist'); Messages.subscribe('Hifi-Object-Manipulation'); +Messages.subscribe('Hifi-Hand-Drop'); var handleHandMessages = function(channel, message, sender) { var data; @@ -3372,6 +3373,15 @@ var handleHandMessages = function(channel, message, sender) { } catch (e) { print("WARNING: handControllerGrab.js -- error parsing Hifi-Hand-RayPick-Blacklist message: " + message); } + } else if (channel === 'Hifi-Hand-Drop') { + if (message === 'left') { + leftController.release(); + } else if (message === 'right') { + rightController.release(); + } else if (message === 'both') { + leftController.release(); + rightController.release(); + } } } }; From 55d049ff5caba783d1cbd9be59da2c53e52b518a Mon Sep 17 00:00:00 2001 From: David Kelly Date: Wed, 1 Mar 2017 12:43:45 -0700 Subject: [PATCH 45/64] style updates --- interface/resources/fonts/hifi-glyphs.ttf | Bin 27900 -> 28048 bytes interface/resources/qml/hifi/NameCard.qml | 5 ++++- interface/resources/qml/hifi/Pal.qml | 4 ++-- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/interface/resources/fonts/hifi-glyphs.ttf b/interface/resources/fonts/hifi-glyphs.ttf index f6efa8e03952a0b2e926c966a08da697e53d8b79..0d724788143efe22b55d56347e0138961ee55d45 100755 GIT binary patch delta 3431 zcmcJSU2Icj9LAsjIqx~|>F3(6r>EQ6jeVWNHbzU=wczHI1coTchYLa&g8}=WQR1#7nm7`@Gf@MgNX%{|MvWH+H9;MwT{qa6WM8yR`u3v9 z`+I)x|2gOU^BukLJ((Z?E}Vdft074tqt#8KLkb!gyfMr{rh{U#h zyr~tSod9+mNVoSlG=2LrAiM_%OqpRxpd7v|u`A>h};Lu4!{Tt{G0n*AW906BX-?*_iukjB!1Y};9u3bz` zds{e>>a`vbPkB>FqYwQUz!3~$7(><%Vhi<-)UlVSpttJN z>gwu~)z@o!Rs>dz)wa}LU3ntX5^WwClXh<($=juqwPxofQ$QFyaGlOU$66p?t`W=R zFcp&)Xb@srKnv8xl8FbYk(%g1iW@qYwWK#f`oyP3sBe&B$wV@dX3WQBZ;*U0C67Fe zd32|bDH>6MmS0+%$7EtISu7GIHOv*}agijuxv9`Trg{ckDiQNky9HbE8@7Ui0Oui_ z-LG@s_`h5hm|JGWVvp?Mc8Hk`lakx;#>H$o5ZBEz*&m24Z12ipf5`6-%@g2Aw5%*z z27qNioYX?+;70>?pv^kn8Z;LyAQ0EHFcc+`1cljbKBmf&L-cW{2MdQeP6%8KA_ihxe$GTP}DtiyWCBa;x>) zo?y5-Cs#%o&GZ|M=F|@07|tvKI_cKxw$S5?GCV7{JQn9%lD8o*s-$%xs*#qH}Oy8MXkGq$it5qhjJRC6bAUNY&~aDG|?A4A<0&elc_- z+cC`0ljUxjET+KiVxkYmpYyp`J~wGt5hcg?mn0~@JcELozlLwl)sD|DGBwP(NFxk#(vIWsPRBb-aa2|t#>KrLD^090}9WtnYm2Oo>NpTSJ-3|I*Hjl&UEh7OdOg;CpTUcG^;8rifod^ zy%l{#5*55MpS;dOU`wa5j^uIa{Q1W>8{DBfzypvOccY$Fn1q zy^II)7Z>=9yv^IwJ6qr-f+*SqCsW8?;UPgF*~9D(#igjprBPu{&ir^$@NN|J&AZv1Ym0c9L&6~W1B@kuiZd#%ofFtYbcN)!}{8B7O6wTOYTTq1lbjiiOh?RBp}CNunc?u>3XhbQu!!BuC8@}7FK2E^lBK;7Av%V8;i6#UIMq~EM5ENf&GRQK_b?unCAseHYwBMJ{ z^L&2a=YDa3=jY(jzGkwj*hNp58nFpGLRVo5M2B8nhtyd4rDF^ShR6#gTC>r z&Yb{l2e7R#+nZ}{zitA;IY4Ml_YUUn-Gc~Zz5rlLXI|O+*~KT{0HhJ1XS6@nyGQ-D z<2Hbw0VMkSQ${aqq!EC;0K)y*p+gmIzB+&|0JxhO=FKee>I!O<`_JRExzZ@{5s|1wRhOtdv1?8D z9ntQhsJM^2!+>asBUTXCHWzu?vPg(~HGKw|eKYFZ>I-_k!GEfKfY{@Wp-^MUg2?is zJLwF(SdLC~nU{71j9KSe6iziHl3-(sQp$W>vNMnDkescvOuc8q&N#1-9f1;?%++K| zxKfcM*%RI|70AO_l=2QwO*-sH&hn_w+N0mB*i~UP@dBgyvHBYk3EV9ycAL}Y69SBh z3S&-Q@j;&Ge>W3`;o)3N612G;^sTMgQ#tKO!=>T$?_ekljr6w44}hP$9yOE*i`4>S}BP%Hnz=u4~PtCtInF zeDNmV)C1z>zPMB!;y$n6H(4`Q-Q-)wuP2s8q+-*=*D}E-Fs~sI=e9*QXKd>6UqYwH zS1Jxqv3nwJN0}^_k1I~+`1kkw=Vgi$aivOecE3RPk34gUesU z4sPRuK(fFTTN!iF_}z&!GEHoAOKs5ts~>G{Uz=Cu0dAG1p~k9P=X!`H!&D|W#Y(aY zjp)J?Is&W08A;+VinMb#lYxJwqi#d`IN z+;Z#S=62c5M0MS+X(6wk2g+t Date: Wed, 1 Mar 2017 22:07:02 +0200 Subject: [PATCH 46/64] Various Properties --- scripts/system/html/entityProperties.html | 2 +- scripts/system/html/js/entityProperties.js | 149 +++++++++++---------- 2 files changed, 78 insertions(+), 73 deletions(-) diff --git a/scripts/system/html/entityProperties.html b/scripts/system/html/entityProperties.html index a0497d6a92..5022dbd6a6 100644 --- a/scripts/system/html/entityProperties.html +++ b/scripts/system/html/entityProperties.html @@ -305,7 +305,7 @@ -
+