diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index 4cf4049824..e28c04379f 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -336,6 +336,10 @@ void Agent::executeScript() { // call model URL setters with empty URLs so our avatar, if user, will have the default models scriptedAvatar->setSkeletonModelURL(QUrl()); + // force lazy initialization of the head data for the scripted avatar + // since it is referenced below by computeLoudness and getAudioLoudness + scriptedAvatar->getHeadOrientation(); + // give this AvatarData object to the script engine _scriptEngine->registerGlobalObject("Avatar", scriptedAvatar.data()); diff --git a/interface/resources/fonts/hifi-glyphs.ttf b/interface/resources/fonts/hifi-glyphs.ttf old mode 100755 new mode 100644 diff --git a/interface/resources/qml/TabletBrowser.qml b/interface/resources/qml/TabletBrowser.qml index 312b811928..85fff59207 100644 --- a/interface/resources/qml/TabletBrowser.qml +++ b/interface/resources/qml/TabletBrowser.qml @@ -110,7 +110,6 @@ Item { Component.onCompleted: { // Ensure the JS from the web-engine makes it to our logging webview.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) { - console.log("TabletBrowser"); console.log("Web Entity JS message: " + sourceID + " " + lineNumber + " " + message); }); @@ -138,33 +137,22 @@ Item { } } - onActiveFocusOnPressChanged: { - console.log("on active focus changed"); - setActiveFocusOnPress(true); - } - - onNewViewRequested:{ - // desktop is not defined for web-entities - if (stackRoot.isDesktop) { - var component = Qt.createComponent("./Browser.qml"); - var newWindow = component.createObject(desktop); - request.openIn(newWindow.webView); - } else { - var component = Qt.createComponent("./TabletBrowser.qml"); - - if (component.status != Component.Ready) { - if (component.status == Component.Error) { - console.log("Error: " + component.errorString()); - } - return; + onNewViewRequested: { + var component = Qt.createComponent("./TabletBrowser.qml"); + + if (component.status != Component.Ready) { + if (component.status == Component.Error) { + console.log("Error: " + component.errorString()); } - var newWindow = component.createObject(); - newWindow.setProfile(webview.profile); - request.openIn(newWindow.webView); - newWindow.eventBridge = web.eventBridge; - stackRoot.push(newWindow); - newWindow.webView.forceActiveFocus(); + return; } + var newWindow = component.createObject(); + newWindow.setProfile(webview.profile); + request.openIn(newWindow.webView); + newWindow.eventBridge = web.eventBridge; + stackRoot.push(newWindow); + newWindow.webView.forceActiveFocus(); + } } diff --git a/interface/resources/qml/controls-uit/CheckBox.qml b/interface/resources/qml/controls-uit/CheckBox.qml index 60e136d13a..916a7d4889 100644 --- a/interface/resources/qml/controls-uit/CheckBox.qml +++ b/interface/resources/qml/controls-uit/CheckBox.qml @@ -19,12 +19,11 @@ Original.CheckBox { property int colorScheme: hifi.colorSchemes.light readonly property bool isLightColorScheme: colorScheme == hifi.colorSchemes.light - + property bool isRedCheck: false property int boxSize: 14 readonly property int boxRadius: 3 readonly property int checkSize: Math.max(boxSize - 8, 10) readonly property int checkRadius: 2 - activeFocusOnPress: true style: CheckBoxStyle { @@ -37,6 +36,7 @@ Original.CheckBox { border.color: pressed || hovered ? hifi.colors.checkboxCheckedBorder : (checkBox.isLightColorScheme ? hifi.colors.checkboxLightFinish : hifi.colors.checkboxDarkFinish) + gradient: Gradient { GradientStop { position: 0.2 @@ -68,9 +68,9 @@ Original.CheckBox { height: checkSize radius: checkRadius anchors.centerIn: parent - color: hifi.colors.checkboxChecked + color: isRedCheck ? hifi.colors.checkboxCheckedRed : hifi.colors.checkboxChecked border.width: 2 - border.color: hifi.colors.checkboxCheckedBorder + border.color: isRedCheck? hifi.colors.checkboxCheckedBorderRed : hifi.colors.checkboxCheckedBorder visible: checked && !pressed || !checked && pressed } diff --git a/interface/resources/qml/controls/TabletWebView.qml b/interface/resources/qml/controls/TabletWebView.qml index 890215a714..742ab2d333 100644 --- a/interface/resources/qml/controls/TabletWebView.qml +++ b/interface/resources/qml/controls/TabletWebView.qml @@ -112,6 +112,7 @@ Item { currentItem.goBack(); } else { stackRoot.pop(); + currentItem.webView.focus = true; currentItem.webView.forceActiveFocus(); web.address = currentItem.webView.url; } @@ -186,7 +187,6 @@ Item { Component.onCompleted: { // Ensure the JS from the web-engine makes it to our logging root.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) { - console.log("WebView.qml"); console.log("Web Entity JS message: " + sourceID + " " + lineNumber + " " + message); }); @@ -202,7 +202,6 @@ Item { keyboardRaised = false; punctuationMode = false; keyboard.resetShiftMode(false); - console.log("[DR] -> printing user string " + root.profile.httpUserAgent); // Required to support clicking on "hifi://" links if (WebEngineView.LoadStartedStatus == loadRequest.status) { var url = loadRequest.url.toString(); @@ -215,30 +214,21 @@ Item { } onNewViewRequested:{ - // desktop is not defined for web-entities - if (web.isDesktop) { - var component = Qt.createComponent("../Browser.qml"); - var newWindow = component.createObject(desktop); - newWindow.setProfile(root.profile); - request.openIn(newWindow.webView); - } else { - var component = Qt.createComponent("../TabletBrowser.qml"); - - if (component.status != Component.Ready) { - if (component.status == Component.Error) { - console.log("Error: " + component.errorString()); - } - return; + var component = Qt.createComponent("../TabletBrowser.qml"); + if (component.status != Component.Ready) { + if (component.status == Component.Error) { + console.log("Error: " + component.errorString()); } - var newWindow = component.createObject(); - newWindow.setProfile(root.profile); - request.openIn(newWindow.webView); - newWindow.eventBridge = web.eventBridge; - stackRoot.push(newWindow); + return; } + var newWindow = component.createObject(); + newWindow.setProfile(root.profile); + request.openIn(newWindow.webView); + newWindow.eventBridge = web.eventBridge; + stackRoot.push(newWindow); } } - + HiFiControls.Keyboard { id: keyboard raised: web.keyboardEnabled && web.keyboardRaised diff --git a/interface/resources/qml/hifi/ComboDialog.qml b/interface/resources/qml/hifi/ComboDialog.qml index f5bb7dcfcc..8905ed9c17 100644 --- a/interface/resources/qml/hifi/ComboDialog.qml +++ b/interface/resources/qml/hifi/ComboDialog.qml @@ -14,11 +14,12 @@ import QtQuick.Controls 1.4 import "../styles-uit" Item { - property var dialogTitleText; - property var optionTitleText; - property var optionBodyText; - property var optionValues; - property var selectedOptionIndex; + property var dialogTitleText : ""; + property var optionTitleText: ""; + property var optionBodyText: ""; + property var optionValues: []; + property var selectedOptionIndex: 0; + property var callbackFunction; property int dialogWidth; property int dialogHeight; property int comboOptionTextSize: 18; @@ -31,6 +32,14 @@ Item { populateComboListViewModel(); } + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.LeftButton + onClicked: { + combo.visible = false; + } + } + Rectangle { id: dialogBackground; anchors.fill: parent; @@ -42,12 +51,12 @@ Item { id: dialogContainer; color: "white"; anchors.centerIn: dialogBackground; - width: dialogWidth; - height: dialogHeight; + width: combo.dialogWidth; + height: combo.dialogHeight; RalewayRegular { id: dialogTitle; - text: dialogTitleText; + text: combo.dialogTitleText; anchors.top: parent.top; anchors.topMargin: 20; anchors.left: parent.left; @@ -69,6 +78,7 @@ Item { anchors.bottom: parent.bottom; anchors.left: parent.left; anchors.right: parent.right; + clip: true; model: comboListViewModel; delegate: comboListViewDelegate; @@ -77,7 +87,7 @@ Item { Rectangle { id: comboListViewItemContainer; // Size - height: childrenRect.height + 10; + height: optionTitle.height + optionBody.height + 20; width: dialogContainer.width; color: selectedOptionIndex === index ? '#cee6ff' : 'white'; Rectangle { @@ -100,9 +110,11 @@ Item { id: optionTitle; text: titleText; anchors.top: parent.top; + anchors.topMargin: 7; anchors.left: comboOptionSelected.right; - anchors.leftMargin: 20; + anchors.leftMargin: 10; anchors.right: parent.right; + anchors.rightMargin: 10; height: 30; size: comboOptionTextSize; wrapMode: Text.WordWrap; @@ -112,10 +124,10 @@ Item { id: optionBody; text: bodyText; anchors.top: optionTitle.bottom; - anchors.bottom: parent.bottom; anchors.left: comboOptionSelected.right; anchors.leftMargin: 25; anchors.right: parent.right; + anchors.rightMargin: 10; size: comboOptionTextSize; wrapMode: Text.WordWrap; } @@ -127,9 +139,8 @@ Item { onEntered: comboListViewItemContainer.color = hifi.colors.blueHighlight onExited: comboListViewItemContainer.color = selectedOptionIndex === index ? '#cee6ff' : 'white'; onClicked: { - GlobalServices.findableBy = optionValue; - UserActivityLogger.palAction("set_availability", optionValue); - print('Setting availability:', optionValue); + callbackFunction(optionValue); + combo.visible = false; } } } @@ -137,18 +148,10 @@ Item { } } - MouseArea { - anchors.fill: parent - acceptedButtons: Qt.LeftButton - onClicked: { - combo.visible = false; - } - } - function populateComboListViewModel() { comboListViewModel.clear(); optionTitleText.forEach(function(titleText, index) { comboListViewModel.insert(index, {"titleText": titleText, "bodyText": optionBodyText[index], "optionValue": optionValues[index]}); }); } -} \ No newline at end of file +} diff --git a/interface/resources/qml/hifi/Pal.qml b/interface/resources/qml/hifi/Pal.qml index 06360ce183..66803621ec 100644 --- a/interface/resources/qml/hifi/Pal.qml +++ b/interface/resources/qml/hifi/Pal.qml @@ -56,7 +56,7 @@ Rectangle { ComboDialog { id: comboDialog; z: 999; // Force the ComboDialog on top of everything else - dialogWidth: parent.width - 100; + dialogWidth: parent.width - 50; dialogHeight: parent.height - 100; } function letterbox(headerGlyph, headerText, message) { @@ -66,7 +66,13 @@ Rectangle { letterboxMessage.visible = true; letterboxMessage.popupRadius = 0; } + function popupComboDialogCallback(availability) { + GlobalServices.findableBy = availability; + UserActivityLogger.palAction("set_availability", availability); + print('Setting availability:', JSON.stringify(GlobalServices.findableBy)); + } function popupComboDialog(dialogTitleText, optionTitleText, optionBodyText, optionValues) { + comboDialog.callbackFunction = popupComboDialogCallback; comboDialog.dialogTitleText = dialogTitleText; comboDialog.optionTitleText = optionTitleText; comboDialog.optionBodyText = optionBodyText; @@ -506,6 +512,7 @@ Rectangle { // If this is an "Ignore" checkbox, disable the checkbox if user isn't present. enabled: styleData.role === "ignore" ? (model ? model["isPresent"] : true) : true; boxSize: 24; + isRedCheck: true onClicked: { var newValue = !model[styleData.role]; nearbyUserModel.setProperty(model.userIndex, styleData.role, newValue); @@ -605,7 +612,7 @@ Rectangle { "Bold names in the list are avatar display names.
" + "Purple borders around profile pictures are connections.
" + "Green borders around profile pictures are friends.
" + - "(TEMPORARY LANGUAGE) In some situations, you can also see others' usernames.
" + + "Others can find you and see your username according to your availability settings.
" + "If you can see someone's username, you can GoTo them by selecting them in the PAL, then clicking their name.
" + "
If someone's display name isn't set, a unique session display name is assigned to them.
" + "
Administrators of this domain can also see the username or machine ID associated with each avatar present."); @@ -942,7 +949,7 @@ Rectangle { } Item { id: upperRightInfoContainer; - width: 160; + width: 200; height: parent.height; anchors.top: parent.top; anchors.right: parent.right; @@ -953,59 +960,53 @@ Rectangle { // Text size size: hifi.fontSizes.tabularData; // Anchors - anchors.top: availabilityComboBox.bottom; - anchors.horizontalCenter: parent.horizontalCenter; + anchors.top: myCard.top; + anchors.left: parent.left; // Style color: hifi.colors.baseGrayHighlight; // Alignment - horizontalAlignment: Text.AlignHCenter; + horizontalAlignment: Text.AlignLeft; verticalAlignment: Text.AlignTop; } - /*Rectangle { + Rectangle { + property var availabilityStrings: ["Everyone", "Friends and Connections", "Friends Only", "Appear Offline"]; id: availabilityComboBox; + color: hifi.colors.textFieldLightBackground // Anchors - anchors.top: parent.top; + anchors.top: availabilityText.bottom; anchors.horizontalCenter: parent.horizontalCenter; // Size width: parent.width; height: 40; + function determineAvailabilityIndex() { + return ['all', 'connections', 'friends', 'none'].indexOf(GlobalServices.findableBy); + } + + function determineAvailabilityString() { + return availabilityStrings[determineAvailabilityIndex()]; + } + RalewayRegular { + text: myData.userName === "Unknown user" ? "Login to Set" : availabilityComboBox.determineAvailabilityString(); + anchors.fill: parent; + anchors.leftMargin: 10; + horizontalAlignment: Text.AlignLeft; + size: 16; + } MouseArea { - anchors.fill: parent + anchors.fill: parent; + enabled: myData.userName !== "Unknown user"; + hoverEnabled: true; onClicked: { - popupComboDialog("Set your list visibility", - ["Everyone", "Friends and Connections", "Friends Only", "Appear Offline"], - ["You will be invisible in everyone's 'People' list.\nAnyone will be able to jump to your location if the domain allows.", - "You will be visible in the 'People' list only for those with whom you are connected or friends.\nThey will be able to jump to your location if the domain allows.", - "You will be visible in the 'People' list only for those with whom you are friends.\nThey will be able to jump to your location if the domain allows.", - "You will not be visible in the 'People' list of any other users."], + popupComboDialog("Set your availability:", + availabilityComboBox.availabilityStrings, + ["Your username will be visible in everyone's 'Nearby' list.\nAnyone will be able to jump to your location from within the 'Nearby' list.", + "Your location will be visible in the 'Connections' list only for those with whom you are connected or friends.\nThey will be able to jump to your location if the domain allows.", + "Your location will be visible in the 'Connections' list only for those with whom you are friends.\nThey will be able to jump to your location if the domain allows.", + "Your location will not be visible in the 'Connections' list of any other users. Only domain admins will be able to see your username in the 'Nearby' list."], ["all", "connections", "friends", "none"]); } - } - }*/ - - HifiControlsUit.ComboBox { - function determineAvailabilityIndex() { - return ['all', 'connections', 'friends', 'none'].indexOf(GlobalServices.findableBy) - } - id: availabilityComboBox; - // Anchors - anchors.top: parent.top; - anchors.horizontalCenter: parent.horizontalCenter; - // Size - width: parent.width; - height: 40; - currentIndex: determineAvailabilityIndex(); - model: ListModel { - id: availabilityComboBoxListItems - ListElement { text: "Everyone"; value: "all"; } - ListElement { text: "All Connections"; value: "connections"; } - ListElement { text: "Friends Only"; value: "friends"; } - ListElement { text: "Appear Offline"; value: "none" } - } - onCurrentIndexChanged: { - GlobalServices.findableBy = availabilityComboBoxListItems.get(currentIndex).value; - UserActivityLogger.palAction("set_availability", availabilityComboBoxListItems.get(currentIndex).value); - print('Setting availability:', JSON.stringify(GlobalServices.findableBy)); + onEntered: availabilityComboBox.color = hifi.colors.lightGrayText; + onExited: availabilityComboBox.color = hifi.colors.textFieldLightBackground; } } } diff --git a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml index a58177640d..b0b8c68789 100644 --- a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml +++ b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml @@ -38,7 +38,7 @@ StackView { root.currentItem.focus = true; root.currentItem.forceActiveFocus(); fillDestinations(); - updateLocationText(); + updateLocationText(false); root.parentChanged.connect(center); center(); } @@ -63,8 +63,8 @@ StackView { root.push(card); return; } - addressLine.text = targetString; - toggleOrGo(true); + location.text = targetString; + toggleOrGo(true, targetString); clearAddressLineTimer.start(); } @@ -539,7 +539,11 @@ StackView { } } - function toggleOrGo(fromSuggestions) { + function toggleOrGo(fromSuggestions, address) { + if (address !== undefined && address !== "") { + addressBarDialog.loadAddress(address, fromSuggestions) + } + if (addressLine.text !== "") { addressBarDialog.loadAddress(addressLine.text, fromSuggestions) } diff --git a/interface/resources/qml/hifi/tablet/tabletWindows/preferences/Section.qml b/interface/resources/qml/hifi/tablet/tabletWindows/preferences/Section.qml index 3d6dfa10ce..9dd0956000 100644 --- a/interface/resources/qml/hifi/tablet/tabletWindows/preferences/Section.qml +++ b/interface/resources/qml/hifi/tablet/tabletWindows/preferences/Section.qml @@ -89,14 +89,17 @@ Preference { if (categoryPreferences) { console.log("Category " + root.name + " with " + categoryPreferences.length + " preferences"); for (var j = 0; j < categoryPreferences.length; ++j) { - buildPreference(categoryPreferences[j]); + //provide component position within column + //lowest numbers on top + buildPreference(categoryPreferences[j], j); } } } - function buildPreference(preference) { + function buildPreference(preference, itemNum) { console.log("\tPreference type " + preference.type + " name " + preference.name) var builder; + var zpos; switch (preference.type) { case Preference.Editable: checkBoxCount = 0; @@ -136,11 +139,14 @@ Preference { case Preference.ComboBox: checkBoxCount = 0; builder = comboBoxBuilder; + //make sure that combo boxes sitting higher will have higher z coordinate + //to be not overlapped when drop down is active + zpos = root.z + 1000 - itemNum break; }; if (builder) { - preferences.push(builder.createObject(contentContainer, { preference: preference, isFirstCheckBox: (checkBoxCount === 1) })); + preferences.push(builder.createObject(contentContainer, { preference: preference, isFirstCheckBox: (checkBoxCount === 1) , z: zpos})); } } } diff --git a/interface/resources/qml/styles-uit/HifiConstants.qml b/interface/resources/qml/styles-uit/HifiConstants.qml index a9d5a0ed2f..386af206e1 100644 --- a/interface/resources/qml/styles-uit/HifiConstants.qml +++ b/interface/resources/qml/styles-uit/HifiConstants.qml @@ -70,6 +70,9 @@ Item { readonly property color indigoAccent: "#9495FF" readonly property color magentaHighlight: "#EF93D1" readonly property color magentaAccent: "#A2277C" + readonly property color checkboxCheckedRed: "#FF0000" + readonly property color checkboxCheckedBorderRed: "#D00000" + // Semitransparent readonly property color darkGray30: "#4d121212" readonly property color darkGray0: "#00121212" diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index b8aef83445..e19eea0a2e 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -5786,22 +5786,10 @@ void Application::toggleRunningScriptsWidget() const { } void Application::showScriptLogs() { - auto tabletScriptingInterface = DependencyManager::get(); - auto tablet = dynamic_cast(tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system")); auto scriptEngines = DependencyManager::get(); QUrl defaultScriptsLoc = defaultScriptsLocation(); defaultScriptsLoc.setPath(defaultScriptsLoc.path() + "developer/debugging/debugWindow.js"); - - if (tablet->getToolbarMode()) { - scriptEngines->loadScript(defaultScriptsLoc.toString()); - } else { - QQuickItem* tabletRoot = tablet->getTabletRoot(); - if (!tabletRoot && !isHMDMode()) { - scriptEngines->loadScript(defaultScriptsLoc.toString()); - } else { - tablet->pushOntoStack("../../hifi/dialogs/TabletDebugWindow.qml"); - } - } + scriptEngines->loadScript(defaultScriptsLoc.toString()); } void Application::showAssetServerWidget(QString filePath) { diff --git a/interface/src/DiscoverabilityManager.cpp b/interface/src/DiscoverabilityManager.cpp index f042330a46..98bfa9c0c7 100644 --- a/interface/src/DiscoverabilityManager.cpp +++ b/interface/src/DiscoverabilityManager.cpp @@ -54,7 +54,7 @@ void DiscoverabilityManager::updateLocation() { const QString CONNECTED_KEY_IN_LOCATION = "connected"; locationObject.insert(CONNECTED_KEY_IN_LOCATION, discoverable && domainHandler.isConnected()); - if (discoverable) { // Don't consider changes to these as update-worthy if we're not discoverable. + if (discoverable || _lastLocationObject.isEmpty()) { // Don't consider changes to these as update-worthy if we're not discoverable. const QString PATH_KEY_IN_LOCATION = "path"; locationObject.insert(PATH_KEY_IN_LOCATION, pathString); diff --git a/interface/src/avatar/Head.cpp b/interface/src/avatar/Head.cpp index f4fb844d9b..4e748037a4 100644 --- a/interface/src/avatar/Head.cpp +++ b/interface/src/avatar/Head.cpp @@ -71,6 +71,8 @@ void Head::reset() { } void Head::simulate(float deltaTime, bool isMine) { + const float NORMAL_HZ = 60.0f; // the update rate the constant values were tuned for + // Update audio trailing average for rendering facial animations const float AUDIO_AVERAGING_SECS = 0.05f; const float AUDIO_LONG_TERM_AVERAGING_SECS = 30.0f; @@ -94,7 +96,7 @@ void Head::simulate(float deltaTime, bool isMine) { if (typeid(*faceTracker) == typeid(DdeFaceTracker)) { if (Menu::getInstance()->isOptionChecked(MenuOption::UseAudioForMouth)) { - calculateMouthShapes(); + calculateMouthShapes(deltaTime); const int JAW_OPEN_BLENDSHAPE = 21; const int MMMM_BLENDSHAPE = 34; @@ -116,7 +118,7 @@ void Head::simulate(float deltaTime, bool isMine) { _isEyeTrackerConnected = eyeTracker->isTracking(); } } - + if (!_isFaceTrackerConnected) { if (!_isEyeTrackerConnected) { @@ -144,22 +146,23 @@ void Head::simulate(float deltaTime, bool isMine) { _timeWithoutTalking += deltaTime; if ((_averageLoudness - _longTermAverageLoudness) > TALKING_LOUDNESS) { _timeWithoutTalking = 0.0f; - + } else if (_timeWithoutTalking < BLINK_AFTER_TALKING && _timeWithoutTalking >= BLINK_AFTER_TALKING) { forceBlink = true; } - + // Update audio attack data for facial animation (eyebrows and mouth) - const float AUDIO_ATTACK_AVERAGING_RATE = 0.9f; - _audioAttack = AUDIO_ATTACK_AVERAGING_RATE * _audioAttack + (1.0f - AUDIO_ATTACK_AVERAGING_RATE) * fabs((_audioLoudness - _longTermAverageLoudness) - _lastLoudness); + float audioAttackAveragingRate = (10.0f - deltaTime * NORMAL_HZ) / 10.0f; // --> 0.9 at 60 Hz + _audioAttack = audioAttackAveragingRate * _audioAttack + + (1.0f - audioAttackAveragingRate) * fabs((_audioLoudness - _longTermAverageLoudness) - _lastLoudness); _lastLoudness = (_audioLoudness - _longTermAverageLoudness); - + const float BROW_LIFT_THRESHOLD = 100.0f; if (_audioAttack > BROW_LIFT_THRESHOLD) { _browAudioLift += sqrtf(_audioAttack) * 0.01f; } _browAudioLift = glm::clamp(_browAudioLift *= 0.7f, 0.0f, 1.0f); - + const float BLINK_SPEED = 10.0f; const float BLINK_SPEED_VARIABILITY = 1.0f; const float BLINK_START_VARIABILITY = 0.25f; @@ -182,23 +185,23 @@ void Head::simulate(float deltaTime, bool isMine) { } else { _leftEyeBlink = glm::clamp(_leftEyeBlink + _leftEyeBlinkVelocity * deltaTime, FULLY_OPEN, FULLY_CLOSED); _rightEyeBlink = glm::clamp(_rightEyeBlink + _rightEyeBlinkVelocity * deltaTime, FULLY_OPEN, FULLY_CLOSED); - + if (_leftEyeBlink == FULLY_CLOSED) { _leftEyeBlinkVelocity = -BLINK_SPEED; - + } else if (_leftEyeBlink == FULLY_OPEN) { _leftEyeBlinkVelocity = 0.0f; } if (_rightEyeBlink == FULLY_CLOSED) { _rightEyeBlinkVelocity = -BLINK_SPEED; - + } else if (_rightEyeBlink == FULLY_OPEN) { _rightEyeBlinkVelocity = 0.0f; } } - + // use data to update fake Faceshift blendshape coefficients - calculateMouthShapes(); + calculateMouthShapes(deltaTime); DependencyManager::get()->updateFakeCoefficients(_leftEyeBlink, _rightEyeBlink, _browAudioLift, @@ -216,7 +219,7 @@ void Head::simulate(float deltaTime, bool isMine) { if (Menu::getInstance()->isOptionChecked(MenuOption::FixGaze)) { // if debug menu turns off, use no saccade _saccade = glm::vec3(); } - + _leftEyePosition = _rightEyePosition = getPosition(); _eyePosition = getPosition(); @@ -230,7 +233,7 @@ void Head::simulate(float deltaTime, bool isMine) { _eyePosition = calculateAverageEyePosition(); } -void Head::calculateMouthShapes() { +void Head::calculateMouthShapes(float deltaTime) { const float JAW_OPEN_SCALE = 0.015f; const float JAW_OPEN_RATE = 0.9f; const float JAW_CLOSE_RATE = 0.90f; @@ -242,20 +245,24 @@ void Head::calculateMouthShapes() { const float SMILE_SPEED = 1.0f; const float FUNNEL_SPEED = 2.335f; const float STOP_GAIN = 5.0f; + const float NORMAL_HZ = 60.0f; // the update rate the constant values were tuned for + + float deltaTimeRatio = deltaTime / (1.0f / NORMAL_HZ); // From the change in loudness, decide how much to open or close the jaw float audioDelta = sqrtf(glm::max(_averageLoudness - _longTermAverageLoudness, 0.0f)) * JAW_OPEN_SCALE; if (audioDelta > _audioJawOpen) { - _audioJawOpen += (audioDelta - _audioJawOpen) * JAW_OPEN_RATE; + _audioJawOpen += (audioDelta - _audioJawOpen) * JAW_OPEN_RATE * deltaTimeRatio; } else { - _audioJawOpen *= JAW_CLOSE_RATE; + _audioJawOpen *= powf(JAW_CLOSE_RATE, deltaTimeRatio); } _audioJawOpen = glm::clamp(_audioJawOpen, 0.0f, 1.0f); - _trailingAudioJawOpen = glm::mix(_trailingAudioJawOpen, _audioJawOpen, 0.99f); + float trailingAudioJawOpenRatio = (100.0f - deltaTime * NORMAL_HZ) / 100.0f; // --> 0.99 at 60 Hz + _trailingAudioJawOpen = glm::mix(_trailingAudioJawOpen, _audioJawOpen, trailingAudioJawOpenRatio); - // Advance time at a rate proportional to loudness, and move the mouth shapes through + // Advance time at a rate proportional to loudness, and move the mouth shapes through // a cycle at differing speeds to create a continuous random blend of shapes. - _mouthTime += sqrtf(_averageLoudness) * TIMESTEP_CONSTANT; + _mouthTime += sqrtf(_averageLoudness) * TIMESTEP_CONSTANT * deltaTimeRatio; _mouth2 = (sinf(_mouthTime * MMMM_SPEED) + 1.0f) * MMMM_POWER * glm::min(1.0f, _trailingAudioJawOpen * STOP_GAIN); _mouth3 = (sinf(_mouthTime * FUNNEL_SPEED) + 1.0f) * FUNNEL_POWER * glm::min(1.0f, _trailingAudioJawOpen * STOP_GAIN); _mouth4 = (sinf(_mouthTime * SMILE_SPEED) + 1.0f) * SMILE_POWER * glm::min(1.0f, _trailingAudioJawOpen * STOP_GAIN); @@ -321,7 +328,7 @@ glm::quat Head::getFinalOrientationInLocalFrame() const { // // Everyone else's head also keeps track of a correctedLookAtPosition that may be different for the same head within // different Interfaces. If that head is not looking at me, the correctedLookAtPosition is the same as the lookAtPosition. -// However, if that head is looking at me, then I will attempt to adjust the lookAtPosition by the difference between +// However, if that head is looking at me, then I will attempt to adjust the lookAtPosition by the difference between // my (singular) eye position and my actual camera position. This adjustment is used on their eyeballs during rendering // (and also on any lookAt vector display for that head, during rendering). Note that: // 1. this adjustment can be made directly to the other head's eyeball joints, because we won't be send their joint information to others. diff --git a/interface/src/avatar/Head.h b/interface/src/avatar/Head.h index aa801e5eb5..fd20e709f5 100644 --- a/interface/src/avatar/Head.h +++ b/interface/src/avatar/Head.h @@ -138,7 +138,7 @@ private: int _rightEyeLookAtID; // private methods - void calculateMouthShapes(); + void calculateMouthShapes(float timeRatio); void applyEyelidOffset(glm::quat headOrientation); }; diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp index e05d7c5bc9..f5d335adea 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp @@ -571,10 +571,15 @@ void OpenGLDisplayPlugin::compositeLayers() { compositeScene(); } + +#ifdef HIFI_ENABLE_NSIGHT_DEBUG + if (false) // do not compositeoverlay if running nsight debug +#endif { PROFILE_RANGE_EX(render_detail, "compositeOverlay", 0xff0077ff, (uint64_t)presentCount()) - // compositeOverlay(); + compositeOverlay(); } + auto compositorHelper = DependencyManager::get(); if (compositorHelper->getReticleVisible()) { PROFILE_RANGE_EX(render_detail, "compositePointer", 0xff0077ff, (uint64_t)presentCount()) diff --git a/libraries/gl/src/gl/Config.h b/libraries/gl/src/gl/Config.h index 7947bd45df..9efae96f2a 100644 --- a/libraries/gl/src/gl/Config.h +++ b/libraries/gl/src/gl/Config.h @@ -28,6 +28,9 @@ #include +// Uncomment this define and recompile to be able to avoid code path preventing to be able to run nsight graphics debug +//#define HIFI_ENABLE_NSIGHT_DEBUG 1 + #endif #endif // hifi_gpu_GPUConfig_h diff --git a/libraries/gl/src/gl/OffscreenQmlSurface.cpp b/libraries/gl/src/gl/OffscreenQmlSurface.cpp index 27f4f4f5dc..3bbd26e010 100644 --- a/libraries/gl/src/gl/OffscreenQmlSurface.cpp +++ b/libraries/gl/src/gl/OffscreenQmlSurface.cpp @@ -278,7 +278,9 @@ void OffscreenQmlSurface::cleanup() { } void OffscreenQmlSurface::render() { +#ifdef HIFI_ENABLE_NSIGHT_DEBUG return; +#endif if (_paused) { return; } @@ -577,7 +579,9 @@ QObject* OffscreenQmlSurface::finishQmlLoad(std::functionsetObjectOwnership(this, QQmlEngine::CppOwnership); newObject->setProperty("eventBridge", QVariant::fromValue(this)); + newContext->setContextProperty("eventBridgeJavaScriptToInject", QVariant(javaScriptToInject)); f(newContext, newObject); diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41BackendTransform.cpp b/libraries/gpu-gl/src/gpu/gl41/GL41BackendTransform.cpp index aa0a83689c..526bac9ec8 100644 --- a/libraries/gpu-gl/src/gpu/gl41/GL41BackendTransform.cpp +++ b/libraries/gpu-gl/src/gpu/gl41/GL41BackendTransform.cpp @@ -95,7 +95,6 @@ void GL41Backend::updateTransform(const Batch& batch) { } else { if (!_transform._enabledDrawcallInfoBuffer) { glEnableVertexAttribArray(gpu::Stream::DRAW_CALL_INFO); // Make sure attrib array is enabled - glBindBuffer(GL_ARRAY_BUFFER, _transform._drawCallInfoBuffer); #ifdef GPU_STEREO_DRAWCALL_INSTANCED glVertexAttribDivisor(gpu::Stream::DRAW_CALL_INFO, (isStereo() ? 2 : 1)); #else @@ -103,6 +102,7 @@ void GL41Backend::updateTransform(const Batch& batch) { #endif _transform._enabledDrawcallInfoBuffer = true; } + glBindBuffer(GL_ARRAY_BUFFER, _transform._drawCallInfoBuffer); glVertexAttribIPointer(gpu::Stream::DRAW_CALL_INFO, 2, GL_UNSIGNED_SHORT, 0, _transform._drawCallInfoOffsets[batch._currentNamedCall]); } diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index a5c94c1bb4..caa42bea5e 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -974,6 +974,8 @@ void ScriptEngine::run() { return; // bail early - avoid setting state in init(), as evaluate() will bail too } + scriptInfoMessage("Script Engine starting:" + getFilename()); + if (!_isInitialized) { init(); } diff --git a/libraries/script-engine/src/TabletScriptingInterface.cpp b/libraries/script-engine/src/TabletScriptingInterface.cpp index 91a1b1b767..b4d8977b6d 100644 --- a/libraries/script-engine/src/TabletScriptingInterface.cpp +++ b/libraries/script-engine/src/TabletScriptingInterface.cpp @@ -201,7 +201,7 @@ void TabletProxy::setToolbarMode(bool toolbarMode) { QObject::connect(quickItem, SIGNAL(windowClosed()), this, SLOT(desktopWindowClosed())); - QObject::connect(tabletRootWindow, SIGNAL(webEventReceived(QVariant)), this, SIGNAL(webEventReceived(QVariant))); + QObject::connect(tabletRootWindow, SIGNAL(webEventReceived(QVariant)), this, SLOT(emitWebEvent(QVariant)), Qt::DirectConnection); // forward qml surface events to interface js connect(tabletRootWindow, &QmlWindowClass::fromQml, this, &TabletProxy::fromQml); @@ -271,12 +271,17 @@ bool TabletProxy::isMessageDialogOpen() { return false; } +void TabletProxy::emitWebEvent(QVariant msg) { + emit webEventReceived(msg); +} + void TabletProxy::setQmlTabletRoot(QQuickItem* qmlTabletRoot, QObject* qmlOffscreenSurface) { std::lock_guard guard(_mutex); _qmlOffscreenSurface = qmlOffscreenSurface; _qmlTabletRoot = qmlTabletRoot; if (_qmlTabletRoot && _qmlOffscreenSurface) { - QObject::connect(_qmlOffscreenSurface, SIGNAL(webEventReceived(QVariant)), this, SIGNAL(webEventReceived(QVariant))); + + QObject::connect(_qmlOffscreenSurface, SIGNAL(webEventReceived(QVariant)), this, SLOT(emitWebEvent(QVariant)), Qt::DirectConnection); // forward qml surface events to interface js connect(dynamic_cast(_qmlOffscreenSurface), &OffscreenQmlSurface::fromQml, [this](QVariant message) { @@ -398,7 +403,7 @@ void TabletProxy::popFromStack() { auto stack = _desktopWindow->asQuickItem()->findChild("stack"); if (stack) { QMetaObject::invokeMethod(stack, "popSource"); - } else { + } else { qCDebug(scriptengine) << "tablet cannot pop QML because _desktopWindow doesn't have child stack"; } } else { @@ -708,4 +713,3 @@ void TabletButtonProxy::editProperties(QVariantMap properties) { } #include "TabletScriptingInterface.moc" - diff --git a/libraries/script-engine/src/TabletScriptingInterface.h b/libraries/script-engine/src/TabletScriptingInterface.h index ef9edfb296..e9ae60fee1 100644 --- a/libraries/script-engine/src/TabletScriptingInterface.h +++ b/libraries/script-engine/src/TabletScriptingInterface.h @@ -226,6 +226,7 @@ signals: protected slots: void addButtonsToHomeScreen(); void desktopWindowClosed(); + void emitWebEvent(QVariant msg); protected: void removeButtonsFromHomeScreen(); void loadHomeScreen(bool forceOntoHomeScreen); diff --git a/scripts/developer/tests/tabletEventBridgeTest.js b/scripts/developer/tests/tabletEventBridgeTest.js index 1fa935bef2..83031741fc 100644 --- a/scripts/developer/tests/tabletEventBridgeTest.js +++ b/scripts/developer/tests/tabletEventBridgeTest.js @@ -16,66 +16,92 @@ var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); var tabletButton = tablet.addButton({ - text: "SOUNDS" + text: "SOUNDS", + icon: "http://s3.amazonaws.com/hifi-public/tony/icons/trombone-i.png", + activeIcon: "http://s3.amazonaws.com/hifi-public/tony/icons/trombone-a.png" }); var WEB_BRIDGE_TEST_HTML = "https://s3.amazonaws.com/hifi-public/tony/webBridgeTest.html?2"; - var TROMBONE_URL = "https://s3.amazonaws.com/hifi-public/tony/audio/sad-trombone.wav"; -var tromboneSound = SoundCache.getSound(TROMBONE_URL); -var tromboneInjector; - var SCREAM_URL = "https://s3.amazonaws.com/hifi-public/tony/audio/wilhelm-scream.wav"; -var screamSound = SoundCache.getSound(SCREAM_URL); -var screamInjector; tabletButton.clicked.connect(function () { - tablet.gotoWebScreen(WEB_BRIDGE_TEST_HTML); + if (shown) { + tablet.gotoHomeScreen(); + } else { + tablet.gotoWebScreen(WEB_BRIDGE_TEST_HTML); + } }); -// hook up to the event bridge -tablet.webEventReceived.connect(function (msg) { +var shown = false; + +function onScreenChanged(type, url) { + if (type === "Web" && url === WEB_BRIDGE_TEST_HTML) { + tabletButton.editProperties({isActive: true}); + + if (!shown) { + // hook up to the event bridge + tablet.webEventReceived.connect(onWebEventReceived); + } + shown = true; + } else { + tabletButton.editProperties({isActive: false}); + + if (shown) { + // disconnect from the event bridge + tablet.webEventReceived.disconnect(onWebEventReceived); + } + shown = false; + } +} + +tablet.screenChanged.connect(onScreenChanged); + +// ctor +function SoundBuddy(url) { + this.sound = SoundCache.getSound(url); + this.injector = null; +} + +SoundBuddy.prototype.play = function (options, doneCallback) { + if (this.sound.downloaded) { + if (this.injector) { + this.injector.setOptions(options); + this.injector.restart(); + } else { + this.injector = Audio.playSound(this.sound, options); + this.injector.finished.connect(function () { + if (doneCallback) { + doneCallback(); + } + }); + } + } +}; + +var tromboneSound = new SoundBuddy(TROMBONE_URL); +var screamSound = new SoundBuddy(SCREAM_URL); +var soundOptions = { position: MyAvatar.position, volume: 1.0, loop: false, localOnly: true }; + +function onWebEventReceived(msg) { Script.print("HIFI: recv web event = " + JSON.stringify(msg)); if (msg === "button-1-play") { - - // play sad trombone - if (tromboneSound.downloaded) { - if (tromboneInjector) { - tromboneInjector.restart(); - } else { - tromboneInjector = Audio.playSound(tromboneSound, { position: MyAvatar.position, - volume: 1.0, - loop: false }); - } - } - - // wait until sound is finished then send a done event - Script.setTimeout(function () { + soundOptions.position = MyAvatar.position; + tromboneSound.play(soundOptions, function () { tablet.emitScriptEvent("button-1-done"); - }, 3500); - } - - if (msg === "button-2-play") { - - // play scream - if (screamSound.downloaded) { - if (screamInjector) { - screamInjector.restart(); - } else { - screamInjector = Audio.playSound(screamSound, { position: MyAvatar.position, - volume: 1.0, - loop: false }); - } - } - - // wait until sound is finished then send a done event - Script.setTimeout(function () { + }); + } else if (msg === "button-2-play") { + soundOptions.position = MyAvatar.position; + screamSound.play(soundOptions, function () { tablet.emitScriptEvent("button-2-done"); - }, 1000); + }); } -}); +} Script.scriptEnding.connect(function () { tablet.removeButton(tabletButton); + if (shown) { + tablet.webEventReceived.disconnect(onWebEventReceived); + } }); diff --git a/scripts/developer/utilities/render/photobooth/html/photobooth.html b/scripts/developer/utilities/render/photobooth/html/photobooth.html index 8964a51f05..f9c79174f3 100644 --- a/scripts/developer/utilities/render/photobooth/html/photobooth.html +++ b/scripts/developer/utilities/render/photobooth/html/photobooth.html @@ -2,166 +2,151 @@ Photo Booth + + - - - - + + body { + margin: 0; + padding: 0; + width: 100%; + color: white; + } + + .top-bar { + height: 90px; + background: linear-gradient(#2b2b2b, #1e1e1e); + font-family: Raleway-Bold; + padding-left: 30px; + padding-right: 30px; + display: flex; + align-items: center; + position: fixed; + width: 480px; + top: 0; + z-index: 1; + font-size: 16px; + } + + .content { + margin-top: 90px; + padding: 30px; + } + + .slider { + margin-left: 70px; + } + + #camera-toggle { + font-family: Raleway-Bold; + font-size: 13px; + text-transform: uppercase; + vertical-align: top; + height: 28px; + min-width: 120px; + padding: 0px 18px; + margin-right: 0px; + border-radius: 5px; + border: none; + color: #121212; + background-color: #afafaf; + background: linear-gradient(#fff 20%, #afafaf 100%); + cursor: pointer; + } + + .dropdown li { + background-color: #ffffff; + } + + + + - -
-
- - - -
- - -
- + +
+
Photobooth
+
+
+
+
+ + + +
+
+ + +
+
+ + +
+
+ +
+ + + + diff --git a/scripts/developer/utilities/render/photobooth/photobooth.js b/scripts/developer/utilities/render/photobooth/photobooth.js deleted file mode 100644 index b78986be1a..0000000000 --- a/scripts/developer/utilities/render/photobooth/photobooth.js +++ /dev/null @@ -1,178 +0,0 @@ -(function () { - var SNAPSHOT_DELAY = 500; // 500ms - var PHOTOBOOTH_WINDOW_HTML_URL = Script.resolvePath("./html/photobooth.html"); - var PHOTOBOOTH_SETUP_JSON_URL = Script.resolvePath("./photoboothSetup.json"); - var toolbar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system"); - var MODEL_BOUNDING_BOX_DIMENSIONS = {x: 1.0174,y: 1.1925,z: 1.0165}; - - var PhotoBooth = {}; - PhotoBooth.init = function () { - var success = Clipboard.importEntities(PHOTOBOOTH_SETUP_JSON_URL); - var forwardFactor = 10; - var forwardUnitVector = Vec3.normalize(Quat.getForward(MyAvatar.orientation)); - var forwardOffset = Vec3.multiply(forwardUnitVector,forwardFactor); - var rightFactor = 3; - // TODO: rightUnitVec is unused and spawnLocation declaration is incorrect - var rightUnitVec = Vec3.normalize(Quat.getRight(MyAvatar.orientation)); - var spawnLocation = Vec3.sum(Vec3.sum(MyAvatar.position,forwardOffset),rightFactor); - if (success) { - this.pastedEntityIDs = Clipboard.pasteEntities(spawnLocation); - this.processPastedEntities(); - - } - }; - - PhotoBooth.processPastedEntities = function () { - var cameraResults = {}; - var modelResult; - var modelPos; - this.pastedEntityIDs.forEach(function(id) { - var props = Entities.getEntityProperties(id); - var parts = props["name"].split(":"); - if (parts[0] === "Photo Booth Camera") { - cameraResults[parts[1]] = id; - } - if (parts[0] === "Photo Booth Model") { - modelResult = id; - modelPos = props.position; - } - }); - print(JSON.stringify(cameraResults)); - print(JSON.stringify(modelResult)); - this.cameraEntities = cameraResults; - this.modelEntityID = modelResult; - this.centrePos = modelPos; - }; - - // replace the model in scene with new model - PhotoBooth.changeModel = function (newModelURL) { - // deletes old model - Entities.deleteEntity(this.modelEntityID); - // create new model at centre of the photobooth - var newProps = { - name: "Photo Booth Model", - type: "Model", - modelURL: newModelURL, - position: this.centrePos - }; - var newModelEntityID = Entities.addEntity(newProps); - - // scale model dimensions to fit in bounding box - var scaleModel = function () { - newProps = Entities.getEntityProperties(newModelEntityID); - var myDimensions = newProps.dimensions; - print("myDimensions: " + JSON.stringify(myDimensions)); - var k; - if (myDimensions.x > MODEL_BOUNDING_BOX_DIMENSIONS.x) { - k = MODEL_BOUNDING_BOX_DIMENSIONS.x / myDimensions.x; - myDimensions = Vec3.multiply(k, myDimensions); - } - if (myDimensions.y > MODEL_BOUNDING_BOX_DIMENSIONS.y) { - k = MODEL_BOUNDING_BOX_DIMENSIONS.y / myDimensions.y; - myDimensions = Vec3.multiply(k, myDimensions); - } - if (myDimensions.z > MODEL_BOUNDING_BOX_DIMENSIONS.z) { - k = MODEL_BOUNDING_BOX_DIMENSIONS.z / myDimensions.z; - myDimensions = Vec3.multiply(k, myDimensions); - } - // position the new model on the table - var y_offset = (MODEL_BOUNDING_BOX_DIMENSIONS.y - myDimensions.y) / 2; - var myPosition = Vec3.sum(newProps.position, {x:0, y:-y_offset, z:0}); - Entities.editEntity(newModelEntityID,{position: myPosition, dimensions: myDimensions}); - }; - - // add a delay before scaling to make sure the entity server have gotten the right model dimensions - Script.setTimeout(function () { - scaleModel(); - }, 400); - - this.modelEntityID = newModelEntityID; - }; - - PhotoBooth.destroy = function () { - this.pastedEntityIDs.forEach(function(id) { - Entities.deleteEntity(id); - }); - Entities.deleteEntity(this.modelEntityID); - }; - - var main = function () { - PhotoBooth.init(); - - var photoboothWindowListener = {}; - photoboothWindowListener.onLoad = function (event) { - print("loaded" + event.value); - if (!event.hasOwnProperty("value")){ - return; - } - }; - - photoboothWindowListener.onSelectCamera = function (event) { - print("selected camera " + event.value); - if (!event.hasOwnProperty("value")){ - return; - } - if (event.value === "First Person Camera") { - Camera.mode = "first person"; - } else { - Camera.mode = "entity"; - var cameraID = PhotoBooth.cameraEntities[event.value]; - Camera.setCameraEntity(cameraID); - } - }; - - photoboothWindowListener.onSelectLightingPreset = function (event) { - print("selected lighting preset" + event.value); - if (!event.hasOwnProperty("value")){ - return; - } - }; - - photoboothWindowListener.onClickPictureButton = function (event) { - print("clicked picture button"); - // hide HUD tool bar - toolbar.writeProperty("visible", false); - // hide Overlays (such as Running Scripts or other Dialog UI) - Menu.setIsOptionChecked("Overlays", false); - // hide mouse cursor - Reticle.visible = false; - // giving a delay here before snapshotting so that there is time to hide toolbar and other UIs - // void WindowScriptingInterface::takeSnapshot(bool notify, bool includeAnimated, float aspectRatio) - Script.setTimeout(function () { - Window.takeSnapshot(false, false, 1.91); - // show hidden items after snapshot is taken - toolbar.writeProperty("visible", true); - Menu.setIsOptionChecked("Overlays", true); - // unknown issue: somehow we don't need to reset cursor to visible in script and the mouse still returns after snapshot - // Reticle.visible = true; - }, SNAPSHOT_DELAY); - }; - - photoboothWindowListener.onClickReloadModelButton = function (event) { - print("clicked reload model button " + event.value); - PhotoBooth.changeModel(event.value); - }; - - var photoboothWindow = new OverlayWebWindow({ - title: 'Photo Booth', - source: PHOTOBOOTH_WINDOW_HTML_URL, - width: 450, - height: 450, - visible: true - }); - - photoboothWindow.webEventReceived.connect(function (data) { - var event = JSON.parse(data); - if (photoboothWindowListener[event.type]) { - photoboothWindowListener[event.type](event); - } - }); - }; - main(); - - function cleanup() { - Camera.mode = "first person"; - PhotoBooth.destroy(); - } - Script.scriptEnding.connect(cleanup); -}()); \ No newline at end of file diff --git a/scripts/developer/utilities/render/photobooth/photoboothApp.js b/scripts/developer/utilities/render/photobooth/photoboothApp.js new file mode 100644 index 0000000000..154028f091 --- /dev/null +++ b/scripts/developer/utilities/render/photobooth/photoboothApp.js @@ -0,0 +1,199 @@ +// +// photobooth.js +// scripts/developer/utilities/render/photobooth +// +// Created by Faye Li on 2 Nov 2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +/* globals Tablet, Toolbars, Script, HMD, Controller, Menu */ +(function () { + var SNAPSHOT_DELAY = 500; // 500ms + var PHOTOBOOTH_WINDOW_HTML_URL = Script.resolvePath("./html/photobooth.html"); + var PHOTOBOOTH_SETUP_JSON_URL = Script.resolvePath("./photoboothSetup.json"); + var MODEL_BOUNDING_BOX_DIMENSIONS = {x: 1.0174,y: 1.1925,z: 1.0165}; + var PhotoBooth = {}; + var photoboothCreated = false; + var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + var button = tablet.addButton({ + icon: "icons/tablet-icons/snap-i.svg", + text: "PHOTOBOOTH" + }); + + function onClicked() { + if (photoboothCreated) { + tablet.gotoHomeScreen(); + PhotoBooth.destroy(); + } else { + tablet.gotoWebScreen(PHOTOBOOTH_WINDOW_HTML_URL); + PhotoBooth.init(); + } + } + + function onScreenChanged() { + if (photoboothCreated) { + tablet.gotoHomeScreen(); + PhotoBooth.destroy(); + button.editProperties({isActive: false}); + } else { + button.editProperties({isActive: true}); + } + } + tablet.screenChanged.connect(onScreenChanged); + button.clicked.connect(onClicked); + tablet.webEventReceived.connect(onWebEventReceived); + + + function onWebEventReceived(event) { + print("photobooth.js received a web event:" + event); + // Converts the event to a JavasScript Object + if (typeof event === "string") { + event = JSON.parse(event); + } + if (event.app === "photobooth") { + if (event.type === "onClickPictureButton") { + print("clicked picture button"); + // // hide HUD tool bar + // toolbar.writeProperty("visible", false); + // hide Overlays (such as Running Scripts or other Dialog UI) + Menu.setIsOptionChecked("Overlays", false); + // hide mouse cursor + Reticle.visible = false; + // hide tablet + HMD.closeTablet(); + // // giving a delay here before snapshotting so that there is time to hide other UIs + // void WindowScriptingInterface::takeSnapshot(bool notify, bool includeAnimated, float aspectRatio) + Script.setTimeout(function () { + Window.takeSnapshot(false, false, 1.91); + // show hidden items after snapshot is taken + // issue: currently there's no way to show tablet via a script command. user will have to manually open tablet again + // issue: somehow we don't need to reset cursor to visible in script and the mouse still returns after snapshot + // Reticle.visible = true; + // toolbar.writeProperty("visible", true); + Menu.setIsOptionChecked("Overlays", true); + }, SNAPSHOT_DELAY); + } else if (event.type === "onClickReloadModelButton") { + print("clicked reload model button " + event.data.value); + PhotoBooth.changeModel(event.data.value); + } else if (event.type === "onSelectCamera") { + print("selected camera " + event.data.value); + if (!event.data.hasOwnProperty("value")){ + return; + } + if (event.data.value === "First Person Camera") { + Camera.mode = "first person"; + } else { + Camera.mode = "entity"; + var cameraID = PhotoBooth.cameraEntities[event.data.value]; + Camera.setCameraEntity(cameraID); + } + } else if (event.type === "onRotateSlider") { + var props = {}; + props.rotation = Quat.fromPitchYawRollDegrees(0, event.data.value, 0); + Entities.editEntity(PhotoBooth.modelEntityID, props); + } + } + } + + PhotoBooth.init = function () { + photoboothCreated = true; + var success = Clipboard.importEntities(PHOTOBOOTH_SETUP_JSON_URL); + var frontFactor = 10; + // getForward is preffered as getFront function is deprecated + var frontUnitVec = Vec3.normalize(Quat.getFront(MyAvatar.orientation)); + var frontOffset = Vec3.multiply(frontUnitVec,frontFactor); + var upFactor = 3; + var upUnitVec = Vec3.normalize(Quat.getUp(MyAvatar.orientation)); + var upOffset = Vec3.multiply(upUnitVec, upFactor); + var spawnLocation = Vec3.sum(MyAvatar.position,frontOffset); + spawnLocation = Vec3.sum(spawnLocation, upOffset); + if (success) { + this.pastedEntityIDs = Clipboard.pasteEntities(spawnLocation); + this.processPastedEntities(); + } + }; + + PhotoBooth.processPastedEntities = function () { + var cameraResults = {}; + var modelResult; + var modelPos; + this.pastedEntityIDs.forEach(function(id) { + var props = Entities.getEntityProperties(id); + var parts = props["name"].split(":"); + if (parts[0] === "Photo Booth Camera") { + cameraResults[parts[1]] = id; + } + if (parts[0] === "Photo Booth Model") { + modelResult = id; + modelPos = props.position; + } + }); + print(JSON.stringify(cameraResults)); + print(JSON.stringify(modelResult)); + this.cameraEntities = cameraResults; + this.modelEntityID = modelResult; + this.centrePos = modelPos; + }; + + // replace the model in scene with new model + PhotoBooth.changeModel = function (newModelURL) { + // deletes old model + Entities.deleteEntity(this.modelEntityID); + // create new model at centre of the photobooth + var newProps = { + name: "Photo Booth Model", + type: "Model", + modelURL: newModelURL, + position: this.centrePos + }; + var newModelEntityID = Entities.addEntity(newProps); + + // scale model dimensions to fit in bounding box + var scaleModel = function () { + newProps = Entities.getEntityProperties(newModelEntityID); + var myDimensions = newProps.dimensions; + print("myDimensions: " + JSON.stringify(myDimensions)); + var k; + if (myDimensions.x > MODEL_BOUNDING_BOX_DIMENSIONS.x) { + k = MODEL_BOUNDING_BOX_DIMENSIONS.x / myDimensions.x; + myDimensions = Vec3.multiply(k, myDimensions); + } + if (myDimensions.y > MODEL_BOUNDING_BOX_DIMENSIONS.y) { + k = MODEL_BOUNDING_BOX_DIMENSIONS.y / myDimensions.y; + myDimensions = Vec3.multiply(k, myDimensions); + } + if (myDimensions.z > MODEL_BOUNDING_BOX_DIMENSIONS.z) { + k = MODEL_BOUNDING_BOX_DIMENSIONS.z / myDimensions.z; + myDimensions = Vec3.multiply(k, myDimensions); + } + // position the new model on the table + var y_offset = (MODEL_BOUNDING_BOX_DIMENSIONS.y - myDimensions.y) / 2; + var myPosition = Vec3.sum(newProps.position, {x:0, y:-y_offset, z:0}); + Entities.editEntity(newModelEntityID,{position: myPosition, dimensions: myDimensions}); + }; + + // add a delay before scaling to make sure the entity server have gotten the right model dimensions + Script.setTimeout(function () { + scaleModel(); + }, 400); + + this.modelEntityID = newModelEntityID; + }; + + PhotoBooth.destroy = function () { + this.pastedEntityIDs.forEach(function(id) { + Entities.deleteEntity(id); + }); + Entities.deleteEntity(this.modelEntityID); + photoboothCreated = false; + Camera.mode = "first person"; + }; + + function cleanup() { + tablet.removeButton(button); + PhotoBooth.destroy(); + } + Script.scriptEnding.connect(cleanup); +}()); \ No newline at end of file diff --git a/scripts/system/tablet-ui/tabletUI.js b/scripts/system/tablet-ui/tabletUI.js index 174153cad3..dbfd3e632e 100644 --- a/scripts/system/tablet-ui/tabletUI.js +++ b/scripts/system/tablet-ui/tabletUI.js @@ -266,6 +266,13 @@ } Script.scriptEnding.connect(function () { + + // if we reload scripts in tablet mode make sure we close the currently open window, by calling gotoHomeScreen + var tabletProxy = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + if (tabletProxy && tabletProxy.toolbarMode) { + tabletProxy.gotoHomeScreen(); + } + var tabletID = HMD.tabletID; Entities.deleteEntity(tabletID); Overlays.deleteOverlay(tabletID);