merge from upstream

This commit is contained in:
Seth Alves 2017-04-03 06:28:59 -07:00
commit 43e1cec8a3
86 changed files with 4457 additions and 1712 deletions

View file

@ -187,7 +187,7 @@ void AvatarMixer::start() {
// NOTE: nodeData->getAvatar() might be side effected, must be called when access to node/nodeData
// is guarenteed to not be accessed by other thread
// is guaranteed to not be accessed by other thread
void AvatarMixer::manageDisplayName(const SharedNodePointer& node) {
AvatarMixerClientData* nodeData = reinterpret_cast<AvatarMixerClientData*>(node->getLinkedData());
if (nodeData && nodeData->getAvatarSessionDisplayNameMustChange()) {
@ -437,17 +437,20 @@ void AvatarMixer::handleNodeIgnoreRequestPacket(QSharedPointer<ReceivedMessage>
while (message->getBytesLeftToRead()) {
// parse out the UUID being ignored from the packet
QUuid ignoredUUID = QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID));
// Reset the lastBroadcastTime for the ignored avatar to 0
// so the AvatarMixer knows it'll have to send identity data about the ignored avatar
// to the ignorer if the ignorer unignores.
nodeData->setLastBroadcastTime(ignoredUUID, 0);
// Reset the lastBroadcastTime for the ignorer (FROM THE PERSPECTIVE OF THE IGNORED) to 0
// so the AvatarMixer knows it'll have to send identity data about the ignorer
// to the ignored if the ignorer unignores.
auto ignoredNode = nodeList->nodeWithUUID(ignoredUUID);
AvatarMixerClientData* ignoredNodeData = reinterpret_cast<AvatarMixerClientData*>(ignoredNode->getLinkedData());
ignoredNodeData->setLastBroadcastTime(senderNode->getUUID(), 0);
if (nodeList->nodeWithUUID(ignoredUUID)) {
// Reset the lastBroadcastTime for the ignored avatar to 0
// so the AvatarMixer knows it'll have to send identity data about the ignored avatar
// to the ignorer if the ignorer unignores.
nodeData->setLastBroadcastTime(ignoredUUID, 0);
// Reset the lastBroadcastTime for the ignorer (FROM THE PERSPECTIVE OF THE IGNORED) to 0
// so the AvatarMixer knows it'll have to send identity data about the ignorer
// to the ignored if the ignorer unignores.
auto ignoredNode = nodeList->nodeWithUUID(ignoredUUID);
AvatarMixerClientData* ignoredNodeData = reinterpret_cast<AvatarMixerClientData*>(ignoredNode->getLinkedData());
ignoredNodeData->setLastBroadcastTime(senderNode->getUUID(), 0);
}
if (addToIgnore) {
senderNode->addIgnoredNode(ignoredUUID);

View file

@ -49,7 +49,7 @@ private:
bool _stop { false };
};
// Slave pool for audio mixers
// Slave pool for avatar mixers
// AvatarMixerSlavePool is not thread-safe! It should be instantiated and used from a single thread.
class AvatarMixerSlavePool {
using Queue = tbb::concurrent_queue<SharedNodePointer>;

View file

@ -1,7 +1,6 @@
import QtQuick 2.5
import QtQuick.Controls 1.4 as Controls
import "../../qml/menus"
import "../../qml/controls-uit"
import "../../qml/styles-uit"

View file

@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 576 576" style="enable-background:new 0 0 576 576;" xml:space="preserve">
<style type="text/css">
.st0{fill:#6D6E71;}
.st1{fill:#1398BB;}
</style>
<path class="st0" d="M498.5,272.1c0-1.8,0.2-3.6,0.4-5.4c-15.5,3.9-31,8-46.4,12.5c-3.9,1.1-7.8,3.5-10.6,6.4
c-10.1,10.2-19.7,21-29.6,31.6c-2.5-1.6-4.8-3-7-4.5c-35.4-23-70.9-45.8-106.2-69c-8.9-5.8-17.2-6.3-26.5-1.3
c-15.8,8.5-31.7,16.8-47.8,24.9c-2.3,1.1-5.7,0.1-8.5,0.1c0.3-2.8-0.3-6.3,1.1-8.3c7.6-10.6,15.5-21,23.8-31c3-3.7,7.3-7.6,11.6-8.7
c20.4-5.2,41.1-10.8,62.4-8.9c16.2,1.4,32.3,5,48.4,8c8.1,1.5,15.1,0.7,22.1-4.6c17.5-13.3,35.6-25.9,53.4-38.8
c1.6-1.2,3.9-2.2,4.5-3.9c1-2.7,2.1-6.8,0.8-8.4c-1.6-2-5.6-2.7-8.6-2.7c-1.9,0-3.8,2.2-5.6,3.5c-16.6,12-33.3,23.7-49.6,36.1
c-5.7,4.4-10.9,5.4-17.8,3.7c-27.4-7-55.1-11.2-83.2-4.6c-12.4,2.9-25.4,4.7-36.8,9.8c-7.4,3.3-13.6,4.7-21.4,3.7
c-10.3-1.4-19-4.9-27-11.9c-14.5-12.7-29.9-24.5-44.9-36.7c-1.6-1.3-3.3-3.3-4.9-3.3c-3,0-7,0.4-8.7,2.2c-1.4,1.5-0.6,5.6,0.2,8.3
c0.5,1.7,2.8,2.8,4.3,4.1c14.6,11.9,29.6,23.4,43.9,35.8c9.8,8.5,20,15.1,33.4,15.3c1.6,0,3.2,0.8,5.5,1.4
c-6.1,7.9-11.7,15.1-17.2,22.5c-7.3,9.7-7.7,18.5-1.1,26.4c6.6,8.1,16.2,9.6,26.7,4.1c15.3-7.9,30.8-15.7,45.9-24.1
c5.7-3.1,10-3,15.4,0.6c35.1,23,70.4,45.8,105.7,68.6c1.9,1.2,3.8,2.4,5.5,3.9c3.9,3.6,4.9,7.9,2.6,12.8c-2.3,4.9-6.3,7.1-11.5,6.2
c-2.8-0.5-5.5-1.9-8-3.3c-18-10.6-35.9-21.4-53.8-32.1c-2.1-1.3-4.4-3.3-6.5-3.2c-2.7,0.1-6.3,1.3-7.8,3.3c-1.2,1.6-0.3,5.5,0.9,7.7
c1,1.9,3.7,3.1,5.8,4.4c16.3,9.9,32.8,19.5,49,29.6c2.6,1.6,5.6,5.9,5.1,8.2c-0.7,3.1-4.3,6.9-7.2,7.6c-3.9,0.9-9,0.1-12.7-1.8
c-14-7.1-27.7-14.8-41.5-22.3c-1.8-1-3.7-2.7-5.4-2.5c-3.2,0.4-7.3,1-9,3.1c-1.2,1.5,0.5,5.9,1.9,8.6c0.7,1.5,3.2,2.3,5,3.2
c12.3,6.9,24.8,13.5,37,20.8c2.4,1.4,5.4,5.6,4.9,7.6c-0.8,3.1-4,7-7,7.8c-3.6,1-8.5,0.2-12.1-1.5c-10.4-4.7-20.5-10.2-30.7-15.4
c-7.4-3.7-12-3.4-13.9,1.4c-2.9,6.9,2.4,9.1,7,11.5c7.4,3.8,14.8,7.5,22.2,11.3c1.4,0.7,2.6,1.8,4.5,3.1c-16.9,8.5-32.4,0.9-48.5,0
c8.7-19.4,6.1-27.4-9-35.7c-2.5-8.3-3-15.8-6.7-20.9c-3.9-5.3-11-8.1-17.7-12.8c-0.2-9.3-5.8-16.9-16.1-20.6
c-10.6-3.9-19.5,0.2-27,7.7c-12.5-11.7-19.9-12.4-36.3-3.8c-4.3-5.7-9.3-11-12.9-17.2c-8.6-14.6-20.8-23.1-37.5-26.5
c-10.7-2.2-21-6.5-31.5-9.7c-0.4-0.1-0.9-0.3-1.3-0.4c0.3,0.8,0.5,1.7,0.6,2.5c0.2,2.3-0.6,5.7-2.3,7.4c-1.2,1.2-2.3,2.5-3.8,3.3
c0,0,0,0,0,0c0.7,0.3,1.4,0.6,2.2,0.8c1.3,0.4,2.6,0.8,3.8,1.2c13.9,4.4,27.8,8.8,41.7,13.3c2.1,0.7,4.5,1.5,5.8,3
c9,11.5,17.7,23.1,26.6,34.9c-2.9,4.2-5.7,8-8.1,12c-6,9.9-5.3,20.9,2.9,28.3c4.3,3.8,11.5,4.4,15.4,8.5c4.1,4.2,4.8,11.5,8.6,16.1
c6.1,7.6,15.2,7.6,23.9,6.2c9.1,13.6,17,16.6,32.2,12.8c10.4,11.1,21.4,14.3,30.7,7.1c5.3-4.1,10.1-4.4,16-3.3
c7.1,1.3,14.2,2.3,21.3,3.5c15.1,2.4,29,0,41.2-10c1.3-1.1,3.1-2,4.7-2.1c15.2-1.3,24.3-9.3,26.9-24.6c14.2-1.6,24.1-8.3,26.9-22.8
c1-0.2,1.6-0.5,2.3-0.5c17.2-1.8,26.5-13.8,24.5-31.1c-0.2-2,1-4.7,2.5-6.3c8.1-9,16.2-18.1,24.9-26.6c3.5-3.4,8.1-6.2,12.8-7.6
c13.9-4.2,28.1-7.6,42.1-11.7C500.9,278.6,498.4,275.6,498.5,272.1z M170.8,362.9c-3,4-7.6,4-11.5,1.3c-3.9-2.7-5.8-6.9-3.1-11.2
c4.6-7.4,9.8-14.5,15.1-21.5c1.3-1.8,4.1-2.5,5.2-3.2c7.9,0,12.5,6.9,9,12.8C181.2,348.7,176.1,355.9,170.8,362.9z M195.9,384.7
c-3.5,5.1-9.1,6.4-13.4,3.5c-4.5-3.1-5.3-8.3-1.7-13.6c9.1-13.6,18.5-27.1,27.6-40.6c2.9-4.3,7-6.3,11.5-4.2c2.8,1.3,4.6,4.6,6.9,7
c-1.6,3.5-2.4,5.9-3.7,7.8C214.1,358,205,371.4,195.9,384.7z M224.8,399.7c-1.4,2-4.3,2.9-5.6,3.7c-8.2,0.3-13.1-7.1-9.3-13.1
c7.2-11.3,14.8-22.4,22.7-33.3c2.9-4,7.7-4.8,12-1.7c4.1,3,5.6,7.3,2.7,11.8C240,378.1,232.5,389,224.8,399.7z M263.2,394.1
c-3.5,5.2-6.6,10.8-10.9,15.2c-1.8,1.8-6.7,2.3-9.2,1.2c-1.6-0.7-2.6-6.2-1.6-8.4c2.9-5.7,6.8-11.1,11-16c1.4-1.7,5.3-2.3,7.6-1.7
c2,0.5,3.5,3.4,5,5.1C264.2,391.6,263.9,393,263.2,394.1z"/>
<path class="st1" d="M288.4,507.4c-58.8,0-114-22.9-155.6-64.4c-41.5-41.5-64.4-96.8-64.4-155.6s22.9-114,64.4-155.6
c41.5-41.5,96.8-64.4,155.6-64.4s114,22.9,155.6,64.4c41.5,41.5,64.4,96.8,64.4,155.6s-22.9,114-64.4,155.6S347.1,507.4,288.4,507.4
z M288.4,80.8c-55.2,0-107,21.5-146.1,60.5s-60.5,90.9-60.5,146.1c0,55.2,21.5,107,60.5,146.1c39,39,90.9,60.5,146.1,60.5
c55.2,0,107-21.5,146.1-60.5c39-39,60.5-90.9,60.5-146.1c0-55.2-21.5-107-60.5-146.1C395.4,102.3,343.5,80.8,288.4,80.8z"/>
<path class="st1" d="M288.4,541.5c-67.9,0-131.7-26.4-179.7-74.4c-48-48-74.4-111.8-74.4-179.7s26.4-131.7,74.4-179.7
c48-48,111.8-74.4,179.7-74.4s131.7,26.4,179.7,74.4s74.4,111.8,74.4,179.7s-26.4,131.7-74.4,179.7S356.3,541.5,288.4,541.5z
M288.4,41.1c-65.8,0-127.6,25.6-174.1,72.1c-46.5,46.5-72.1,108.3-72.1,174.1S67.7,415,114.3,461.5
c46.5,46.5,108.3,72.1,174.1,72.1S416,508,462.5,461.5c46.5-46.5,72.1-108.3,72.1-174.1S509,159.8,462.5,113.3
C416,66.8,354.1,41.1,288.4,41.1z"/>
</svg>

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

View file

@ -33,6 +33,10 @@ ScrollingWindow {
addressBar.text = webview.url
}
function setProfile(profile) {
webview.profile = profile;
}
function showPermissionsBar(){
permissionsContainer.visible=true;
}

View file

@ -0,0 +1,186 @@
import QtQuick 2.5
import QtQuick.Controls 1.2
import QtWebChannel 1.0
import QtWebEngine 1.2
import "controls"
import "styles" as HifiStyles
import "styles-uit"
import "windows"
import HFWebEngineProfile 1.0
Item {
id: root
HifiConstants { id: hifi }
HifiStyles.HifiConstants { id: hifistyles }
//width: parent.width
height: 600
property variant permissionsBar: {'securityOrigin':'none','feature':'none'}
property alias url: webview.url
property WebEngineView webView: webview
property alias eventBridge: eventBridgeWrapper.eventBridge
property bool canGoBack: webview.canGoBack
property bool canGoForward: webview.canGoForward
signal loadingChanged(int status)
x: 0
y: 0
function goBack() {
webview.goBack();
}
function goForward() {
webview.goForward();
}
function gotoPage(url) {
webview.url = url;
}
function setProfile(profile) {
webview.profile = profile;
}
function reloadPage() {
webview.reloadAndBypassCache();
webview.setActiveFocusOnPress(true);
webview.setEnabled(true);
}
Item {
id:item
width: parent.width
implicitHeight: parent.height
QtObject {
id: eventBridgeWrapper
WebChannel.id: "eventBridgeWrapper"
property var eventBridge;
}
WebEngineView {
id: webview
objectName: "webEngineView"
x: 0
y: 0
width: parent.width
height: keyboardEnabled && keyboardRaised ? parent.height - keyboard.height : parent.height
profile: HFWebEngineProfile {
id: webviewProfile
storageName: "qmlWebEngine"
}
property string userScriptUrl: ""
// creates a global EventBridge object.
WebEngineScript {
id: createGlobalEventBridge
sourceCode: eventBridgeJavaScriptToInject
injectionPoint: WebEngineScript.DocumentCreation
worldId: WebEngineScript.MainWorld
}
// detects when to raise and lower virtual keyboard
WebEngineScript {
id: raiseAndLowerKeyboard
injectionPoint: WebEngineScript.Deferred
sourceUrl: resourceDirectoryUrl + "/html/raiseAndLowerKeyboard.js"
worldId: WebEngineScript.MainWorld
}
// User script.
WebEngineScript {
id: userScript
sourceUrl: webview.userScriptUrl
injectionPoint: WebEngineScript.DocumentReady // DOM ready but page load may not be finished.
worldId: WebEngineScript.MainWorld
}
userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard, userScript ]
property string newUrl: ""
webChannel.registeredObjects: [eventBridgeWrapper]
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);
});
webview.profile.httpUserAgent = "Mozilla/5.0 Chrome (HighFidelityInterface";
web.address = url;
}
onFeaturePermissionRequested: {
grantFeaturePermission(securityOrigin, feature, true);
}
onLoadingChanged: {
keyboardRaised = false;
punctuationMode = false;
keyboard.resetShiftMode(false);
// Required to support clicking on "hifi://" links
if (WebEngineView.LoadStartedStatus == loadRequest.status) {
var url = loadRequest.url.toString();
if (urlHandler.canHandleUrl(url)) {
if (urlHandler.handleUrl(url)) {
root.stop();
}
}
}
}
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;
}
var newWindow = component.createObject();
newWindow.setProfile(webview.profile);
request.openIn(newWindow.webView);
newWindow.eventBridge = web.eventBridge;
stackRoot.push(newWindow);
newWindow.webView.forceActiveFocus();
}
}
}
} // item
Keys.onPressed: {
switch(event.key) {
case Qt.Key_L:
if (event.modifiers == Qt.ControlModifier) {
event.accepted = true
addressBar.selectAll()
addressBar.forceActiveFocus()
}
break;
}
}
} // dialog

View file

@ -90,7 +90,7 @@ Original.CheckBox {
label: Label {
text: control.text
colorScheme: checkBox.colorScheme
x: checkBox.boxSize / 2
x: 2
wrapMode: Text.Wrap
enabled: checkBox.enabled
}

View file

@ -216,7 +216,7 @@ FocusScope {
anchors.leftMargin: hifi.dimensions.textPadding
anchors.verticalCenter: parent.verticalCenter
id: popupText
text: listView.model[index] ? listView.model[index] : ""
text: listView.model[index] ? listView.model[index] : (listView.model.get(index).text ? listView.model.get(index).text : "")
size: hifi.fontSizes.textFieldInput
color: hifi.colors.baseGray
}

View file

@ -48,11 +48,12 @@ TableView {
HiFiGlyphs {
id: titleSort
text: sortIndicatorOrder == Qt.AscendingOrder ? hifi.glyphs.caratUp : hifi.glyphs.caratDn
color: hifi.colors.baseGrayHighlight
color: hifi.colors.darkGray
opacity: 0.6;
size: hifi.fontSizes.tableHeadingIcon
anchors {
left: titleText.right
leftMargin: -hifi.fontSizes.tableHeadingIcon / 3 - (centerHeaderText ? 3 : 0)
leftMargin: -hifi.fontSizes.tableHeadingIcon / 3 - (centerHeaderText ? 5 : 0)
right: parent.right
rightMargin: hifi.dimensions.tablePadding
verticalCenter: titleText.verticalCenter
@ -89,7 +90,6 @@ TableView {
Rectangle {
color: "#00000000"
anchors { fill: parent; margins: -2 }
radius: hifi.dimensions.borderRadius
border.color: isLightColorScheme ? hifi.colors.lightGrayText : hifi.colors.baseGrayHighlight
border.width: 2
}

View file

@ -0,0 +1,271 @@
import QtQuick 2.5
import QtQuick.Controls 1.4
import QtWebEngine 1.1
import QtWebChannel 1.0
import "../controls-uit" as HiFiControls
import "../styles" as HifiStyles
import "../styles-uit"
import HFWebEngineProfile 1.0
import HFTabletWebEngineProfile 1.0
import "../"
Item {
id: web
width: parent.width
height: parent.height
property var parentStackItem: null
property int headerHeight: 38
property alias url: root.url
property string address: url
property alias scriptURL: root.userScriptUrl
property alias eventBridge: eventBridgeWrapper.eventBridge
property bool keyboardEnabled: HMD.active
property bool keyboardRaised: false
property bool punctuationMode: false
property bool isDesktop: false
property WebEngineView view: root
Row {
id: buttons
HifiConstants { id: hifi }
HifiStyles.HifiConstants { id: hifistyles }
height: headerHeight
spacing: 4
anchors.top: parent.top
anchors.topMargin: 8
anchors.left: parent.left
anchors.leftMargin: 8
HiFiGlyphs {
id: back;
enabled: true;
text: hifi.glyphs.backward
color: enabled ? hifistyles.colors.text : hifistyles.colors.disabledText
size: 48
MouseArea { anchors.fill: parent; onClicked: stackRoot.goBack() }
}
HiFiGlyphs {
id: forward;
enabled: stackRoot.currentItem.canGoForward;
text: hifi.glyphs.forward
color: enabled ? hifistyles.colors.text : hifistyles.colors.disabledText
size: 48
MouseArea { anchors.fill: parent; onClicked: stackRoot.currentItem.goForward() }
}
HiFiGlyphs {
id: reload;
enabled: true;
text: webview.loading ? hifi.glyphs.close : hifi.glyphs.reload
color: enabled ? hifistyles.colors.text : hifistyles.colors.disabledText
size: 48
MouseArea { anchors.fill: parent; onClicked: stackRoot.currentItem.reloadPage(); }
}
}
TextField {
id: addressBar
height: 30
anchors.right: parent.right
anchors.rightMargin: 8
anchors.left: buttons.right
anchors.leftMargin: 0
anchors.verticalCenter: buttons.verticalCenter
focus: true
text: address
Component.onCompleted: ScriptDiscoveryService.scriptsModelFilter.filterRegExp = new RegExp("^.*$", "i")
Keys.onPressed: {
switch (event.key) {
case Qt.Key_Enter:
case Qt.Key_Return:
event.accepted = true;
if (text.indexOf("http") != 0) {
text = "http://" + text;
}
//root.hidePermissionsBar();
web.keyboardRaised = false;
stackRoot.currentItem.gotoPage(text);
break;
}
}
}
StackView {
id: stackRoot
width: parent.width
height: parent.height - web.headerHeight
anchors.top: buttons.bottom
//property var goBack: currentItem.goBack();
property WebEngineView view: root
initialItem: root;
function goBack() {
if (depth > 1 ) {
if (currentItem.canGoBack) {
currentItem.goBack();
} else {
stackRoot.pop();
currentItem.webView.forceActiveFocus();
web.address = currentItem.webView.url;
}
} else {
if (currentItem.canGoBack) {
currentItem.goBack();
} else if (parentStackItem) {
web.parentStackItem.pop();
}
}
}
QtObject {
id: eventBridgeWrapper
WebChannel.id: "eventBridgeWrapper"
property var eventBridge;
}
WebEngineView {
id: root
objectName: "webEngineView"
x: 0
y: 0
width: parent.width
height: keyboardEnabled && keyboardRaised ? (parent.height - keyboard.height) : parent.height
profile: HFTabletWebEngineProfile {
id: webviewTabletProfile
storageName: "qmlTabletWebEngine"
}
property WebEngineView webView: root
function reloadPage() {
root.reload();
}
function gotoPage(url) {
root.url = url;
}
property string userScriptUrl: ""
// creates a global EventBridge object.
WebEngineScript {
id: createGlobalEventBridge
sourceCode: eventBridgeJavaScriptToInject
injectionPoint: WebEngineScript.DocumentCreation
worldId: WebEngineScript.MainWorld
}
// detects when to raise and lower virtual keyboard
WebEngineScript {
id: raiseAndLowerKeyboard
injectionPoint: WebEngineScript.Deferred
sourceUrl: resourceDirectoryUrl + "/html/raiseAndLowerKeyboard.js"
worldId: WebEngineScript.MainWorld
}
// User script.
WebEngineScript {
id: userScript
sourceUrl: root.userScriptUrl
injectionPoint: WebEngineScript.DocumentReady // DOM ready but page load may not be finished.
worldId: WebEngineScript.MainWorld
}
userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard, userScript ]
property string newUrl: ""
webChannel.registeredObjects: [eventBridgeWrapper]
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);
});
root.profile.httpUserAgent = "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Mobile Safari/537.36"
}
onFeaturePermissionRequested: {
grantFeaturePermission(securityOrigin, feature, true);
}
onLoadingChanged: {
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();
if (urlHandler.canHandleUrl(url)) {
if (urlHandler.handleUrl(url)) {
root.stop();
}
}
}
}
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 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
numeric: web.punctuationMode
anchors {
left: parent.left
right: parent.right
bottom: parent.bottom
}
}
}
Component.onCompleted: {
web.isDesktop = (typeof desktop !== "undefined");
address = url;
}
Keys.onPressed: {
switch(event.key) {
case Qt.Key_L:
if (event.modifiers == Qt.ControlModifier) {
event.accepted = true
addressBar.selectAll()
addressBar.forceActiveFocus()
}
break;
}
}
}

View file

@ -8,6 +8,8 @@ Item {
property alias url: root.url
property alias scriptURL: root.userScriptUrl
property alias eventBridge: eventBridgeWrapper.eventBridge
property alias canGoBack: root.canGoBack;
property var goBack: root.goBack;
property bool keyboardEnabled: true // FIXME - Keyboard HMD only: Default to false
property bool keyboardRaised: false
property bool punctuationMode: false
@ -101,11 +103,11 @@ Item {
}
onNewViewRequested:{
// desktop is not defined for web-entities
if (desktop) {
var component = Qt.createComponent("../Browser.qml");
var newWindow = component.createObject(desktop);
request.openIn(newWindow.webView);
// desktop is not defined for web-entities or tablet
if (typeof desktop !== "undefined") {
desktop.openBrowserWindow(request, profile);
} else {
console.log("onNewViewRequested: desktop not defined");
}
}
}

View file

@ -490,6 +490,13 @@ FocusScope {
desktop.forceActiveFocus();
}
function openBrowserWindow(request, profile) {
var component = Qt.createComponent("../Browser.qml");
var newWindow = component.createObject(desktop);
newWindow.webView.profile = profile;
request.openIn(newWindow.webView);
}
FocusHack { id: focusHack; }
Rectangle {

View file

@ -0,0 +1,154 @@
//
// ComboDialog.qml
// qml/hifi
//
// Created by Zach Fox on 3/31/2017
// Copyright 2017 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
//
import QtQuick 2.5
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 int dialogWidth;
property int dialogHeight;
property int comboOptionTextSize: 18;
FontLoader { id: ralewayRegular; source: "../../fonts/Raleway-Regular.ttf"; }
FontLoader { id: ralewaySemiBold; source: "../../fonts/Raleway-SemiBold.ttf"; }
visible: false;
id: combo;
anchors.fill: parent;
onVisibleChanged: {
populateComboListViewModel();
}
Rectangle {
id: dialogBackground;
anchors.fill: parent;
color: "black";
opacity: 0.5;
}
Rectangle {
id: dialogContainer;
color: "white";
anchors.centerIn: dialogBackground;
width: dialogWidth;
height: dialogHeight;
RalewayRegular {
id: dialogTitle;
text: dialogTitleText;
anchors.top: parent.top;
anchors.topMargin: 20;
anchors.left: parent.left;
anchors.leftMargin: 20;
size: 24;
color: 'black';
horizontalAlignment: Text.AlignLeft;
verticalAlignment: Text.AlignTop;
}
ListModel {
id: comboListViewModel;
}
ListView {
id: comboListView;
anchors.top: dialogTitle.bottom;
anchors.topMargin: 20;
anchors.bottom: parent.bottom;
anchors.left: parent.left;
anchors.right: parent.right;
model: comboListViewModel;
delegate: comboListViewDelegate;
Component {
id: comboListViewDelegate;
Rectangle {
id: comboListViewItemContainer;
// Size
height: childrenRect.height + 10;
width: dialogContainer.width;
color: selectedOptionIndex === index ? '#cee6ff' : 'white';
Rectangle {
id: comboOptionSelected;
visible: selectedOptionIndex === index ? true : false;
color: hifi.colors.blueAccent;
anchors.left: parent.left;
anchors.leftMargin: 20;
anchors.top: parent.top;
anchors.topMargin: 20;
width: 25;
height: width;
radius: width;
border.width: 3;
border.color: hifi.colors.blueHighlight;
}
RalewaySemiBold {
id: optionTitle;
text: titleText;
anchors.top: parent.top;
anchors.left: comboOptionSelected.right;
anchors.leftMargin: 20;
anchors.right: parent.right;
height: 30;
size: comboOptionTextSize;
wrapMode: Text.WordWrap;
}
RalewayRegular {
id: optionBody;
text: bodyText;
anchors.top: optionTitle.bottom;
anchors.bottom: parent.bottom;
anchors.left: comboOptionSelected.right;
anchors.leftMargin: 25;
anchors.right: parent.right;
size: comboOptionTextSize;
wrapMode: Text.WordWrap;
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton
hoverEnabled: true;
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);
}
}
}
}
}
}
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]});
});
}
}

View file

@ -14,392 +14,499 @@ import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import QtGraphicalEffects 1.0
import "../styles-uit"
import "toolbars"
// references Users, UserActivityLogger, MyAvatar, Vec3, Quat, AddressManager from root context
Item {
id: thisNameCard
// Anchors
anchors {
verticalCenter: parent.verticalCenter
leftMargin: 10
rightMargin: 10
}
// Size
width: isMyCard ? pal.myCardWidth - anchors.leftMargin : pal.nearbyNameCardWidth;
height: isMyCard ? pal.myCardHeight : pal.rowHeight;
anchors.left: parent.left
anchors.leftMargin: 5
anchors.top: parent.top;
// Properties
property string profileUrl: "";
property string defaultBaseUrl: AddressManager.metaverseServerUrl;
property string connectionStatus : ""
property string uuid: ""
property string displayName: ""
property string userName: ""
property real displayNameTextPixelSize: 18
property int usernameTextHeight: 12
property int usernameTextPixelSize: 14
property real audioLevel: 0.0
property real avgAudioLevel: 0.0
property bool isMyCard: false
property bool selected: false
property bool isAdmin: false
property bool currentlyEditingDisplayName: false
property bool isPresent: true
property string profilePicBorderColor: (connectionStatus == "connection" ? hifi.colors.indigoAccent : (connectionStatus == "friend" ? hifi.colors.greenHighlight : "transparent"))
/* User image commented out for now - will probably be re-introduced later.
Column {
Item {
id: avatarImage
visible: profileUrl !== "" && userName !== "";
// Size
height: parent.height
width: height
height: isMyCard ? 70 : 42;
width: visible ? height : 0;
anchors.top: parent.top;
anchors.topMargin: isMyCard ? 0 : 8;
anchors.left: parent.left
clip: true
Image {
id: userImage
source: "../../icons/defaultNameCardUser.png"
source: profileUrl !== "" ? ((0 === profileUrl.indexOf("http")) ? profileUrl : (defaultBaseUrl + profileUrl)) : "";
mipmap: true;
// Anchors
anchors.fill: parent
layer.enabled: true
layer.effect: OpacityMask {
maskSource: Item {
width: userImage.width;
height: userImage.height;
Rectangle {
anchors.centerIn: parent;
width: userImage.width; // This works because userImage is square
height: width;
radius: width;
}
}
}
}
AnimatedImage {
source: "../../icons/profilePicLoading.gif"
anchors.fill: parent;
visible: userImage.status != Image.Ready;
}
StateImage {
id: infoHoverImage;
visible: false;
imageURL: "../../images/info-icon-2-state.svg";
size: 32;
buttonState: 1;
anchors.centerIn: parent;
}
MouseArea {
anchors.fill: parent
enabled: selected || isMyCard;
hoverEnabled: enabled
onClicked: {
userInfoViewer.url = defaultBaseUrl + "/users/" + userName;
userInfoViewer.visible = true;
}
onEntered: infoHoverImage.visible = true;
onExited: infoHoverImage.visible = false;
}
}
// Colored border around avatarImage
Rectangle {
id: avatarImageBorder;
visible: avatarImage.visible;
anchors.verticalCenter: avatarImage.verticalCenter;
anchors.horizontalCenter: avatarImage.horizontalCenter;
width: avatarImage.width + border.width;
height: avatarImage.height + border.width;
color: "transparent"
radius: avatarImage.height;
border.color: profilePicBorderColor;
border.width: 4;
}
// DisplayName field for my card
Rectangle {
id: myDisplayName
visible: isMyCard
// Size
width: parent.width - avatarImage.width - anchors.leftMargin - anchors.rightMargin*2;
height: 40
// Anchors
anchors.top: avatarImage.top
anchors.left: avatarImage.right
anchors.leftMargin: avatarImage.visible ? 5 : 0;
anchors.rightMargin: 5;
// Style
color: hifi.colors.textFieldLightBackground
border.color: hifi.colors.blueHighlight
border.width: 0
TextInput {
id: myDisplayNameText
// Properties
text: thisNameCard.displayName
maximumLength: 256
clip: true
// Size
width: parent.width
height: parent.height
}
}
*/
Item {
id: textContainer
// Size
width: parent.width - /*avatarImage.width - parent.spacing - */parent.anchors.leftMargin - parent.anchors.rightMargin
height: selected || isMyCard ? childrenRect.height : childrenRect.height - 15
anchors.verticalCenter: parent.verticalCenter
// DisplayName field for my card
Rectangle {
id: myDisplayName
visible: isMyCard
// Size
width: parent.width + 70
height: 35
// Anchors
anchors.top: parent.top
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: -10
anchors.leftMargin: 10
anchors.right: parent.right
anchors.rightMargin: editGlyph.width + editGlyph.anchors.rightMargin
// Style
color: hifi.colors.textFieldLightBackground
border.color: hifi.colors.blueHighlight
border.width: 0
TextInput {
id: myDisplayNameText
// Properties
text: thisNameCard.displayName
maximumLength: 256
clip: true
// Size
width: parent.width
height: parent.height
// Anchors
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: 10
anchors.right: parent.right
anchors.rightMargin: editGlyph.width + editGlyph.anchors.rightMargin
// Style
color: hifi.colors.darkGray
FontLoader { id: firaSansSemiBold; source: "../../fonts/FiraSans-SemiBold.ttf"; }
font.family: firaSansSemiBold.name
font.pixelSize: displayNameTextPixelSize
selectionColor: hifi.colors.blueHighlight
selectedTextColor: "black"
// Text Positioning
verticalAlignment: TextInput.AlignVCenter
horizontalAlignment: TextInput.AlignLeft
// Signals
onEditingFinished: {
pal.sendToScript({method: 'displayNameUpdate', params: text})
cursorPosition = 0
focus = false
myDisplayName.border.width = 0
color = hifi.colors.darkGray
currentlyEditingDisplayName = false
}
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton
hoverEnabled: true
onClicked: {
myDisplayName.border.width = 1
myDisplayNameText.focus ? myDisplayNameText.cursorPosition = myDisplayNameText.positionAt(mouseX, mouseY, TextInput.CursorOnCharacter) : myDisplayNameText.selectAll();
myDisplayNameText.focus = true
myDisplayNameText.color = "black"
currentlyEditingDisplayName = true
}
onDoubleClicked: {
myDisplayNameText.selectAll();
myDisplayNameText.focus = true;
currentlyEditingDisplayName = true
}
onEntered: myDisplayName.color = hifi.colors.lightGrayText
onExited: myDisplayName.color = hifi.colors.textFieldLightBackground
}
// Edit pencil glyph
HiFiGlyphs {
id: editGlyph
text: hifi.glyphs.editPencil
// Text Size
size: displayNameTextPixelSize*1.5
// Anchors
anchors.right: parent.right
anchors.rightMargin: 5
anchors.verticalCenter: parent.verticalCenter
// Style
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
color: hifi.colors.baseGray
}
}
// Spacer for DisplayName for my card
Item {
id: myDisplayNameSpacer
width: 1
height: 4
// Anchors
anchors.top: myDisplayName.bottom
}
// DisplayName container for others' cards
Item {
id: displayNameContainer
visible: !isMyCard
// Size
width: parent.width
height: displayNameTextPixelSize + 4
// Anchors
anchors.top: parent.top
anchors.left: parent.left
// DisplayName Text for others' cards
FiraSansSemiBold {
id: displayNameText
// Properties
text: thisNameCard.displayName
elide: Text.ElideRight
// Size
width: isAdmin ? Math.min(displayNameTextMetrics.tightBoundingRect.width + 8, parent.width - adminLabelText.width - adminLabelQuestionMark.width + 8) : parent.width
// Anchors
anchors.top: parent.top
anchors.left: parent.left
// Text Size
size: displayNameTextPixelSize
// Text Positioning
verticalAlignment: Text.AlignVCenter
// Style
color: hifi.colors.darkGray
}
TextMetrics {
id: displayNameTextMetrics
font: displayNameText.font
text: displayNameText.text
}
// "ADMIN" label for other users' cards
RalewaySemiBold {
id: adminLabelText
visible: isAdmin
text: "ADMIN"
// Text size
size: displayNameText.size - 4
// Anchors
anchors.verticalCenter: parent.verticalCenter
anchors.left: displayNameText.right
// Style
font.capitalization: Font.AllUppercase
color: hifi.colors.redHighlight
// Alignment
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignTop
}
// This Rectangle refers to the [?] popup button next to "ADMIN"
Item {
id: adminLabelQuestionMark
visible: isAdmin
// Size
width: 20
height: displayNameText.height
// Anchors
anchors.verticalCenter: parent.verticalCenter
anchors.left: adminLabelText.right
RalewayRegular {
id: adminLabelQuestionMarkText
text: "[?]"
size: adminLabelText.size
font.capitalization: Font.AllUppercase
color: hifi.colors.redHighlight
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
anchors.fill: parent
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton
hoverEnabled: true
onClicked: letterbox(hifi.glyphs.question,
"Domain Admin",
"This user is an admin on this domain. Admins can <b>Silence</b> and <b>Ban</b> other users at their discretion - so be extra nice!")
onEntered: adminLabelQuestionMarkText.color = "#94132e"
onExited: adminLabelQuestionMarkText.color = hifi.colors.redHighlight
}
}
}
// UserName Text
FiraSansRegular {
id: userNameText
// Properties
text: thisNameCard.userName
elide: Text.ElideRight
visible: thisNameCard.displayName
// Size
width: parent.width
// Anchors
anchors.top: isMyCard ? myDisplayNameSpacer.bottom : displayNameContainer.bottom
// Text Size
size: thisNameCard.usernameTextHeight
color: hifi.colors.darkGray
FontLoader { id: firaSansSemiBold; source: "../../fonts/FiraSans-SemiBold.ttf"; }
font.family: firaSansSemiBold.name
font.pixelSize: displayNameTextPixelSize
selectionColor: hifi.colors.blueAccent
selectedTextColor: "black"
// Text Positioning
verticalAlignment: Text.AlignVCenter
verticalAlignment: TextInput.AlignVCenter
horizontalAlignment: TextInput.AlignLeft
autoScroll: false;
// Signals
onEditingFinished: {
if (MyAvatar.displayName !== text) {
MyAvatar.displayName = text;
UserActivityLogger.palAction("display_name_change", text);
}
cursorPosition = 0
focus = false
myDisplayName.border.width = 0
color = hifi.colors.darkGray
pal.currentlyEditingDisplayName = false
autoScroll = false;
}
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
onClicked: {
myDisplayName.border.width = 1
myDisplayNameText.focus ? myDisplayNameText.cursorPosition = myDisplayNameText.positionAt(mouseX, mouseY, TextInput.CursorOnCharacter) : myDisplayNameText.selectAll();
myDisplayNameText.focus = true
myDisplayNameText.color = "black"
pal.currentlyEditingDisplayName = true
myDisplayNameText.autoScroll = true;
}
onDoubleClicked: {
myDisplayNameText.selectAll();
myDisplayNameText.focus = true;
pal.currentlyEditingDisplayName = true
myDisplayNameText.autoScroll = true;
}
onEntered: myDisplayName.color = hifi.colors.lightGrayText;
onExited: myDisplayName.color = hifi.colors.textFieldLightBackground;
}
// Edit pencil glyph
HiFiGlyphs {
id: editGlyph
text: hifi.glyphs.editPencil
// Text Size
size: displayNameTextPixelSize*1.5
// Anchors
anchors.right: parent.right
anchors.rightMargin: 5
anchors.verticalCenter: parent.verticalCenter
// Style
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
color: hifi.colors.baseGray
}
// Spacer
Item {
id: userNameSpacer
height: 4
width: parent.width
// Anchors
anchors.top: userNameText.bottom
}
// VU Meter
Rectangle {
id: nameCardVUMeter
// Size
width: isMyCard ? myDisplayName.width - 70 : ((gainSlider.value - gainSlider.minimumValue)/(gainSlider.maximumValue - gainSlider.minimumValue)) * parent.width
height: 8
// Anchors
anchors.top: userNameSpacer.bottom
// Style
radius: 4
color: "#c5c5c5"
visible: isMyCard || selected
// Rectangle for the zero-gain point on the VU meter
Rectangle {
id: vuMeterZeroGain
visible: gainSlider.visible
// Size
width: 4
height: 18
// Style
color: hifi.colors.darkGray
// Anchors
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: (-gainSlider.minimumValue)/(gainSlider.maximumValue - gainSlider.minimumValue) * gainSlider.width - 4
}
// Rectangle for the VU meter line
Rectangle {
id: vuMeterLine
width: gainSlider.width
visible: gainSlider.visible
// Style
color: vuMeterBase.color
radius: nameCardVUMeter.radius
height: nameCardVUMeter.height / 2
anchors.verticalCenter: nameCardVUMeter.verticalCenter
}
// Rectangle for the VU meter base
Rectangle {
id: vuMeterBase
// Anchors
anchors.fill: parent
visible: isMyCard || selected
// Style
color: parent.color
radius: parent.radius
}
// Rectangle for the VU meter audio level
Rectangle {
id: vuMeterLevel
visible: isMyCard || selected
// Size
width: (thisNameCard.audioLevel) * parent.width
// Style
color: parent.color
radius: parent.radius
// Anchors
anchors.bottom: parent.bottom
anchors.top: parent.top
anchors.left: parent.left
}
// Gradient for the VU meter audio level
LinearGradient {
anchors.fill: vuMeterLevel
source: vuMeterLevel
start: Qt.point(0, 0)
end: Qt.point(parent.width, 0)
gradient: Gradient {
GradientStop { position: 0.0; color: "#2c8e72" }
GradientStop { position: 0.9; color: "#1fc6a6" }
GradientStop { position: 0.91; color: "#ea4c5f" }
GradientStop { position: 1.0; color: "#ea4c5f" }
}
}
}
// Per-Avatar Gain Slider
Slider {
id: gainSlider
// Size
width: parent.width
height: 14
// Anchors
anchors.verticalCenter: nameCardVUMeter.verticalCenter
}
// DisplayName container for others' cards
Item {
id: displayNameContainer
visible: !isMyCard && pal.activeTab !== "connectionsTab"
// Size
width: parent.width - anchors.leftMargin - avatarImage.width - anchors.leftMargin;
height: displayNameTextPixelSize + 4
// Anchors
anchors.top: avatarImage.top;
anchors.left: avatarImage.right
anchors.leftMargin: avatarImage.visible ? 5 : 0;
// DisplayName Text for others' cards
FiraSansSemiBold {
id: displayNameText
// Properties
visible: !isMyCard && selected
value: Users.getAvatarGain(uuid)
minimumValue: -60.0
maximumValue: 20.0
stepSize: 5
updateValueWhileDragging: true
onValueChanged: updateGainFromQML(uuid, value, false)
onPressedChanged: {
if (!pressed) {
updateGainFromQML(uuid, value, true)
text: thisNameCard.displayName
elide: Text.ElideRight
// Size
width: isAdmin ? Math.min(displayNameTextMetrics.tightBoundingRect.width + 8, parent.width - adminLabelText.width - adminLabelQuestionMark.width + 8) : parent.width
// Anchors
anchors.top: parent.top
anchors.left: parent.left
// Text Size
size: displayNameTextPixelSize
// Text Positioning
verticalAlignment: Text.AlignTop
// Style
color: hifi.colors.darkGray;
MouseArea {
anchors.fill: parent
enabled: selected && pal.activeTab == "nearbyTab" && thisNameCard.userName !== "" && isPresent;
hoverEnabled: enabled
onClicked: {
goToUserInDomain(thisNameCard.uuid);
UserActivityLogger.palAction("go_to_user_in_domain", thisNameCard.uuid);
}
onEntered: {
displayNameText.color = hifi.colors.blueHighlight;
userNameText.color = hifi.colors.blueHighlight;
}
onExited: {
displayNameText.color = hifi.colors.darkGray
userNameText.color = hifi.colors.blueAccent;
}
}
}
TextMetrics {
id: displayNameTextMetrics
font: displayNameText.font
text: displayNameText.text
}
// "ADMIN" label for other users' cards
RalewaySemiBold {
id: adminLabelText
visible: isAdmin
text: "ADMIN"
// Text size
size: displayNameText.size - 4
// Anchors
anchors.verticalCenter: parent.verticalCenter
anchors.left: displayNameText.right
// Style
font.capitalization: Font.AllUppercase
color: hifi.colors.redHighlight
// Alignment
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignTop
}
// This Rectangle refers to the [?] popup button next to "ADMIN"
Item {
id: adminLabelQuestionMark
visible: isAdmin
// Size
width: 20
height: displayNameText.height
// Anchors
anchors.verticalCenter: parent.verticalCenter
anchors.left: adminLabelText.right
RalewayRegular {
id: adminLabelQuestionMarkText
text: "[?]"
size: adminLabelText.size
font.capitalization: Font.AllUppercase
color: hifi.colors.redHighlight
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
anchors.fill: parent
}
MouseArea {
anchors.fill: parent
onWheel: {
// Do nothing.
}
onDoubleClicked: {
gainSlider.value = 0.0
}
onPressed: {
// Pass through to Slider
mouse.accepted = false
}
onReleased: {
// the above mouse.accepted seems to make this
// never get called, nonetheless...
mouse.accepted = false
}
}
style: SliderStyle {
groove: Rectangle {
color: "#c5c5c5"
implicitWidth: gainSlider.width
implicitHeight: 4
radius: 2
opacity: 0
}
handle: Rectangle {
anchors.centerIn: parent
color: (control.pressed || control.hovered) ? "#00b4ef" : "#8F8F8F"
implicitWidth: 10
implicitHeight: 16
}
enabled: isPresent
hoverEnabled: enabled
onClicked: letterbox(hifi.glyphs.question,
"Domain Admin",
"This user is an admin on this domain. Admins can <b>Silence</b> and <b>Ban</b> other users at their discretion - so be extra nice!")
onEntered: adminLabelQuestionMarkText.color = "#94132e"
onExited: adminLabelQuestionMarkText.color = hifi.colors.redHighlight
}
}
}
// UserName Text
FiraSansRegular {
id: userNameText
// Properties
text: thisNameCard.userName === "Unknown user" ? "not logged in" : thisNameCard.userName;
elide: Text.ElideRight
visible: thisNameCard.userName !== "";
// Size
width: parent.width
height: pal.activeTab == "nearbyTab" || isMyCard ? usernameTextPixelSize + 4 : parent.height;
// Anchors
anchors.top: isMyCard ? myDisplayName.bottom : (pal.activeTab == "nearbyTab" ? displayNameContainer.bottom : parent.top);
anchors.left: avatarImage.right;
anchors.leftMargin: avatarImage.visible ? 5 : 0;
anchors.rightMargin: 5;
// Text Size
size: pal.activeTab == "nearbyTab" || isMyCard ? usernameTextPixelSize : displayNameTextPixelSize;
// Text Positioning
verticalAlignment: Text.AlignVCenter;
// Style
color: hifi.colors.blueAccent;
MouseArea {
anchors.fill: parent
enabled: selected && pal.activeTab == "nearbyTab" && thisNameCard.userName !== "" && isPresent;
hoverEnabled: enabled
onClicked: {
goToUserInDomain(thisNameCard.uuid);
UserActivityLogger.palAction("go_to_user_in_domain", thisNameCard.uuid);
}
onEntered: {
displayNameText.color = hifi.colors.blueHighlight;
userNameText.color = hifi.colors.blueHighlight;
}
onExited: {
displayNameText.color = hifi.colors.darkGray;
userNameText.color = hifi.colors.blueAccent;
}
}
}
// VU Meter
Rectangle {
id: nameCardVUMeter
// Size
width: isMyCard ? myDisplayName.width - 20 : ((gainSlider.value - gainSlider.minimumValue)/(gainSlider.maximumValue - gainSlider.minimumValue)) * (gainSlider.width);
height: 8
// Anchors
anchors.bottom: isMyCard ? avatarImage.bottom : parent.bottom;
anchors.bottomMargin: isMyCard ? 0 : height;
anchors.left: isMyCard ? userNameText.left : parent.left;
// Style
radius: 4
color: "#c5c5c5"
visible: (isMyCard || (selected && pal.activeTab == "nearbyTab")) && isPresent
// Rectangle for the zero-gain point on the VU meter
Rectangle {
id: vuMeterZeroGain
visible: gainSlider.visible
// Size
width: 4
height: 18
// Style
color: hifi.colors.darkGray
// Anchors
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: (-gainSlider.minimumValue)/(gainSlider.maximumValue - gainSlider.minimumValue) * gainSlider.width - 4
}
// Rectangle for the VU meter line
Rectangle {
id: vuMeterLine
width: gainSlider.width
visible: gainSlider.visible
// Style
color: vuMeterBase.color
radius: nameCardVUMeter.radius
height: nameCardVUMeter.height / 2
anchors.verticalCenter: nameCardVUMeter.verticalCenter
}
// Rectangle for the VU meter base
Rectangle {
id: vuMeterBase
// Anchors
anchors.fill: parent
visible: isMyCard || selected
// Style
color: parent.color
radius: parent.radius
}
// Rectangle for the VU meter audio level
Rectangle {
id: vuMeterLevel
visible: isMyCard || selected
// Size
width: (thisNameCard.audioLevel) * parent.width
// Style
color: parent.color
radius: parent.radius
// Anchors
anchors.bottom: parent.bottom
anchors.top: parent.top
anchors.left: parent.left
}
// Gradient for the VU meter audio level
LinearGradient {
anchors.fill: vuMeterLevel
source: vuMeterLevel
start: Qt.point(0, 0)
end: Qt.point(parent.width, 0)
gradient: Gradient {
GradientStop { position: 0.0; color: "#2c8e72" }
GradientStop { position: 0.9; color: "#1fc6a6" }
GradientStop { position: 0.91; color: "#ea4c5f" }
GradientStop { position: 1.0; color: "#ea4c5f" }
}
}
}
// Per-Avatar Gain Slider
Slider {
id: gainSlider
// Size
width: thisNameCard.width;
height: 14
// Anchors
anchors.verticalCenter: nameCardVUMeter.verticalCenter;
anchors.left: nameCardVUMeter.left;
// Properties
visible: !isMyCard && selected && pal.activeTab == "nearbyTab" && isPresent;
value: Users.getAvatarGain(uuid)
minimumValue: -60.0
maximumValue: 20.0
stepSize: 5
updateValueWhileDragging: true
onValueChanged: {
if (uuid !== "") {
updateGainFromQML(uuid, value, false);
}
}
onPressedChanged: {
if (!pressed) {
updateGainFromQML(uuid, value, true)
}
}
MouseArea {
anchors.fill: parent
onWheel: {
// Do nothing.
}
onDoubleClicked: {
gainSlider.value = 0.0
}
onPressed: {
// Pass through to Slider
mouse.accepted = false
}
onReleased: {
// the above mouse.accepted seems to make this
// never get called, nonetheless...
mouse.accepted = false
}
}
style: SliderStyle {
groove: Rectangle {
color: "#c5c5c5"
implicitWidth: gainSlider.width
implicitHeight: 4
radius: 2
opacity: 0
}
handle: Rectangle {
anchors.centerIn: parent
color: (control.pressed || control.hovered) ? "#00b4ef" : "#8F8F8F"
implicitWidth: 10
implicitHeight: 16
}
}
}
function updateGainFromQML(avatarUuid, sliderValue, isReleased) {
Users.setAvatarGain(avatarUuid, sliderValue);
if (isReleased) {
UserActivityLogger.palAction("avatar_gain_changed", avatarUuid);
}
}
// Function body by Howard Stearns 2017-01-08
function goToUserInDomain(avatarUuid) {
var avatar = AvatarList.getAvatar(avatarUuid);
if (!avatar) {
console.log("This avatar is no longer present. goToUserInDomain() failed.");
return;
}
var vector = Vec3.subtract(avatar.position, MyAvatar.position);
var distance = Vec3.length(vector);
var target = Vec3.multiply(Vec3.normalize(vector), distance - 2.0);
// FIXME: We would like the avatar to recompute the avatar's "maybe fly" test at the new position, so that if high enough up,
// the avatar goes into fly mode rather than falling. However, that is not exposed to Javascript right now.
// FIXME: it would be nice if this used the same teleport steps and smoothing as in the teleport.js script.
// Note, however, that this script allows teleporting to a person in the air, while teleport.js is going to a grounded target.
MyAvatar.orientation = Quat.lookAtSimple(MyAvatar.position, avatar.position);
MyAvatar.position = Vec3.sum(MyAvatar.position, target);
}
}

File diff suppressed because it is too large Load diff

View file

@ -27,7 +27,7 @@ StackView {
initialItem: addressBarDialog
width: parent.width
height: parent.height
property var eventBridge;
property var allStories: [];
property int cardWidth: 460;
property int cardHeight: 320;
@ -59,6 +59,7 @@ StackView {
if (0 !== targetString.indexOf('hifi://')) {
var card = tabletStoryCard.createObject();
card.setUrl(addressBarDialog.metaverseServerUrl + targetString);
card.eventBridge = root.eventBridge;
root.push(card);
return;
}

View file

@ -17,7 +17,8 @@ import "../../windows"
import "../"
import "../toolbars"
import "../../styles-uit" as HifiStyles
import "../../controls-uit" as HifiControls
import "../../controls-uit" as HifiControlsUit
import "../../controls" as HifiControls
Rectangle {
@ -26,104 +27,17 @@ Rectangle {
width: parent.width
height: parent.height
property string address: ""
property alias eventBridge: webview.eventBridge
function setUrl(url) {
cardRoot.address = url;
webview.url = url;
}
function goBack() {
}
function visit() {
}
Rectangle {
id: header
anchors {
left: parent.left
right: parent.right
top: parent.top
}
width: parent.width
height: 50
color: hifi.colors.white
Row {
anchors.fill: parent
spacing: 80
Item {
id: backButton
anchors {
top: parent.top
left: parent.left
leftMargin: 100
}
height: parent.height
width: parent.height
HifiStyles.FiraSansSemiBold {
text: "BACK"
elide: Text.ElideRight
anchors.fill: parent
size: 16
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
color: hifi.colors.lightGray
MouseArea {
id: backButtonMouseArea
anchors.fill: parent
hoverEnabled: enabled
onClicked: {
webview.goBack();
}
}
}
}
Item {
id: closeButton
anchors {
top: parent.top
right: parent.right
rightMargin: 100
}
height: parent.height
width: parent.height
HifiStyles.FiraSansSemiBold {
text: "CLOSE"
elide: Text.ElideRight
anchors.fill: parent
size: 16
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
color: hifi.colors.lightGray
MouseArea {
id: closeButtonMouseArea
anchors.fill: parent
hoverEnabled: enabled
onClicked: root.pop();
}
}
}
}
}
HifiControls.WebView {
HifiControls.TabletWebView {
id: webview
parentStackItem: root
anchors {
top: header.bottom
top: parent.top
right: parent.right
left: parent.left
bottom: parent.bottom

View file

@ -172,7 +172,7 @@ Item {
readonly property real textFieldInputLabel: dimensions.largeScreen ? 13 : 9
readonly property real textFieldSearchIcon: dimensions.largeScreen ? 30 : 24
readonly property real tableHeading: dimensions.largeScreen ? 12 : 10
readonly property real tableHeadingIcon: dimensions.largeScreen ? 40 : 33
readonly property real tableHeadingIcon: dimensions.largeScreen ? 60 : 33
readonly property real tableText: dimensions.largeScreen ? 15 : 12
readonly property real buttonLabel: dimensions.largeScreen ? 13 : 9
readonly property real iconButton: dimensions.largeScreen ? 13 : 9

View file

@ -141,6 +141,7 @@
#include "LODManager.h"
#include "ModelPackager.h"
#include "networking/HFWebEngineProfile.h"
#include "networking/HFTabletWebEngineProfile.h"
#include "scripting/TestScriptingInterface.h"
#include "scripting/AccountScriptingInterface.h"
#include "scripting/AssetMappingsScriptingInterface.h"
@ -179,6 +180,7 @@
#include "FrameTimingsScriptingInterface.h"
#include <GPUIdent.h>
#include <gl/GLHelpers.h>
#include <src/scripting/LimitlessVoiceRecognitionScriptingInterface.h>
#include <EntityScriptClient.h>
#include <ModelScriptingInterface.h>
@ -521,7 +523,9 @@ bool setupEssentials(int& argc, char** argv) {
DependencyManager::set<OffscreenQmlSurfaceCache>();
DependencyManager::set<EntityScriptClient>();
DependencyManager::set<EntityScriptServerLogClient>();
DependencyManager::set<LimitlessVoiceRecognitionScriptingInterface>();
DependencyManager::set<OctreeStatsProvider>(nullptr, qApp->getOcteeSceneStats());
return previousSessionCrashed;
}
@ -548,7 +552,7 @@ const float DEFAULT_HMD_TABLET_SCALE_PERCENT = 100.0f;
const float DEFAULT_DESKTOP_TABLET_SCALE_PERCENT = 75.0f;
const bool DEFAULT_DESKTOP_TABLET_BECOMES_TOOLBAR = true;
const bool DEFAULT_HMD_TABLET_BECOMES_TOOLBAR = false;
const bool DEFAULT_PREFER_AVATAR_FINGER_OVER_STYLUS = true;
const bool DEFAULT_PREFER_AVATAR_FINGER_OVER_STYLUS = false;
Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bool runServer, QString runServerPathOption) :
QApplication(argc, argv),
@ -1929,6 +1933,7 @@ void Application::initializeUi() {
qmlRegisterType<Preference>("Hifi", 1, 0, "Preference");
qmlRegisterType<HFWebEngineProfile>("HFWebEngineProfile", 1, 0, "HFWebEngineProfile");
qmlRegisterType<HFTabletWebEngineProfile>("HFTabletWebEngineProfile", 1, 0, "HFTabletWebEngineProfile");
auto offscreenUi = DependencyManager::get<OffscreenUi>();
offscreenUi->create(_glWidget->qglContext());
@ -4562,6 +4567,8 @@ void Application::update(float deltaTime) {
}
AnimDebugDraw::getInstance().update();
DependencyManager::get<LimitlessVoiceRecognitionScriptingInterface>()->update();
}
void Application::sendAvatarViewFrustum() {
@ -5508,6 +5515,8 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri
scriptEngine->registerGlobalObject("UserActivityLogger", DependencyManager::get<UserActivityLoggerScriptingInterface>().data());
scriptEngine->registerGlobalObject("Users", DependencyManager::get<UsersScriptingInterface>().data());
scriptEngine->registerGlobalObject("LimitlessSpeechRecognition", DependencyManager::get<LimitlessVoiceRecognitionScriptingInterface>().data());
if (auto steamClient = PluginManager::getInstance()->getSteamClientPlugin()) {
scriptEngine->registerGlobalObject("Steam", new SteamScriptingInterface(scriptEngine, steamClient.get()));
}

View file

@ -40,9 +40,10 @@ void DiscoverabilityManager::updateLocation() {
auto accountManager = DependencyManager::get<AccountManager>();
auto addressManager = DependencyManager::get<AddressManager>();
auto& domainHandler = DependencyManager::get<NodeList>()->getDomainHandler();
bool discoverable = (_mode.get() != Discoverability::None);
if (_mode.get() != Discoverability::None && accountManager->isLoggedIn()) {
if (accountManager->isLoggedIn()) {
// construct a QJsonObject given the user's current address information
QJsonObject rootObject;
@ -50,34 +51,40 @@ void DiscoverabilityManager::updateLocation() {
QString pathString = addressManager->currentPath();
const QString PATH_KEY_IN_LOCATION = "path";
locationObject.insert(PATH_KEY_IN_LOCATION, pathString);
const QString CONNECTED_KEY_IN_LOCATION = "connected";
locationObject.insert(CONNECTED_KEY_IN_LOCATION, domainHandler.isConnected());
locationObject.insert(CONNECTED_KEY_IN_LOCATION, discoverable && domainHandler.isConnected());
if (!addressManager->getRootPlaceID().isNull()) {
const QString PLACE_ID_KEY_IN_LOCATION = "place_id";
locationObject.insert(PLACE_ID_KEY_IN_LOCATION,
uuidStringWithoutCurlyBraces(addressManager->getRootPlaceID()));
if (discoverable) { // 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);
if (!addressManager->getRootPlaceID().isNull()) {
const QString PLACE_ID_KEY_IN_LOCATION = "place_id";
locationObject.insert(PLACE_ID_KEY_IN_LOCATION,
uuidStringWithoutCurlyBraces(addressManager->getRootPlaceID()));
}
if (!domainHandler.getUUID().isNull()) {
const QString DOMAIN_ID_KEY_IN_LOCATION = "domain_id";
locationObject.insert(DOMAIN_ID_KEY_IN_LOCATION,
uuidStringWithoutCurlyBraces(domainHandler.getUUID()));
}
// in case the place/domain isn't in the database, we send the network address and port
auto& domainSockAddr = domainHandler.getSockAddr();
const QString NETWORK_ADDRESS_KEY_IN_LOCATION = "network_address";
locationObject.insert(NETWORK_ADDRESS_KEY_IN_LOCATION, domainSockAddr.getAddress().toString());
const QString NETWORK_ADDRESS_PORT_IN_LOCATION = "network_port";
locationObject.insert(NETWORK_ADDRESS_PORT_IN_LOCATION, domainSockAddr.getPort());
const QString NODE_ID_IN_LOCATION = "node_id";
const int UUID_REAL_LENGTH = 36;
locationObject.insert(NODE_ID_IN_LOCATION, DependencyManager::get<NodeList>()->getSessionUUID().toString().mid(1, UUID_REAL_LENGTH));
}
if (!domainHandler.getUUID().isNull()) {
const QString DOMAIN_ID_KEY_IN_LOCATION = "domain_id";
locationObject.insert(DOMAIN_ID_KEY_IN_LOCATION,
uuidStringWithoutCurlyBraces(domainHandler.getUUID()));
}
// in case the place/domain isn't in the database, we send the network address and port
auto& domainSockAddr = domainHandler.getSockAddr();
const QString NETWORK_ADRESS_KEY_IN_LOCATION = "network_address";
locationObject.insert(NETWORK_ADRESS_KEY_IN_LOCATION, domainSockAddr.getAddress().toString());
const QString NETWORK_ADDRESS_PORT_IN_LOCATION = "network_port";
locationObject.insert(NETWORK_ADDRESS_PORT_IN_LOCATION, domainSockAddr.getPort());
const QString FRIENDS_ONLY_KEY_IN_LOCATION = "friends_only";
locationObject.insert(FRIENDS_ONLY_KEY_IN_LOCATION, (_mode.get() == Discoverability::Friends));
const QString AVAILABILITY_KEY_IN_LOCATION = "availability";
locationObject.insert(AVAILABILITY_KEY_IN_LOCATION, findableByString(static_cast<Discoverability::Mode>(_mode.get())));
JSONCallbackParameters callbackParameters;
callbackParameters.jsonCallbackReceiver = this;
@ -139,19 +146,29 @@ void DiscoverabilityManager::setDiscoverabilityMode(Discoverability::Mode discov
// update the setting to the new value
_mode.set(static_cast<int>(discoverabilityMode));
if (static_cast<int>(_mode.get()) == Discoverability::None) {
// if we just got set to no discoverability, make sure that we delete our location in DB
removeLocation();
} else {
// we have a discoverability mode that says we should send a location, do that right away
updateLocation();
}
updateLocation(); // update right away
emit discoverabilityModeChanged(discoverabilityMode);
}
}
QString DiscoverabilityManager::findableByString(Discoverability::Mode discoverabilityMode) {
if (discoverabilityMode == Discoverability::None) {
return "none";
} else if (discoverabilityMode == Discoverability::Friends) {
return "friends";
} else if (discoverabilityMode == Discoverability::Connections) {
return "connections";
} else if (discoverabilityMode == Discoverability::All) {
return "all";
} else {
qDebug() << "GlobalServices findableByString called with an unrecognized value.";
return "";
}
}
void DiscoverabilityManager::setVisibility() {
Menu* menu = Menu::getInstance();

View file

@ -19,6 +19,7 @@ namespace Discoverability {
enum Mode {
None,
Friends,
Connections,
All
};
}
@ -42,6 +43,9 @@ public slots:
signals:
void discoverabilityModeChanged(Discoverability::Mode discoverabilityMode);
public:
static QString findableByString(Discoverability::Mode discoverabilityMode);
private slots:
void handleHeartbeatResponse(QNetworkReply& requestReply);

View file

@ -0,0 +1,26 @@
//
// HFTabletWebEngineProfile.h
// interface/src/networking
//
// Created by Dante Ruiz on 2017-03-31.
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "HFTabletWebEngineProfile.h"
#include "HFTabletWebEngineRequestInterceptor.h"
static const QString QML_WEB_ENGINE_NAME = "qmlTabletWebEngine";
HFTabletWebEngineProfile::HFTabletWebEngineProfile(QObject* parent) : QQuickWebEngineProfile(parent) {
static const QString WEB_ENGINE_USER_AGENT = "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Mobile Safari/537.36";
setHttpUserAgent(WEB_ENGINE_USER_AGENT);
auto requestInterceptor = new HFTabletWebEngineRequestInterceptor(this);
setRequestInterceptor(requestInterceptor);
}

View file

@ -0,0 +1,23 @@
//
// HFTabletWebEngineProfile.h
// interface/src/networking
//
// Created by Dante Ruiz on 2017-03-31.
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_HFTabletWebEngineProfile_h
#define hifi_HFTabletWebEngineProfile_h
#include <QtWebEngine/QQuickWebEngineProfile>
class HFTabletWebEngineProfile : public QQuickWebEngineProfile {
public:
HFTabletWebEngineProfile(QObject* parent = Q_NULLPTR);
};
#endif // hifi_HFTabletWebEngineProfile_h

View file

@ -0,0 +1,42 @@
//
// HFTabletWebEngineRequestInterceptor.cpp
// interface/src/networking
//
// Created by Dante Ruiz on 2017-3-31.
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "HFTabletWebEngineRequestInterceptor.h"
#include <QtCore/QDebug>
#include <AccountManager.h>
bool isTabletAuthableHighFidelityURL(const QUrl& url) {
static const QStringList HF_HOSTS = {
"highfidelity.com", "highfidelity.io",
"metaverse.highfidelity.com", "metaverse.highfidelity.io"
};
return url.scheme() == "https" && HF_HOSTS.contains(url.host());
}
void HFTabletWebEngineRequestInterceptor::interceptRequest(QWebEngineUrlRequestInfo& info) {
// check if this is a request to a highfidelity URL
if (isTabletAuthableHighFidelityURL(info.requestUrl())) {
// if we have an access token, add it to the right HTTP header for authorization
auto accountManager = DependencyManager::get<AccountManager>();
if (accountManager->hasValidAccessToken()) {
static const QString OAUTH_AUTHORIZATION_HEADER = "Authorization";
QString bearerTokenString = "Bearer " + accountManager->getAccountInfo().getAccessToken().token;
info.setHttpHeader(OAUTH_AUTHORIZATION_HEADER.toLocal8Bit(), bearerTokenString.toLocal8Bit());
}
}
static const QString USER_AGENT = "User-Agent";
QString tokenString = "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Mobile Safari/537.36";
info.setHttpHeader(USER_AGENT.toLocal8Bit(), tokenString.toLocal8Bit());
}

View file

@ -0,0 +1,24 @@
//
// HFTabletWebEngineRequestInterceptor.h
// interface/src/networking
//
// Created by Dante Ruiz on 2017-3-31.
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_HFTabletWebEngineRequestInterceptor_h
#define hifi_HFTabletWebEngineRequestInterceptor_h
#include <QWebEngineUrlRequestInterceptor>
class HFTabletWebEngineRequestInterceptor : public QWebEngineUrlRequestInterceptor {
public:
HFTabletWebEngineRequestInterceptor(QObject* parent) : QWebEngineUrlRequestInterceptor(parent) {};
virtual void interceptRequest(QWebEngineUrlRequestInfo& info) override;
};
#endif // hifi_HFWebEngineRequestInterceptor_h

View file

@ -10,6 +10,7 @@
//
#include "HFWebEngineRequestInterceptor.h"
#include "NetworkingConstants.h"
#include <QtCore/QDebug>
@ -20,8 +21,11 @@ bool isAuthableHighFidelityURL(const QUrl& url) {
"highfidelity.com", "highfidelity.io",
"metaverse.highfidelity.com", "metaverse.highfidelity.io"
};
const auto& scheme = url.scheme();
const auto& host = url.host();
return url.scheme() == "https" && HF_HOSTS.contains(url.host());
return (scheme == "https" && HF_HOSTS.contains(host)) ||
((scheme == NetworkingConstants::METAVERSE_SERVER_URL.scheme()) && (host == NetworkingConstants::METAVERSE_SERVER_URL.host()));
}
void HFWebEngineRequestInterceptor::interceptRequest(QWebEngineUrlRequestInfo& info) {

View file

@ -53,33 +53,19 @@ void GlobalServicesScriptingInterface::loggedOut() {
emit GlobalServicesScriptingInterface::disconnected(QString("logout"));
}
QString GlobalServicesScriptingInterface::findableByString(Discoverability::Mode discoverabilityMode) const {
if (discoverabilityMode == Discoverability::None) {
return "none";
} else if (discoverabilityMode == Discoverability::Friends) {
return "friends";
} else if (discoverabilityMode == Discoverability::All) {
return "all";
} else {
qDebug() << "GlobalServices findableByString called with an unrecognized value.";
return "";
}
}
QString GlobalServicesScriptingInterface::getFindableBy() const {
auto discoverabilityManager = DependencyManager::get<DiscoverabilityManager>();
return findableByString(discoverabilityManager->getDiscoverabilityMode());
return DiscoverabilityManager::findableByString(discoverabilityManager->getDiscoverabilityMode());
}
void GlobalServicesScriptingInterface::setFindableBy(const QString& discoverabilityMode) {
auto discoverabilityManager = DependencyManager::get<DiscoverabilityManager>();
if (discoverabilityMode.toLower() == "none") {
discoverabilityManager->setDiscoverabilityMode(Discoverability::None);
} else if (discoverabilityMode.toLower() == "friends") {
discoverabilityManager->setDiscoverabilityMode(Discoverability::Friends);
} else if (discoverabilityMode.toLower() == "connections") {
discoverabilityManager->setDiscoverabilityMode(Discoverability::Connections);
} else if (discoverabilityMode.toLower() == "all") {
discoverabilityManager->setDiscoverabilityMode(Discoverability::All);
} else {
@ -88,7 +74,7 @@ void GlobalServicesScriptingInterface::setFindableBy(const QString& discoverabil
}
void GlobalServicesScriptingInterface::discoverabilityModeChanged(Discoverability::Mode discoverabilityMode) {
emit findableByChanged(findableByString(discoverabilityMode));
emit findableByChanged(DiscoverabilityManager::findableByString(discoverabilityMode));
}
DownloadInfoResult::DownloadInfoResult() :

View file

@ -18,6 +18,7 @@
#include <QScriptValue>
#include <QString>
#include <QStringList>
#include <DiscoverabilityManager.h>
class DownloadInfoResult {
public:
@ -35,7 +36,7 @@ class GlobalServicesScriptingInterface : public QObject {
Q_OBJECT
Q_PROPERTY(QString username READ getUsername)
Q_PROPERTY(QString findableBy READ getFindableBy WRITE setFindableBy)
Q_PROPERTY(QString findableBy READ getFindableBy WRITE setFindableBy NOTIFY findableByChanged)
public:
static GlobalServicesScriptingInterface* getInstance();
@ -65,8 +66,6 @@ private:
GlobalServicesScriptingInterface();
~GlobalServicesScriptingInterface();
QString findableByString(Discoverability::Mode discoverabilityMode) const;
bool _downloading;
};

View file

@ -27,7 +27,7 @@ class HMDScriptingInterface : public AbstractHMDScriptingInterface, public Depen
Q_OBJECT
Q_PROPERTY(glm::vec3 position READ getPosition)
Q_PROPERTY(glm::quat orientation READ getOrientation)
Q_PROPERTY(bool mounted READ isMounted)
Q_PROPERTY(bool mounted READ isMounted NOTIFY mountedChanged)
Q_PROPERTY(bool showTablet READ getShouldShowTablet)
Q_PROPERTY(QUuid tabletID READ getCurrentTabletFrameID WRITE setCurrentTabletFrameID)
Q_PROPERTY(QUuid homeButtonID READ getCurrentHomeButtonID WRITE setCurrentHomeButtonID)
@ -80,6 +80,7 @@ public:
signals:
bool shouldShowHandControllersChanged();
void mountedChanged();
public:
HMDScriptingInterface();

View file

@ -0,0 +1,91 @@
#include <QJsonDocument>
#include <QJsonArray>
#include <src/InterfaceLogging.h>
#include <src/ui/AvatarInputs.h>
#include "LimitlessConnection.h"
#include "LimitlessVoiceRecognitionScriptingInterface.h"
LimitlessConnection::LimitlessConnection() :
_streamingAudioForTranscription(false)
{
}
void LimitlessConnection::startListening(QString authCode) {
_transcribeServerSocket.reset(new QTcpSocket(this));
connect(_transcribeServerSocket.get(), &QTcpSocket::readyRead, this,
&LimitlessConnection::transcriptionReceived);
connect(_transcribeServerSocket.get(), &QTcpSocket::disconnected, this, [this](){stopListening();});
static const auto host = "gserv_devel.studiolimitless.com";
_transcribeServerSocket->connectToHost(host, 1407);
_transcribeServerSocket->waitForConnected();
QString requestHeader = QString::asprintf("Authorization: %s\r\nfs: %i\r\n",
authCode.toLocal8Bit().data(), AudioConstants::SAMPLE_RATE);
qCDebug(interfaceapp) << "Sending Limitless Audio Stream Request: " << requestHeader;
_transcribeServerSocket->write(requestHeader.toLocal8Bit());
_transcribeServerSocket->waitForBytesWritten();
}
void LimitlessConnection::stopListening() {
emit onFinishedSpeaking(_currentTranscription);
_streamingAudioForTranscription = false;
_currentTranscription = "";
if (!isConnected())
return;
_transcribeServerSocket->close();
disconnect(_transcribeServerSocket.get(), &QTcpSocket::readyRead, this,
&LimitlessConnection::transcriptionReceived);
_transcribeServerSocket.release()->deleteLater();
disconnect(DependencyManager::get<AudioClient>().data(), &AudioClient::inputReceived, this,
&LimitlessConnection::audioInputReceived);
qCDebug(interfaceapp) << "Connection to Limitless Voice Server closed.";
}
void LimitlessConnection::audioInputReceived(const QByteArray& inputSamples) {
if (isConnected()) {
_transcribeServerSocket->write(inputSamples.data(), inputSamples.size());
_transcribeServerSocket->waitForBytesWritten();
}
}
void LimitlessConnection::transcriptionReceived() {
while (_transcribeServerSocket && _transcribeServerSocket->bytesAvailable() > 0) {
const QByteArray data = _transcribeServerSocket->readAll();
_serverDataBuffer.append(data);
int begin = _serverDataBuffer.indexOf('<');
int end = _serverDataBuffer.indexOf('>');
while (begin > -1 && end > -1) {
const int len = end - begin;
const QByteArray serverMessage = _serverDataBuffer.mid(begin+1, len-1);
if (serverMessage.contains("1407")) {
qCDebug(interfaceapp) << "Limitless Speech Server denied the request.";
// Don't spam the server with further false requests please.
DependencyManager::get<LimitlessVoiceRecognitionScriptingInterface>()->setListeningToVoice(true);
stopListening();
return;
} else if (serverMessage.contains("1408")) {
qCDebug(interfaceapp) << "Limitless Audio request authenticated!";
_serverDataBuffer.clear();
connect(DependencyManager::get<AudioClient>().data(), &AudioClient::inputReceived, this,
&LimitlessConnection::audioInputReceived);
return;
}
QJsonObject json = QJsonDocument::fromJson(serverMessage.data()).object();
_serverDataBuffer.remove(begin, len+1);
_currentTranscription = json["alternatives"].toArray()[0].toObject()["transcript"].toString();
emit onReceivedTranscription(_currentTranscription);
if (json["isFinal"] == true) {
qCDebug(interfaceapp) << "Final transcription: " << _currentTranscription;
stopListening();
return;
}
begin = _serverDataBuffer.indexOf('<');
end = _serverDataBuffer.indexOf('>');
}
}
}
bool LimitlessConnection::isConnected() const {
return _transcribeServerSocket.get() && _transcribeServerSocket->isWritable()
&& _transcribeServerSocket->state() != QAbstractSocket::SocketState::UnconnectedState;
}

View file

@ -0,0 +1,44 @@
//
// SpeechRecognitionScriptingInterface.h
// interface/src/scripting
//
// Created by Trevor Berninger on 3/24/17.
// Copyright 2017 Limitless ltd.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_LimitlessConnection_h
#define hifi_LimitlessConnection_h
#include <AudioClient.h>
#include <QObject>
#include <QFuture>
class LimitlessConnection : public QObject {
Q_OBJECT
public:
LimitlessConnection();
Q_INVOKABLE void startListening(QString authCode);
Q_INVOKABLE void stopListening();
std::atomic<bool> _streamingAudioForTranscription;
signals:
void onReceivedTranscription(QString speech);
void onFinishedSpeaking(QString speech);
private:
void transcriptionReceived();
void audioInputReceived(const QByteArray& inputSamples);
bool isConnected() const;
std::unique_ptr<QTcpSocket> _transcribeServerSocket;
QByteArray _serverDataBuffer;
QString _currentTranscription;
};
#endif //hifi_LimitlessConnection_h

View file

@ -0,0 +1,64 @@
//
// SpeechRecognitionScriptingInterface.h
// interface/src/scripting
//
// Created by Trevor Berninger on 3/20/17.
// Copyright 2017 Limitless ltd.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <src/InterfaceLogging.h>
#include <src/ui/AvatarInputs.h>
#include <QtConcurrent/QtConcurrentRun>
#include "LimitlessVoiceRecognitionScriptingInterface.h"
const float LimitlessVoiceRecognitionScriptingInterface::_audioLevelThreshold = 0.33f;
const int LimitlessVoiceRecognitionScriptingInterface::_voiceTimeoutDuration = 2000;
LimitlessVoiceRecognitionScriptingInterface::LimitlessVoiceRecognitionScriptingInterface() :
_shouldStartListeningForVoice(false)
{
_voiceTimer.setSingleShot(true);
connect(&_voiceTimer, &QTimer::timeout, this, &LimitlessVoiceRecognitionScriptingInterface::voiceTimeout);
connect(&_connection, &LimitlessConnection::onReceivedTranscription, this, [this](QString transcription){emit onReceivedTranscription(transcription);});
connect(&_connection, &LimitlessConnection::onFinishedSpeaking, this, [this](QString transcription){emit onFinishedSpeaking(transcription);});
_connection.moveToThread(&_connectionThread);
_connectionThread.setObjectName("Limitless Connection");
_connectionThread.start();
}
void LimitlessVoiceRecognitionScriptingInterface::update() {
const float audioLevel = AvatarInputs::getInstance()->loudnessToAudioLevel(DependencyManager::get<AudioClient>()->getAudioAverageInputLoudness());
if (_shouldStartListeningForVoice) {
if (_connection._streamingAudioForTranscription) {
if (audioLevel > _audioLevelThreshold) {
if (_voiceTimer.isActive()) {
_voiceTimer.stop();
}
} else if (!_voiceTimer.isActive()){
_voiceTimer.start(_voiceTimeoutDuration);
}
} else if (audioLevel > _audioLevelThreshold) {
// to make sure invoke doesn't get called twice before the method actually gets called
_connection._streamingAudioForTranscription = true;
QMetaObject::invokeMethod(&_connection, "startListening", Q_ARG(QString, authCode));
}
}
}
void LimitlessVoiceRecognitionScriptingInterface::setListeningToVoice(bool listening) {
_shouldStartListeningForVoice = listening;
}
void LimitlessVoiceRecognitionScriptingInterface::setAuthKey(QString key) {
authCode = key;
}
void LimitlessVoiceRecognitionScriptingInterface::voiceTimeout() {
if (_connection._streamingAudioForTranscription) {
QMetaObject::invokeMethod(&_connection, "stopListening");
}
}

View file

@ -0,0 +1,50 @@
//
// SpeechRecognitionScriptingInterface.h
// interface/src/scripting
//
// Created by Trevor Berninger on 3/20/17.
// Copyright 2017 Limitless ltd.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_SpeechRecognitionScriptingInterface_h
#define hifi_SpeechRecognitionScriptingInterface_h
#include <AudioClient.h>
#include <QObject>
#include <QFuture>
#include "LimitlessConnection.h"
class LimitlessVoiceRecognitionScriptingInterface : public QObject, public Dependency {
Q_OBJECT
public:
LimitlessVoiceRecognitionScriptingInterface();
void update();
QString authCode;
public slots:
void setListeningToVoice(bool listening);
void setAuthKey(QString key);
signals:
void onReceivedTranscription(QString speech);
void onFinishedSpeaking(QString speech);
private:
bool _shouldStartListeningForVoice;
static const float _audioLevelThreshold;
static const int _voiceTimeoutDuration;
QTimer _voiceTimer;
QThread _connectionThread;
LimitlessConnection _connection;
void voiceTimeout();
};
#endif //hifi_SpeechRecognitionScriptingInterface_h

View file

@ -235,6 +235,14 @@ void WindowScriptingInterface::shareSnapshot(const QString& path, const QUrl& hr
qApp->shareSnapshot(path, href);
}
void WindowScriptingInterface::makeConnection(bool success, const QString& userNameOrError) {
if (success) {
emit connectionAdded(userNameOrError);
} else {
emit connectionError(userNameOrError);
}
}
bool WindowScriptingInterface::isPhysicsEnabled() {
return qApp->isPhysicsEnabled();
}
@ -255,7 +263,7 @@ int WindowScriptingInterface::openMessageBox(QString title, QString text, int bu
}
int WindowScriptingInterface::createMessageBox(QString title, QString text, int buttons, int defaultButton) {
auto messageBox = DependencyManager::get<OffscreenUi>()->createMessageBox(OffscreenUi::ICON_INFORMATION, title, text,
auto messageBox = DependencyManager::get<OffscreenUi>()->createMessageBox(OffscreenUi::ICON_INFORMATION, title, text,
static_cast<QFlags<QMessageBox::StandardButton>>(buttons), static_cast<QMessageBox::StandardButton>(defaultButton));
connect(messageBox, SIGNAL(selected(int)), this, SLOT(onMessageBoxSelected(int)));

View file

@ -56,6 +56,7 @@ public slots:
void showAssetServer(const QString& upload = "");
void copyToClipboard(const QString& text);
void takeSnapshot(bool notify = true, bool includeAnimated = false, float aspectRatio = 0.0f);
void makeConnection(bool success, const QString& userNameOrError);
void shareSnapshot(const QString& path, const QUrl& href = QUrl(""));
bool isPhysicsEnabled();
@ -74,6 +75,9 @@ signals:
void snapshotShared(const QString& error);
void processingGif();
void connectionAdded(const QString& connectionName);
void connectionError(const QString& errorString);
void messageBoxClosed(int id, int button);
// triggered when window size or position changes

View file

@ -85,7 +85,6 @@ void ApplicationOverlay::renderOverlay(RenderArgs* renderArgs) {
renderAudioScope(renderArgs); // audio scope in the very back - NOTE: this is the debug audio scope, not the VU meter
renderOverlays(renderArgs); // renders Scripts Overlay and AudioScope
renderQmlUi(renderArgs); // renders a unit quad with the QML UI texture, and the text overlays from scripts
renderStatsAndLogs(renderArgs); // currently renders nothing
});
renderArgs->_batch = nullptr; // so future users of renderArgs don't try to use our batch
@ -159,27 +158,6 @@ void ApplicationOverlay::renderOverlays(RenderArgs* renderArgs) {
qApp->getOverlays().renderHUD(renderArgs);
}
void ApplicationOverlay::renderStatsAndLogs(RenderArgs* renderArgs) {
// Display stats and log text onscreen
// Determine whether to compute timing details
/*
// Show on-screen msec timer
if (Menu::getInstance()->isOptionChecked(MenuOption::FrameTimer)) {
auto canvasSize = qApp->getCanvasSize();
quint64 mSecsNow = floor(usecTimestampNow() / 1000.0 + 0.5);
QString frameTimer = QString("%1\n").arg((int)(mSecsNow % 1000));
int timerBottom =
(Menu::getInstance()->isOptionChecked(MenuOption::Stats))
? 80 : 20;
drawText(canvasSize.x - 100, canvasSize.y - timerBottom,
0.30f, 0.0f, 0, frameTimer.toUtf8().constData(), WHITE_TEXT);
}
*/
}
void ApplicationOverlay::renderDomainConnectionStatusBorder(RenderArgs* renderArgs) {
auto geometryCache = DependencyManager::get<GeometryCache>();
static std::once_flag once;
@ -229,13 +207,13 @@ void ApplicationOverlay::buildFramebufferObject() {
auto width = uiSize.x;
auto height = uiSize.y;
if (!_overlayFramebuffer->getDepthStencilBuffer()) {
auto overlayDepthTexture = gpu::TexturePointer(gpu::Texture::createRenderBuffer(DEPTH_FORMAT, width, height, DEFAULT_SAMPLER));
auto overlayDepthTexture = gpu::TexturePointer(gpu::Texture::createRenderBuffer(DEPTH_FORMAT, width, height, gpu::Texture::SINGLE_MIP, DEFAULT_SAMPLER));
_overlayFramebuffer->setDepthStencilBuffer(overlayDepthTexture, DEPTH_FORMAT);
}
if (!_overlayFramebuffer->getRenderBuffer(0)) {
const gpu::Sampler OVERLAY_SAMPLER(gpu::Sampler::FILTER_MIN_MAG_LINEAR, gpu::Sampler::WRAP_CLAMP);
auto colorBuffer = gpu::TexturePointer(gpu::Texture::createRenderBuffer(COLOR_FORMAT, width, height, OVERLAY_SAMPLER));
auto colorBuffer = gpu::TexturePointer(gpu::Texture::createRenderBuffer(COLOR_FORMAT, width, height, gpu::Texture::SINGLE_MIP, OVERLAY_SAMPLER));
_overlayFramebuffer->setRenderBuffer(0, colorBuffer);
}
}

View file

@ -62,24 +62,13 @@ AvatarInputs::AvatarInputs(QQuickItem* parent) : QQuickItem(parent) {
} \
}
void AvatarInputs::update() {
if (!Menu::getInstance()) {
return;
}
AI_UPDATE(cameraEnabled, !Menu::getInstance()->isOptionChecked(MenuOption::NoFaceTracking));
AI_UPDATE(cameraMuted, Menu::getInstance()->isOptionChecked(MenuOption::MuteFaceTracking));
AI_UPDATE(isHMD, qApp->isHMDMode());
AI_UPDATE_WRITABLE(showAudioTools, Menu::getInstance()->isOptionChecked(MenuOption::AudioTools));
auto audioIO = DependencyManager::get<AudioClient>();
float AvatarInputs::loudnessToAudioLevel(float loudness) {
const float AUDIO_METER_AVERAGING = 0.5;
const float LOG2 = log(2.0f);
const float METER_LOUDNESS_SCALE = 2.8f / 5.0f;
const float LOG2_LOUDNESS_FLOOR = 11.0f;
float audioLevel = 0.0f;
auto audio = DependencyManager::get<AudioClient>();
float loudness = audio->getLastInputLoudness() + 1.0f;
loudness += 1.0f;
_trailingAudioLoudness = AUDIO_METER_AVERAGING * _trailingAudioLoudness + (1.0f - AUDIO_METER_AVERAGING) * loudness;
@ -93,6 +82,24 @@ void AvatarInputs::update() {
if (audioLevel > 1.0f) {
audioLevel = 1.0;
}
return audioLevel;
}
void AvatarInputs::update() {
if (!Menu::getInstance()) {
return;
}
AI_UPDATE(cameraEnabled, !Menu::getInstance()->isOptionChecked(MenuOption::NoFaceTracking));
AI_UPDATE(cameraMuted, Menu::getInstance()->isOptionChecked(MenuOption::MuteFaceTracking));
AI_UPDATE(isHMD, qApp->isHMDMode());
AI_UPDATE_WRITABLE(showAudioTools, Menu::getInstance()->isOptionChecked(MenuOption::AudioTools));
auto audioIO = DependencyManager::get<AudioClient>();
const float audioLevel = loudnessToAudioLevel(DependencyManager::get<AudioClient>()->getLastInputLoudness());
AI_UPDATE_FLOAT(audioLevel, audioLevel, 0.01f);
AI_UPDATE(audioClipping, ((audioIO->getTimeSinceLastClip() > 0.0f) && (audioIO->getTimeSinceLastClip() < 1.0f)));
AI_UPDATE(audioMuted, audioIO->isMuted());

View file

@ -34,6 +34,7 @@ class AvatarInputs : public QQuickItem {
public:
static AvatarInputs* getInstance();
float loudnessToAudioLevel(float loudness);
AvatarInputs(QQuickItem* parent = nullptr);
void update();
bool showAudioTools() const { return _showAudioTools; }

View file

@ -49,6 +49,8 @@
#include "ui/DomainConnectionModel.h"
#include "scripting/AudioDeviceScriptingInterface.h"
#include "ui/AvatarInputs.h"
#include "avatar/AvatarManager.h"
#include "scripting/GlobalServicesScriptingInterface.h"
static const float DPI = 30.47f;
static const float INCHES_TO_METERS = 1.0f / 39.3701f;
@ -194,6 +196,8 @@ void Web3DOverlay::loadSourceURL() {
_webSurface->getRootContext()->setContextProperty("DCModel", DependencyManager::get<DomainConnectionModel>().data());
_webSurface->getRootContext()->setContextProperty("AudioDevice", AudioDeviceScriptingInterface::getInstance());
_webSurface->getRootContext()->setContextProperty("AvatarInputs", AvatarInputs::getInstance());
_webSurface->getRootContext()->setContextProperty("GlobalServices", GlobalServicesScriptingInterface::getInstance());
_webSurface->getRootContext()->setContextProperty("AvatarList", DependencyManager::get<AvatarManager>().data());
_webSurface->getRootContext()->setContextProperty("pathToFonts", "../../");
tabletScriptingInterface->setQmlTabletRoot("com.highfidelity.interface.tablet.system", _webSurface->getRootItem(), _webSurface.data());
@ -355,35 +359,33 @@ void Web3DOverlay::handlePointerEventAsTouch(const PointerEvent& event) {
glm::vec2 windowPos = event.getPos2D() * (METERS_TO_INCHES * _dpi);
QPointF windowPoint(windowPos.x, windowPos.y);
if (event.getButtons() == PointerEvent::NoButtons && event.getType() == PointerEvent::Move) {
// Forward a mouse move event to the Web surface.
QMouseEvent* mouseEvent = new QMouseEvent(QEvent::MouseMove, windowPoint, windowPoint, windowPoint, Qt::NoButton, Qt::NoButton, Qt::NoModifier);
QCoreApplication::postEvent(_webSurface->getWindow(), mouseEvent);
return;
}
if (event.getType() == PointerEvent::Press && event.getButton() == PointerEvent::PrimaryButton) {
this->_pressed = true;
} else if (event.getType() == PointerEvent::Release && event.getButton() == PointerEvent::PrimaryButton) {
this->_pressed = false;
}
QEvent::Type type;
QEvent::Type touchType;
Qt::TouchPointState touchPointState;
QEvent::Type mouseType;
switch (event.getType()) {
case PointerEvent::Press:
type = QEvent::TouchBegin;
touchType = QEvent::TouchBegin;
touchPointState = Qt::TouchPointPressed;
mouseType = QEvent::MouseButtonPress;
break;
case PointerEvent::Release:
type = QEvent::TouchEnd;
touchType = QEvent::TouchEnd;
touchPointState = Qt::TouchPointReleased;
mouseType = QEvent::MouseButtonRelease;
break;
case PointerEvent::Move:
default:
type = QEvent::TouchUpdate;
touchType = QEvent::TouchUpdate;
touchPointState = Qt::TouchPointMoved;
mouseType = QEvent::MouseMove;
break;
default:
return;
}
QTouchEvent::TouchPoint point;
@ -394,13 +396,30 @@ void Web3DOverlay::handlePointerEventAsTouch(const PointerEvent& event) {
QList<QTouchEvent::TouchPoint> touchPoints;
touchPoints.push_back(point);
QTouchEvent* touchEvent = new QTouchEvent(type, &_touchDevice, event.getKeyboardModifiers());
QTouchEvent* touchEvent = new QTouchEvent(touchType, &_touchDevice, event.getKeyboardModifiers());
touchEvent->setWindow(_webSurface->getWindow());
touchEvent->setTarget(_webSurface->getRootItem());
touchEvent->setTouchPoints(touchPoints);
touchEvent->setTouchPointStates(touchPointState);
QCoreApplication::postEvent(_webSurface->getWindow(), touchEvent);
// Send mouse events to the Web surface so that HTML dialog elements work with mouse press and hover.
// FIXME: Scroll bar dragging is a bit unstable in the tablet (content can jump up and down at times).
// This may be improved in Qt 5.8. Release notes: "Cleaned up touch and mouse event delivery".
Qt::MouseButtons buttons = Qt::NoButton;
if (event.getButtons() & PointerEvent::PrimaryButton) {
buttons |= Qt::LeftButton;
}
Qt::MouseButton button = Qt::NoButton;
if (event.getButton() == PointerEvent::PrimaryButton) {
button = Qt::LeftButton;
}
QMouseEvent* mouseEvent = new QMouseEvent(mouseType, windowPoint, windowPoint, windowPoint, button, buttons, Qt::NoModifier);
QCoreApplication::postEvent(_webSurface->getWindow(), mouseEvent);
}
void Web3DOverlay::handlePointerEventAsMouse(const PointerEvent& event) {
@ -422,11 +441,12 @@ void Web3DOverlay::handlePointerEventAsMouse(const PointerEvent& event) {
buttons |= Qt::LeftButton;
}
QEvent::Type type;
Qt::MouseButton button = Qt::NoButton;
if (event.getButton() == PointerEvent::PrimaryButton) {
button = Qt::LeftButton;
}
QEvent::Type type;
switch (event.getType()) {
case PointerEvent::Press:
type = QEvent::MouseButtonPress;
@ -435,9 +455,10 @@ void Web3DOverlay::handlePointerEventAsMouse(const PointerEvent& event) {
type = QEvent::MouseButtonRelease;
break;
case PointerEvent::Move:
default:
type = QEvent::MouseMove;
break;
default:
return;
}
QMouseEvent* mouseEvent = new QMouseEvent(type, windowPoint, windowPoint, windowPoint, button, buttons, Qt::NoModifier);

View file

@ -1495,6 +1495,9 @@ void AvatarData::processAvatarIdentity(const Identity& identity, bool& identityC
setAvatarEntityData(identity.avatarEntityData);
identityChanged = true;
}
// flag this avatar as non-stale by updating _averageBytesReceived
const int BOGUS_NUM_BYTES = 1;
_averageBytesReceived.updateAverage(BOGUS_NUM_BYTES);
}
QByteArray AvatarData::identityByteArray() const {

View file

@ -340,7 +340,7 @@ class AvatarData : public QObject, public SpatiallyNestable {
Q_PROPERTY(float audioLoudness READ getAudioLoudness WRITE setAudioLoudness)
Q_PROPERTY(float audioAverageLoudness READ getAudioAverageLoudness WRITE setAudioAverageLoudness)
Q_PROPERTY(QString displayName READ getDisplayName WRITE setDisplayName)
Q_PROPERTY(QString displayName READ getDisplayName WRITE setDisplayName NOTIFY displayNameChanged)
// sessionDisplayName is sanitized, defaulted version displayName that is defined by the AvatarMixer rather than by Interface clients.
// The result is unique among all avatars present at the time.
Q_PROPERTY(QString sessionDisplayName READ getSessionDisplayName WRITE setSessionDisplayName)
@ -614,6 +614,9 @@ public:
signals:
void displayNameChanged();
public slots:
void sendAvatarDataPacket();
void sendIdentityPacket();

View file

@ -356,15 +356,16 @@ void OpenGLDisplayPlugin::customizeContext() {
cursorData.texture.reset(
gpu::Texture::createStrict(
gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA),
image.width(), image.height(),
gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR)));
gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA),
image.width(), image.height(),
gpu::Texture::MAX_NUM_MIPS,
gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR)));
cursorData.texture->setSource("cursor texture");
auto usage = gpu::Texture::Usage::Builder().withColor().withAlpha();
cursorData.texture->setUsage(usage.build());
cursorData.texture->setStoredMipFormat(gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA));
cursorData.texture->assignStoredMip(0, image.byteCount(), image.constBits());
cursorData.texture->autoGenerateMips(-1);
cursorData.texture->setAutoGenerateMips(true);
}
}
}

View file

@ -299,12 +299,13 @@ void HmdDisplayPlugin::internalPresent() {
gpu::Texture::createStrict(
gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA),
image.width(), image.height(),
gpu::Texture::MAX_NUM_MIPS,
gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR)));
_previewTexture->setSource("HMD Preview Texture");
_previewTexture->setUsage(gpu::Texture::Usage::Builder().withColor().build());
_previewTexture->setStoredMipFormat(gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA));
_previewTexture->assignStoredMip(0, image.byteCount(), image.constBits());
_previewTexture->autoGenerateMips(-1);
_previewTexture->setAutoGenerateMips(true);
}
auto viewport = getViewportForSourceSize(uvec2(_previewTexture->getDimensions()));

View file

@ -229,7 +229,7 @@ void GLBackend::do_setResourceTexture(const Batch& batch, size_t paramOffset) {
_resource._textures[slot] = resourceTexture;
_stats._RSAmountTextureMemoryBounded += object->size();
_stats._RSAmountTextureMemoryBounded += (int) object->size();
} else {
releaseResourceTexture(slot);

View file

@ -43,7 +43,7 @@ public:
static const GLenum WRAP_MODES[Sampler::NUM_WRAP_MODES];
protected:
virtual uint32 size() const = 0;
virtual Size size() const = 0;
virtual void generateMips() const = 0;
GLTexture(const std::weak_ptr<gl::GLBackend>& backend, const Texture& texture, GLuint id);
@ -57,7 +57,7 @@ public:
protected:
GLExternalTexture(const std::weak_ptr<gl::GLBackend>& backend, const Texture& texture, GLuint id);
void generateMips() const override {}
uint32 size() const override { return 0; }
Size size() const override { return 0; }
};

View file

@ -40,30 +40,59 @@ public:
class GL41Texture : public GLTexture {
using Parent = GLTexture;
static GLuint allocate();
public:
~GL41Texture();
private:
GL41Texture(const std::weak_ptr<GLBackend>& backend, const Texture& buffer);
void generateMips() const override;
uint32 size() const override;
friend class GL41Backend;
const Stamp _storageStamp;
mutable Stamp _contentStamp { 0 };
mutable Stamp _samplerStamp { 0 };
const uint32 _size;
static GLuint allocate(const Texture& texture);
protected:
GL41Texture(const std::weak_ptr<GLBackend>& backend, const Texture& texture);
void generateMips() const override;
void copyMipFaceFromTexture(uint16_t sourceMip, uint16_t targetMip, uint8_t face) const;
void copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const uvec3& size, uint32_t yOffset, GLenum format, GLenum type, const void* sourcePointer) const;
virtual void syncSampler() const;
bool isOutdated() const;
void withPreservedTexture(std::function<void()> f) const;
void syncContent() const;
void syncSampler() const;
};
//
// Textures that have fixed allocation sizes and cannot be managed at runtime
//
class GL41FixedAllocationTexture : public GL41Texture {
using Parent = GL41Texture;
friend class GL41Backend;
public:
GL41FixedAllocationTexture(const std::weak_ptr<GLBackend>& backend, const Texture& texture);
~GL41FixedAllocationTexture();
protected:
Size size() const override { return _size; }
void allocateStorage() const;
void syncSampler() const override;
const Size _size { 0 };
};
class GL41AttachmentTexture : public GL41FixedAllocationTexture {
using Parent = GL41FixedAllocationTexture;
friend class GL41Backend;
protected:
GL41AttachmentTexture(const std::weak_ptr<GLBackend>& backend, const Texture& texture);
~GL41AttachmentTexture();
};
class GL41StrictResourceTexture : public GL41FixedAllocationTexture {
using Parent = GL41FixedAllocationTexture;
friend class GL41Backend;
protected:
GL41StrictResourceTexture(const std::weak_ptr<GLBackend>& backend, const Texture& texture);
};
class GL41ResourceTexture : public GL41FixedAllocationTexture {
using Parent = GL41FixedAllocationTexture;
friend class GL41Backend;
protected:
GL41ResourceTexture(const std::weak_ptr<GLBackend>& backend, const Texture& texture);
~GL41ResourceTexture();
};
protected:
GLuint getFramebufferID(const FramebufferPointer& framebuffer) override;

View file

@ -19,20 +19,11 @@ using namespace gpu;
using namespace gpu::gl;
using namespace gpu::gl41;
using GL41TexelFormat = GLTexelFormat;
using GL41Texture = GL41Backend::GL41Texture;
GLuint GL41Texture::allocate() {
Backend::incrementTextureGPUCount();
GLuint result;
glGenTextures(1, &result);
return result;
}
GLTexture* GL41Backend::syncGPUObject(const TexturePointer& texturePointer) {
if (!texturePointer) {
return nullptr;
}
const Texture& texture = *texturePointer;
if (TextureUsageType::EXTERNAL == texture.getUsageType()) {
return Parent::syncGPUObject(texturePointer);
@ -43,90 +34,58 @@ GLTexture* GL41Backend::syncGPUObject(const TexturePointer& texturePointer) {
return nullptr;
}
// If the object hasn't been created, or the object definition is out of date, drop and re-create
GL41Texture* object = Backend::getGPUObject<GL41Texture>(texture);
if (!object || object->_storageStamp < texture.getStamp()) {
// This automatically any previous texture
object = new GL41Texture(shared_from_this(), texture);
}
if (!object) {
switch (texture.getUsageType()) {
case TextureUsageType::RENDERBUFFER:
object = new GL41AttachmentTexture(shared_from_this(), texture);
break;
// FIXME internalize to GL41Texture 'sync' function
if (object->isOutdated()) {
object->withPreservedTexture([&] {
if (object->_contentStamp <= texture.getDataStamp()) {
// FIXME implement synchronous texture transfer here
object->syncContent();
case TextureUsageType::STRICT_RESOURCE:
qCDebug(gpugllogging) << "Strict texture " << texture.source().c_str();
object = new GL41StrictResourceTexture(shared_from_this(), texture);
break;
case TextureUsageType::RESOURCE: {
qCDebug(gpugllogging) << "variable / Strict texture " << texture.source().c_str();
object = new GL41ResourceTexture(shared_from_this(), texture);
break;
}
if (object->_samplerStamp <= texture.getSamplerStamp()) {
object->syncSampler();
}
});
default:
Q_UNREACHABLE();
}
}
return object;
}
GL41Texture::GL41Texture(const std::weak_ptr<GLBackend>& backend, const Texture& texture)
: GLTexture(backend, texture, allocate()), _storageStamp { texture.getStamp() }, _size(texture.evalTotalSize()) {
using GL41Texture = GL41Backend::GL41Texture;
GL41Texture::GL41Texture(const std::weak_ptr<GLBackend>& backend, const Texture& texture)
: GLTexture(backend, texture, allocate(texture)) {
incrementTextureGPUCount();
withPreservedTexture([&] {
GLTexelFormat texelFormat = GLTexelFormat::evalGLTexelFormat(_gpuObject.getTexelFormat(), _gpuObject.getStoredMipFormat());
auto numMips = _gpuObject.getNumMipLevels();
for (uint16_t mipLevel = 0; mipLevel < numMips; ++mipLevel) {
// Get the mip level dimensions, accounting for the downgrade level
Vec3u dimensions = _gpuObject.evalMipDimensions(mipLevel);
uint8_t face = 0;
for (GLenum target : getFaceTargets(_target)) {
const Byte* mipData = nullptr;
if (_gpuObject.isStoredMipFaceAvailable(mipLevel, face)) {
auto mip = _gpuObject.accessStoredMipFace(mipLevel, face);
mipData = mip->readData();
}
glTexImage2D(target, mipLevel, texelFormat.internalFormat, dimensions.x, dimensions.y, 0, texelFormat.format, texelFormat.type, mipData);
(void)CHECK_GL_ERROR();
++face;
}
}
});
}
GL41Texture::~GL41Texture() {
GLuint GL41Texture::allocate(const Texture& texture) {
GLuint result;
glGenTextures(1, &result);
return result;
}
bool GL41Texture::isOutdated() const {
if (_samplerStamp <= _gpuObject.getSamplerStamp()) {
return true;
}
if (TextureUsageType::RESOURCE == _gpuObject.getUsageType() && _contentStamp <= _gpuObject.getDataStamp()) {
return true;
}
return false;
}
void GL41Texture::withPreservedTexture(std::function<void()> f) const {
GLint boundTex = -1;
switch (_target) {
case GL_TEXTURE_2D:
glGetIntegerv(GL_TEXTURE_BINDING_2D, &boundTex);
break;
case GL_TEXTURE_CUBE_MAP:
glGetIntegerv(GL_TEXTURE_BINDING_CUBE_MAP, &boundTex);
break;
default:
qFatal("Unsupported texture type");
}
const GLint TRANSFER_TEXTURE_UNIT = 32;
glActiveTexture(GL_TEXTURE0 + TRANSFER_TEXTURE_UNIT);
glBindTexture(_target, _texture);
(void)CHECK_GL_ERROR();
glBindTexture(_target, _texture);
f();
glBindTexture(_target, boundTex);
glBindTexture(_target, 0);
(void)CHECK_GL_ERROR();
}
void GL41Texture::generateMips() const {
withPreservedTexture([&] {
glGenerateMipmap(_target);
@ -134,13 +93,35 @@ void GL41Texture::generateMips() const {
(void)CHECK_GL_ERROR();
}
void GL41Texture::syncContent() const {
// FIXME actually copy the texture data
_contentStamp = _gpuObject.getDataStamp() + 1;
void GL41Texture::copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const uvec3& size, uint32_t yOffset, GLenum format, GLenum type, const void* sourcePointer) const {
if (GL_TEXTURE_2D == _target) {
glTexSubImage2D(_target, mip, 0, yOffset, size.x, size.y, format, type, sourcePointer);
} else if (GL_TEXTURE_CUBE_MAP == _target) {
auto target = GLTexture::CUBE_FACE_LAYOUT[face];
glTexSubImage2D(target, mip, 0, yOffset, size.x, size.y, format, type, sourcePointer);
} else {
assert(false);
}
(void)CHECK_GL_ERROR();
}
void GL41Texture::copyMipFaceFromTexture(uint16_t sourceMip, uint16_t targetMip, uint8_t face) const {
if (!_gpuObject.isStoredMipFaceAvailable(sourceMip)) {
return;
}
auto size = _gpuObject.evalMipDimensions(sourceMip);
auto mipData = _gpuObject.accessStoredMipFace(sourceMip, face);
if (mipData) {
GLTexelFormat texelFormat = GLTexelFormat::evalGLTexelFormat(_gpuObject.getTexelFormat(), _gpuObject.getStoredMipFormat());
copyMipFaceLinesFromTexture(targetMip, face, size, 0, texelFormat.format, texelFormat.type, mipData->readData());
} else {
qCDebug(gpugllogging) << "Missing mipData level=" << sourceMip << " face=" << (int)face << " for texture " << _gpuObject.source().c_str();
}
}
void GL41Texture::syncSampler() const {
const Sampler& sampler = _gpuObject.getSampler();
const auto& fm = FILTER_MODES[sampler.getFilter()];
glTexParameteri(_target, GL_TEXTURE_MIN_FILTER, fm.minFilter);
glTexParameteri(_target, GL_TEXTURE_MAG_FILTER, fm.magFilter);
@ -158,12 +139,106 @@ void GL41Texture::syncSampler() const {
glTexParameterfv(_target, GL_TEXTURE_BORDER_COLOR, (const float*)&sampler.getBorderColor());
glTexParameteri(_target, GL_TEXTURE_BASE_LEVEL, (uint16)sampler.getMipOffset());
glTexParameterf(_target, GL_TEXTURE_MIN_LOD, (float)sampler.getMinMip());
glTexParameterf(_target, GL_TEXTURE_MAX_LOD, (sampler.getMaxMip() == Sampler::MAX_MIP_LEVEL ? 1000.f : sampler.getMaxMip()));
glTexParameterf(_target, GL_TEXTURE_MAX_ANISOTROPY_EXT, sampler.getMaxAnisotropy());
_samplerStamp = _gpuObject.getSamplerStamp() + 1;
}
uint32 GL41Texture::size() const {
return _size;
using GL41FixedAllocationTexture = GL41Backend::GL41FixedAllocationTexture;
GL41FixedAllocationTexture::GL41FixedAllocationTexture(const std::weak_ptr<GLBackend>& backend, const Texture& texture) : GL41Texture(backend, texture), _size(texture.evalTotalSize()) {
withPreservedTexture([&] {
allocateStorage();
syncSampler();
});
}
GL41FixedAllocationTexture::~GL41FixedAllocationTexture() {
}
void GL41FixedAllocationTexture::allocateStorage() const {
const GLTexelFormat texelFormat = GLTexelFormat::evalGLTexelFormat(_gpuObject.getTexelFormat());
const auto numMips = _gpuObject.getNumMips();
// glTextureStorage2D(_id, mips, texelFormat.internalFormat, dimensions.x, dimensions.y);
for (GLint level = 0; level < numMips; level++) {
Vec3u dimensions = _gpuObject.evalMipDimensions(level);
for (GLenum target : getFaceTargets(_target)) {
glTexImage2D(target, level, texelFormat.internalFormat, dimensions.x, dimensions.y, 0, texelFormat.format, texelFormat.type, nullptr);
}
}
glTexParameteri(_target, GL_TEXTURE_BASE_LEVEL, 0);
glTexParameteri(_target, GL_TEXTURE_MAX_LEVEL, numMips - 1);
}
void GL41FixedAllocationTexture::syncSampler() const {
Parent::syncSampler();
const Sampler& sampler = _gpuObject.getSampler();
auto baseMip = std::max<uint16_t>(sampler.getMipOffset(), sampler.getMinMip());
glTexParameteri(_target, GL_TEXTURE_BASE_LEVEL, baseMip);
glTexParameterf(_target, GL_TEXTURE_MIN_LOD, (float)sampler.getMinMip());
glTexParameterf(_target, GL_TEXTURE_MAX_LOD, (sampler.getMaxMip() == Sampler::MAX_MIP_LEVEL ? 1000.0f : sampler.getMaxMip()));
}
// Renderbuffer attachment textures
using GL41AttachmentTexture = GL41Backend::GL41AttachmentTexture;
GL41AttachmentTexture::GL41AttachmentTexture(const std::weak_ptr<GLBackend>& backend, const Texture& texture) : GL41FixedAllocationTexture(backend, texture) {
Backend::updateTextureGPUFramebufferMemoryUsage(0, size());
}
GL41AttachmentTexture::~GL41AttachmentTexture() {
Backend::updateTextureGPUFramebufferMemoryUsage(size(), 0);
}
// Strict resource textures
using GL41StrictResourceTexture = GL41Backend::GL41StrictResourceTexture;
GL41StrictResourceTexture::GL41StrictResourceTexture(const std::weak_ptr<GLBackend>& backend, const Texture& texture) : GL41FixedAllocationTexture(backend, texture) {
withPreservedTexture([&] {
auto mipLevels = _gpuObject.getNumMips();
for (uint16_t sourceMip = 0; sourceMip < mipLevels; sourceMip++) {
uint16_t targetMip = sourceMip;
size_t maxFace = GLTexture::getFaceCount(_target);
for (uint8_t face = 0; face < maxFace; face++) {
copyMipFaceFromTexture(sourceMip, targetMip, face);
}
}
});
if (texture.isAutogenerateMips()) {
generateMips();
}
}
// resource textures
using GL41ResourceTexture = GL41Backend::GL41ResourceTexture;
GL41ResourceTexture::GL41ResourceTexture(const std::weak_ptr<GLBackend>& backend, const Texture& texture) : GL41FixedAllocationTexture(backend, texture) {
Backend::updateTextureGPUMemoryUsage(0, size());
withPreservedTexture([&] {
auto mipLevels = _gpuObject.getNumMips();
for (uint16_t sourceMip = 0; sourceMip < mipLevels; sourceMip++) {
uint16_t targetMip = sourceMip;
size_t maxFace = GLTexture::getFaceCount(_target);
for (uint8_t face = 0; face < maxFace; face++) {
copyMipFaceFromTexture(sourceMip, targetMip, face);
}
}
});
if (texture.isAutogenerateMips()) {
generateMips();
}
}
GL41ResourceTexture::~GL41ResourceTexture() {
Backend::updateTextureGPUMemoryUsage(size(), 0);
}

View file

@ -58,10 +58,10 @@ public:
~GL45FixedAllocationTexture();
protected:
uint32 size() const override { return _size; }
Size size() const override { return _size; }
void allocateStorage() const;
void syncSampler() const override;
const uint32 _size { 0 };
const Size _size { 0 };
};
class GL45AttachmentTexture : public GL45FixedAllocationTexture {
@ -173,7 +173,7 @@ public:
bool canDemote() const { return _allocatedMip < _maxAllocatedMip; }
bool hasPendingTransfers() const { return _populatedMip > _allocatedMip; }
void executeNextTransfer(const TexturePointer& currentTexture);
uint32 size() const override { return _size; }
Size size() const override { return _size; }
virtual void populateTransferQueue() = 0;
virtual void promote() = 0;
virtual void demote() = 0;
@ -188,7 +188,7 @@ public:
// The highest (lowest resolution) mip that we will support, relative to the number
// of mips in the gpu::Texture object
uint16 _maxAllocatedMip { 0 };
uint32 _size { 0 };
Size _size { 0 };
// Contains a series of lambdas that when executed will transfer data to the GPU, modify
// the _populatedMip and update the sampler in order to fully populate the allocated texture
// until _populatedMip == _allocatedMip

View file

@ -18,7 +18,6 @@
#include <glm/gtx/component_wise.hpp>
#include <QtCore/QDebug>
#include <QtCore/QThread>
#include <NumericalConstants.h>
#include "../gl/GLTexelFormat.h"
@ -167,8 +166,10 @@ void GL45Texture::syncSampler() const {
glTextureParameteri(_id, GL_TEXTURE_WRAP_S, WRAP_MODES[sampler.getWrapModeU()]);
glTextureParameteri(_id, GL_TEXTURE_WRAP_T, WRAP_MODES[sampler.getWrapModeV()]);
glTextureParameteri(_id, GL_TEXTURE_WRAP_R, WRAP_MODES[sampler.getWrapModeW()]);
glTextureParameterf(_id, GL_TEXTURE_MAX_ANISOTROPY_EXT, sampler.getMaxAnisotropy());
glTextureParameterfv(_id, GL_TEXTURE_BORDER_COLOR, (const float*)&sampler.getBorderColor());
glTextureParameterf(_id, GL_TEXTURE_MIN_LOD, sampler.getMinMip());
glTextureParameterf(_id, GL_TEXTURE_MAX_LOD, (sampler.getMaxMip() == Sampler::MAX_MIP_LEVEL ? 1000.f : sampler.getMaxMip()));
}
@ -186,10 +187,12 @@ GL45FixedAllocationTexture::~GL45FixedAllocationTexture() {
void GL45FixedAllocationTexture::allocateStorage() const {
const GLTexelFormat texelFormat = GLTexelFormat::evalGLTexelFormat(_gpuObject.getTexelFormat());
const auto dimensions = _gpuObject.getDimensions();
const auto mips = _gpuObject.getNumMipLevels();
const auto mips = _gpuObject.getNumMips();
glTextureStorage2D(_id, mips, texelFormat.internalFormat, dimensions.x, dimensions.y);
glTextureParameteri(_id, GL_TEXTURE_BASE_LEVEL, 0);
glTextureParameteri(_id, GL_TEXTURE_MAX_LEVEL, mips - 1);
}
void GL45FixedAllocationTexture::syncSampler() const {
@ -216,7 +219,7 @@ GL45AttachmentTexture::~GL45AttachmentTexture() {
using GL45StrictResourceTexture = GL45Backend::GL45StrictResourceTexture;
GL45StrictResourceTexture::GL45StrictResourceTexture(const std::weak_ptr<GLBackend>& backend, const Texture& texture) : GL45FixedAllocationTexture(backend, texture) {
auto mipLevels = _gpuObject.getNumMipLevels();
auto mipLevels = _gpuObject.getNumMips();
for (uint16_t sourceMip = 0; sourceMip < mipLevels; ++sourceMip) {
uint16_t targetMip = sourceMip;
size_t maxFace = GLTexture::getFaceCount(_target);

View file

@ -441,7 +441,7 @@ void GL45VariableAllocationTexture::executeNextTransfer(const TexturePointer& cu
using GL45ResourceTexture = GL45Backend::GL45ResourceTexture;
GL45ResourceTexture::GL45ResourceTexture(const std::weak_ptr<GLBackend>& backend, const Texture& texture) : GL45VariableAllocationTexture(backend, texture) {
auto mipLevels = texture.evalNumMips();
auto mipLevels = texture.getNumMips();
_allocatedMip = mipLevels;
uvec3 mipDimensions;
for (uint16_t mip = 0; mip < mipLevels; ++mip) {
@ -463,10 +463,10 @@ void GL45ResourceTexture::allocateStorage(uint16 allocatedMip) {
_allocatedMip = allocatedMip;
const GLTexelFormat texelFormat = GLTexelFormat::evalGLTexelFormat(_gpuObject.getTexelFormat());
const auto dimensions = _gpuObject.evalMipDimensions(_allocatedMip);
const auto totalMips = _gpuObject.getNumMipLevels();
const auto totalMips = _gpuObject.getNumMips();
const auto mips = totalMips - _allocatedMip;
glTextureStorage2D(_id, mips, texelFormat.internalFormat, dimensions.x, dimensions.y);
auto mipLevels = _gpuObject.getNumMipLevels();
auto mipLevels = _gpuObject.getNumMips();
_size = 0;
for (uint16_t mip = _allocatedMip; mip < mipLevels; ++mip) {
_size += _gpuObject.evalMipSize(mip);
@ -476,7 +476,7 @@ void GL45ResourceTexture::allocateStorage(uint16 allocatedMip) {
}
void GL45ResourceTexture::copyMipsFromTexture() {
auto mipLevels = _gpuObject.getNumMipLevels();
auto mipLevels = _gpuObject.getNumMips();
size_t maxFace = GLTexture::getFaceCount(_target);
for (uint16_t sourceMip = _populatedMip; sourceMip < mipLevels; ++sourceMip) {
uint16_t targetMip = sourceMip - _allocatedMip;
@ -495,13 +495,13 @@ void GL45ResourceTexture::promote() {
PROFILE_RANGE(render_gpu_gl, __FUNCTION__);
Q_ASSERT(_allocatedMip > 0);
GLuint oldId = _id;
uint32_t oldSize = _size;
auto oldSize = _size;
// create new texture
const_cast<GLuint&>(_id) = allocate(_gpuObject);
uint16_t oldAllocatedMip = _allocatedMip;
// allocate storage for new level
allocateStorage(_allocatedMip - std::min<uint16_t>(_allocatedMip, 2));
uint16_t mips = _gpuObject.getNumMipLevels();
uint16_t mips = _gpuObject.getNumMips();
// copy pre-existing mips
for (uint16_t mip = _populatedMip; mip < mips; ++mip) {
auto mipDimensions = _gpuObject.evalMipDimensions(mip);
@ -534,7 +534,7 @@ void GL45ResourceTexture::demote() {
const_cast<GLuint&>(_id) = allocate(_gpuObject);
allocateStorage(_allocatedMip + 1);
_populatedMip = std::max(_populatedMip, _allocatedMip);
uint16_t mips = _gpuObject.getNumMipLevels();
uint16_t mips = _gpuObject.getNumMips();
// copy pre-existing mips
for (uint16_t mip = _populatedMip; mip < mips; ++mip) {
auto mipDimensions = _gpuObject.evalMipDimensions(mip);

View file

@ -265,7 +265,7 @@ void Context::incrementBufferGPUCount() {
auto total = ++_bufferGPUCount;
if (total > max.load()) {
max = total;
qCDebug(gpulogging) << "New max GPU buffers " << total;
// qCDebug(gpulogging) << "New max GPU buffers " << total;
}
}
void Context::decrementBufferGPUCount() {
@ -299,7 +299,7 @@ void Context::incrementTextureGPUCount() {
auto total = ++_textureGPUCount;
if (total > max.load()) {
max = total;
qCDebug(gpulogging) << "New max GPU textures " << total;
// qCDebug(gpulogging) << "New max GPU textures " << total;
}
}
void Context::decrementTextureGPUCount() {
@ -311,7 +311,7 @@ void Context::incrementTextureGPUSparseCount() {
auto total = ++_textureGPUSparseCount;
if (total > max.load()) {
max = total;
qCDebug(gpulogging) << "New max GPU textures " << total;
// qCDebug(gpulogging) << "New max GPU textures " << total;
}
}
void Context::decrementTextureGPUSparseCount() {
@ -378,7 +378,7 @@ void Context::incrementTextureGPUTransferCount() {
auto total = ++_textureGPUTransferCount;
if (total > max.load()) {
max = total;
qCDebug(gpulogging) << "New max GPU textures transfers" << total;
// qCDebug(gpulogging) << "New max GPU textures transfers" << total;
}
}

View file

@ -32,7 +32,7 @@ Framebuffer* Framebuffer::create(const std::string& name) {
Framebuffer* Framebuffer::create(const std::string& name, const Format& colorBufferFormat, uint16 width, uint16 height) {
auto framebuffer = Framebuffer::create(name);
auto colorTexture = TexturePointer(Texture::createRenderBuffer(colorBufferFormat, width, height, Sampler(Sampler::FILTER_MIN_MAG_POINT)));
auto colorTexture = TexturePointer(Texture::createRenderBuffer(colorBufferFormat, width, height, Texture::SINGLE_MIP, Sampler(Sampler::FILTER_MIN_MAG_POINT)));
colorTexture->setSource("Framebuffer::colorTexture");
framebuffer->setRenderBuffer(0, colorTexture);
@ -43,8 +43,8 @@ Framebuffer* Framebuffer::create(const std::string& name, const Format& colorBuf
Framebuffer* Framebuffer::create(const std::string& name, const Format& colorBufferFormat, const Format& depthStencilBufferFormat, uint16 width, uint16 height) {
auto framebuffer = Framebuffer::create(name);
auto colorTexture = TexturePointer(Texture::createRenderBuffer(colorBufferFormat, width, height, Sampler(Sampler::FILTER_MIN_MAG_POINT)));
auto depthTexture = TexturePointer(Texture::createRenderBuffer(depthStencilBufferFormat, width, height, Sampler(Sampler::FILTER_MIN_MAG_POINT)));
auto colorTexture = TexturePointer(Texture::createRenderBuffer(colorBufferFormat, width, height, Texture::SINGLE_MIP, Sampler(Sampler::FILTER_MIN_MAG_POINT)));
auto depthTexture = TexturePointer(Texture::createRenderBuffer(depthStencilBufferFormat, width, height, Texture::SINGLE_MIP, Sampler(Sampler::FILTER_MIN_MAG_POINT)));
framebuffer->setRenderBuffer(0, colorTexture);
framebuffer->setDepthStencilBuffer(depthTexture, depthStencilBufferFormat);

View file

@ -149,8 +149,14 @@ PixelsPointer MemoryStorage::getMipFace(uint16 level, uint8 face) const {
return PixelsPointer();
}
Size MemoryStorage::getMipFaceSize(uint16 level, uint8 face) const {
return getMipFace(level, face)->getSize();
PixelsPointer mipFace = getMipFace(level, face);
if (mipFace) {
return mipFace->getSize();
} else {
return 0;
}
}
bool MemoryStorage::isMipAvailable(uint16 level, uint8 face) const {
@ -209,44 +215,43 @@ void Texture::MemoryStorage::assignMipFaceData(uint16 level, uint8 face, const s
Texture* Texture::createExternal(const ExternalRecycler& recycler, const Sampler& sampler) {
Texture* tex = new Texture(TextureUsageType::EXTERNAL);
tex->_type = TEX_2D;
tex->_maxMip = 0;
tex->_maxMipLevel = 0;
tex->_sampler = sampler;
tex->setExternalRecycler(recycler);
return tex;
}
Texture* Texture::createRenderBuffer(const Element& texelFormat, uint16 width, uint16 height, const Sampler& sampler) {
return create(TextureUsageType::RENDERBUFFER, TEX_2D, texelFormat, width, height, 1, 1, 0, sampler);
Texture* Texture::createRenderBuffer(const Element& texelFormat, uint16 width, uint16 height, uint16 numMips, const Sampler& sampler) {
return create(TextureUsageType::RENDERBUFFER, TEX_2D, texelFormat, width, height, 1, 1, 0, numMips, sampler);
}
Texture* Texture::create1D(const Element& texelFormat, uint16 width, const Sampler& sampler) {
return create(TextureUsageType::RESOURCE, TEX_1D, texelFormat, width, 1, 1, 1, 0, sampler);
Texture* Texture::create1D(const Element& texelFormat, uint16 width, uint16 numMips, const Sampler& sampler) {
return create(TextureUsageType::RESOURCE, TEX_1D, texelFormat, width, 1, 1, 1, 0, numMips, sampler);
}
Texture* Texture::create2D(const Element& texelFormat, uint16 width, uint16 height, const Sampler& sampler) {
return create(TextureUsageType::RESOURCE, TEX_2D, texelFormat, width, height, 1, 1, 0, sampler);
Texture* Texture::create2D(const Element& texelFormat, uint16 width, uint16 height, uint16 numMips, const Sampler& sampler) {
return create(TextureUsageType::RESOURCE, TEX_2D, texelFormat, width, height, 1, 1, 0, numMips, sampler);
}
Texture* Texture::createStrict(const Element& texelFormat, uint16 width, uint16 height, const Sampler& sampler) {
return create(TextureUsageType::STRICT_RESOURCE, TEX_2D, texelFormat, width, height, 1, 1, 0, sampler);
Texture* Texture::createStrict(const Element& texelFormat, uint16 width, uint16 height, uint16 numMips, const Sampler& sampler) {
return create(TextureUsageType::STRICT_RESOURCE, TEX_2D, texelFormat, width, height, 1, 1, 0, numMips, sampler);
}
Texture* Texture::create3D(const Element& texelFormat, uint16 width, uint16 height, uint16 depth, const Sampler& sampler) {
return create(TextureUsageType::RESOURCE, TEX_3D, texelFormat, width, height, depth, 1, 0, sampler);
Texture* Texture::create3D(const Element& texelFormat, uint16 width, uint16 height, uint16 depth, uint16 numMips, const Sampler& sampler) {
return create(TextureUsageType::RESOURCE, TEX_3D, texelFormat, width, height, depth, 1, 0, numMips, sampler);
}
Texture* Texture::createCube(const Element& texelFormat, uint16 width, const Sampler& sampler) {
return create(TextureUsageType::RESOURCE, TEX_CUBE, texelFormat, width, width, 1, 1, 0, sampler);
Texture* Texture::createCube(const Element& texelFormat, uint16 width, uint16 numMips, const Sampler& sampler) {
return create(TextureUsageType::RESOURCE, TEX_CUBE, texelFormat, width, width, 1, 1, 0, numMips, sampler);
}
Texture* Texture::create(TextureUsageType usageType, Type type, const Element& texelFormat, uint16 width, uint16 height, uint16 depth, uint16 numSamples, uint16 numSlices, const Sampler& sampler)
Texture* Texture::create(TextureUsageType usageType, Type type, const Element& texelFormat, uint16 width, uint16 height, uint16 depth, uint16 numSamples, uint16 numSlices, uint16 numMips, const Sampler& sampler)
{
Texture* tex = new Texture(usageType);
tex->_storage.reset(new MemoryStorage());
tex->_type = type;
tex->_storage->assignTexture(tex);
tex->_maxMip = 0;
tex->resize(type, texelFormat, width, height, depth, numSamples, numSlices);
tex->resize(type, texelFormat, width, height, depth, numSamples, numSlices, numMips);
tex->_sampler = sampler;
@ -278,7 +283,7 @@ Texture::~Texture() {
}
}
Texture::Size Texture::resize(Type type, const Element& texelFormat, uint16 width, uint16 height, uint16 depth, uint16 numSamples, uint16 numSlices) {
Texture::Size Texture::resize(Type type, const Element& texelFormat, uint16 width, uint16 height, uint16 depth, uint16 numSamples, uint16 numSlices, uint16 numMips) {
if (width && height && depth && numSamples) {
bool changed = false;
@ -313,9 +318,19 @@ Texture::Size Texture::resize(Type type, const Element& texelFormat, uint16 widt
_depth = depth;
changed = true;
}
if ((_maxMipLevel + 1) != numMips) {
_maxMipLevel = safeNumMips(numMips) - 1;
changed = true;
}
if (texelFormat != _texelFormat) {
_texelFormat = texelFormat;
changed = true;
}
// Evaluate the new size with the new format
uint32_t size = NUM_FACES_PER_TYPE[_type] *_width * _height * _depth * _numSamples * texelFormat.getSize();
Size size = NUM_FACES_PER_TYPE[_type] * _height * _depth * evalPaddedSize(_numSamples * _width * _texelFormat.getSize());
// If size change then we need to reset
if (changed || (size != getSize())) {
@ -324,12 +339,6 @@ Texture::Size Texture::resize(Type type, const Element& texelFormat, uint16 widt
_stamp++;
}
// TexelFormat might have change, but it's mostly interpretation
if (texelFormat != _texelFormat) {
_texelFormat = texelFormat;
_stamp++;
}
// Here the Texture has been fully defined from the gpu point of view (size and format)
_defined = true;
} else {
@ -339,23 +348,6 @@ Texture::Size Texture::resize(Type type, const Element& texelFormat, uint16 widt
return _size;
}
Texture::Size Texture::resize1D(uint16 width, uint16 numSamples) {
return resize(TEX_1D, getTexelFormat(), width, 1, 1, numSamples, 0);
}
Texture::Size Texture::resize2D(uint16 width, uint16 height, uint16 numSamples) {
return resize(TEX_2D, getTexelFormat(), width, height, 1, numSamples, 0);
}
Texture::Size Texture::resize3D(uint16 width, uint16 height, uint16 depth, uint16 numSamples) {
return resize(TEX_3D, getTexelFormat(), width, height, depth, numSamples, 0);
}
Texture::Size Texture::resizeCube(uint16 width, uint16 numSamples) {
return resize(TEX_CUBE, getTexelFormat(), width, 1, 1, numSamples, 0);
}
Texture::Size Texture::reformat(const Element& texelFormat) {
return resize(_type, texelFormat, getWidth(), getHeight(), getDepth(), getNumSamples(), _numSlices);
}
bool Texture::isColorRenderTarget() const {
return (_texelFormat.getSemantic() == gpu::RGBA);
}
@ -364,7 +356,7 @@ bool Texture::isDepthStencilRenderTarget() const {
return (_texelFormat.getSemantic() == gpu::DEPTH) || (_texelFormat.getSemantic() == gpu::DEPTH_STENCIL);
}
uint16 Texture::evalDimNumMips(uint16 size) {
uint16 Texture::evalDimMaxNumMips(uint16 size) {
double largerDim = size;
double val = log(largerDim)/log(2.0);
return 1 + (uint16) val;
@ -372,7 +364,7 @@ uint16 Texture::evalDimNumMips(uint16 size) {
static const double LOG_2 = log(2.0);
uint16 Texture::evalNumMips(const Vec3u& dimensions) {
uint16 Texture::evalMaxNumMips(const Vec3u& dimensions) {
double largerDim = glm::compMax(dimensions);
double val = log(largerDim) / LOG_2;
return 1 + (uint16)val;
@ -380,8 +372,34 @@ uint16 Texture::evalNumMips(const Vec3u& dimensions) {
// The number mips that the texture could have if all existed
// = log2(max(width, height, depth))
uint16 Texture::evalNumMips() const {
return evalNumMips({ _width, _height, _depth });
uint16 Texture::evalMaxNumMips() const {
return evalMaxNumMips({ _width, _height, _depth });
}
// Check a num of mips requested against the maximum possible specified
// if passing -1 then answer the max
// simply does (askedNumMips == 0 ? maxNumMips : (numstd::min(askedNumMips, maxNumMips))
uint16 Texture::safeNumMips(uint16 askedNumMips, uint16 maxNumMips) {
if (askedNumMips > 0) {
return std::min(askedNumMips, maxNumMips);
} else {
return maxNumMips;
}
}
// Same but applied to this texture's num max mips from evalNumMips()
uint16 Texture::safeNumMips(uint16 askedNumMips) const {
return safeNumMips(askedNumMips, evalMaxNumMips());
}
Size Texture::evalTotalSize(uint16 startingMip) const {
Size size = 0;
uint16 minMipLevel = std::max(getMinMip(), startingMip);
uint16 maxMipLevel = getMaxMip();
for (uint16 level = minMipLevel; level <= maxMipLevel; level++) {
size += evalMipSize(level);
}
return size * getNumSlices();
}
void Texture::setStoredMipFormat(const Element& format) {
@ -408,7 +426,7 @@ void Texture::assignStoredMip(uint16 level, storage::StoragePointer& storage) {
if (_autoGenerateMips) {
return;
}
if (level >= evalNumMips()) {
if (level >= getNumMips()) {
return;
}
}
@ -418,7 +436,6 @@ void Texture::assignStoredMip(uint16 level, storage::StoragePointer& storage) {
auto size = storage->size();
if (storage->size() == expectedSize) {
_storage->assignMipData(level, storage);
_maxMip = std::max(_maxMip, level);
_stamp++;
} else if (size > expectedSize) {
// NOTE: We are facing this case sometime because apparently QImage (from where we get the bits) is generating images
@ -426,7 +443,6 @@ void Texture::assignStoredMip(uint16 level, storage::StoragePointer& storage) {
// We should probably consider something a bit more smart to get the correct result but for now (UI elements)
// it seems to work...
_storage->assignMipData(level, storage);
_maxMip = std::max(_maxMip, level);
_stamp++;
}
}
@ -437,7 +453,7 @@ void Texture::assignStoredMipFace(uint16 level, uint8 face, storage::StoragePoin
if (_autoGenerateMips) {
return;
}
if (level >= evalNumMips()) {
if (level >= getNumMips()) {
return;
}
}
@ -447,7 +463,6 @@ void Texture::assignStoredMipFace(uint16 level, uint8 face, storage::StoragePoin
auto size = storage->size();
if (size == expectedSize) {
_storage->assignMipFaceData(level, face, storage);
_maxMip = std::max(_maxMip, level);
_stamp++;
} else if (size > expectedSize) {
// NOTE: We are facing this case sometime because apparently QImage (from where we get the bits) is generating images
@ -455,71 +470,36 @@ void Texture::assignStoredMipFace(uint16 level, uint8 face, storage::StoragePoin
// We should probably consider something a bit more smart to get the correct result but for now (UI elements)
// it seems to work...
_storage->assignMipFaceData(level, face, storage);
_maxMip = std::max(_maxMip, level);
_stamp++;
}
}
uint16 Texture::autoGenerateMips(uint16 maxMip) {
void Texture::setAutoGenerateMips(bool enable) {
bool changed = false;
if (!_autoGenerateMips) {
changed = true;
_autoGenerateMips = true;
}
auto newMaxMip = std::min((uint16)(evalNumMips() - 1), maxMip);
if (newMaxMip != _maxMip) {
changed = true;
_maxMip = newMaxMip;;
}
if (changed) {
_stamp++;
}
return _maxMip;
}
uint16 Texture::getStoredMipWidth(uint16 level) const {
if (!isStoredMipFaceAvailable(level)) {
return 0;
Size Texture::getStoredMipSize(uint16 level) const {
PixelsPointer mipFace = accessStoredMipFace(level);
Size size = 0;
if (mipFace && mipFace->getSize()) {
for (int face = 0; face < getNumFaces(); face++) {
size += getStoredMipFaceSize(level, face);
}
}
return evalMipWidth(level);
return size;
}
uint16 Texture::getStoredMipHeight(uint16 level) const {
if (!isStoredMipFaceAvailable(level)) {
return 0;
}
return evalMipHeight(level);
}
uint16 Texture::getStoredMipDepth(uint16 level) const {
if (!isStoredMipFaceAvailable(level)) {
return 0;
}
return evalMipDepth(level);
}
uint32 Texture::getStoredMipNumTexels(uint16 level) const {
if (!isStoredMipFaceAvailable(level)) {
return 0;
}
return evalMipWidth(level) * evalMipHeight(level) * evalMipDepth(level);
}
uint32 Texture::getStoredMipSize(uint16 level) const {
if (!isStoredMipFaceAvailable(level)) {
return 0;
}
return evalMipWidth(level) * evalMipHeight(level) * evalMipDepth(level) * getTexelFormat().getSize();
}
gpu::Resource::Size Texture::getStoredSize() const {
auto size = 0;
for (int level = 0; level < evalNumMips(); ++level) {
Size Texture::getStoredSize() const {
Size size = 0;
for (int level = 0; level < getNumMips(); level++) {
size += getStoredMipSize(level);
}
return size;
@ -937,7 +917,7 @@ bool TextureSource::isDefined() const {
bool Texture::setMinMip(uint16 newMinMip) {
uint16 oldMinMip = _minMip;
_minMip = std::min(std::max(_minMip, newMinMip), _maxMip);
_minMip = std::min(std::max(_minMip, newMinMip), getMaxMip());
return oldMinMip != _minMip;
}

View file

@ -232,9 +232,7 @@ public:
bool operator!=(const Usage& usage) { return (_flags != usage._flags); }
};
using PixelsPointer = storage::StoragePointer;
enum Type {
enum Type : uint8 {
TEX_1D = 0,
TEX_2D,
TEX_3D,
@ -255,7 +253,13 @@ public:
NUM_CUBE_FACES, // Not a valid vace index
};
// Lines of pixels are padded to be a multiple of "PACKING_SIZE" which is 4 bytes
static const uint32 PACKING_SIZE = 4;
static uint8 evalPaddingNumBytes(Size byteSize) { return (uint8) (3 - (byteSize + 3) % PACKING_SIZE); }
static Size evalPaddedSize(Size byteSize) { return byteSize + (Size) evalPaddingNumBytes(byteSize); }
using PixelsPointer = storage::StoragePointer;
class Storage {
public:
Storage() {}
@ -322,14 +326,19 @@ public:
friend class Texture;
};
static Texture* create1D(const Element& texelFormat, uint16 width, const Sampler& sampler = Sampler());
static Texture* create2D(const Element& texelFormat, uint16 width, uint16 height, const Sampler& sampler = Sampler());
static Texture* create3D(const Element& texelFormat, uint16 width, uint16 height, uint16 depth, const Sampler& sampler = Sampler());
static Texture* createCube(const Element& texelFormat, uint16 width, const Sampler& sampler = Sampler());
static Texture* createRenderBuffer(const Element& texelFormat, uint16 width, uint16 height, const Sampler& sampler = Sampler());
static Texture* createStrict(const Element& texelFormat, uint16 width, uint16 height, const Sampler& sampler = Sampler());
static const uint16 MAX_NUM_MIPS = 0;
static const uint16 SINGLE_MIP = 1;
static Texture* create1D(const Element& texelFormat, uint16 width, uint16 numMips = SINGLE_MIP, const Sampler& sampler = Sampler());
static Texture* create2D(const Element& texelFormat, uint16 width, uint16 height, uint16 numMips = SINGLE_MIP, const Sampler& sampler = Sampler());
static Texture* create3D(const Element& texelFormat, uint16 width, uint16 height, uint16 depth, uint16 numMips = SINGLE_MIP, const Sampler& sampler = Sampler());
static Texture* createCube(const Element& texelFormat, uint16 width, uint16 numMips = 1, const Sampler& sampler = Sampler());
static Texture* createRenderBuffer(const Element& texelFormat, uint16 width, uint16 height, uint16 numMips = SINGLE_MIP, const Sampler& sampler = Sampler());
static Texture* createStrict(const Element& texelFormat, uint16 width, uint16 height, uint16 numMips = SINGLE_MIP, const Sampler& sampler = Sampler());
static Texture* createExternal(const ExternalRecycler& recycler, const Sampler& sampler = Sampler());
// After the texture has been created, it should be defined
bool isDefined() const { return _defined; }
Texture(TextureUsageType usageType);
Texture(const Texture& buf); // deep copy of the sysmem texture
Texture& operator=(const Texture& buf); // deep copy of the sysmem texture
@ -339,20 +348,9 @@ public:
Stamp getDataStamp() const { return _storage->getStamp(); }
// The theoretical size in bytes of data stored in the texture
// For the master (level) first level of mip
Size getSize() const override { return _size; }
// The actual size in bytes of data stored in the texture
Size getStoredSize() const;
// Resize, unless auto mips mode would destroy all the sub mips
Size resize1D(uint16 width, uint16 numSamples);
Size resize2D(uint16 width, uint16 height, uint16 numSamples);
Size resize3D(uint16 width, uint16 height, uint16 depth, uint16 numSamples);
Size resizeCube(uint16 width, uint16 numSamples);
// Reformat, unless auto mips mode would destroy all the sub mips
Size reformat(const Element& texelFormat);
// Size and format
Type getType() const { return _type; }
TextureUsageType getUsageType() const { return _usageType; }
@ -361,23 +359,18 @@ public:
bool isDepthStencilRenderTarget() const;
const Element& getTexelFormat() const { return _texelFormat; }
bool hasBorder() const { return false; }
Vec3u getDimensions() const { return Vec3u(_width, _height, _depth); }
uint16 getWidth() const { return _width; }
uint16 getHeight() const { return _height; }
uint16 getDepth() const { return _depth; }
uint32 getRowPitch() const { return getWidth() * getTexelFormat().getSize(); }
// The number of faces is mostly used for cube map, and maybe for stereo ? otherwise it's 1
// For cube maps, this means the pixels of the different faces are supposed to be packed back to back in a mip
// as if the height was NUM_FACES time bigger.
static uint8 NUM_FACES_PER_TYPE[NUM_TYPES];
uint8 getNumFaces() const { return NUM_FACES_PER_TYPE[getType()]; }
uint32 getNumTexels() const { return _width * _height * _depth * getNumFaces(); }
// The texture is an array if the _numSlices is not 0.
// otherwise, if _numSLices is 0, then the texture is NOT an array
// The number of slices returned is 1 at the minimum (if not an array) or the actual _numSlices.
@ -385,79 +378,74 @@ public:
uint16 getNumSlices() const { return (isArray() ? _numSlices : 1); }
uint16 getNumSamples() const { return _numSamples; }
// NumSamples can only have certain values based on the hw
static uint16 evalNumSamplesUsed(uint16 numSamplesTried);
// max mip is in the range [ 0 if no sub mips, log2(max(width, height, depth))]
// It is defined at creation time (immutable)
uint16 getMaxMip() const { return _maxMipLevel; }
uint16 getNumMips() const { return _maxMipLevel + 1; }
// Mips size evaluation
// The number mips that a dimension could haves
// = 1 + log2(size)
static uint16 evalDimNumMips(uint16 size);
static uint16 evalDimMaxNumMips(uint16 size);
// The number mips that the texture could have if all existed
// = 1 + log2(max(width, height, depth))
uint16 evalNumMips() const;
uint16 evalMaxNumMips() const;
static uint16 evalMaxNumMips(const Vec3u& dimensions);
static uint16 evalNumMips(const Vec3u& dimensions);
// Check a num of mips requested against the maximum possible specified
// if passing -1 then answer the max
// simply does (askedNumMips == -1 ? maxMips : (numstd::min(askedNumMips, max))
static uint16 safeNumMips(uint16 askedNumMips, uint16 maxMips);
// Same but applied to this texture's num max mips from evalNumMips()
uint16 safeNumMips(uint16 askedNumMips) const;
// Eval the size that the mips level SHOULD have
// not the one stored in the Texture
static const uint MIN_DIMENSION = 1;
Vec3u evalMipDimensions(uint16 level) const;
uint16 evalMipWidth(uint16 level) const { return std::max(_width >> level, 1); }
uint16 evalMipHeight(uint16 level) const { return std::max(_height >> level, 1); }
uint16 evalMipDepth(uint16 level) const { return std::max(_depth >> level, 1); }
// The size of a face is a multiple of the padded line = (width * texelFormat_size + alignment padding)
Size evalMipLineSize(uint16 level) const { return evalPaddedSize(evalMipWidth(level) * getTexelFormat().getSize()); }
// Size for each face of a mip at a particular level
uint32 evalMipFaceNumTexels(uint16 level) const { return evalMipWidth(level) * evalMipHeight(level) * evalMipDepth(level); }
uint32 evalMipFaceSize(uint16 level) const { return evalMipFaceNumTexels(level) * getTexelFormat().getSize(); }
Size evalMipFaceSize(uint16 level) const { return evalMipLineSize(level) * evalMipHeight(level) * evalMipDepth(level); }
// Total size for the mip
uint32 evalMipNumTexels(uint16 level) const { return evalMipFaceNumTexels(level) * getNumFaces(); }
uint32 evalMipSize(uint16 level) const { return evalMipNumTexels(level) * getTexelFormat().getSize(); }
Size evalMipSize(uint16 level) const { return evalMipFaceSize(level) * getNumFaces(); }
uint32 evalStoredMipFaceSize(uint16 level, const Element& format) const { return evalMipFaceNumTexels(level) * format.getSize(); }
uint32 evalStoredMipSize(uint16 level, const Element& format) const { return evalMipNumTexels(level) * format.getSize(); }
// Total size for all the mips of the texture
Size evalTotalSize(uint16 startingMip = 0) const;
uint32 evalTotalSize(uint16 startingMip = 0) const {
uint32 size = 0;
uint16 minMipLevel = std::max(getMinMip(), startingMip);
uint16 maxMipLevel = getMaxMip();
for (uint16 l = minMipLevel; l <= maxMipLevel; l++) {
size += evalMipSize(l);
}
return size * getNumSlices();
}
// max mip is in the range [ 0 if no sub mips, log2(max(width, height, depth))]
// if autoGenerateMip is on => will provide the maxMIp level specified
// else provide the deepest mip level provided through assignMip
uint16 getMaxMip() const { return _maxMip; }
uint16 getMinMip() const { return _minMip; }
uint16 getNumMipLevels() const { return _maxMip + 1; }
uint16 usedMipLevels() const { return (_maxMip - _minMip) + 1; }
// Compute the theorical size of the texture elements storage depending on the specified format
Size evalStoredMipLineSize(uint16 level, const Element& format) const { return evalPaddedSize(evalMipWidth(level) * format.getSize()); }
Size evalStoredMipFaceSize(uint16 level, const Element& format) const { return evalMipFaceNumTexels(level) * format.getSize(); }
Size evalStoredMipSize(uint16 level, const Element& format) const { return evalMipNumTexels(level) * format.getSize(); }
// For convenience assign a source name
const std::string& source() const { return _source; }
void setSource(const std::string& source) { _source = source; }
// Potentially change the minimum mip (mostly for debugging purpose)
bool setMinMip(uint16 newMinMip);
bool incremementMinMip(uint16 count = 1);
uint16 getMinMip() const { return _minMip; }
uint16 usedMipLevels() const { return (getNumMips() - _minMip); }
// Generate the mips automatically
// But the sysmem version is not available
// Generate the sub mips automatically for the texture
// If the storage version is not available (from CPU memory)
// Only works for the standard formats
// Specify the maximum Mip level available
// 0 is the default one
// 1 is the first level
// ...
// nbMips - 1 is the last mip level
//
// If -1 then all the mips are generated
//
// Return the totalnumber of mips that will be available
uint16 autoGenerateMips(uint16 maxMip);
void setAutoGenerateMips(bool enable);
bool isAutogenerateMips() const { return _autoGenerateMips; }
// Managing Storage and mips
@ -471,30 +459,22 @@ public:
// in case autoGen is on, this doesn't allocate
// Explicitely assign mip data for a certain level
// If Bytes is NULL then simply allocate the space so mip sysmem can be accessed
void assignStoredMip(uint16 level, Size size, const Byte* bytes);
void assignStoredMipFace(uint16 level, uint8 face, Size size, const Byte* bytes);
void assignStoredMip(uint16 level, storage::StoragePointer& storage);
void assignStoredMipFace(uint16 level, uint8 face, storage::StoragePointer& storage);
// Access the the sub mips
bool isStoredMipFaceAvailable(uint16 level, uint8 face = 0) const { return _storage->isMipAvailable(level, face); }
// Access the stored mips and faces
const PixelsPointer accessStoredMipFace(uint16 level, uint8 face = 0) const { return _storage->getMipFace(level, face); }
bool isStoredMipFaceAvailable(uint16 level, uint8 face = 0) const { return _storage->isMipAvailable(level, face); }
Size getStoredMipFaceSize(uint16 level, uint8 face = 0) const { return _storage->getMipFaceSize(level, face); }
Size getStoredMipSize(uint16 level) const;
Size getStoredSize() const;
void setStorage(std::unique_ptr<Storage>& newStorage);
void setKtxBacking(const std::string& filename);
// access sizes for the stored mips
uint16 getStoredMipWidth(uint16 level) const;
uint16 getStoredMipHeight(uint16 level) const;
uint16 getStoredMipDepth(uint16 level) const;
uint32 getStoredMipNumTexels(uint16 level) const;
uint32 getStoredMipSize(uint16 level) const;
bool isDefined() const { return _defined; }
// Usage is a a set of flags providing Semantic about the usage of the Texture.
void setUsage(const Usage& usage) { _usage = usage; }
Usage getUsage() const { return _usage; }
@ -520,7 +500,7 @@ public:
ExternalUpdates getUpdates() const;
// Textures can be serialized directly to ktx data file, here is how
// Textures can be serialized directly to ktx data file, here is how
static ktx::KTXUniquePointer serialize(const Texture& texture);
static Texture* unserialize(const std::string& ktxFile, TextureUsageType usageType = TextureUsageType::RESOURCE, Usage usage = Usage(), const Sampler::Desc& sampler = Sampler::Desc());
static bool evalKTXFormat(const Element& mipFormat, const Element& texelFormat, ktx::Header& header);
@ -545,7 +525,7 @@ protected:
Sampler _sampler;
Stamp _samplerStamp { 0 };
uint32 _size { 0 };
Size _size { 0 };
Element _texelFormat;
uint16 _width { 1 };
@ -553,9 +533,14 @@ protected:
uint16 _depth { 1 };
uint16 _numSamples { 1 };
uint16 _numSlices { 0 }; // if _numSlices is 0, the texture is not an "Array", the getNumSlices reported is 1
uint16 _maxMip { 0 };
// if _numSlices is 0, the texture is not an "Array", the getNumSlices reported is 1
uint16 _numSlices { 0 };
// valid _maxMipLevel is in the range [ 0 if no sub mips, log2(max(width, height, depth) ]
// The num of mips returned is _maxMipLevel + 1
uint16 _maxMipLevel { 0 };
uint16 _minMip { 0 };
Type _type { TEX_1D };
@ -567,9 +552,9 @@ protected:
bool _isIrradianceValid = false;
bool _defined = false;
static Texture* create(TextureUsageType usageType, Type type, const Element& texelFormat, uint16 width, uint16 height, uint16 depth, uint16 numSamples, uint16 numSlices, const Sampler& sampler);
static Texture* create(TextureUsageType usageType, Type type, const Element& texelFormat, uint16 width, uint16 height, uint16 depth, uint16 numSamples, uint16 numSlices, uint16 numMips, const Sampler& sampler);
Size resize(Type type, const Element& texelFormat, uint16 width, uint16 height, uint16 depth, uint16 numSamples, uint16 numSlices);
Size resize(Type type, const Element& texelFormat, uint16 width, uint16 height, uint16 depth, uint16 numSamples, uint16 numSlices, uint16 numMips);
};
typedef std::shared_ptr<Texture> TexturePointer;

View file

@ -128,7 +128,7 @@ ktx::KTXUniquePointer Texture::serialize(const Texture& texture) {
}
// Number level of mips coming
header.numberOfMipmapLevels = texture.getNumMipLevels();
header.numberOfMipmapLevels = texture.getNumMips();
ktx::Images images;
for (uint32_t level = 0; level < header.numberOfMipmapLevels; level++) {
@ -224,6 +224,7 @@ Texture* Texture::unserialize(const std::string& ktxfile, TextureUsageType usage
header.getPixelDepth(),
1, // num Samples
header.getNumberOfSlices(),
header.getNumberOfLevels(),
(isGPUKTXPayload ? gpuktxKeyValue._samplerDesc : sampler));
tex->setUsage((isGPUKTXPayload ? gpuktxKeyValue._usage : usage));

View file

@ -148,7 +148,7 @@ void Light::setAmbientSpherePreset(gpu::SphericalHarmonics::Preset preset) {
void Light::setAmbientMap(gpu::TexturePointer ambientMap) {
_ambientMap = ambientMap;
if (ambientMap) {
setAmbientMapNumMips(_ambientMap->evalNumMips());
setAmbientMapNumMips(_ambientMap->getNumMips());
} else {
setAmbientMapNumMips(0);
}

View file

@ -210,7 +210,7 @@ const QImage& image, bool isLinear, bool doCompress) {
void generateMips(gpu::Texture* texture, QImage& image, bool fastResize) {
#if CPU_MIPMAPS
PROFILE_RANGE(resource_parse, "generateMips");
auto numMips = texture->evalNumMips();
auto numMips = texture->getNumMips();
for (uint16 level = 1; level < numMips; ++level) {
QSize mipSize(texture->evalMipWidth(level), texture->evalMipHeight(level));
if (fastResize) {
@ -230,7 +230,7 @@ void generateMips(gpu::Texture* texture, QImage& image, bool fastResize) {
void generateFaceMips(gpu::Texture* texture, QImage& image, uint8 face) {
#if CPU_MIPMAPS
PROFILE_RANGE(resource_parse, "generateFaceMips");
auto numMips = texture->evalNumMips();
auto numMips = texture->getNumMips();
for (uint16 level = 1; level < numMips; ++level) {
QSize mipSize(texture->evalMipWidth(level), texture->evalMipHeight(level));
QImage mipImage = image.scaled(mipSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
@ -255,9 +255,9 @@ gpu::Texture* TextureUsage::process2DTextureColorFromImage(const QImage& srcImag
defineColorTexelFormats(formatGPU, formatMip, image, isLinear, doCompress);
if (isStrict) {
theTexture = (gpu::Texture::createStrict(formatGPU, image.width(), image.height(), gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR)));
theTexture = (gpu::Texture::createStrict(formatGPU, image.width(), image.height(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR)));
} else {
theTexture = (gpu::Texture::create2D(formatGPU, image.width(), image.height(), gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR)));
theTexture = (gpu::Texture::create2D(formatGPU, image.width(), image.height(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR)));
}
theTexture->setSource(srcImageName);
auto usage = gpu::Texture::Usage::Builder().withColor();
@ -317,7 +317,7 @@ gpu::Texture* TextureUsage::createNormalTextureFromNormalImage(const QImage& src
gpu::Element formatMip = gpu::Element::COLOR_BGRA_32;
gpu::Element formatGPU = gpu::Element::COLOR_RGBA_32;
theTexture = (gpu::Texture::create2D(formatGPU, image.width(), image.height(), gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR)));
theTexture = (gpu::Texture::create2D(formatGPU, image.width(), image.height(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR)));
theTexture->setSource(srcImageName);
theTexture->setStoredMipFormat(formatMip);
theTexture->assignStoredMip(0, image.byteCount(), image.constBits());
@ -345,8 +345,8 @@ gpu::Texture* TextureUsage::createNormalTextureFromBumpImage(const QImage& srcIm
PROFILE_RANGE(resource_parse, "createNormalTextureFromBumpImage");
QImage image = processSourceImage(srcImage, false);
if (image.format() != QImage::Format_RGB888) {
image = image.convertToFormat(QImage::Format_RGB888);
if (image.format() != QImage::Format_Grayscale8) {
image = image.convertToFormat(QImage::Format_Grayscale8);
}
// PR 5540 by AlessandroSigna integrated here as a specialized TextureLoader for bumpmaps
@ -395,7 +395,7 @@ gpu::Texture* TextureUsage::createNormalTextureFromBumpImage(const QImage& srcIm
glm::normalize(v);
// convert to rgb from the value obtained computing the filter
QRgb qRgbValue = qRgba(mapComponent(v.x), mapComponent(v.y), mapComponent(v.z), 1.0);
QRgb qRgbValue = qRgba(mapComponent(v.z), mapComponent(v.y), mapComponent(v.x), 1.0);
result.setPixel(i, j, qRgbValue);
}
}
@ -407,7 +407,7 @@ gpu::Texture* TextureUsage::createNormalTextureFromBumpImage(const QImage& srcIm
gpu::Element formatGPU = gpu::Element::COLOR_RGBA_32;
theTexture = (gpu::Texture::create2D(formatGPU, result.width(), result.height(), gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR)));
theTexture = (gpu::Texture::create2D(formatGPU, result.width(), result.height(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR)));
theTexture->setSource(srcImageName);
theTexture->setStoredMipFormat(formatMip);
theTexture->assignStoredMip(0, result.byteCount(), result.constBits());
@ -443,7 +443,7 @@ gpu::Texture* TextureUsage::createRoughnessTextureFromImage(const QImage& srcIma
#endif
gpu::Element formatMip = gpu::Element::COLOR_R_8;
theTexture = (gpu::Texture::create2D(formatGPU, image.width(), image.height(), gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR)));
theTexture = (gpu::Texture::create2D(formatGPU, image.width(), image.height(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR)));
theTexture->setSource(srcImageName);
theTexture->setStoredMipFormat(formatMip);
theTexture->assignStoredMip(0, image.byteCount(), image.constBits());
@ -483,7 +483,7 @@ gpu::Texture* TextureUsage::createRoughnessTextureFromGlossImage(const QImage& s
#endif
gpu::Element formatMip = gpu::Element::COLOR_R_8;
theTexture = (gpu::Texture::create2D(formatGPU, image.width(), image.height(), gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR)));
theTexture = (gpu::Texture::create2D(formatGPU, image.width(), image.height(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR)));
theTexture->setSource(srcImageName);
theTexture->setStoredMipFormat(formatMip);
theTexture->assignStoredMip(0, image.byteCount(), image.constBits());
@ -520,7 +520,7 @@ gpu::Texture* TextureUsage::createMetallicTextureFromImage(const QImage& srcImag
#endif
gpu::Element formatMip = gpu::Element::COLOR_R_8;
theTexture = (gpu::Texture::create2D(formatGPU, image.width(), image.height(), gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR)));
theTexture = (gpu::Texture::create2D(formatGPU, image.width(), image.height(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR)));
theTexture->setSource(srcImageName);
theTexture->setStoredMipFormat(formatMip);
theTexture->assignStoredMip(0, image.byteCount(), image.constBits());
@ -836,7 +836,7 @@ gpu::Texture* TextureUsage::processCubeTextureColorFromImage(const QImage& srcIm
// If the 6 faces have been created go on and define the true Texture
if (faces.size() == gpu::Texture::NUM_FACES_PER_TYPE[gpu::Texture::TEX_CUBE]) {
theTexture = gpu::Texture::createCube(formatGPU, faces[0].width(), gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR, gpu::Sampler::WRAP_CLAMP));
theTexture = gpu::Texture::createCube(formatGPU, faces[0].width(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR, gpu::Sampler::WRAP_CLAMP));
theTexture->setSource(srcImageName);
theTexture->setStoredMipFormat(formatMip);
int f = 0;
@ -848,11 +848,6 @@ gpu::Texture* TextureUsage::processCubeTextureColorFromImage(const QImage& srcIm
f++;
}
if (generateMips) {
PROFILE_RANGE(resource_parse, "generateMips");
theTexture->autoGenerateMips(-1);
}
// Generate irradiance while we are at it
if (generateIrradiance) {
PROFILE_RANGE(resource_parse, "generateIrradiance");

View file

@ -41,7 +41,7 @@ class AddressManager : public QObject, public Dependency {
Q_PROPERTY(QString pathname READ currentPath)
Q_PROPERTY(QString placename READ getPlaceName)
Q_PROPERTY(QString domainId READ getDomainId)
Q_PROPERTY(QUrl metaverseServerUrl READ getMetaverseServerUrl)
Q_PROPERTY(QUrl metaverseServerUrl READ getMetaverseServerUrl NOTIFY metaverseServerUrlChanged)
public:
Q_INVOKABLE QString protocolVersion();
using PositionGetter = std::function<glm::vec3()>;
@ -123,6 +123,8 @@ signals:
void goBackPossible(bool isPossible);
void goForwardPossible(bool isPossible);
void metaverseServerUrlChanged();
protected:
AddressManager();
private slots:

View file

@ -325,7 +325,7 @@ void Procedural::prepare(gpu::Batch& batch, const glm::vec3& position, const glm
auto gpuTexture = _channels[i]->getGPUTexture();
if (gpuTexture) {
gpuTexture->setSampler(sampler);
gpuTexture->autoGenerateMips(-1);
gpuTexture->setAutoGenerateMips(true);
}
batch.setResourceTexture((gpu::uint32)i, gpuTexture);
}

View file

@ -74,11 +74,11 @@ void AmbientOcclusionFramebuffer::allocate() {
auto width = _frameSize.x;
auto height = _frameSize.y;
_occlusionTexture = gpu::TexturePointer(gpu::Texture::createRenderBuffer(gpu::Element::COLOR_RGBA_32, width, height, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR_MIP_POINT)));
_occlusionTexture = gpu::TexturePointer(gpu::Texture::createRenderBuffer(gpu::Element::COLOR_RGBA_32, width, height, gpu::Texture::SINGLE_MIP, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR_MIP_POINT)));
_occlusionFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("occlusion"));
_occlusionFramebuffer->setRenderBuffer(0, _occlusionTexture);
_occlusionBlurredTexture = gpu::TexturePointer(gpu::Texture::createRenderBuffer(gpu::Element::COLOR_RGBA_32, width, height, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR_MIP_POINT)));
_occlusionBlurredTexture = gpu::TexturePointer(gpu::Texture::createRenderBuffer(gpu::Element::COLOR_RGBA_32, width, height, gpu::Texture::SINGLE_MIP, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR_MIP_POINT)));
_occlusionBlurredFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("occlusionBlurred"));
_occlusionBlurredFramebuffer->setRenderBuffer(0, _occlusionBlurredTexture);
}

View file

@ -52,7 +52,7 @@ const gpu::PipelinePointer& Antialiasing::getAntialiasingPipeline() {
_antialiasingBuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("antialiasing"));
auto format = gpu::Element::COLOR_SRGBA_32; // DependencyManager::get<FramebufferCache>()->getLightingTexture()->getTexelFormat();
auto defaultSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_POINT);
_antialiasingTexture = gpu::TexturePointer(gpu::Texture::createRenderBuffer(format, width, height, defaultSampler));
_antialiasingTexture = gpu::TexturePointer(gpu::Texture::createRenderBuffer(format, width, height, gpu::Texture::SINGLE_MIP, defaultSampler));
_antialiasingBuffer->setRenderBuffer(0, _antialiasingTexture);
}

View file

@ -53,9 +53,9 @@ void DeferredFramebuffer::allocate() {
auto defaultSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_POINT);
_deferredColorTexture = gpu::TexturePointer(gpu::Texture::createRenderBuffer(colorFormat, width, height, defaultSampler));
_deferredNormalTexture = gpu::TexturePointer(gpu::Texture::createRenderBuffer(linearFormat, width, height, defaultSampler));
_deferredSpecularTexture = gpu::TexturePointer(gpu::Texture::createRenderBuffer(colorFormat, width, height, defaultSampler));
_deferredColorTexture = gpu::TexturePointer(gpu::Texture::createRenderBuffer(colorFormat, width, height, gpu::Texture::SINGLE_MIP, defaultSampler));
_deferredNormalTexture = gpu::TexturePointer(gpu::Texture::createRenderBuffer(linearFormat, width, height, gpu::Texture::SINGLE_MIP, defaultSampler));
_deferredSpecularTexture = gpu::TexturePointer(gpu::Texture::createRenderBuffer(colorFormat, width, height, gpu::Texture::SINGLE_MIP, defaultSampler));
_deferredFramebuffer->setRenderBuffer(0, _deferredColorTexture);
_deferredFramebuffer->setRenderBuffer(1, _deferredNormalTexture);
@ -65,7 +65,7 @@ void DeferredFramebuffer::allocate() {
auto depthFormat = gpu::Element(gpu::SCALAR, gpu::UINT32, gpu::DEPTH_STENCIL); // Depth24_Stencil8 texel format
if (!_primaryDepthTexture) {
_primaryDepthTexture = gpu::TexturePointer(gpu::Texture::createRenderBuffer(depthFormat, width, height, defaultSampler));
_primaryDepthTexture = gpu::TexturePointer(gpu::Texture::createRenderBuffer(depthFormat, width, height, gpu::Texture::SINGLE_MIP, defaultSampler));
}
_deferredFramebuffer->setDepthStencilBuffer(_primaryDepthTexture, depthFormat);
@ -75,7 +75,7 @@ void DeferredFramebuffer::allocate() {
auto smoothSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR);
_lightingTexture = gpu::TexturePointer(gpu::Texture::createRenderBuffer(gpu::Element(gpu::SCALAR, gpu::FLOAT, gpu::R11G11B10), width, height, defaultSampler));
_lightingTexture = gpu::TexturePointer(gpu::Texture::createRenderBuffer(gpu::Element(gpu::SCALAR, gpu::FLOAT, gpu::R11G11B10), width, height, gpu::Texture::SINGLE_MIP, defaultSampler));
_lightingFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("lighting"));
_lightingFramebuffer->setRenderBuffer(0, _lightingTexture);
_lightingFramebuffer->setDepthStencilBuffer(_primaryDepthTexture, depthFormat);

View file

@ -496,14 +496,14 @@ void PreparePrimaryFramebuffer::run(const SceneContextPointer& sceneContext, con
auto colorFormat = gpu::Element::COLOR_SRGBA_32;
auto defaultSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_POINT);
auto primaryColorTexture = gpu::TexturePointer(gpu::Texture::createRenderBuffer(colorFormat, frameSize.x, frameSize.y, defaultSampler));
auto primaryColorTexture = gpu::TexturePointer(gpu::Texture::createRenderBuffer(colorFormat, frameSize.x, frameSize.y, gpu::Texture::SINGLE_MIP, defaultSampler));
_primaryFramebuffer->setRenderBuffer(0, primaryColorTexture);
auto depthFormat = gpu::Element(gpu::SCALAR, gpu::UINT32, gpu::DEPTH_STENCIL); // Depth24_Stencil8 texel format
auto primaryDepthTexture = gpu::TexturePointer(gpu::Texture::createRenderBuffer(depthFormat, frameSize.x, frameSize.y, defaultSampler));
auto primaryDepthTexture = gpu::TexturePointer(gpu::Texture::createRenderBuffer(depthFormat, frameSize.x, frameSize.y, gpu::Texture::SINGLE_MIP, defaultSampler));
_primaryFramebuffer->setDepthStencilBuffer(primaryDepthTexture, depthFormat);
}

View file

@ -168,8 +168,6 @@ void MeshPartPayload::bindMaterial(gpu::Batch& batch, const ShapePipeline::Locat
} else {
batch.setResourceTexture(ShapePipeline::Slot::ALBEDO, textureCache->getGrayTexture());
}
} else {
batch.setResourceTexture(ShapePipeline::Slot::ALBEDO, textureCache->getWhiteTexture());
}
// Roughness map
@ -182,8 +180,6 @@ void MeshPartPayload::bindMaterial(gpu::Batch& batch, const ShapePipeline::Locat
} else {
batch.setResourceTexture(ShapePipeline::Slot::MAP::ROUGHNESS, textureCache->getWhiteTexture());
}
} else {
batch.setResourceTexture(ShapePipeline::Slot::MAP::ROUGHNESS, textureCache->getWhiteTexture());
}
// Normal map
@ -196,8 +192,6 @@ void MeshPartPayload::bindMaterial(gpu::Batch& batch, const ShapePipeline::Locat
} else {
batch.setResourceTexture(ShapePipeline::Slot::MAP::NORMAL, textureCache->getBlueTexture());
}
} else {
batch.setResourceTexture(ShapePipeline::Slot::MAP::NORMAL, nullptr);
}
// Metallic map
@ -210,8 +204,6 @@ void MeshPartPayload::bindMaterial(gpu::Batch& batch, const ShapePipeline::Locat
} else {
batch.setResourceTexture(ShapePipeline::Slot::MAP::METALLIC, textureCache->getBlackTexture());
}
} else {
batch.setResourceTexture(ShapePipeline::Slot::MAP::METALLIC, nullptr);
}
// Occlusion map
@ -224,8 +216,6 @@ void MeshPartPayload::bindMaterial(gpu::Batch& batch, const ShapePipeline::Locat
} else {
batch.setResourceTexture(ShapePipeline::Slot::MAP::OCCLUSION, textureCache->getWhiteTexture());
}
} else {
batch.setResourceTexture(ShapePipeline::Slot::MAP::OCCLUSION, nullptr);
}
// Scattering map
@ -238,8 +228,6 @@ void MeshPartPayload::bindMaterial(gpu::Batch& batch, const ShapePipeline::Locat
} else {
batch.setResourceTexture(ShapePipeline::Slot::MAP::SCATTERING, textureCache->getWhiteTexture());
}
} else {
batch.setResourceTexture(ShapePipeline::Slot::MAP::SCATTERING, nullptr);
}
// Emissive / Lightmap
@ -259,8 +247,6 @@ void MeshPartPayload::bindMaterial(gpu::Batch& batch, const ShapePipeline::Locat
} else {
batch.setResourceTexture(ShapePipeline::Slot::MAP::EMISSIVE_LIGHTMAP, textureCache->getBlackTexture());
}
} else {
batch.setResourceTexture(ShapePipeline::Slot::MAP::EMISSIVE_LIGHTMAP, nullptr);
}
}

View file

@ -73,11 +73,11 @@ void PrepareFramebuffer::run(const SceneContextPointer& sceneContext, const Rend
auto colorFormat = gpu::Element::COLOR_SRGBA_32;
auto defaultSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_POINT);
auto colorTexture = gpu::TexturePointer(gpu::Texture::create2D(colorFormat, frameSize.x, frameSize.y, defaultSampler));
auto colorTexture = gpu::TexturePointer(gpu::Texture::create2D(colorFormat, frameSize.x, frameSize.y, gpu::Texture::SINGLE_MIP, defaultSampler));
_framebuffer->setRenderBuffer(0, colorTexture);
auto depthFormat = gpu::Element(gpu::SCALAR, gpu::UINT32, gpu::DEPTH_STENCIL); // Depth24_Stencil8 texel format
auto depthTexture = gpu::TexturePointer(gpu::Texture::create2D(depthFormat, frameSize.x, frameSize.y, defaultSampler));
auto depthTexture = gpu::TexturePointer(gpu::Texture::create2D(depthFormat, frameSize.x, frameSize.y, gpu::Texture::SINGLE_MIP, defaultSampler));
_framebuffer->setDepthStencilBuffer(depthTexture, depthFormat);
}

View file

@ -414,7 +414,7 @@ gpu::TexturePointer SubsurfaceScatteringResource::generateScatteringProfile(Rend
const int PROFILE_RESOLUTION = 512;
// const auto pixelFormat = gpu::Element::COLOR_SRGBA_32;
const auto pixelFormat = gpu::Element::COLOR_R11G11B10;
auto profileMap = gpu::TexturePointer(gpu::Texture::createRenderBuffer(pixelFormat, PROFILE_RESOLUTION, 1, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR, gpu::Sampler::WRAP_CLAMP)));
auto profileMap = gpu::TexturePointer(gpu::Texture::createRenderBuffer(pixelFormat, PROFILE_RESOLUTION, 1, gpu::Texture::SINGLE_MIP, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR, gpu::Sampler::WRAP_CLAMP)));
profileMap->setSource("Generated Scattering Profile");
diffuseProfileGPU(profileMap, args);
return profileMap;
@ -425,7 +425,7 @@ gpu::TexturePointer SubsurfaceScatteringResource::generatePreIntegratedScatterin
const int TABLE_RESOLUTION = 512;
// const auto pixelFormat = gpu::Element::COLOR_SRGBA_32;
const auto pixelFormat = gpu::Element::COLOR_R11G11B10;
auto scatteringLUT = gpu::TexturePointer(gpu::Texture::createRenderBuffer(pixelFormat, TABLE_RESOLUTION, TABLE_RESOLUTION, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR, gpu::Sampler::WRAP_CLAMP)));
auto scatteringLUT = gpu::TexturePointer(gpu::Texture::createRenderBuffer(pixelFormat, TABLE_RESOLUTION, TABLE_RESOLUTION, gpu::Texture::SINGLE_MIP, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR, gpu::Sampler::WRAP_CLAMP)));
//diffuseScatter(scatteringLUT);
scatteringLUT->setSource("Generated pre-integrated scattering");
diffuseScatterGPU(profile, scatteringLUT, args);
@ -434,7 +434,7 @@ gpu::TexturePointer SubsurfaceScatteringResource::generatePreIntegratedScatterin
gpu::TexturePointer SubsurfaceScatteringResource::generateScatteringSpecularBeckmann(RenderArgs* args) {
const int SPECULAR_RESOLUTION = 256;
auto beckmannMap = gpu::TexturePointer(gpu::Texture::createRenderBuffer(gpu::Element::COLOR_RGBA_32 /*gpu::Element(gpu::SCALAR, gpu::HALF, gpu::RGB)*/, SPECULAR_RESOLUTION, SPECULAR_RESOLUTION, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR, gpu::Sampler::WRAP_CLAMP)));
auto beckmannMap = gpu::TexturePointer(gpu::Texture::createRenderBuffer(gpu::Element::COLOR_RGBA_32, SPECULAR_RESOLUTION, SPECULAR_RESOLUTION, gpu::Texture::SINGLE_MIP, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR, gpu::Sampler::WRAP_CLAMP)));
beckmannMap->setSource("Generated beckmannMap");
computeSpecularBeckmannGPU(beckmannMap, args);
return beckmannMap;

View file

@ -72,18 +72,18 @@ void LinearDepthFramebuffer::allocate() {
auto height = _frameSize.y;
// For Linear Depth:
_linearDepthTexture = gpu::TexturePointer(gpu::Texture::createRenderBuffer(gpu::Element(gpu::SCALAR, gpu::FLOAT, gpu::RED), width, height,
_linearDepthTexture = gpu::TexturePointer(gpu::Texture::createRenderBuffer(gpu::Element(gpu::SCALAR, gpu::FLOAT, gpu::RED), width, height, gpu::Texture::SINGLE_MIP,
gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR_MIP_POINT)));
_linearDepthFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("linearDepth"));
_linearDepthFramebuffer->setRenderBuffer(0, _linearDepthTexture);
_linearDepthFramebuffer->setDepthStencilBuffer(_primaryDepthTexture, _primaryDepthTexture->getTexelFormat());
// For Downsampling:
_halfLinearDepthTexture = gpu::TexturePointer(gpu::Texture::createRenderBuffer(gpu::Element(gpu::SCALAR, gpu::FLOAT, gpu::RED), _halfFrameSize.x, _halfFrameSize.y,
const uint16_t HALF_LINEAR_DEPTH_MAX_MIP_LEVEL = 5;
_halfLinearDepthTexture = gpu::TexturePointer(gpu::Texture::createRenderBuffer(gpu::Element(gpu::SCALAR, gpu::FLOAT, gpu::RED), _halfFrameSize.x, _halfFrameSize.y, HALF_LINEAR_DEPTH_MAX_MIP_LEVEL,
gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR_MIP_POINT)));
_halfLinearDepthTexture->autoGenerateMips(5);
_halfNormalTexture = gpu::TexturePointer(gpu::Texture::createRenderBuffer(gpu::Element::COLOR_RGBA_32, _halfFrameSize.x, _halfFrameSize.y,
_halfNormalTexture = gpu::TexturePointer(gpu::Texture::createRenderBuffer(gpu::Element::COLOR_RGBA_32, _halfFrameSize.x, _halfFrameSize.y, gpu::Texture::SINGLE_MIP,
gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR_MIP_POINT)));
_downsampleFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("halfLinearDepth"));
@ -304,15 +304,15 @@ void SurfaceGeometryFramebuffer::allocate() {
auto width = _frameSize.x;
auto height = _frameSize.y;
_curvatureTexture = gpu::TexturePointer(gpu::Texture::createRenderBuffer(gpu::Element::COLOR_RGBA_32, width, height, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR_MIP_POINT)));
_curvatureTexture = gpu::TexturePointer(gpu::Texture::createRenderBuffer(gpu::Element::COLOR_RGBA_32, width, height, gpu::Texture::SINGLE_MIP, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR_MIP_POINT)));
_curvatureFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("surfaceGeometry::curvature"));
_curvatureFramebuffer->setRenderBuffer(0, _curvatureTexture);
_lowCurvatureTexture = gpu::TexturePointer(gpu::Texture::createRenderBuffer(gpu::Element::COLOR_RGBA_32, width, height, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR_MIP_POINT)));
_lowCurvatureTexture = gpu::TexturePointer(gpu::Texture::createRenderBuffer(gpu::Element::COLOR_RGBA_32, width, height, gpu::Texture::SINGLE_MIP, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR_MIP_POINT)));
_lowCurvatureFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("surfaceGeometry::lowCurvature"));
_lowCurvatureFramebuffer->setRenderBuffer(0, _lowCurvatureTexture);
_blurringTexture = gpu::TexturePointer(gpu::Texture::createRenderBuffer(gpu::Element::COLOR_RGBA_32, width, height, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR_MIP_POINT)));
_blurringTexture = gpu::TexturePointer(gpu::Texture::createRenderBuffer(gpu::Element::COLOR_RGBA_32, width, height, gpu::Texture::SINGLE_MIP, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR_MIP_POINT)));
_blurringFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("surfaceGeometry::blurring"));
_blurringFramebuffer->setRenderBuffer(0, _blurringTexture);
}

View file

@ -21,17 +21,17 @@ out vec4 outLinearDepth;
out vec4 outNormal;
void main(void) {
// Gather 2 by 2 quads from texture
// Gather 2 by 2 quads from texture and downsample
// Try different filters for Z
//vec4 Zeyes = textureGather(linearDepthMap, varTexCoord0, 0);
//float Zeye = min(min(Zeyes.x, Zeyes.y), min(Zeyes.z, Zeyes.w));
float Zeye = texture(linearDepthMap, varTexCoord0).x;
vec4 Zeyes = textureGather(linearDepthMap, varTexCoord0, 0);
// float Zeye = texture(linearDepthMap, varTexCoord0).x;
vec4 rawNormalsX = textureGather(normalMap, varTexCoord0, 0);
vec4 rawNormalsY = textureGather(normalMap, varTexCoord0, 1);
vec4 rawNormalsZ = textureGather(normalMap, varTexCoord0, 2);
float Zeye = min(min(Zeyes.x, Zeyes.y), min(Zeyes.z, Zeyes.w));
vec3 normal = vec3(0.0);
normal += unpackNormal(vec3(rawNormalsX[0], rawNormalsY[0], rawNormalsZ[0]));

View file

@ -207,7 +207,7 @@ void Font::read(QIODevice& in) {
formatGPU = gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA);
formatMip = gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::BGRA);
}
_texture = gpu::TexturePointer(gpu::Texture::create2D(formatGPU, image.width(), image.height(),
_texture = gpu::TexturePointer(gpu::Texture::create2D(formatGPU, image.width(), image.height(), gpu::Texture::SINGLE_MIP,
gpu::Sampler(gpu::Sampler::FILTER_MIN_POINT_MAG_LINEAR)));
_texture->setStoredMipFormat(formatMip);
_texture->assignStoredMip(0, image.byteCount(), image.constBits());

View file

@ -108,7 +108,7 @@ bool BlurInOutResource::updateResources(const gpu::FramebufferPointer& sourceFra
// _blurredFramebuffer->setDepthStencilBuffer(sourceFramebuffer->getDepthStencilBuffer(), sourceFramebuffer->getDepthStencilBufferFormat());
//}
auto blurringSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR_MIP_POINT);
auto blurringTarget = gpu::TexturePointer(gpu::Texture::create2D(sourceFramebuffer->getRenderBuffer(0)->getTexelFormat(), sourceFramebuffer->getWidth(), sourceFramebuffer->getHeight(), blurringSampler));
auto blurringTarget = gpu::TexturePointer(gpu::Texture::create2D(sourceFramebuffer->getRenderBuffer(0)->getTexelFormat(), sourceFramebuffer->getWidth(), sourceFramebuffer->getHeight(), gpu::Texture::SINGLE_MIP, blurringSampler));
_blurredFramebuffer->setRenderBuffer(0, blurringTarget);
}
@ -131,7 +131,7 @@ bool BlurInOutResource::updateResources(const gpu::FramebufferPointer& sourceFra
_outputFramebuffer->setDepthStencilBuffer(sourceFramebuffer->getDepthStencilBuffer(), sourceFramebuffer->getDepthStencilBufferFormat());
}*/
auto blurringSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR_MIP_POINT);
auto blurringTarget = gpu::TexturePointer(gpu::Texture::create2D(sourceFramebuffer->getRenderBuffer(0)->getTexelFormat(), sourceFramebuffer->getWidth(), sourceFramebuffer->getHeight(), blurringSampler));
auto blurringTarget = gpu::TexturePointer(gpu::Texture::create2D(sourceFramebuffer->getRenderBuffer(0)->getTexelFormat(), sourceFramebuffer->getWidth(), sourceFramebuffer->getHeight(), gpu::Texture::SINGLE_MIP, blurringSampler));
_outputFramebuffer->setRenderBuffer(0, blurringTarget);
}

View file

@ -251,7 +251,7 @@ static QString getUsername() {
}
void TabletProxy::initialScreen(const QVariant& url) {
if (getQmlTablet()) {
if (_qmlTabletRoot) {
pushOntoStack(url);
} else {
_initialScreen = true;

View file

@ -86,6 +86,7 @@ class TabletProxy : public QObject {
Q_PROPERTY(QString name READ getName)
Q_PROPERTY(bool toolbarMode READ getToolbarMode WRITE setToolbarMode)
Q_PROPERTY(bool landscape READ getLandscape WRITE setLandscape)
Q_PROPERTY(bool tabletShown MEMBER _tabletShown NOTIFY tabletShownChanged)
public:
TabletProxy(QString name);
@ -215,6 +216,13 @@ signals:
*/
void screenChanged(QVariant type, QVariant url);
/** jsdoc
* Signaled when the tablet becomes visible or becomes invisible
* @function TabletProxy#isTabletShownChanged
* @returns {Signal}
*/
void tabletShownChanged();
protected slots:
void addButtonsToHomeScreen();
void desktopWindowClosed();
@ -233,6 +241,7 @@ protected:
QObject* _qmlOffscreenSurface { nullptr };
QmlWindowClass* _desktopWindow { nullptr };
bool _toolbarMode { false };
bool _tabletShown { false };
enum class State { Uninitialized, Home, Web, Menu, QML };
State _state { State::Uninitialized };

View file

@ -143,7 +143,7 @@ void XMLHttpRequestClass::open(const QString& method, const QString& url, bool a
if (url.toLower().left(METAVERSE_API_URL.length()) == METAVERSE_API_URL) {
auto accountManager = DependencyManager::get<AccountManager>();
if (_url.scheme() == "https" && accountManager->hasValidAccessToken()) {
if (accountManager->hasValidAccessToken()) {
static const QString HTTP_AUTHORIZATION_HEADER = "Authorization";
QString bearerString = "Bearer " + accountManager->getAccountInfo().getAccessToken().token;
_request.setRawHeader(HTTP_AUTHORIZATION_HEADER.toLocal8Bit(), bearerString.toLocal8Bit());

View file

@ -494,7 +494,7 @@ void OpenVrDisplayPlugin::customizeContext() {
_compositeInfos[0].texture = _compositeFramebuffer->getRenderBuffer(0);
for (size_t i = 0; i < COMPOSITING_BUFFER_SIZE; ++i) {
if (0 != i) {
_compositeInfos[i].texture = gpu::TexturePointer(gpu::Texture::createRenderBuffer(gpu::Element::COLOR_RGBA_32, _renderTargetSize.x, _renderTargetSize.y, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_POINT)));
_compositeInfos[i].texture = gpu::TexturePointer(gpu::Texture::createRenderBuffer(gpu::Element::COLOR_RGBA_32, _renderTargetSize.x, _renderTargetSize.y, gpu::Texture::SINGLE_MIP, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_POINT)));
}
_compositeInfos[i].textureID = getGLBackend()->getTextureID(_compositeInfos[i].texture);
}

View file

@ -21,10 +21,10 @@ var DEFAULT_SCRIPTS_COMBINED = [
"system/snapshot.js",
"system/help.js",
"system/pal.js", // "system/mod.js", // older UX, if you prefer
"system/makeUserConnection.js",
"system/tablet-goto.js",
"system/marketplaces/marketplaces.js",
"system/edit.js",
"system/tablet-users.js",
"system/selectAudioDevice.js",
"system/notifications.js",
"system/dialTone.js",

View file

@ -1181,7 +1181,7 @@ function MyController(hand) {
this.updateStylusTip();
var DEFAULT_USE_FINGER_AS_STYLUS = true;
var DEFAULT_USE_FINGER_AS_STYLUS = false;
var USE_FINGER_AS_STYLUS = Settings.getValue("preferAvatarFingerOverStylus");
if (USE_FINGER_AS_STYLUS === "") {
USE_FINGER_AS_STYLUS = DEFAULT_USE_FINGER_AS_STYLUS;

View file

@ -5,6 +5,93 @@
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
*/
/*
CSS rules copied from edit-style.css.
Edit-style.css is not used in its entirety because don't want custom scrollbars; default scrollbar styling is used in order
to match other marketplace pages.
*/
@font-face {
font-family: Raleway-Regular;
src: url(../../../../resources/fonts/Raleway-Regular.ttf), /* Windows production */
url(../../../../fonts/Raleway-Regular.ttf), /* OSX production */
url(../../../../interface/resources/fonts/Raleway-Regular.ttf); /* Development, running script in /HiFi/examples */
}
@font-face {
font-family: Raleway-Bold;
src: url(../../../../resources/fonts/Raleway-Bold.ttf),
url(../../../../fonts/Raleway-Bold.ttf),
url(../../../../interface/resources/fonts/Raleway-Bold.ttf);
}
@font-face {
font-family: Raleway-SemiBold;
src: url(../../../../resources/fonts/Raleway-SemiBold.ttf),
url(../../../../fonts/Raleway-SemiBold.ttf),
url(../../../../interface/resources/fonts/Raleway-SemiBold.ttf);
}
@font-face {
font-family: FiraSans-SemiBold;
src: url(../../../../resources/fonts/FiraSans-SemiBold.ttf),
url(../../../../fonts/FiraSans-SemiBold.ttf),
url(../../../../interface/resources/fonts/FiraSans-SemiBold.ttf);
}
* {
margin: 0;
padding: 0;
}
body {
padding: 21px 21px 21px 21px;
color: #afafaf;
background-color: #404040;
font-family: Raleway-Regular;
font-size: 15px;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
overflow-x: hidden;
overflow-y: auto;
}
input[type=button] {
font-family: Raleway-Bold;
font-size: 13px;
text-transform: uppercase;
vertical-align: top;
height: 28px;
min-width: 120px;
padding: 0px 18px;
margin-right: 6px;
border-radius: 5px;
border: none;
color: #fff;
background-color: #000;
background: linear-gradient(#343434 20%, #000 100%);
cursor: pointer;
}
input[type=button].blue {
color: #fff;
background-color: #1080b8;
background: linear-gradient(#00b4ef 20%, #1080b8 100%);
}
/*
Marketplaces-specific CSS.
*/
body {
background: white;
padding: 0 0 0 0;

View file

@ -1,349 +0,0 @@
//
// marketplacesInject.js
//
// Created by David Rowe on 12 Nov 2016.
// Copyright 2016 High Fidelity, Inc.
//
// Injected into marketplace Web pages.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
(function () {
// Event bridge messages.
var CLARA_IO_DOWNLOAD = "CLARA.IO DOWNLOAD";
var CLARA_IO_STATUS = "CLARA.IO STATUS";
var CLARA_IO_CANCEL_DOWNLOAD = "CLARA.IO CANCEL DOWNLOAD";
var CLARA_IO_CANCELLED_DOWNLOAD = "CLARA.IO CANCELLED DOWNLOAD";
var GOTO_DIRECTORY = "GOTO_DIRECTORY";
var QUERY_CAN_WRITE_ASSETS = "QUERY_CAN_WRITE_ASSETS";
var CAN_WRITE_ASSETS = "CAN_WRITE_ASSETS";
var WARN_USER_NO_PERMISSIONS = "WARN_USER_NO_PERMISSIONS";
var canWriteAssets = false;
var xmlHttpRequest = null;
var isPreparing = false; // Explicitly track download request status.
function injectCommonCode(isDirectoryPage) {
// Supporting styles from marketplaces.css.
// Glyph font family, size, and spacing adjusted because HiFi-Glyphs cannot be used cross-domain.
$("head").append(
'<style>' +
'#marketplace-navigation { font-family: Arial, Helvetica, sans-serif; width: 100%; height: 50px; background: #00b4ef; position: fixed; bottom: 0; z-index: 1000; }' +
'#marketplace-navigation .glyph { margin-left: 20px; margin-right: 3px; font-family: sans-serif; color: #fff; font-size: 24px; line-height: 50px; }' +
'#marketplace-navigation .text { color: #fff; font-size: 18px; line-height: 50px; vertical-align: top; position: relative; top: 1px; }' +
'#marketplace-navigation input#back-button { position: absolute; left: 20px; margin-top: 12px; padding-left: 0; padding-right: 5px; }' +
'#marketplace-navigation input#all-markets { position: absolute; right: 20px; margin-top: 12px; padding-left: 15px; padding-right: 15px; }' +
'#marketplace-navigation .right { position: absolute; right: 20px; }' +
'::-webkit-scrollbar { width: 0px; background: transparent; }' +
'</style>'
);
// Supporting styles from edit-style.css.
// Font family, size, and position adjusted because Raleway-Bold cannot be used cross-domain.
$("head").append(
'<style>' +
'input[type=button] { font-family: Arial, Helvetica, sans-serif; font-weight: bold; font-size: 12px; text-transform: uppercase; vertical-align: center; height: 28px; min-width: 100px; padding: 0 15px; border-radius: 5px; border: none; color: #fff; background-color: #000; background: linear-gradient(#343434 20%, #000 100%); cursor: pointer; }' +
'input[type=button].white { color: #121212; background-color: #afafaf; background: linear-gradient(#fff 20%, #afafaf 100%); }' +
'input[type=button].white:enabled:hover { background: linear-gradient(#fff, #fff); border: none; }' +
'input[type=button].white:active { background: linear-gradient(#afafaf, #afafaf); }' +
'</style>'
);
// Footer.
var isInitialHiFiPage = location.href === "https://metaverse.highfidelity.com/marketplace?";
$("body").append(
'<div id="marketplace-navigation">' +
(!isInitialHiFiPage ? '<input id="back-button" type="button" class="white" value="&lt; Back" />' : '') +
(isInitialHiFiPage ? '<span class="glyph">&#x1f6c8;</span> <span class="text">See also other marketplaces.</span>' : '') +
(!isDirectoryPage ? '<input id="all-markets" type="button" class="white" value="See All Markets" />' : '') +
(isDirectoryPage ? '<span class="right"><span class="glyph">&#x1f6c8;</span> <span class="text">Select a marketplace to explore.</span><span>' : '') +
'</div>'
);
// Footer actions.
$("#back-button").on("click", function () {
window.history.back();
});
$("#all-markets").on("click", function () {
EventBridge.emitWebEvent(GOTO_DIRECTORY);
});
}
function injectDirectoryCode() {
// Remove e-mail hyperlink.
var letUsKnow = $("#letUsKnow");
letUsKnow.replaceWith(letUsKnow.html());
// Add button links.
$('#exploreClaraMarketplace').on('click', function () {
window.location = "https://clara.io/library?gameCheck=true&public=true";
});
$('#exploreHifiMarketplace').on('click', function () {
window.location = "http://www.highfidelity.com/marketplace";
});
}
function injectHiFiCode() {
// Nothing to do.
}
function updateClaraCode() {
// Have to repeatedly update Clara page because its content can change dynamically without location.href changing.
// Clara library page.
if (location.href.indexOf("clara.io/library") !== -1) {
// Make entries navigate to "Image" view instead of default "Real Time" view.
var elements = $("a.thumbnail");
for (var i = 0, length = elements.length; i < length; i++) {
var value = elements[i].getAttribute("href");
if (value.slice(-6) !== "/image") {
elements[i].setAttribute("href", value + "/image");
}
}
}
// Clara item page.
if (location.href.indexOf("clara.io/view/") !== -1) {
// Make site navigation links retain gameCheck etc. parameters.
var element = $("a[href^=\'/library\']")[0];
var parameters = "?gameCheck=true&public=true";
var href = element.getAttribute("href");
if (href.slice(-parameters.length) !== parameters) {
element.setAttribute("href", href + parameters);
}
// Remove unwanted buttons and replace download options with a single "Download to High Fidelity" button.
var buttons = $("a.embed-button").parent("div");
var downloadFBX;
if (buttons.find("div.btn-group").length > 0) {
buttons.children(".btn-primary, .btn-group , .embed-button").each(function () { this.remove(); });
if ($("#hifi-download-container").length === 0) { // Button hasn't been moved already.
downloadFBX = $('<a class="btn btn-primary"><i class=\'glyphicon glyphicon-download-alt\'></i> Download to High Fidelity</a>');
buttons.prepend(downloadFBX);
downloadFBX[0].addEventListener("click", startAutoDownload);
}
}
// Move the "Download to High Fidelity" button to be more visible on tablet.
if ($("#hifi-download-container").length === 0 && window.innerWidth < 700) {
var downloadContainer = $('<div id="hifi-download-container"></div>');
$(".top-title .col-sm-4").append(downloadContainer);
downloadContainer.append(downloadFBX);
}
// Automatic download to High Fidelity.
function startAutoDownload() {
// One file request at a time.
if (isPreparing) {
console.log("WARNIKNG: Clara.io FBX: Prepare only one download at a time");
return;
}
// User must be able to write to Asset Server.
if (!canWriteAssets) {
console.log("ERROR: Clara.io FBX: File download cancelled because no permissions to write to Asset Server");
EventBridge.emitWebEvent(WARN_USER_NO_PERMISSIONS);
return;
}
// User must be logged in.
var loginButton = $("#topnav a[href='/signup']");
if (loginButton.length > 0) {
loginButton[0].click();
return;
}
// Obtain zip file to download for requested asset.
// Reference: https://clara.io/learn/sdk/api/export
//var XMLHTTPREQUEST_URL = "https://clara.io/api/scenes/{uuid}/export/fbx?zip=true&centerScene=true&alignSceneGround=true&fbxUnit=Meter&fbxVersion=7&fbxEmbedTextures=true&imageFormat=WebGL";
// 13 Jan 2017: Specify FBX version 5 and remove some options in order to make Clara.io site more likely to
// be successful in generating zip files.
var XMLHTTPREQUEST_URL = "https://clara.io/api/scenes/{uuid}/export/fbx?fbxUnit=Meter&fbxVersion=5&fbxEmbedTextures=true&imageFormat=WebGL";
var uuid = location.href.match(/\/view\/([a-z0-9\-]*)/)[1];
var url = XMLHTTPREQUEST_URL.replace("{uuid}", uuid);
xmlHttpRequest = new XMLHttpRequest();
var responseTextIndex = 0;
var zipFileURL = "";
xmlHttpRequest.onreadystatechange = function () {
// Messages are appended to responseText; process the new ones.
var message = this.responseText.slice(responseTextIndex);
var statusMessage = "";
if (isPreparing) { // Ignore messages in flight after finished/cancelled.
var lines = message.split(/[\n\r]+/);
for (var i = 0, length = lines.length; i < length; i++) {
if (lines[i].slice(0, 5) === "data:") {
// Parse line.
var data;
try {
data = JSON.parse(lines[i].slice(5));
}
catch (e) {
data = {};
}
// Extract status message.
if (data.hasOwnProperty("message") && data.message !== null) {
statusMessage = data.message;
console.log("Clara.io FBX: " + statusMessage);
}
// Extract zip file URL.
if (data.hasOwnProperty("files") && data.files.length > 0) {
zipFileURL = data.files[0].url;
if (zipFileURL.slice(-4) !== ".zip") {
console.log(JSON.stringify(data)); // Data for debugging.
}
}
}
}
if (statusMessage !== "") {
// Update the UI with the most recent status message.
EventBridge.emitWebEvent(CLARA_IO_STATUS + " " + statusMessage);
}
}
responseTextIndex = this.responseText.length;
};
// Note: onprogress doesn't have computable total length so can't use it to determine % complete.
xmlHttpRequest.onload = function () {
var statusMessage = "";
if (!isPreparing) {
return;
}
isPreparing = false;
var HTTP_OK = 200;
if (this.status !== HTTP_OK) {
statusMessage = "Zip file request terminated with " + this.status + " " + this.statusText;
console.log("ERROR: Clara.io FBX: " + statusMessage);
EventBridge.emitWebEvent(CLARA_IO_STATUS + " " + statusMessage);
} else if (zipFileURL.slice(-4) !== ".zip") {
statusMessage = "Error creating zip file for download.";
console.log("ERROR: Clara.io FBX: " + statusMessage + ": " + zipFileURL);
EventBridge.emitWebEvent(CLARA_IO_STATUS + " " + statusMessage);
} else {
EventBridge.emitWebEvent(CLARA_IO_DOWNLOAD + " " + zipFileURL);
console.log("Clara.io FBX: File download initiated for " + zipFileURL);
}
xmlHttpRequest = null;
}
isPreparing = true;
console.log("Clara.io FBX: Request zip file for " + uuid);
EventBridge.emitWebEvent(CLARA_IO_STATUS + " Initiating download");
xmlHttpRequest.open("POST", url, true);
xmlHttpRequest.setRequestHeader("Accept", "text/event-stream");
xmlHttpRequest.send();
}
}
}
function injectClaraCode() {
// Make space for marketplaces footer in Clara pages.
$("head").append(
'<style>' +
'#app { margin-bottom: 135px; }' +
'.footer { bottom: 50px; }' +
'</style>'
);
// Condense space.
$("head").append(
'<style>' +
'div.page-title { line-height: 1.2; font-size: 13px; }' +
'div.page-title-row { padding-bottom: 0; }' +
'</style>'
);
// Move "Download to High Fidelity" button.
$("head").append(
'<style>' +
'#hifi-download-container { position: absolute; top: 6px; right: 16px; }' +
'</style>'
);
// Update code injected per page displayed.
var updateClaraCodeInterval = undefined;
updateClaraCode();
updateClaraCodeInterval = setInterval(function () {
updateClaraCode();
}, 1000);
window.addEventListener("unload", function () {
clearInterval(updateClaraCodeInterval);
updateClaraCodeInterval = undefined;
});
EventBridge.emitWebEvent(QUERY_CAN_WRITE_ASSETS);
}
function cancelClaraDownload() {
isPreparing = false;
if (xmlHttpRequest) {
xmlHttpRequest.abort();
xmlHttpRequest = null;
console.log("Clara.io FBX: File download cancelled");
EventBridge.emitWebEvent(CLARA_IO_CANCELLED_DOWNLOAD);
}
}
function onLoad() {
EventBridge.scriptEventReceived.connect(function (message) {
if (message.slice(0, CAN_WRITE_ASSETS.length) === CAN_WRITE_ASSETS) {
canWriteAssets = message.slice(-4) === "true";
}
if (message.slice(0, CLARA_IO_CANCEL_DOWNLOAD.length) === CLARA_IO_CANCEL_DOWNLOAD) {
cancelClaraDownload();
}
});
var DIRECTORY = 0;
var HIFI = 1;
var CLARA = 2;
var pageType = DIRECTORY;
if (location.href.indexOf("highfidelity.com/") !== -1) { pageType = HIFI; }
if (location.href.indexOf("clara.io/") !== -1) { pageType = CLARA; }
injectCommonCode(pageType === DIRECTORY);
switch (pageType) {
case DIRECTORY:
injectDirectoryCode();
break;
case HIFI:
injectHiFiCode();
break;
case CLARA:
injectClaraCode();
break;
}
}
// Load / unload.
window.addEventListener("load", onLoad); // More robust to Web site issues than using $(document).ready().
}());

View file

@ -10,7 +10,6 @@
<head>
<title>Marketplaces</title>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
<link rel="stylesheet" type="text/css" href="css/edit-style.css">
<link rel="stylesheet" type="text/css" href="css/marketplaces.css">
<script src="js/jquery-2.1.4.min.js"></script>
</head>

View file

@ -0,0 +1,848 @@
"use strict";
//
// makeUserConnetion.js
// scripts/system
//
// Created by David Kelly on 3/7/2017.
// Copyright 2017 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
//
(function() { // BEGIN LOCAL_SCOPE
const label = "makeUserConnection";
const MAX_AVATAR_DISTANCE = 0.2; // m
const GRIP_MIN = 0.05; // goes from 0-1, so 5% pressed is pressed
const MESSAGE_CHANNEL = "io.highfidelity.makeUserConnection";
const STATES = {
inactive : 0,
waiting: 1,
connecting: 2,
makingConnection: 3
};
const STATE_STRINGS = ["inactive", "waiting", "connecting", "makingConnection"];
const WAITING_INTERVAL = 100; // ms
const CONNECTING_INTERVAL = 100; // ms
const MAKING_CONNECTION_TIMEOUT = 800; // ms
const CONNECTING_TIME = 1600; // ms
const PARTICLE_RADIUS = 0.15; // m
const PARTICLE_ANGLE_INCREMENT = 360/45; // 1hz
const HANDSHAKE_SOUND_URL = "https://s3-us-west-1.amazonaws.com/hifi-content/davidkelly/production/audio/4beat_sweep.wav";
const SUCCESSFUL_HANDSHAKE_SOUND_URL = "https://s3-us-west-1.amazonaws.com/hifi-content/davidkelly/production/audio/3rdbeat_success_bell.wav";
const HAPTIC_DATA = {
initial: { duration: 20, strength: 0.6}, // duration is in ms
background: { duration: 100, strength: 0.3 }, // duration is in ms
success: { duration: 60, strength: 1.0} // duration is in ms
};
const PARTICLE_EFFECT_PROPS = {
"alpha": 0.8,
"azimuthFinish": Math.PI,
"azimuthStart": -1*Math.PI,
"emitRate": 500,
"emitSpeed": 0.0,
"emitterShouldTrail": 1,
"isEmitting": 1,
"lifespan": 3,
"maxParticles": 1000,
"particleRadius": 0.003,
"polarStart": 1,
"polarFinish": 1,
"radiusFinish": 0.008,
"radiusStart": 0.0025,
"speedSpread": 0.025,
"textures": "http://hifi-content.s3.amazonaws.com/alan/dev/Particles/Bokeh-Particle.png",
"color": {"red": 255, "green": 255, "blue": 255},
"colorFinish": {"red": 0, "green": 164, "blue": 255},
"colorStart": {"red": 255, "green": 255, "blue": 255},
"emitOrientation": {"w": -0.71, "x":0.0, "y":0.0, "z": 0.71},
"emitAcceleration": {"x": 0.0, "y": 0.0, "z": 0.0},
"accelerationSpread": {"x": 0.0, "y": 0.0, "z": 0.0},
"dimensions": {"x":0.05, "y": 0.05, "z": 0.05},
"type": "ParticleEffect"
};
const MAKING_CONNECTION_PARTICLE_PROPS = {
"alpha": 0.07,
"alphaStart":0.011,
"alphaSpread": 0,
"alphaFinish": 0,
"azimuthFinish": Math.PI,
"azimuthStart": -1*Math.PI,
"emitRate": 2000,
"emitSpeed": 0.0,
"emitterShouldTrail": 1,
"isEmitting": 1,
"lifespan": 3.6,
"maxParticles": 4000,
"particleRadius": 0.048,
"polarStart": 0,
"polarFinish": 1,
"radiusFinish": 0.3,
"radiusStart": 0.04,
"speedSpread": 0.01,
"radiusSpread": 0.9,
"textures": "http://hifi-content.s3.amazonaws.com/alan/dev/Particles/Bokeh-Particle.png",
"color": {"red": 200, "green": 170, "blue": 255},
"colorFinish": {"red": 0, "green": 134, "blue": 255},
"colorStart": {"red": 185, "green": 222, "blue": 255},
"emitOrientation": {"w": -0.71, "x":0.0, "y":0.0, "z": 0.71},
"emitAcceleration": {"x": 0.0, "y": 0.0, "z": 0.0},
"accelerationSpread": {"x": 0.0, "y": 0.0, "z": 0.0},
"dimensions": {"x":0.05, "y": 0.05, "z": 0.05},
"type": "ParticleEffect"
};
var currentHand;
var state = STATES.inactive;
var connectingInterval;
var waitingInterval;
var makingConnectionTimeout;
var animHandlerId;
var connectingId;
var connectingHand;
var waitingList = {};
var particleEffect;
var waitingBallScale;
var particleRotationAngle = 0.0;
var makingConnectionParticleEffect;
var makingConnectionEmitRate = 2000;
var particleEmitRate = 500;
var handshakeInjector;
var successfulHandshakeInjector;
var handshakeSound;
var successfulHandshakeSound;
function debug() {
var stateString = "<" + STATE_STRINGS[state] + ">";
var connecting = "[" + connectingId + "/" + connectingHand + "]";
print.apply(null, [].concat.apply([label, stateString, JSON.stringify(waitingList), connecting], [].map.call(arguments, JSON.stringify)));
}
function cleanId(guidWithCurlyBraces) {
return guidWithCurlyBraces.slice(1, -1);
}
function request(options, callback) { // cb(error, responseOfCorrectContentType) of url. A subset of npm request.
var httpRequest = new XMLHttpRequest(), key;
// QT bug: apparently doesn't handle onload. Workaround using readyState.
httpRequest.onreadystatechange = function () {
var READY_STATE_DONE = 4;
var HTTP_OK = 200;
if (httpRequest.readyState >= READY_STATE_DONE) {
var error = (httpRequest.status !== HTTP_OK) && httpRequest.status.toString() + ':' + httpRequest.statusText,
response = !error && httpRequest.responseText,
contentType = !error && httpRequest.getResponseHeader('content-type');
debug('FIXME REMOVE: server response', options, error, response, contentType);
if (!error && contentType.indexOf('application/json') === 0) { // ignoring charset, etc.
try {
response = JSON.parse(response);
} catch (e) {
error = e;
}
}
callback(error, response);
}
};
if (typeof options === 'string') {
options = {uri: options};
}
if (options.url) {
options.uri = options.url;
}
if (!options.method) {
options.method = 'GET';
}
if (options.body && (options.method === 'GET')) { // add query parameters
var params = [], appender = (-1 === options.uri.search('?')) ? '?' : '&';
for (key in options.body) {
params.push(key + '=' + options.body[key]);
}
options.uri += appender + params.join('&');
delete options.body;
}
if (options.json) {
options.headers = options.headers || {};
options.headers["Content-type"] = "application/json";
options.body = JSON.stringify(options.body);
}
debug("FIXME REMOVE: final options to send", options);
for (key in options.headers || {}) {
httpRequest.setRequestHeader(key, options.headers[key]);
}
httpRequest.open(options.method, options.uri, true);
httpRequest.send(options.body);
}
function handToString(hand) {
if (hand === Controller.Standard.RightHand) {
return "RightHand";
} else if (hand === Controller.Standard.LeftHand) {
return "LeftHand";
}
debug("handToString called without valid hand!");
return "";
}
function stringToHand(hand) {
if (hand == "RightHand") {
return Controller.Standard.RightHand;
} else if (hand == "LeftHand") {
return Controller.Standard.LeftHand;
}
debug("stringToHand called with bad hand string:", hand);
return 0;
}
function handToHaptic(hand) {
if (hand === Controller.Standard.RightHand) {
return 1;
} else if (hand === Controller.Standard.LeftHand) {
return 0;
}
debug("handToHaptic called without a valid hand!");
return -1;
}
function stopWaiting() {
if (waitingInterval) {
waitingInterval = Script.clearInterval(waitingInterval);
}
}
function stopConnecting() {
if (connectingInterval) {
connectingInterval = Script.clearInterval(connectingInterval);
}
}
function stopMakingConnection() {
if (makingConnectionTimeout) {
makingConnectionTimeout = Script.clearTimeout(makingConnectionTimeout);
}
}
// This returns the position of the palm, really. Which relies on the avatar
// having the expected middle1 joint. TODO: fallback for when this isn't part
// of the avatar?
function getHandPosition(avatar, hand) {
if (!hand) {
debug("calling getHandPosition with no hand! (returning avatar position but this is a BUG)");
debug(new Error().stack);
return avatar.position;
}
var jointName = handToString(hand) + "Middle1";
return avatar.getJointPosition(avatar.getJointIndex(jointName));
}
function shakeHandsAnimation(animationProperties) {
// all we are doing here is moving the right hand to a spot
// that is in front of and a bit above the hips. Basing how
// far in front as scaling with the avatar's height (say hips
// to head distance)
var headIndex = MyAvatar.getJointIndex("Head");
var offset = 0.5; // default distance of hand in front of you
var result = {};
if (headIndex) {
offset = 0.8 * MyAvatar.getAbsoluteJointTranslationInObjectFrame(headIndex).y;
}
var handPos = Vec3.multiply(offset, {x: -0.25, y: 0.8, z: 1.3});
result.rightHandPosition = handPos;
result.rightHandRotation = Quat.fromPitchYawRollDegrees(90, 0, 90);
return result;
}
function positionFractionallyTowards(posA, posB, frac) {
return Vec3.sum(posA, Vec3.multiply(frac, Vec3.subtract(posB, posA)));
}
function deleteParticleEffect() {
if (particleEffect) {
particleEffect = Entities.deleteEntity(particleEffect);
}
}
function deleteMakeConnectionParticleEffect() {
if (makingConnectionParticleEffect) {
makingConnectionParticleEffect = Entities.deleteEntity(makingConnectionParticleEffect);
}
}
function stopHandshakeSound() {
if (handshakeInjector) {
handshakeInjector.stop();
handshakeInjector = null;
}
}
function calcParticlePos(myHand, otherHand, otherOrientation, reset) {
if (reset) {
particleRotationAngle = 0.0;
}
var position = positionFractionallyTowards(myHand, otherHand, 0.5);
particleRotationAngle += PARTICLE_ANGLE_INCREMENT; // about 0.5 hz
var radius = Math.min(PARTICLE_RADIUS, PARTICLE_RADIUS * particleRotationAngle / 360);
var axis = Vec3.mix(Quat.getFront(MyAvatar.orientation), Quat.inverse(Quat.getFront(otherOrientation)), 0.5);
return Vec3.sum(position, Vec3.multiplyQbyV(Quat.angleAxis(particleRotationAngle, axis), {x: 0, y: radius, z: 0}));
}
// this is called frequently, but usually does nothing
function updateVisualization() {
if (state == STATES.inactive) {
deleteParticleEffect();
deleteMakeConnectionParticleEffect();
// this should always be true if inactive, but just in case:
currentHand = undefined;
return;
}
var myHandPosition = getHandPosition(MyAvatar, currentHand);
var otherHand;
var otherOrientation;
if (connectingId) {
var other = AvatarList.getAvatar(connectingId);
if (other) {
otherOrientation = other.orientation;
otherHand = getHandPosition(other, stringToHand(connectingHand));
}
}
var wrist = MyAvatar.getJointPosition(MyAvatar.getJointIndex(handToString(currentHand)));
var d = Math.min(MAX_AVATAR_DISTANCE, Vec3.distance(wrist, myHandPosition));
switch (state) {
case STATES.waiting:
// no visualization while waiting
deleteParticleEffect();
deleteMakeConnectionParticleEffect();
stopHandshakeSound();
break;
case STATES.connecting:
var particleProps = {};
// put the position between the 2 hands, if we have a connectingId. This
// helps define the plane in which the particles move.
positionFractionallyTowards(myHandPosition, otherHand, 0.5);
// now manage the rest of the entity
if (!particleEffect) {
particleRotationAngle = 0.0;
particleEmitRate = 500;
particleProps = PARTICLE_EFFECT_PROPS;
particleProps.isEmitting = 0;
particleProps.position = calcParticlePos(myHandPosition, otherHand, otherOrientation);
particleProps.parentID = MyAvatar.sessionUUID;
particleEffect = Entities.addEntity(particleProps, true);
} else {
particleProps.position = calcParticlePos(myHandPosition, otherHand, otherOrientation);
particleProps.isEmitting = 1;
Entities.editEntity(particleEffect, particleProps);
}
if (!makingConnectionParticleEffect) {
var props = MAKING_CONNECTION_PARTICLE_PROPS;
props.parentID = MyAvatar.sessionUUID;
makingConnectionEmitRate = 2000;
props.emitRate = makingConnectionEmitRate;
props.position = myHandPosition;
makingConnectionParticleEffect = Entities.addEntity(props, true);
} else {
makingConnectionEmitRate *= 0.5;
Entities.editEntity(makingConnectionParticleEffect, {emitRate: makingConnectionEmitRate, position: myHandPosition, isEmitting: 1});
}
break;
case STATES.makingConnection:
particleEmitRate = Math.max(50, particleEmitRate * 0.5);
Entities.editEntity(makingConnectionParticleEffect, {emitRate: 0, isEmitting: 0, position: myHandPosition});
Entities.editEntity(particleEffect, {position: calcParticlePos(myHandPosition, otherHand, otherOrientation), emitRate: particleEmitRate});
break;
default:
debug("unexpected state", state);
break;
}
}
function isNearby(id, hand) {
if (currentHand) {
var handPos = getHandPosition(MyAvatar, currentHand);
var avatar = AvatarList.getAvatar(id);
if (avatar) {
var otherHand = stringToHand(hand);
var distance = Vec3.distance(getHandPosition(avatar, otherHand), handPos);
return (distance < MAX_AVATAR_DISTANCE);
}
}
return false;
}
function findNearestWaitingAvatar() {
var handPos = getHandPosition(MyAvatar, currentHand);
var minDistance = MAX_AVATAR_DISTANCE;
var nearestAvatar = {};
Object.keys(waitingList).forEach(function (identifier) {
var avatar = AvatarList.getAvatar(identifier);
if (avatar) {
var hand = stringToHand(waitingList[identifier]);
var distance = Vec3.distance(getHandPosition(avatar, hand), handPos);
if (distance < minDistance) {
minDistance = distance;
nearestAvatar = {avatar: identifier, hand: hand};
}
}
});
return nearestAvatar;
}
// As currently implemented, we select the closest waiting avatar (if close enough) and send
// them a connectionRequest. If nobody is close enough we send a waiting message, and wait for a
// connectionRequest. If the 2 people who want to connect are both somewhat out of range when they
// initiate the shake, they will race to see who sends the connectionRequest after noticing the
// waiting message. Either way, they will start connecting eachother at that point.
function startHandshake(fromKeyboard) {
if (fromKeyboard) {
debug("adding animation");
// just in case order of press/unpress is broken
if (animHandlerId) {
animHandlerId = MyAvatar.removeAnimationStateHandler(animHandlerId);
}
animHandlerId = MyAvatar.addAnimationStateHandler(shakeHandsAnimation, []);
}
debug("starting handshake for", currentHand);
pollCount = 0;
state = STATES.waiting;
connectingId = undefined;
connectingHand = undefined;
// just in case
stopWaiting();
stopConnecting();
stopMakingConnection();
var nearestAvatar = findNearestWaitingAvatar();
if (nearestAvatar.avatar) {
connectingId = nearestAvatar.avatar;
connectingHand = handToString(nearestAvatar.hand);
debug("sending connectionRequest to", connectingId);
messageSend({
key: "connectionRequest",
id: connectingId,
hand: handToString(currentHand)
});
} else {
// send waiting message
debug("sending waiting message");
messageSend({
key: "waiting",
hand: handToString(currentHand)
});
lookForWaitingAvatar();
}
}
function endHandshake() {
debug("ending handshake for", currentHand);
deleteParticleEffect();
deleteMakeConnectionParticleEffect();
currentHand = undefined;
// note that setting the state to inactive should really
// only be done here, unless we change how the triggering works,
// as we ignore the key release event when inactive. See updateTriggers
// below.
state = STATES.inactive;
connectingId = undefined;
connectingHand = undefined;
stopWaiting();
stopConnecting();
stopMakingConnection();
stopHandshakeSound();
// send done to let connection know you are not making connections now
messageSend({
key: "done"
});
if (animHandlerId) {
debug("removing animation");
MyAvatar.removeAnimationStateHandler(animHandlerId);
}
// No-op if we were successful, but this way we ensure that failures and abandoned handshakes don't leave us in a weird state.
request({uri: requestUrl, method: 'DELETE'}, debug);
}
function updateTriggers(value, fromKeyboard, hand) {
if (currentHand && hand !== currentHand) {
debug("currentHand", currentHand, "ignoring messages from", hand);
return;
}
if (!currentHand) {
currentHand = hand;
}
// ok now, we are either initiating or quitting...
var isGripping = value > GRIP_MIN;
if (isGripping) {
debug("updateTriggers called - gripping", handToString(hand));
if (state != STATES.inactive) {
return;
} else {
startHandshake(fromKeyboard);
}
} else {
// TODO: should we end handshake even when inactive? Ponder
debug("updateTriggers called -- no longer gripping", handToString(hand));
if (state != STATES.inactive) {
endHandshake();
} else {
return;
}
}
}
function messageSend(message) {
Messages.sendMessage(MESSAGE_CHANNEL, JSON.stringify(message));
}
function lookForWaitingAvatar() {
// we started with nobody close enough, but maybe I've moved
// or they did. Note that 2 people doing this race, so stop
// as soon as you have a connectingId (which means you got their
// message before noticing they were in range in this loop)
// just in case we reenter before stopping
stopWaiting();
debug("started looking for waiting avatars");
waitingInterval = Script.setInterval(function () {
if (state == STATES.waiting && !connectingId) {
// find the closest in-range avatar, and send connection request
var nearestAvatar = findNearestWaitingAvatar();
if (nearestAvatar.avatar) {
connectingId = nearestAvatar.avatar;
connectingHand = handToString(nearestAvatar.hand);
debug("sending connectionRequest to", connectingId);
messageSend({
key: "connectionRequest",
id: connectingId,
hand: handToString(currentHand)
});
}
} else {
// something happened, stop looking for avatars to connect
stopWaiting();
debug("stopped looking for waiting avatars");
}
}, WAITING_INTERVAL);
}
/* There is a mini-state machine after entering STATES.makingConnection.
We make a request (which might immediately succeed, fail, or neither.
If we immediately fail, we tell the user.
Otherwise, we wait MAKING_CONNECTION_TIMEOUT. At that time, we poll until success or fail.
*/
var result, requestBody, pollCount = 0, requestUrl = location.metaverseServerUrl + '/api/v1/user/connection_request';
function connectionRequestCompleted() { // Final result is in. Do effects.
if (result.status === 'success') { // set earlier
if (!successfulHandshakeInjector) {
successfulHandshakeInjector = Audio.playSound(successfulHandshakeSound, {position: getHandPosition(MyAvatar, currentHand), volume: 0.5, localOnly: true});
} else {
successfulHandshakeInjector.restart();
}
Controller.triggerHapticPulse(HAPTIC_DATA.success.strength, HAPTIC_DATA.success.duration, handToHaptic(currentHand));
// don't change state (so animation continues while gripped)
// but do send a notification, by calling the slot that emits the signal for it
Window.makeConnection(true, result.connection.new_connection ? "You and " + result.connection.username + " are now connected!" : result.connection.username);
return;
} // failed
endHandshake();
debug("failing with result data", result);
// IWBNI we also did some fail sound/visual effect.
Window.makeConnection(false, result.connection);
}
var POLL_INTERVAL_MS = 200, POLL_LIMIT = 5;
function handleConnectionResponseAndMaybeRepeat(error, response) {
// If response is 'pending', set a short timeout to try again.
// If we fail other than pending, set result and immediately call connectionRequestCompleted.
// If we succceed, set result and call connectionRequestCompleted immediately (if we've been polling), and otherwise on a timeout.
if (response && (response.connection === 'pending')) {
debug(response, 'pollCount', pollCount);
if (pollCount++ >= POLL_LIMIT) { // server will expire, but let's not wait that long.
debug('POLL LIMIT REACHED; TIMEOUT: expired message generated by CLIENT');
result = {status: 'error', connection: 'expired'};
connectionRequestCompleted();
} else { // poll
Script.setTimeout(function () {
request({
uri: requestUrl,
// N.B.: server gives bad request if we specify json content type, so don't do that.
body: requestBody
}, handleConnectionResponseAndMaybeRepeat);
}, POLL_INTERVAL_MS);
}
} else if (error || (response.status !== 'success')) {
debug('server fail', error, response.status);
result = error ? {status: 'error', connection: error} : response;
connectionRequestCompleted();
} else {
debug('server success', result);
result = response;
if (pollCount++) {
connectionRequestCompleted();
} else { // Wait for other guy, so that final succcess is at roughly the same time.
Script.setTimeout(connectionRequestCompleted, MAKING_CONNECTION_TIMEOUT);
}
}
}
// this should be where we make the appropriate connection call. For now just make the
// visualization change.
function makeConnection(id) {
// send done to let the connection know you have made connection.
messageSend({
key: "done",
connectionId: id
});
state = STATES.makingConnection;
// continue the haptic background until the timeout fires. When we make calls, we will have an interval
// probably, in which we do this.
Controller.triggerHapticPulse(HAPTIC_DATA.background.strength, MAKING_CONNECTION_TIMEOUT, handToHaptic(currentHand));
requestBody = {node_id: cleanId(MyAvatar.sessionUUID), proposed_node_id: cleanId(id)}; // for use when repeating
// This will immediately set response if successfull (e.g., the other guy got his request in first), or immediate failure,
// and will otherwise poll (using the requestBody we just set).
request({ //
uri: requestUrl,
method: 'POST',
json: true,
body: {user_connection_request: requestBody}
}, handleConnectionResponseAndMaybeRepeat);
}
// we change states, start the connectionInterval where we check
// to be sure the hand is still close enough. If not, we terminate
// the interval, go back to the waiting state. If we make it
// the entire CONNECTING_TIME, we make the connection.
function startConnecting(id, hand) {
var count = 0;
debug("connecting", id, "hand", hand);
// do we need to do this?
connectingId = id;
connectingHand = hand;
state = STATES.connecting;
// play sound
if (!handshakeInjector) {
handshakeInjector = Audio.playSound(handshakeSound, {position: getHandPosition(MyAvatar, currentHand), volume: 0.5, localOnly: true});
} else {
handshakeInjector.restart();
}
// send message that we are connecting with them
messageSend({
key: "connecting",
id: id,
hand: handToString(currentHand)
});
Controller.triggerHapticPulse(HAPTIC_DATA.initial.strength, HAPTIC_DATA.initial.duration, handToHaptic(currentHand));
connectingInterval = Script.setInterval(function () {
count += 1;
Controller.triggerHapticPulse(HAPTIC_DATA.background.strength, HAPTIC_DATA.background.duration, handToHaptic(currentHand));
if (state != STATES.connecting) {
debug("stopping connecting interval, state changed");
stopConnecting();
} else if (!isNearby(id, hand)) {
// gotta go back to waiting
debug(id, "moved, back to waiting");
stopConnecting();
messageSend({
key: "done"
});
startHandshake();
} else if (count > CONNECTING_TIME/CONNECTING_INTERVAL) {
debug("made connection with " + id);
makeConnection(id);
stopConnecting();
}
}, CONNECTING_INTERVAL);
}
/*
A simple sequence diagram: NOTE that the ConnectionAck is somewhat
vestigial, and probably should be removed shortly.
Avatar A Avatar B
| |
| <-----(waiting) ----- startHandshake
startHandshake - (connectionRequest) -> |
| |
| <----(connectionAck) -------- |
| <-----(connecting) -- startConnecting
startConnecting ---(connecting) ----> |
| |
| connected
connected |
| <--------- (done) ---------- |
| ---------- (done) ---------> |
*/
function messageHandler(channel, messageString, senderID) {
if (channel !== MESSAGE_CHANNEL) {
return;
}
if (MyAvatar.sessionUUID === senderID) { // ignore my own
return;
}
var message = {};
try {
message = JSON.parse(messageString);
} catch (e) {
debug(e);
}
switch (message.key) {
case "waiting":
// add this guy to waiting object. Any other message from this person will
// remove it from the list
waitingList[senderID] = message.hand;
break;
case "connectionRequest":
delete waitingList[senderID];
if (state == STATES.waiting && message.id == MyAvatar.sessionUUID && (!connectingId || connectingId == senderID)) {
// you were waiting for a connection request, so send the ack. Or, you and the other
// guy raced and both send connectionRequests. Handle that too
connectingId = senderID;
connectingHand = message.hand;
messageSend({
key: "connectionAck",
id: senderID,
hand: handToString(currentHand)
});
} else {
if (state == STATES.waiting && connectingId == senderID) {
// the person you are trying to connect sent a request to someone else. See the
// if statement above. So, don't cry, just start the handshake over again
startHandshake();
}
}
break;
case "connectionAck":
delete waitingList[senderID];
if (state == STATES.waiting && (!connectingId || connectingId == senderID)) {
if (message.id == MyAvatar.sessionUUID) {
// start connecting...
connectingId = senderID;
connectingHand = message.hand;
stopWaiting();
startConnecting(senderID, message.hand);
} else {
if (connectingId) {
// this is for someone else (we lost race in connectionRequest),
// so lets start over
startHandshake();
}
}
}
// TODO: check to see if we are waiting for this but the person we are connecting sent it to
// someone else, and try again
break;
case "connecting":
delete waitingList[senderID];
if (state == STATES.waiting && senderID == connectingId) {
// temporary logging
if (connectingHand != message.hand) {
debug("connecting hand", connectingHand, "not same as connecting hand in message", message.hand);
}
connectingHand = message.hand;
if (message.id != MyAvatar.sessionUUID) {
// the person we were trying to connect is connecting to someone else
// so try again
startHandshake();
break;
}
startConnecting(senderID, message.hand);
}
break;
case "done":
delete waitingList[senderID];
if (state == STATES.connecting && connectingId == senderID) {
// if they are done, and didn't connect us, terminate our
// connecting
if (message.connectionId !== MyAvatar.sessionUUID) {
stopConnecting();
// now just call startHandshake. Should be ok to do so without a
// value for isKeyboard, as we should not change the animation
// state anyways (if any)
startHandshake();
}
} else {
// if waiting or inactive, lets clear the connecting id. If in makingConnection,
// do nothing
if (state != STATES.makingConnection && connectingId == senderID) {
connectingId = undefined;
connectingHand = undefined;
if (state != STATES.inactive) {
startHandshake();
}
}
}
break;
default:
debug("unknown message", message);
break;
}
}
Messages.subscribe(MESSAGE_CHANNEL);
Messages.messageReceived.connect(messageHandler);
function makeGripHandler(hand, animate) {
// determine if we are gripping or un-gripping
if (animate) {
return function(value) {
updateTriggers(value, true, hand);
};
} else {
return function (value) {
updateTriggers(value, false, hand);
};
}
}
function keyPressEvent(event) {
if ((event.text === "x") && !event.isAutoRepeat && !event.isShifted && !event.isMeta && !event.isControl && !event.isAlt) {
updateTriggers(1.0, true, Controller.Standard.RightHand);
}
}
function keyReleaseEvent(event) {
if ((event.text === "x") && !event.isAutoRepeat && !event.isShifted && !event.isMeta && !event.isControl && !event.isAlt) {
updateTriggers(0.0, true, Controller.Standard.RightHand);
}
}
// map controller actions
var connectionMapping = Controller.newMapping(Script.resolvePath('') + '-grip');
connectionMapping.from(Controller.Standard.LeftGrip).peek().to(makeGripHandler(Controller.Standard.LeftHand));
connectionMapping.from(Controller.Standard.RightGrip).peek().to(makeGripHandler(Controller.Standard.RightHand));
// setup keyboard initiation
Controller.keyPressEvent.connect(keyPressEvent);
Controller.keyReleaseEvent.connect(keyReleaseEvent);
// xbox controller cuz that's important
connectionMapping.from(Controller.Standard.RB).peek().to(makeGripHandler(Controller.Standard.RightHand, true));
// it is easy to forget this and waste a lot of time for nothing
connectionMapping.enable();
// connect updateVisualization to update frequently
Script.update.connect(updateVisualization);
// load the sounds when the script loads
handshakeSound = SoundCache.getSound(HANDSHAKE_SOUND_URL);
successfulHandshakeSound = SoundCache.getSound(SUCCESSFUL_HANDSHAKE_SOUND_URL);
Script.scriptEnding.connect(function () {
debug("removing controller mappings");
connectionMapping.disable();
debug("removing key mappings");
Controller.keyPressEvent.disconnect(keyPressEvent);
Controller.keyReleaseEvent.disconnect(keyReleaseEvent);
debug("disconnecting updateVisualization");
Script.update.disconnect(updateVisualization);
deleteParticleEffect();
deleteMakeConnectionParticleEffect();
});
}()); // END LOCAL_SCOPE

View file

@ -19,7 +19,6 @@ var MARKETPLACE_URL = "https://metaverse.highfidelity.com/marketplace";
var MARKETPLACE_URL_INITIAL = MARKETPLACE_URL + "?"; // Append "?" to signal injected script that it's the initial page.
var MARKETPLACES_URL = Script.resolvePath("../html/marketplaces.html");
var MARKETPLACES_INJECT_SCRIPT_URL = Script.resolvePath("../html/js/marketplacesInject.js");
var MARKETPLACES_INJECT_NO_SCROLLBAR_SCRIPT_URL = Script.resolvePath("../html/js/marketplacesInjectNoScrollbar.js");
var HOME_BUTTON_TEXTURE = "http://hifi-content.s3.amazonaws.com/alan/dev/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-root.png";
// var HOME_BUTTON_TEXTURE = Script.resourcesPath() + "meshes/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-root.png";
@ -61,12 +60,7 @@ function showMarketplace() {
shouldActivateButton = true;
// by default the marketplace should NOT have a scrollbar, except when tablet is in toolbar mode.
var injectURL = MARKETPLACES_INJECT_NO_SCROLLBAR_SCRIPT_URL;
if (tablet.toolbarMode) {
injectURL = MARKETPLACES_INJECT_SCRIPT_URL;
}
tablet.gotoWebScreen(MARKETPLACE_URL_INITIAL, injectURL);
tablet.gotoWebScreen(MARKETPLACE_URL_INITIAL, MARKETPLACES_INJECT_SCRIPT_URL);
onMarketplaceScreen = true;
tablet.webEventReceived.connect(function (message) {

View file

@ -95,12 +95,14 @@ var NotificationType = {
CONNECTION_REFUSED: 3,
EDIT_ERROR: 4,
TABLET: 5,
CONNECTION: 6,
properties: [
{ text: "Snapshot" },
{ text: "Level of Detail" },
{ text: "Connection Refused" },
{ text: "Edit error" },
{ text: "Tablet" }
{ text: "Tablet" },
{ text: "Connection" }
],
getTypeFromMenuItem: function(menuItemName) {
if (menuItemName.substr(menuItemName.length - NOTIFICATION_MENU_ITEM_POST.length) !== NOTIFICATION_MENU_ITEM_POST) {
@ -545,6 +547,14 @@ function processingGif() {
createNotification("Processing GIF snapshot...", NotificationType.SNAPSHOT);
}
function connectionAdded(connectionName) {
createNotification(connectionName, NotificationType.CONNECTION);
}
function connectionError(error) {
createNotification(wordWrap("Error trying to make connection: " + error), NotificationType.CONNECTION);
}
// handles mouse clicks on buttons
function mousePressEvent(event) {
var pickRay,
@ -645,6 +655,8 @@ Menu.menuItemEvent.connect(menuItemEvent);
Window.domainConnectionRefused.connect(onDomainConnectionRefused);
Window.snapshotTaken.connect(onSnapshotTaken);
Window.processingGif.connect(processingGif);
Window.connectionAdded.connect(connectionAdded);
Window.connectionError.connect(connectionError);
Window.notifyEditError = onEditError;
Window.notify = onNotify;
Tablet.tabletNotification.connect(tabletNotification);

View file

@ -1,6 +1,6 @@
"use strict";
/* jslint vars: true, plusplus: true, forin: true*/
/* globals Tablet, Script, AvatarList, Users, Entities, MyAvatar, Camera, Overlays, Vec3, Quat, Controller, print, getControllerWorldLocation */
/*jslint vars:true, plusplus:true, forin:true*/
/*global Tablet, Settings, Script, AvatarList, Users, Entities, MyAvatar, Camera, Overlays, Vec3, Quat, HMD, Controller, Account, UserActivityLogger, Messages, Window, XMLHttpRequest, print, location, getControllerWorldLocation*/
/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */
//
// pal.js
@ -14,6 +14,12 @@
(function() { // BEGIN LOCAL_SCOPE
var populateNearbyUserList, color, textures, removeOverlays,
controllerComputePickRay, onTabletButtonClicked, onTabletScreenChanged,
receiveMessage, avatarDisconnected, clearLocalQMLDataAndClosePAL,
createAudioInterval, tablet, CHANNEL, getConnectionData, findableByChanged,
avatarAdded, avatarRemoved, avatarSessionChanged; // forward references;
// hardcoding these as it appears we cannot traverse the originalTextures in overlays??? Maybe I've missed
// something, will revisit as this is sorta horrible.
var UNSELECTED_TEXTURES = {
@ -97,9 +103,8 @@ ExtendedOverlay.prototype.hover = function (hovering) {
if (this.key === lastHoveringId) {
if (hovering) {
return;
} else {
lastHoveringId = 0;
}
lastHoveringId = 0;
}
this.editOverlay({color: color(this.selected, hovering, this.audioLevel)});
if (this.model) {
@ -214,9 +219,8 @@ 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.
var data;
switch (message.method) {
@ -247,7 +251,7 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See
});
}
break;
case 'refresh':
case 'refreshNearby':
data = {};
ExtendedOverlay.some(function (overlay) { // capture the audio data
data[overlay.key] = overlay;
@ -257,14 +261,45 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See
if (message.params.filter !== undefined) {
Settings.setValue('pal/filtered', !!message.params.filter);
}
populateUserList(message.params.selected, data);
UserActivityLogger.palAction("refresh", "");
populateNearbyUserList(message.params.selected, data);
UserActivityLogger.palAction("refresh_nearby", "");
break;
case 'displayNameUpdate':
if (MyAvatar.displayName !== message.params) {
MyAvatar.displayName = message.params;
UserActivityLogger.palAction("display_name_change", "");
}
case 'refreshConnections':
print('Refreshing Connections...');
getConnectionData();
UserActivityLogger.palAction("refresh_connections", "");
break;
case 'removeFriend':
friendUserName = message.params;
request({
uri: METAVERSE_BASE + '/api/v1/user/friends/' + friendUserName,
method: 'DELETE'
}, function (error, response) {
print(JSON.stringify(response));
if (error || (response.status !== 'success')) {
print("Error: unable to unfriend", friendUserName, error || response.status);
return;
}
getConnectionData();
});
break
case 'addFriend':
friendUserName = message.params;
request({
uri: METAVERSE_BASE + '/api/v1/user/friends',
method: 'POST',
json: true,
body: {
username: friendUserName,
}
}, function (error, response) {
if (error || (response.status !== 'success')) {
print("Error: unable to friend " + friendUserName, error || response.status);
return;
}
getConnectionData(); // For now, just refresh all connection data. Later, just refresh the one friended row.
}
);
break;
default:
print('Unrecognized message from Pal.qml:', JSON.stringify(message));
@ -274,6 +309,141 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See
function sendToQml(message) {
tablet.sendToQml(message);
}
function updateUser(data) {
print('PAL update:', JSON.stringify(data));
sendToQml({ method: 'updateUsername', params: data });
}
//
// User management services
//
// These are prototype versions that will be changed when the back end changes.
var METAVERSE_BASE = location.metaverseServerUrl;
function request(options, callback) { // cb(error, responseOfCorrectContentType) of url. A subset of npm request.
var httpRequest = new XMLHttpRequest(), key;
// QT bug: apparently doesn't handle onload. Workaround using readyState.
httpRequest.onreadystatechange = function () {
var READY_STATE_DONE = 4;
var HTTP_OK = 200;
if (httpRequest.readyState >= READY_STATE_DONE) {
var error = (httpRequest.status !== HTTP_OK) && httpRequest.status.toString() + ':' + httpRequest.statusText,
response = !error && httpRequest.responseText,
contentType = !error && httpRequest.getResponseHeader('content-type');
if (!error && contentType.indexOf('application/json') === 0) { // ignoring charset, etc.
try {
response = JSON.parse(response);
} catch (e) {
error = e;
}
}
callback(error, response);
}
};
if (typeof options === 'string') {
options = {uri: options};
}
if (options.url) {
options.uri = options.url;
}
if (!options.method) {
options.method = 'GET';
}
if (options.body && (options.method === 'GET')) { // add query parameters
var params = [], appender = (-1 === options.uri.search('?')) ? '?' : '&';
for (key in options.body) {
params.push(key + '=' + options.body[key]);
}
options.uri += appender + params.join('&');
delete options.body;
}
if (options.json) {
options.headers = options.headers || {};
options.headers["Content-type"] = "application/json";
options.body = JSON.stringify(options.body);
}
for (key in options.headers || {}) {
httpRequest.setRequestHeader(key, options.headers[key]);
}
httpRequest.open(options.method, options.uri, true);
httpRequest.send(options.body);
}
function requestJSON(url, callback) { // callback(data) if successfull. Logs otherwise.
request({
uri: url
}, function (error, response) {
if (error || (response.status !== 'success')) {
print("Error: unable to get", url, error || response.status);
return;
}
callback(response.data);
});
}
function getProfilePicture(username, callback) { // callback(url) if successfull. (Logs otherwise)
// FIXME Prototype scrapes profile picture. We should include in user status, and also make available somewhere for myself
request({
uri: METAVERSE_BASE + '/users/' + username
}, function (error, html) {
var matched = !error && html.match(/img class="users-img" src="([^"]*)"/);
if (!matched) {
print('Error: Unable to get profile picture for', username, error);
return;
}
callback(matched[1]);
});
}
function getAvailableConnections(domain, callback) { // callback([{usename, location}...]) if successfull. (Logs otherwise)
// The back end doesn't do user connections yet. Fake it by getting all users that have made themselves accessible to us,
// and pretending that they are all connections.
url = METAVERSE_BASE + '/api/v1/users?'
if (domain) {
url += 'status=' + domain.slice(1, -1); // without curly braces
} else {
url += 'filter=connections'; // regardless of whether online
}
requestJSON(url, function (connectionsData) {
// The back end doesn't include the profile picture data, but we can add that here.
// For our current purposes, there's no need to be fancy and try to reduce latency by doing some number of requests in parallel,
// so these requests are all sequential.
var users = connectionsData.users;
function addPicture(index) {
if (index >= users.length) {
return callback(users);
}
var user = users[index];
getProfilePicture(user.username, function (url) {
user.profileUrl = url;
addPicture(index + 1);
});
}
addPicture(0);
});
}
function getConnectionData(domain) { // Update all the usernames that I am entitled to see, using my login but not dependent on canKick.
function frob(user) { // get into the right format
var formattedSessionId = user.location.node_id || '';
if (formattedSessionId !== '' && formattedSessionId.indexOf("{") != 0) {
formattedSessionId = "{" + formattedSessionId + "}";
}
return {
sessionId: formattedSessionId,
userName: user.username,
connection: user.connection,
profileUrl: user.profileUrl,
placeName: (user.location.root || user.location.domain || {}).name || ''
};
}
getAvailableConnections(domain, function (users) {
if (domain) {
users.forEach(function (user) {
updateUser(frob(user));
});
} else {
sendToQml({ method: 'connections', params: users.map(frob) });
}
});
}
//
// Main operations.
@ -285,15 +455,16 @@ function addAvatarNode(id) {
solid: true,
alpha: 0.8,
color: color(selected, false, 0.0),
ignoreRayIntersection: false}, selected, !conserveResources);
ignoreRayIntersection: false
}, selected, !conserveResources);
}
// Each open/refresh will capture a stable set of avatarsOfInterest, within the specified filter.
var avatarsOfInterest = {};
function populateUserList(selectData, oldAudioData) {
var filter = Settings.getValue('pal/filtered') && {distance: Settings.getValue('pal/nearDistance')};
var data = [], avatars = AvatarList.getAvatarIdentifiers();
avatarsOfInterest = {};
var myPosition = filter && Camera.position,
function populateNearbyUserList(selectData, oldAudioData) {
var filter = Settings.getValue('pal/filtered') && {distance: Settings.getValue('pal/nearDistance')},
data = [],
avatars = AvatarList.getAvatarIdentifiers(),
myPosition = filter && Camera.position,
frustum = filter && Camera.frustum,
verticalHalfAngle = filter && (frustum.fieldOfView / 2),
horizontalHalfAngle = filter && (verticalHalfAngle * frustum.aspectRatio),
@ -301,7 +472,8 @@ function populateUserList(selectData, oldAudioData) {
forward = filter && Quat.getForward(orientation),
verticalAngleNormal = filter && Quat.getRight(orientation),
horizontalAngleNormal = filter && Quat.getUp(orientation);
avatars.forEach(function (id) { // sorting the identifiers is just an aid for debugging
avatarsOfInterest = {};
avatars.forEach(function (id) {
var avatar = AvatarList.getAvatar(id);
var name = avatar.sessionDisplayName;
if (!name) {
@ -323,26 +495,33 @@ function populateUserList(selectData, oldAudioData) {
}
var oldAudio = oldAudioData && oldAudioData[id];
var avatarPalDatum = {
profileUrl: '',
displayName: name,
userName: '',
connection: '',
sessionId: id || '',
audioLevel: (oldAudio && oldAudio.audioLevel) || 0.0,
avgAudioLevel: (oldAudio && oldAudio.avgAudioLevel) || 0.0,
admin: false,
personalMute: !!id && Users.getPersonalMuteStatus(id), // expects proper boolean, not null
ignore: !!id && Users.getIgnoreStatus(id) // ditto
ignore: !!id && Users.getIgnoreStatus(id), // ditto
isPresent: true
};
if (id) {
addAvatarNode(id); // No overlay for ourselves
// Everyone needs to see admin status. Username and fingerprint returns default constructor output if the requesting user isn't an admin.
Users.requestUsernameFromID(id);
avatarsOfInterest[id] = true;
} else {
// Return our username from the Account API
avatarPalDatum.userName = Account.username;
}
data.push(avatarPalDatum);
print('PAL data:', JSON.stringify(avatarPalDatum));
});
getConnectionData(location.domainId); // Even admins don't get relationship data in requestUsernameFromID (which is still needed for admin status, which comes from domain).
conserveResources = Object.keys(avatarsOfInterest).length > 20;
sendToQml({ method: 'users', params: data });
sendToQml({ method: 'nearbyUsers', params: data });
if (selectData) {
selectData[2] = true;
sendToQml({ method: 'select', params: selectData });
@ -351,15 +530,15 @@ function populateUserList(selectData, oldAudioData) {
// The function that handles the reply from the server
function usernameFromIDReply(id, username, machineFingerprint, isAdmin) {
var data = [
(MyAvatar.sessionUUID === id) ? '' : id, // Pal.qml recognizes empty id specially.
var data = {
sessionId: (MyAvatar.sessionUUID === id) ? '' : id, // Pal.qml recognizes empty id specially.
// If we get username (e.g., if in future we receive it when we're friends), use it.
// Otherwise, use valid machineFingerprint (which is not valid when not an admin).
username || (Users.canKick && machineFingerprint) || '',
isAdmin
];
userName: username || (Users.canKick && machineFingerprint) || '',
admin: isAdmin
};
// Ship the data off to QML
sendToQml({ method: 'updateUsername', params: data });
updateUser(data);
}
var pingPong = true;
@ -381,16 +560,12 @@ 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");
var diff = Vec3.subtract(target, eye); // get diff between target and eye (a vector pointing to the eye from avatar position)
var headIndex = avatar.getJointIndex("Head"); // base offset on 1/2 distance from hips to head if we can
if (headIndex > 0) {
offset = avatar.getAbsoluteJointTranslationInObjectFrame(headIndex).y / 2;
}
// 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));
@ -418,7 +593,7 @@ function updateOverlays() {
overlay.deleteOverlay();
}
});
// We could re-populateUserList if anything added or removed, but not for now.
// We could re-populateNearbyUserList if anything added or removed, but not for now.
HighlightedEntity.updateOverlays();
}
function removeOverlays() {
@ -543,6 +718,9 @@ function startup() {
Messages.subscribe(CHANNEL);
Messages.messageReceived.connect(receiveMessage);
Users.avatarDisconnected.connect(avatarDisconnected);
AvatarList.avatarAddedEvent.connect(avatarAdded);
AvatarList.avatarRemovedEvent.connect(avatarRemoved);
AvatarList.avatarSessionChangedEvent.connect(avatarSessionChanged);
}
startup();
@ -556,6 +734,7 @@ function off() {
Script.update.disconnect(updateOverlays);
Controller.mousePressEvent.disconnect(handleMouseEvent);
Controller.mouseMoveEvent.disconnect(handleMouseMoveEvent);
tablet.tabletShownChanged.disconnect(tabletVisibilityChanged);
isWired = false;
}
if (audioTimer) {
@ -567,6 +746,12 @@ function off() {
Users.requestsDomainListData = false;
}
function tabletVisibilityChanged() {
if (!tablet.tabletShown) {
tablet.gotoHomeScreen();
}
}
var onPalScreen = false;
var shouldActivateButton = false;
@ -577,9 +762,10 @@ function onTabletButtonClicked() {
} else {
shouldActivateButton = true;
tablet.loadQMLSource("../Pal.qml");
tablet.tabletShownChanged.connect(tabletVisibilityChanged);
onPalScreen = true;
Users.requestsDomainListData = true;
populateUserList();
populateNearbyUserList();
isWired = true;
Script.update.connect(updateOverlays);
Controller.mousePressEvent.connect(handleMouseEvent);
@ -607,8 +793,7 @@ function onTabletScreenChanged(type, url) {
//
var CHANNEL = 'com.highfidelity.pal';
function receiveMessage(channel, messageString, senderID) {
if ((channel !== CHANNEL) ||
(senderID !== MyAvatar.sessionUUID)) {
if ((channel !== CHANNEL) || (senderID !== MyAvatar.sessionUUID)) {
return;
}
var message = JSON.parse(messageString);
@ -633,7 +818,7 @@ function scaleAudio(val) {
if (val <= LOUDNESS_FLOOR) {
audioLevel = val / LOUDNESS_FLOOR * LOUDNESS_SCALE;
} else {
audioLevel = (val -(LOUDNESS_FLOOR -1 )) * LOUDNESS_SCALE;
audioLevel = (val - (LOUDNESS_FLOOR - 1)) * LOUDNESS_SCALE;
}
if (audioLevel > 1.0) {
audioLevel = 1;
@ -659,14 +844,14 @@ function getAudioLevel(id) {
audioLevel = scaleAudio(Math.log(data.accumulatedLevel + 1) / LOG2);
// decay avgAudioLevel
avgAudioLevel = Math.max((1-AUDIO_PEAK_DECAY) * (data.avgAudioLevel || 0), audioLevel);
avgAudioLevel = Math.max((1 - AUDIO_PEAK_DECAY) * (data.avgAudioLevel || 0), audioLevel);
data.avgAudioLevel = avgAudioLevel;
data.audioLevel = audioLevel;
// 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)));
avgAudioLevel = Math.min(1.0, Math.sqrt(avgAudioLevel * (sessionGains[id] || 0.75)));
}
return [audioLevel, avgAudioLevel];
}
@ -677,9 +862,8 @@ function createAudioInterval(interval) {
return Script.setInterval(function () {
var param = {};
AvatarList.getAvatarIdentifiers().forEach(function (id) {
var level = getAudioLevel(id);
// qml didn't like an object with null/empty string for a key, so...
var userId = id || 0;
var level = getAudioLevel(id),
userId = id || 0; // qml didn't like an object with null/empty string for a key, so...
param[userId] = level;
});
sendToQml({method: 'updateAudioLevel', params: param});
@ -695,6 +879,18 @@ function clearLocalQMLDataAndClosePAL() {
sendToQml({ method: 'clearLocalQMLData' });
}
function avatarAdded(avatarID) {
sendToQml({ method: 'palIsStale', params: [avatarID, 'avatarAdded'] });
}
function avatarRemoved(avatarID) {
sendToQml({ method: 'palIsStale', params: [avatarID, 'avatarRemoved'] });
}
function avatarSessionChanged(avatarID) {
sendToQml({ method: 'palIsStale', params: [avatarID, 'avatarSessionChanged'] });
}
function shutdown() {
if (onPalScreen) {
tablet.gotoHomeScreen();
@ -708,6 +904,9 @@ function shutdown() {
Messages.subscribe(CHANNEL);
Messages.messageReceived.disconnect(receiveMessage);
Users.avatarDisconnected.disconnect(avatarDisconnected);
AvatarList.avatarAddedEvent.disconnect(avatarAdded);
AvatarList.avatarRemovedEvent.disconnect(avatarRemoved);
AvatarList.avatarSessionChangedEvent.disconnect(avatarSessionChanged);
off();
}

View file

@ -15,95 +15,94 @@
(function() { // BEGIN LOCAL_SCOPE
if (typeof String.prototype.startsWith != 'function') {
String.prototype.startsWith = function (str){
return this.slice(0, str.length) == str;
};
}
if (typeof String.prototype.endsWith != 'function') {
String.prototype.endsWith = function (str){
return this.slice(-str.length) == str;
};
}
if (typeof String.prototype.trimStartsWith != 'function') {
String.prototype.trimStartsWith = function (str){
if (this.startsWith(str)) {
return this.substr(str.length);
}
return this;
};
}
if (typeof String.prototype.trimEndsWith != 'function') {
String.prototype.trimEndsWith = function (str){
if (this.endsWith(str)) {
return this.substr(0,this.length - str.length);
}
return this;
};
const INPUT = "Input";
const OUTPUT = "Output";
function parseMenuItem(item) {
const USE = "Use ";
const FOR_INPUT = " for " + INPUT;
const FOR_OUTPUT = " for " + OUTPUT;
if (item.slice(0, USE.length) == USE) {
if (item.slice(-FOR_INPUT.length) == FOR_INPUT) {
return { device: item.slice(USE.length, -FOR_INPUT.length), mode: INPUT };
} else if (item.slice(-FOR_OUTPUT.length) == FOR_OUTPUT) {
return { device: item.slice(USE.length, -FOR_OUTPUT.length), mode: OUTPUT };
}
}
}
//
// VAR DEFINITIONS
//
var debugPrintStatements = true;
const INPUT_DEVICE_SETTING = "audio_input_device";
const OUTPUT_DEVICE_SETTING = "audio_output_device";
var audioDevicesList = [];
var wasHmdActive = false; // assume it's not active to start
var switchedAudioInputToHMD = false;
var switchedAudioOutputToHMD = false;
var previousSelectedInputAudioDevice = "";
var previousSelectedOutputAudioDevice = "";
var skipMenuEvents = true;
var selectedInputMenu = "";
var selectedOutputMenu = "";
var audioDevicesList = [];
function setupAudioMenus() {
removeAudioMenus();
Menu.addSeparator("Audio", "Input Audio Device");
var inputDeviceSetting = Settings.getValue(INPUT_DEVICE_SETTING);
var inputDevices = AudioDevice.getInputDevices();
var selectedInputDevice = AudioDevice.getInputDevice();
if (inputDevices.indexOf(inputDeviceSetting) != -1 && selectedInputDevice != inputDeviceSetting) {
if (AudioDevice.setInputDevice(inputDeviceSetting)) {
selectedInputDevice = inputDeviceSetting;
}
//
// BEGIN FUNCTION DEFINITIONS
//
function debug() {
if (debugPrintStatements) {
print.apply(null, [].concat.apply(["selectAudioDevice.js:"], [].map.call(arguments, JSON.stringify)));
}
print("audio input devices: " + inputDevices);
for(var i = 0; i < inputDevices.length; i++) {
var thisDeviceSelected = (inputDevices[i] == selectedInputDevice);
var menuItem = "Use " + inputDevices[i] + " for Input";
}
function setupAudioMenus() {
// menu events can be triggered asynchronously; skip them for 200ms to avoid recursion and false switches
skipMenuEvents = true;
Script.setTimeout(function() { skipMenuEvents = false; }, 200);
removeAudioMenus();
// Setup audio input devices
Menu.addSeparator("Audio", "Input Audio Device");
var inputDevices = AudioDevice.getInputDevices();
for (var i = 0; i < inputDevices.length; i++) {
var audioDeviceMenuString = "Use " + inputDevices[i] + " for Input";
Menu.addMenuItem({
menuName: "Audio",
menuItemName: menuItem,
menuItemName: audioDeviceMenuString,
isCheckable: true,
isChecked: thisDeviceSelected
isChecked: inputDevices[i] == AudioDevice.getInputDevice()
});
audioDevicesList.push(menuItem);
if (thisDeviceSelected) {
selectedInputMenu = menuItem;
}
audioDevicesList.push(audioDeviceMenuString);
}
// Setup audio output devices
Menu.addSeparator("Audio", "Output Audio Device");
var outputDevices = AudioDevice.getOutputDevices();
for (var i = 0; i < outputDevices.length; i++) {
var audioDeviceMenuString = "Use " + outputDevices[i] + " for Output";
Menu.addMenuItem({
menuName: "Audio",
menuItemName: audioDeviceMenuString,
isCheckable: true,
isChecked: outputDevices[i] == AudioDevice.getOutputDevice()
});
audioDevicesList.push(audioDeviceMenuString);
}
}
function checkDeviceMismatch() {
var inputDeviceSetting = Settings.getValue(INPUT_DEVICE_SETTING);
var interfaceInputDevice = AudioDevice.getInputDevice();
if (interfaceInputDevice != inputDeviceSetting) {
debug("Input Setting & Device mismatch! Input SETTING: " + inputDeviceSetting + "Input DEVICE IN USE: " + interfaceInputDevice);
switchAudioDevice("Use " + inputDeviceSetting + " for Input");
}
var outputDeviceSetting = Settings.getValue(OUTPUT_DEVICE_SETTING);
var outputDevices = AudioDevice.getOutputDevices();
var selectedOutputDevice = AudioDevice.getOutputDevice();
if (outputDevices.indexOf(outputDeviceSetting) != -1 && selectedOutputDevice != outputDeviceSetting) {
if (AudioDevice.setOutputDevice(outputDeviceSetting)) {
selectedOutputDevice = outputDeviceSetting;
}
}
print("audio output devices: " + outputDevices);
for (var i = 0; i < outputDevices.length; i++) {
var thisDeviceSelected = (outputDevices[i] == selectedOutputDevice);
var menuItem = "Use " + outputDevices[i] + " for Output";
Menu.addMenuItem({
menuName: "Audio",
menuItemName: menuItem,
isCheckable: true,
isChecked: thisDeviceSelected
});
audioDevicesList.push(menuItem);
if (thisDeviceSelected) {
selectedOutputMenu = menuItem;
}
var interfaceOutputDevice = AudioDevice.getOutputDevice();
if (interfaceOutputDevice != outputDeviceSetting) {
debug("Output Setting & Device mismatch! Output SETTING: " + outputDeviceSetting + "Output DEVICE IN USE: " + interfaceOutputDevice);
switchAudioDevice("Use " + outputDeviceSetting + " for Output");
}
}
@ -112,130 +111,170 @@ function removeAudioMenus() {
Menu.removeSeparator("Audio", "Output Audio Device");
for (var index = 0; index < audioDevicesList.length; index++) {
Menu.removeMenuItem("Audio", audioDevicesList[index]);
if (Menu.menuItemExists("Audio", audioDevicesList[index])) {
Menu.removeMenuItem("Audio", audioDevicesList[index]);
}
}
Menu.removeMenu("Audio > Devices");
audioDevicesList = [];
}
function onDevicechanged() {
print("audio devices changed, removing Audio > Devices menu...");
Menu.removeMenu("Audio > Devices");
print("now setting up Audio > Devices menu");
debug("System audio devices changed. Removing and replacing Audio Menus...");
setupAudioMenus();
checkDeviceMismatch();
}
// Have a small delay before the menu's get setup and the audio devices can switch to the last selected ones
Script.setTimeout(function () {
print("connecting deviceChanged");
AudioDevice.deviceChanged.connect(onDevicechanged);
print("setting up Audio > Devices menu for first time");
setupAudioMenus();
}, 5000);
function scriptEnding() {
Menu.removeMenu("Audio > Devices");
function onMenuEvent(audioDeviceMenuString) {
if (!skipMenuEvents) {
switchAudioDevice(audioDeviceMenuString);
}
}
Script.scriptEnding.connect(scriptEnding);
function switchAudioDevice(audioDeviceMenuString) {
// if the device is not plugged in, short-circuit
if (!~audioDevicesList.indexOf(audioDeviceMenuString)) {
return;
}
function menuItemEvent(menuItem) {
if (menuItem.startsWith("Use ")) {
if (menuItem.endsWith(" for Output")) {
var selectedDevice = menuItem.trimStartsWith("Use ").trimEndsWith(" for Output");
print("output audio selection..." + selectedDevice);
Menu.menuItemEvent.disconnect(menuItemEvent);
Menu.setIsOptionChecked(selectedOutputMenu, false);
selectedOutputMenu = menuItem;
Menu.setIsOptionChecked(selectedOutputMenu, true);
if (AudioDevice.setOutputDevice(selectedDevice)) {
Settings.setValue(OUTPUT_DEVICE_SETTING, selectedDevice);
}
Menu.menuItemEvent.connect(menuItemEvent);
} else if (menuItem.endsWith(" for Input")) {
var selectedDevice = menuItem.trimStartsWith("Use ").trimEndsWith(" for Input");
print("input audio selection..." + selectedDevice);
Menu.menuItemEvent.disconnect(menuItemEvent);
Menu.setIsOptionChecked(selectedInputMenu, false);
selectedInputMenu = menuItem;
Menu.setIsOptionChecked(selectedInputMenu, true);
var selection = parseMenuItem(audioDeviceMenuString);
if (!selection) {
debug("Invalid Audio audioDeviceMenuString! Doesn't end with 'for Input' or 'for Output'");
return;
}
// menu events can be triggered asynchronously; skip them for 200ms to avoid recursion and false switches
skipMenuEvents = true;
Script.setTimeout(function() { skipMenuEvents = false; }, 200);
var selectedDevice = selection.device;
if (selection.mode == INPUT) {
var currentInputDevice = AudioDevice.getInputDevice();
if (selectedDevice != currentInputDevice) {
debug("Switching audio INPUT device from " + currentInputDevice + " to " + selectedDevice);
Menu.setIsOptionChecked("Use " + currentInputDevice + " for Input", false);
if (AudioDevice.setInputDevice(selectedDevice)) {
Settings.setValue(INPUT_DEVICE_SETTING, selectedDevice);
Menu.setIsOptionChecked(audioDeviceMenuString, true);
} else {
debug("Error setting audio input device!")
Menu.setIsOptionChecked(audioDeviceMenuString, false);
}
Menu.menuItemEvent.connect(menuItemEvent);
} else {
debug("Selected input device is the same as the current input device!")
Settings.setValue(INPUT_DEVICE_SETTING, selectedDevice);
Menu.setIsOptionChecked(audioDeviceMenuString, true);
AudioDevice.setInputDevice(selectedDevice); // Still try to force-set the device (in case the user's trying to forcefully debug an issue)
}
} else if (selection.mode == OUTPUT) {
var currentOutputDevice = AudioDevice.getOutputDevice();
if (selectedDevice != currentOutputDevice) {
debug("Switching audio OUTPUT device from " + currentOutputDevice + " to " + selectedDevice);
Menu.setIsOptionChecked("Use " + currentOutputDevice + " for Output", false);
if (AudioDevice.setOutputDevice(selectedDevice)) {
Settings.setValue(OUTPUT_DEVICE_SETTING, selectedDevice);
Menu.setIsOptionChecked(audioDeviceMenuString, true);
} else {
debug("Error setting audio output device!")
Menu.setIsOptionChecked(audioDeviceMenuString, false);
}
} else {
debug("Selected output device is the same as the current output device!")
Settings.setValue(OUTPUT_DEVICE_SETTING, selectedDevice);
Menu.setIsOptionChecked(audioDeviceMenuString, true);
AudioDevice.setOutputDevice(selectedDevice); // Still try to force-set the device (in case the user's trying to forcefully debug an issue)
}
}
}
Menu.menuItemEvent.connect(menuItemEvent);
function restoreAudio() {
if (switchedAudioInputToHMD) {
debug("Switching back from HMD preferred audio input to: " + previousSelectedInputAudioDevice);
switchAudioDevice("Use " + previousSelectedInputAudioDevice + " for Input");
switchedAudioInputToHMD = false;
}
if (switchedAudioOutputToHMD) {
debug("Switching back from HMD preferred audio output to: " + previousSelectedOutputAudioDevice);
switchAudioDevice("Use " + previousSelectedOutputAudioDevice + " for Output");
switchedAudioOutputToHMD = false;
}
}
// Some HMDs (like Oculus CV1) have a built in audio device. If they
// do, then this function will handle switching to that device automatically
// when you goActive with the HMD active.
var wasHmdMounted = false; // assume it's un-mounted to start
var switchedAudioInputToHMD = false;
var switchedAudioOutputToHMD = false;
var previousSelectedInputAudioDevice = "";
var previousSelectedOutputAudioDevice = "";
function restoreAudio() {
if (switchedAudioInputToHMD) {
print("switching back from HMD preferred audio input to:" + previousSelectedInputAudioDevice);
menuItemEvent("Use " + previousSelectedInputAudioDevice + " for Input");
}
if (switchedAudioOutputToHMD) {
print("switching back from HMD preferred audio output to:" + previousSelectedOutputAudioDevice);
menuItemEvent("Use " + previousSelectedOutputAudioDevice + " for Output");
}
}
function checkHMDAudio() {
// Mounted state is changing... handle switching
if (HMD.mounted != wasHmdMounted) {
print("HMD mounted changed...");
// HMD Active state is changing; handle switching
if (HMD.active != wasHmdActive) {
debug("HMD Active state changed!");
// We're putting the HMD on... switch to those devices
if (HMD.mounted) {
print("NOW mounted...");
// We're putting the HMD on; switch to those devices
if (HMD.active) {
debug("HMD is now Active.");
var hmdPreferredAudioInput = HMD.preferredAudioInput();
var hmdPreferredAudioOutput = HMD.preferredAudioOutput();
print("hmdPreferredAudioInput:" + hmdPreferredAudioInput);
print("hmdPreferredAudioOutput:" + hmdPreferredAudioOutput);
debug("hmdPreferredAudioInput: " + hmdPreferredAudioInput);
debug("hmdPreferredAudioOutput: " + hmdPreferredAudioOutput);
var hmdHasPreferredAudio = (hmdPreferredAudioInput !== "") || (hmdPreferredAudioOutput !== "");
if (hmdHasPreferredAudio) {
print("HMD has preferred audio!");
if (hmdPreferredAudioInput !== "") {
debug("HMD has preferred audio input device.");
previousSelectedInputAudioDevice = Settings.getValue(INPUT_DEVICE_SETTING);
previousSelectedOutputAudioDevice = Settings.getValue(OUTPUT_DEVICE_SETTING);
print("previousSelectedInputAudioDevice:" + previousSelectedInputAudioDevice);
print("previousSelectedOutputAudioDevice:" + previousSelectedOutputAudioDevice);
if (hmdPreferredAudioInput != previousSelectedInputAudioDevice && hmdPreferredAudioInput !== "") {
print("switching to HMD preferred audio input to:" + hmdPreferredAudioInput);
debug("previousSelectedInputAudioDevice: " + previousSelectedInputAudioDevice);
if (hmdPreferredAudioInput != previousSelectedInputAudioDevice) {
switchedAudioInputToHMD = true;
menuItemEvent("Use " + hmdPreferredAudioInput + " for Input");
switchAudioDevice("Use " + hmdPreferredAudioInput + " for Input");
}
if (hmdPreferredAudioOutput != previousSelectedOutputAudioDevice && hmdPreferredAudioOutput !== "") {
print("switching to HMD preferred audio output to:" + hmdPreferredAudioOutput);
}
if (hmdPreferredAudioOutput !== "") {
debug("HMD has preferred audio output device.");
previousSelectedOutputAudioDevice = Settings.getValue(OUTPUT_DEVICE_SETTING);
debug("previousSelectedOutputAudioDevice: " + previousSelectedOutputAudioDevice);
if (hmdPreferredAudioOutput != previousSelectedOutputAudioDevice) {
switchedAudioOutputToHMD = true;
menuItemEvent("Use " + hmdPreferredAudioOutput + " for Output");
switchAudioDevice("Use " + hmdPreferredAudioOutput + " for Output");
}
}
} else {
print("HMD NOW un-mounted...");
debug("HMD no longer active. Restoring audio I/O devices...");
restoreAudio();
}
}
wasHmdMounted = HMD.mounted;
wasHmdActive = HMD.active;
}
Script.update.connect(checkHMDAudio);
//
// END FUNCTION DEFINITIONS
//
//
// BEGIN SCRIPT BODY
//
// Wait for the C++ systems to fire up before trying to do anything with audio devices
Script.setTimeout(function () {
debug("Connecting deviceChanged(), displayModeChanged(), and switchAudioDevice()...");
AudioDevice.deviceChanged.connect(onDevicechanged);
HMD.displayModeChanged.connect(checkHMDAudio);
Menu.menuItemEvent.connect(onMenuEvent);
debug("Setting up Audio I/O menu for the first time...");
setupAudioMenus();
checkDeviceMismatch();
debug("Checking HMD audio status...")
checkHMDAudio();
}, 3000);
debug("Connecting scriptEnding()");
Script.scriptEnding.connect(function () {
restoreAudio();
removeAudioMenus();
Menu.menuItemEvent.disconnect(menuItemEvent);
Script.update.disconnect(checkHMDAudio);
Menu.menuItemEvent.disconnect(onMenuEvent);
HMD.displayModeChanged.disconnect(checkHMDAudio);
AudioDevice.deviceChanged.disconnect(onDevicechanged);
});
//
// END SCRIPT BODY
//
}()); // END LOCAL_SCOPE

View file

@ -16,8 +16,6 @@
MyAvatar, Menu */
(function() { // BEGIN LOCAL_SCOPE
var _this = this;
var tabletShown = false;
var tabletRezzed = false;
var activeHand = null;
var DEFAULT_WIDTH = 0.4375;
@ -94,7 +92,7 @@
}
function showTabletUI() {
tabletShown = true;
Tablet.getTablet("com.highfidelity.interface.tablet.system").tabletShown = true;
if (!tabletRezzed || !tabletIsValid()) {
closeTabletUI();
@ -116,7 +114,7 @@
}
function hideTabletUI() {
tabletShown = false;
Tablet.getTablet("com.highfidelity.interface.tablet.system").tabletShown = false;
if (!UIWebTablet) {
return;
}
@ -132,7 +130,7 @@
}
function closeTabletUI() {
tabletShown = false;
Tablet.getTablet("com.highfidelity.interface.tablet.system").tabletShown = false;
if (UIWebTablet) {
if (UIWebTablet.onClose) {
UIWebTablet.onClose();
@ -159,6 +157,7 @@
var now = Date.now();
// close the WebTablet if it we go into toolbar mode.
var tabletShown = Tablet.getTablet("com.highfidelity.interface.tablet.system").tabletShown;
var toolbarMode = Tablet.getTablet("com.highfidelity.interface.tablet.system").toolbarMode;
var landscape = Tablet.getTablet("com.highfidelity.interface.tablet.system").landscape;