mirror of
https://github.com/overte-org/overte.git
synced 2025-04-07 12:53:44 +02:00
merge from upstream
This commit is contained in:
commit
43e1cec8a3
86 changed files with 4457 additions and 1712 deletions
|
@ -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);
|
||||
|
|
|
@ -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>;
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
48
interface/resources/icons/connection.svg
Normal file
48
interface/resources/icons/connection.svg
Normal 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 |
BIN
interface/resources/icons/profilePicLoading.gif
Normal file
BIN
interface/resources/icons/profilePicLoading.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 87 KiB |
|
@ -33,6 +33,10 @@ ScrollingWindow {
|
|||
addressBar.text = webview.url
|
||||
}
|
||||
|
||||
function setProfile(profile) {
|
||||
webview.profile = profile;
|
||||
}
|
||||
|
||||
function showPermissionsBar(){
|
||||
permissionsContainer.visible=true;
|
||||
}
|
||||
|
|
186
interface/resources/qml/TabletBrowser.qml
Normal file
186
interface/resources/qml/TabletBrowser.qml
Normal 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
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
271
interface/resources/qml/controls/TabletWebView.qml
Normal file
271
interface/resources/qml/controls/TabletWebView.qml
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
154
interface/resources/qml/hifi/ComboDialog.qml
Normal file
154
interface/resources/qml/hifi/ComboDialog.qml
Normal 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]});
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()));
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
26
interface/src/networking/HFTabletWebEngineProfile.cpp
Normal file
26
interface/src/networking/HFTabletWebEngineProfile.cpp
Normal 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);
|
||||
}
|
||||
|
23
interface/src/networking/HFTabletWebEngineProfile.h
Normal file
23
interface/src/networking/HFTabletWebEngineProfile.h
Normal 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
|
|
@ -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());
|
||||
}
|
|
@ -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
|
|
@ -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) {
|
||||
|
|
|
@ -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() :
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
91
interface/src/scripting/LimitlessConnection.cpp
Normal file
91
interface/src/scripting/LimitlessConnection.cpp
Normal 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;
|
||||
}
|
44
interface/src/scripting/LimitlessConnection.h
Normal file
44
interface/src/scripting/LimitlessConnection.h
Normal 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
|
|
@ -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");
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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)));
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()));
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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; }
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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]));
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -251,7 +251,7 @@ static QString getUsername() {
|
|||
}
|
||||
|
||||
void TabletProxy::initialScreen(const QVariant& url) {
|
||||
if (getQmlTablet()) {
|
||||
if (_qmlTabletRoot) {
|
||||
pushOntoStack(url);
|
||||
} else {
|
||||
_initialScreen = true;
|
||||
|
|
|
@ -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 };
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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="< Back" />' : '') +
|
||||
(isInitialHiFiPage ? '<span class="glyph">🛈</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">🛈</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¢erScene=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().
|
||||
|
||||
}());
|
|
@ -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>
|
||||
|
|
848
scripts/system/makeUserConnection.js
Normal file
848
scripts/system/makeUserConnection.js
Normal 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
|
||||
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
Loading…
Reference in a new issue