Merge branch 'master' into feature/pole-vector

This commit is contained in:
Anthony J. Thibault 2017-06-13 15:37:59 -07:00
commit 03a6f7082e
102 changed files with 2213 additions and 1165 deletions

View file

@ -1,14 +1,16 @@
{ {
"RenderShadowTask": { "RenderMainView": {
"Enabled": { "RenderShadowTask": {
"enabled": true
}
},
"RenderDeferredTask": {
"AmbientOcclusion": {
"Enabled": { "Enabled": {
"enabled": true "enabled": true
} }
},
"RenderDeferredTask": {
"AmbientOcclusion": {
"Enabled": {
"enabled": true
}
}
} }
} }
} }

View file

@ -32,7 +32,7 @@ var EventBridge;
var webChannel = new QWebChannel(qt.webChannelTransport, function (channel) { var webChannel = new QWebChannel(qt.webChannelTransport, function (channel) {
// replace the TempEventBridge with the real one. // replace the TempEventBridge with the real one.
var tempEventBridge = EventBridge; var tempEventBridge = EventBridge;
EventBridge = channel.objects.eventBridgeWrapper.eventBridge; EventBridge = channel.objects.eventBridge;
tempEventBridge._callbacks.forEach(function (callback) { tempEventBridge._callbacks.forEach(function (callback) {
EventBridge.scriptEventReceived.connect(callback); EventBridge.scriptEventReceived.connect(callback);
}); });

View file

@ -1,85 +1,89 @@
name = Jointy3 name = mannequin
type = body+head type = body+head
scale = 1 scale = 1
filename = Jointy3/Jointy3.fbx filename = mannequin/mannequin.baked.fbx
texdir = Jointy3/textures joint = jointEyeLeft = LeftEye
joint = jointRightHand = RightHand
joint = jointHead = Head
joint = jointEyeRight = RightEye
joint = jointLean = Spine
joint = jointNeck = Neck joint = jointNeck = Neck
joint = jointLeftHand = LeftHand joint = jointLeftHand = LeftHand
joint = jointEyeRight = RightEye
joint = jointHead = Head
joint = jointRightHand = RightHand
joint = jointRoot = Hips joint = jointRoot = Hips
joint = jointLean = Spine
joint = jointEyeLeft = LeftEye
freeJoint = LeftArm freeJoint = LeftArm
freeJoint = LeftForeArm freeJoint = LeftForeArm
freeJoint = RightArm freeJoint = RightArm
freeJoint = RightForeArm freeJoint = RightForeArm
jointIndex = RightHand = 17 bs = EyeBlink_L = blink = 1
jointIndex = LeftHandIndex3 = 56 bs = JawOpen = mouth_Open = 1
jointIndex = Hips = 0 bs = LipsFunnel = Oo = 1
jointIndex = LeftHandRing2 = 47 bs = BrowsU_L = brow_Up = 1
jointIndex = LeftHandThumb3 = 60 jointIndex = RightHandIndex2 = 27
jointIndex = RightShoulder = 14 jointIndex = LeftHandIndex2 = 51
jointIndex = RightHandRing1 = 30 jointIndex = RightUpLeg = 6
jointIndex = RightHandRing3 = 32 jointIndex = RightToe_End = 10
jointIndex = LeftHandPinky4 = 45 jointIndex = RightEye = 65
jointIndex = LeftHandRing1 = 46
jointIndex = LeftFoot = 8
jointIndex = RightHandIndex2 = 23
jointIndex = RightToeBase = 4
jointIndex = RightHandMiddle4 = 29
jointIndex = RightHandPinky4 = 37
jointIndex = LeftToe_End = 10
jointIndex = RightEye = 66
jointIndex = RightHandPinky2 = 35
jointIndex = RightHandRing2 = 31
jointIndex = LeftHand = 41
jointIndex = RightToe_End = 5
jointIndex = LeftEye = 65
jointIndex = LeftHandThumb2 = 59
jointIndex = pCylinder73Shape1 = 67
jointIndex = LeftShoulder = 38
jointIndex = LeftHandIndex2 = 55
jointIndex = RightForeArm = 16
jointIndex = LeftHandMiddle2 = 51
jointIndex = RightHandRing4 = 33
jointIndex = LeftLeg = 7
jointIndex = LeftHandThumb4 = 61
jointIndex = LeftForeArm = 40
jointIndex = HeadTop_End = 64
jointIndex = RightHandPinky1 = 34
jointIndex = RightHandIndex1 = 22
jointIndex = LeftHandIndex1 = 54
jointIndex = RightLeg = 2
jointIndex = RightHandIndex4 = 25
jointIndex = Neck = 62
jointIndex = LeftHandMiddle1 = 50
jointIndex = RightHandPinky3 = 36
jointIndex = LeftHandPinky2 = 43
jointIndex = RightHandMiddle3 = 28
jointIndex = RightHandThumb4 = 21
jointIndex = LeftUpLeg = 6
jointIndex = RightFoot = 3
jointIndex = LeftHandThumb1 = 58
jointIndex = LeftArm = 39
jointIndex = RightHandMiddle1 = 26
jointIndex = LeftHandRing3 = 48
jointIndex = LeftHandMiddle4 = 53
jointIndex = RightUpLeg = 1
jointIndex = RightHandMiddle2 = 27
jointIndex = LeftToeBase = 9
jointIndex = RightHandThumb2 = 19
jointIndex = Spine2 = 13
jointIndex = Spine = 11
jointIndex = LeftHandRing4 = 49
jointIndex = Head = 63
jointIndex = LeftHandPinky3 = 44
jointIndex = LeftHandPinky1 = 42 jointIndex = LeftHandPinky1 = 42
jointIndex = RightHandThumb1 = 18 jointIndex = RightHandRing1 = 22
jointIndex = LeftHandIndex4 = 57 jointIndex = face = 67
jointIndex = LeftHandMiddle3 = 52 jointIndex = LeftUpLeg = 1
jointIndex = RightHandIndex3 = 24 jointIndex = LeftHand = 41
jointIndex = Spine1 = 12 jointIndex = LeftHandMiddle1 = 58
jointIndex = LeftHandIndex1 = 50
jointIndex = LeftEye = 64
jointIndex = RightHandIndex1 = 26
jointIndex = LeftHandPinky4 = 45
jointIndex = RightArm = 15 jointIndex = RightArm = 15
jointIndex = RightHandThumb3 = 20 jointIndex = LeftShoulder = 38
jointIndex = RightHandPinky2 = 19
jointIndex = RightHandThumb1 = 30
jointIndex = RightForeArm = 16
jointIndex = LeftHandMiddle3 = 60
jointIndex = Neck = 62
jointIndex = LeftHandThumb1 = 54
jointIndex = RightHandMiddle2 = 35
jointIndex = LeftHandMiddle4 = 61
jointIndex = mannequin = 68
jointIndex = Spine1 = 12
jointIndex = RightFoot = 8
jointIndex = RightHand = 17
jointIndex = LeftHandIndex3 = 52
jointIndex = RightHandIndex3 = 28
jointIndex = RightHandMiddle4 = 37
jointIndex = LeftLeg = 2
jointIndex = RightHandMiddle1 = 34
jointIndex = Spine2 = 13
jointIndex = LeftHandMiddle2 = 59
jointIndex = LeftHandPinky3 = 44
jointIndex = LeftHandThumb3 = 56
jointIndex = LeftHandRing4 = 49
jointIndex = RightHandThumb2 = 31
jointIndex = LeftHandRing3 = 48
jointIndex = HeadTop_End = 66
jointIndex = LeftHandThumb4 = 57
jointIndex = RightHandThumb3 = 32
jointIndex = RightHandPinky1 = 18
jointIndex = RightLeg = 7
jointIndex = RightHandMiddle3 = 36
jointIndex = RightHandPinky3 = 20
jointIndex = LeftToeBase = 4
jointIndex = LeftForeArm = 40
jointIndex = RightShoulder = 14
jointIndex = LeftHandRing2 = 47
jointIndex = LeftHandThumb2 = 55
jointIndex = Head = 63
jointIndex = RightHandRing4 = 25
jointIndex = LeftHandRing1 = 46
jointIndex = LeftFoot = 3
jointIndex = RightHandRing3 = 24
jointIndex = RightHandThumb4 = 33
jointIndex = LeftArm = 39
jointIndex = LeftToe_End = 5
jointIndex = RightToeBase = 9
jointIndex = RightHandPinky4 = 21
jointIndex = Spine = 11
jointIndex = LeftHandIndex4 = 53
jointIndex = LeftHandPinky2 = 43
jointIndex = RightHandIndex4 = 29
jointIndex = Hips = 0
jointIndex = RightHandRing2 = 23

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -21,8 +21,6 @@ ScrollingWindow {
property alias url: webview.url property alias url: webview.url
property alias webView: webview property alias webView: webview
property alias eventBridge: eventBridgeWrapper.eventBridge
signal loadingChanged(int status) signal loadingChanged(int status)
x: 100 x: 100
@ -210,17 +208,6 @@ ScrollingWindow {
url: "https://highfidelity.com/" url: "https://highfidelity.com/"
profile: FileTypeProfile; profile: FileTypeProfile;
property alias eventBridgeWrapper: eventBridgeWrapper
QtObject {
id: eventBridgeWrapper
WebChannel.id: "eventBridgeWrapper"
property var eventBridge;
}
webChannel.registeredObjects: [eventBridgeWrapper]
// Create a global EventBridge object for raiseAndLowerKeyboard. // Create a global EventBridge object for raiseAndLowerKeyboard.
WebEngineScript { WebEngineScript {
id: createGlobalEventBridge id: createGlobalEventBridge
@ -267,6 +254,8 @@ ScrollingWindow {
} }
Component.onCompleted: { Component.onCompleted: {
webChannel.registerObject("eventBridge", eventBridge);
webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper);
desktop.initWebviewProfileHandlers(webview.profile); desktop.initWebviewProfileHandlers(webview.profile);
} }
} }

View file

@ -26,15 +26,8 @@ Windows.ScrollingWindow {
// Don't destroy on close... otherwise the JS/C++ will have a dangling pointer // Don't destroy on close... otherwise the JS/C++ will have a dangling pointer
destroyOnCloseButton: false destroyOnCloseButton: false
property alias source: webview.url property alias source: webview.url
property alias eventBridge: eventBridgeWrapper.eventBridge;
property alias scriptUrl: webview.userScriptUrl property alias scriptUrl: webview.userScriptUrl
QtObject {
id: eventBridgeWrapper
WebChannel.id: "eventBridgeWrapper"
property var eventBridge;
}
// This is for JS/QML communication, which is unused in a WebWindow, // This is for JS/QML communication, which is unused in a WebWindow,
// but not having this here results in spurious warnings about a // but not having this here results in spurious warnings about a
// missing signal // missing signal
@ -70,7 +63,6 @@ Windows.ScrollingWindow {
url: "about:blank" url: "about:blank"
anchors.fill: parent anchors.fill: parent
focus: true focus: true
webChannel.registeredObjects: [eventBridgeWrapper]
property string userScriptUrl: "" property string userScriptUrl: ""
@ -107,6 +99,8 @@ Windows.ScrollingWindow {
} }
Component.onCompleted: { Component.onCompleted: {
webChannel.registerObject("eventBridge", eventBridge);
webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper);
eventBridge.webEventReceived.connect(onWebEventReceived); eventBridge.webEventReceived.connect(onWebEventReceived);
} }
} }

View file

@ -30,15 +30,6 @@ Windows.Window {
property bool keyboardRaised: false property bool keyboardRaised: false
property bool punctuationMode: false property bool punctuationMode: false
// JavaScript event bridge object in case QML content includes Web content.
property alias eventBridge: eventBridgeWrapper.eventBridge;
QtObject {
id: eventBridgeWrapper
WebChannel.id: "eventBridgeWrapper"
property var eventBridge;
}
onSourceChanged: { onSourceChanged: {
if (dynamicContent) { if (dynamicContent) {
dynamicContent.destroy(); dynamicContent.destroy();

View file

@ -18,7 +18,6 @@ Item {
property variant permissionsBar: {'securityOrigin':'none','feature':'none'} property variant permissionsBar: {'securityOrigin':'none','feature':'none'}
property alias url: webview.url property alias url: webview.url
property WebEngineView webView: webview property WebEngineView webView: webview
property alias eventBridge: eventBridgeWrapper.eventBridge
property bool canGoBack: webview.canGoBack property bool canGoBack: webview.canGoBack
property bool canGoForward: webview.canGoForward property bool canGoForward: webview.canGoForward
@ -32,12 +31,6 @@ Item {
webview.profile = profile; webview.profile = profile;
} }
QtObject {
id: eventBridgeWrapper
WebChannel.id: "eventBridgeWrapper"
property var eventBridge;
}
WebEngineView { WebEngineView {
id: webview id: webview
objectName: "webEngineView" objectName: "webEngineView"
@ -78,9 +71,10 @@ Item {
property string newUrl: "" property string newUrl: ""
webChannel.registeredObjects: [eventBridgeWrapper]
Component.onCompleted: { Component.onCompleted: {
webChannel.registerObject("eventBridge", eventBridge);
webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper);
// Ensure the JS from the web-engine makes it to our logging // Ensure the JS from the web-engine makes it to our logging
webview.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) { webview.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) {
console.log("Web Entity JS message: " + sourceID + " " + lineNumber + " " + message); console.log("Web Entity JS message: " + sourceID + " " + lineNumber + " " + message);

View file

@ -79,15 +79,11 @@ ScrollingWindow {
id: webView id: webView
anchors.fill: parent anchors.fill: parent
enabled: false enabled: false
property alias eventBridgeWrapper: eventBridgeWrapper Component.onCompleted: {
webChannel.registerObject("eventBridge", eventBridge);
QtObject { webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper);
id: eventBridgeWrapper
WebChannel.id: "eventBridgeWrapper"
property var eventBridge
} }
webChannel.registeredObjects: [eventBridgeWrapper]
onEnabledChanged: toolWindow.updateVisiblity() onEnabledChanged: toolWindow.updateVisiblity()
} }
} }
@ -251,12 +247,9 @@ ScrollingWindow {
tab.enabled = true; tab.enabled = true;
tab.originalUrl = properties.source; tab.originalUrl = properties.source;
var eventBridge = properties.eventBridge;
var result = tab.item; var result = tab.item;
result.enabled = true; result.enabled = true;
tabView.tabCount++; tabView.tabCount++;
result.eventBridgeWrapper.eventBridge = eventBridge;
result.url = properties.source; result.url = properties.source;
return result; return result;
} }

View file

@ -6,7 +6,6 @@ import "../controls-uit" as HiFiControls
Item { Item {
property alias url: root.url property alias url: root.url
property alias scriptURL: root.userScriptUrl property alias scriptURL: root.userScriptUrl
property alias eventBridge: eventBridgeWrapper.eventBridge
property alias canGoBack: root.canGoBack; property alias canGoBack: root.canGoBack;
property var goBack: root.goBack; property var goBack: root.goBack;
property alias urlTag: root.urlTag property alias urlTag: root.urlTag
@ -22,12 +21,6 @@ Item {
} }
*/ */
QtObject {
id: eventBridgeWrapper
WebChannel.id: "eventBridgeWrapper"
property var eventBridge;
}
property alias viewProfile: root.profile property alias viewProfile: root.profile
WebEngineView { WebEngineView {
@ -72,9 +65,10 @@ Item {
property string newUrl: "" property string newUrl: ""
webChannel.registeredObjects: [eventBridgeWrapper]
Component.onCompleted: { Component.onCompleted: {
webChannel.registerObject("eventBridge", eventBridge);
webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper);
// Ensure the JS from the web-engine makes it to our logging // Ensure the JS from the web-engine makes it to our logging
root.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) { root.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) {
console.log("Web Entity JS message: " + sourceID + " " + lineNumber + " " + message); console.log("Web Entity JS message: " + sourceID + " " + lineNumber + " " + message);

View file

@ -17,7 +17,6 @@ Item {
property int headerHeight: 70 property int headerHeight: 70
property string url property string url
property string scriptURL property string scriptURL
property alias eventBridge: eventBridgeWrapper.eventBridge
property bool keyboardEnabled: false property bool keyboardEnabled: false
property bool keyboardRaised: false property bool keyboardRaised: false
property bool punctuationMode: false property bool punctuationMode: false
@ -135,12 +134,6 @@ Item {
loadUrl(url); loadUrl(url);
} }
QtObject {
id: eventBridgeWrapper
WebChannel.id: "eventBridgeWrapper"
property var eventBridge;
}
WebEngineView { WebEngineView {
id: webview id: webview
objectName: "webEngineView" objectName: "webEngineView"
@ -182,9 +175,9 @@ Item {
property string newUrl: "" property string newUrl: ""
webChannel.registeredObjects: [eventBridgeWrapper]
Component.onCompleted: { Component.onCompleted: {
webChannel.registerObject("eventBridge", eventBridge);
webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper);
// Ensure the JS from the web-engine makes it to our logging // Ensure the JS from the web-engine makes it to our logging
webview.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) { webview.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) {
console.log("Web Entity JS message: " + sourceID + " " + lineNumber + " " + message); console.log("Web Entity JS message: " + sourceID + " " + lineNumber + " " + message);

View file

@ -6,7 +6,6 @@ import "../controls-uit" as HiFiControls
Item { Item {
property alias url: root.url property alias url: root.url
property alias scriptURL: root.userScriptUrl property alias scriptURL: root.userScriptUrl
property alias eventBridge: eventBridgeWrapper.eventBridge
property alias canGoBack: root.canGoBack; property alias canGoBack: root.canGoBack;
property var goBack: root.goBack; property var goBack: root.goBack;
property alias urlTag: root.urlTag property alias urlTag: root.urlTag
@ -22,12 +21,6 @@ Item {
} }
*/ */
QtObject {
id: eventBridgeWrapper
WebChannel.id: "eventBridgeWrapper"
property var eventBridge;
}
property alias viewProfile: root.profile property alias viewProfile: root.profile
WebEngineView { WebEngineView {
@ -72,9 +65,9 @@ Item {
property string newUrl: "" property string newUrl: ""
webChannel.registeredObjects: [eventBridgeWrapper]
Component.onCompleted: { Component.onCompleted: {
webChannel.registerObject("eventBridge", eventBridge);
webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper);
// Ensure the JS from the web-engine makes it to our logging // Ensure the JS from the web-engine makes it to our logging
root.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) { root.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) {
console.log("Web Entity JS message: " + sourceID + " " + lineNumber + " " + message); console.log("Web Entity JS message: " + sourceID + " " + lineNumber + " " + message);

View file

@ -20,7 +20,6 @@ TabletModalWindow {
id: loginDialogRoot id: loginDialogRoot
objectName: "LoginDialog" objectName: "LoginDialog"
property var eventBridge;
signal sendToScript(var message); signal sendToScript(var message);
property bool isHMD: false property bool isHMD: false
property bool gotoPreviousApp: false; property bool gotoPreviousApp: false;

View file

@ -24,8 +24,6 @@ Window {
resizable: true resizable: true
modality: Qt.ApplicationModal modality: Qt.ApplicationModal
property alias eventBridge: eventBridgeWrapper.eventBridge
Item { Item {
anchors.fill: parent anchors.fill: parent
@ -45,16 +43,6 @@ Window {
bottom: keyboard.top bottom: keyboard.top
} }
property alias eventBridgeWrapper: eventBridgeWrapper
QtObject {
id: eventBridgeWrapper
WebChannel.id: "eventBridgeWrapper"
property var eventBridge;
}
webChannel.registeredObjects: [eventBridgeWrapper]
// Create a global EventBridge object for raiseAndLowerKeyboard. // Create a global EventBridge object for raiseAndLowerKeyboard.
WebEngineScript { WebEngineScript {
id: createGlobalEventBridge id: createGlobalEventBridge
@ -73,6 +61,10 @@ Window {
userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard ] userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard ]
Component.onCompleted: {
webChannel.registerObject("eventBridge", eventBridge);
webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper);
}
} }
Keyboard { Keyboard {

View file

@ -116,9 +116,7 @@ Preference {
Component { Component {
id: tabletAvatarBrowserBuilder; id: tabletAvatarBrowserBuilder;
TabletAvatarBrowser { TabletAvatarBrowser { }
eventBridge: tabletRoot.eventBridge
}
} }
} }

View file

@ -31,7 +31,6 @@ Rectangle {
HifiConstants { id: hifi; } HifiConstants { id: hifi; }
objectName: "AudioWindow" objectName: "AudioWindow"
property var eventBridge;
property string title: "Audio Options" property string title: "Audio Options"
signal sendToScript(var message); signal sendToScript(var message);
@ -161,7 +160,12 @@ Rectangle {
text.text: devicename text.text: devicename
onCheckBoxClicked: { onCheckBoxClicked: {
if (checked) { if (checked) {
AudioDevice.setInputDeviceAsync(devicename) if (devicename.length > 0) {
console.log("Audio.qml about to call AudioDevice.setInputDeviceAsync().devicename:" + devicename);
AudioDevice.setInputDeviceAsync(devicename);
} else {
console.log("Audio.qml attempted to set input device to empty device name.");
}
} }
} }
} }
@ -217,7 +221,13 @@ Rectangle {
text.text: devicename text.text: devicename
onCheckBoxClicked: { onCheckBoxClicked: {
if (checked) { if (checked) {
AudioDevice.setOutputDeviceAsync(devicename) if (devicename.length > 0) {
console.log("Audio.qml about to call AudioDevice.setOutputDeviceAsync().devicename:" + devicename);
AudioDevice.setOutputDeviceAsync(devicename);
} else {
console.log("Audio.qml attempted to set output device to empty device name.");
}
} }
} }
} }

View file

@ -44,7 +44,6 @@ Rectangle {
property var activeTab: "nearbyTab"; property var activeTab: "nearbyTab";
property bool currentlyEditingDisplayName: false property bool currentlyEditingDisplayName: false
property bool punctuationMode: false; property bool punctuationMode: false;
property var eventBridge;
HifiConstants { id: hifi; } HifiConstants { id: hifi; }
@ -1043,7 +1042,6 @@ Rectangle {
} // Keyboard } // Keyboard
HifiControls.TabletWebView { HifiControls.TabletWebView {
eventBridge: pal.eventBridge;
id: userInfoViewer; id: userInfoViewer;
anchors { anchors {
top: parent.top; top: parent.top;

View file

@ -24,7 +24,6 @@ Rectangle {
property string title: "Asset Browser" property string title: "Asset Browser"
property bool keyboardRaised: false property bool keyboardRaised: false
property var eventBridge;
signal sendToScript(var message); signal sendToScript(var message);
property bool isHMD: false property bool isHMD: false

View file

@ -20,7 +20,6 @@ Rectangle {
id: root id: root
objectName: "DCConectionTiming" objectName: "DCConectionTiming"
property var eventBridge;
signal sendToScript(var message); signal sendToScript(var message);
property bool isHMD: false property bool isHMD: false

View file

@ -19,7 +19,6 @@ Rectangle {
id: root id: root
objectName: "DebugWindow" objectName: "DebugWindow"
property var eventBridge;
property var title: "Debug Window" property var title: "Debug Window"
property bool isHMD: false property bool isHMD: false

View file

@ -20,7 +20,6 @@ Rectangle {
id: root id: root
objectName: "EntityStatistics" objectName: "EntityStatistics"
property var eventBridge;
signal sendToScript(var message); signal sendToScript(var message);
property bool isHMD: false property bool isHMD: false

View file

@ -20,7 +20,6 @@ Rectangle {
id: root id: root
objectName: "LODTools" objectName: "LODTools"
property var eventBridge;
signal sendToScript(var message); signal sendToScript(var message);
property bool isHMD: false property bool isHMD: false

View file

@ -23,7 +23,6 @@ Rectangle {
property string title: "Running Scripts" property string title: "Running Scripts"
HifiConstants { id: hifi } HifiConstants { id: hifi }
signal sendToScript(var message); signal sendToScript(var message);
property var eventBridge;
property var scripts: ScriptDiscoveryService; property var scripts: ScriptDiscoveryService;
property var scriptsModel: scripts.scriptsModelFilter property var scriptsModel: scripts.scriptsModelFilter
property var runningScriptsModel: ListModel { } property var runningScriptsModel: ListModel { }

View file

@ -7,14 +7,12 @@ StackView {
objectName: "stack" objectName: "stack"
initialItem: Qt.resolvedUrl('EditTabView.qml') initialItem: Qt.resolvedUrl('EditTabView.qml')
property var eventBridge;
signal sendToScript(var message); signal sendToScript(var message);
HifiConstants { id: hifi } HifiConstants { id: hifi }
function pushSource(path) { function pushSource(path) {
editRoot.push(Qt.resolvedUrl(path)); editRoot.push(Qt.resolvedUrl(path));
editRoot.currentItem.eventBridge = editRoot.eventBridge;
editRoot.currentItem.sendToScript.connect(editRoot.sendToScript); editRoot.currentItem.sendToScript.connect(editRoot.sendToScript);
} }

View file

@ -181,7 +181,6 @@ TabView {
WebView { WebView {
id: entityListToolWebView id: entityListToolWebView
url: "../../../../../scripts/system/html/entityList.html" url: "../../../../../scripts/system/html/entityList.html"
eventBridge: editRoot.eventBridge
anchors.fill: parent anchors.fill: parent
enabled: true enabled: true
} }
@ -196,7 +195,6 @@ TabView {
WebView { WebView {
id: entityPropertiesWebView id: entityPropertiesWebView
url: "../../../../../scripts/system/html/entityProperties.html" url: "../../../../../scripts/system/html/entityProperties.html"
eventBridge: editRoot.eventBridge
anchors.fill: parent anchors.fill: parent
enabled: true enabled: true
} }
@ -211,7 +209,6 @@ TabView {
WebView { WebView {
id: gridControlsWebView id: gridControlsWebView
url: "../../../../../scripts/system/html/gridControls.html" url: "../../../../../scripts/system/html/gridControls.html"
eventBridge: editRoot.eventBridge
anchors.fill: parent anchors.fill: parent
enabled: true enabled: true
} }
@ -226,7 +223,6 @@ TabView {
WebView { WebView {
id: particleExplorerWebView id: particleExplorerWebView
url: "../../../../../scripts/system/particle_explorer/particleExplorer.html" url: "../../../../../scripts/system/particle_explorer/particleExplorer.html"
eventBridge: editRoot.eventBridge
anchors.fill: parent anchors.fill: parent
enabled: true enabled: true
} }

View file

@ -18,7 +18,6 @@ import "../../dialogs"
Rectangle { Rectangle {
id: inputRecorder id: inputRecorder
property var eventBridge;
HifiConstants { id: hifi } HifiConstants { id: hifi }
signal sendToScript(var message); signal sendToScript(var message);
color: hifi.colors.baseGray; color: hifi.colors.baseGray;

View file

@ -20,7 +20,6 @@ Rectangle {
// height: parent.height // height: parent.height
HifiConstants { id: hifi } HifiConstants { id: hifi }
color: hifi.colors.baseGray; color: hifi.colors.baseGray;
property var eventBridge;
signal sendToScript(var message); signal sendToScript(var message);
property bool keyboardEnabled: false property bool keyboardEnabled: false
property bool punctuationMode: false property bool punctuationMode: false

View file

@ -29,7 +29,6 @@ StackView {
initialItem: addressBarDialog initialItem: addressBarDialog
width: parent !== null ? parent.width : undefined width: parent !== null ? parent.width : undefined
height: parent !== null ? parent.height : undefined height: parent !== null ? parent.height : undefined
property var eventBridge;
property int cardWidth: 212; property int cardWidth: 212;
property int cardHeight: 152; property int cardHeight: 152;
property string metaverseBase: addressBarDialog.metaverseServerUrl + "/api/v1/"; property string metaverseBase: addressBarDialog.metaverseServerUrl + "/api/v1/";
@ -80,7 +79,6 @@ StackView {
var card = tabletWebView.createObject(); var card = tabletWebView.createObject();
card.url = addressBarDialog.metaverseServerUrl + targetString; card.url = addressBarDialog.metaverseServerUrl + targetString;
card.parentStackItem = root; card.parentStackItem = root;
card.eventBridge = root.eventBridge;
root.push(card); root.push(card);
return; return;
} }

View file

@ -25,7 +25,6 @@ Item {
property bool keyboardRaised: false property bool keyboardRaised: false
property bool punctuationMode: false property bool punctuationMode: false
property var eventBridge;
signal sendToScript(var message); signal sendToScript(var message);
anchors.fill: parent anchors.fill: parent

View file

@ -19,7 +19,6 @@ StackView {
objectName: "stack" objectName: "stack"
property string title: "Audio Settings" property string title: "Audio Settings"
property var eventBridge;
signal sendToScript(var message); signal sendToScript(var message);
function pushSource(path) { function pushSource(path) {

View file

@ -19,7 +19,6 @@ StackView {
objectName: "stack" objectName: "stack"
property string title: "Avatar Settings" property string title: "Avatar Settings"
property var eventBridge;
signal sendToScript(var message); signal sendToScript(var message);
function pushSource(path) { function pushSource(path) {

View file

@ -19,7 +19,6 @@ StackView {
objectName: "stack" objectName: "stack"
property string title: "General Settings" property string title: "General Settings"
property alias gotoPreviousApp: root.gotoPreviousApp; property alias gotoPreviousApp: root.gotoPreviousApp;
property var eventBridge;
signal sendToScript(var message); signal sendToScript(var message);
function pushSource(path) { function pushSource(path) {

View file

@ -19,7 +19,6 @@ StackView {
objectName: "stack" objectName: "stack"
property string title: "Graphics Settings" property string title: "Graphics Settings"
property var eventBridge;
signal sendToScript(var message); signal sendToScript(var message);
function pushSource(path) { function pushSource(path) {

View file

@ -19,7 +19,6 @@ StackView {
objectName: "stack" objectName: "stack"
property string title: "LOD Settings" property string title: "LOD Settings"
property var eventBridge;
signal sendToScript(var message); signal sendToScript(var message);
function pushSource(path) { function pushSource(path) {

View file

@ -21,7 +21,6 @@ FocusScope {
property var point: Qt.point(50, 50); property var point: Qt.point(50, 50);
TabletMenuStack { id: menuPopperUpper } TabletMenuStack { id: menuPopperUpper }
property string subMenu: "" property string subMenu: ""
property var eventBridge;
signal sendToScript(var message); signal sendToScript(var message);
Rectangle { Rectangle {

View file

@ -49,7 +49,6 @@ Item {
function pushSource(path) { function pushSource(path) {
d.push(Qt.resolvedUrl(path)); d.push(Qt.resolvedUrl(path));
d.currentItem.eventBridge = tabletMenu.eventBridge
d.currentItem.sendToScript.connect(tabletMenu.sendToScript); d.currentItem.sendToScript.connect(tabletMenu.sendToScript);
d.currentItem.focus = true; d.currentItem.focus = true;
d.currentItem.forceActiveFocus(); d.currentItem.forceActiveFocus();

View file

@ -19,7 +19,6 @@ StackView {
objectName: "stack" objectName: "stack"
property var title: "Networking Settings" property var title: "Networking Settings"
property var eventBridge;
signal sendToScript(var message); signal sendToScript(var message);
function pushSource(path) { function pushSource(path) {

View file

@ -8,7 +8,6 @@ Item {
id: tabletRoot id: tabletRoot
objectName: "tabletRoot" objectName: "tabletRoot"
property string username: "Unknown user" property string username: "Unknown user"
property var eventBridge;
property var rootMenu; property var rootMenu;
property var openModal: null; property var openModal: null;
property var openMessage: null; property var openMessage: null;
@ -111,7 +110,6 @@ Item {
function openBrowserWindow(request, profile) { function openBrowserWindow(request, profile) {
var component = Qt.createComponent("../../controls/TabletWebView.qml"); var component = Qt.createComponent("../../controls/TabletWebView.qml");
var newWindow = component.createObject(tabletRoot); var newWindow = component.createObject(tabletRoot);
newWindow.eventBridge = tabletRoot.eventBridge;
newWindow.remove = true; newWindow.remove = true;
newWindow.profile = profile; newWindow.profile = profile;
request.openIn(newWindow.webView); request.openIn(newWindow.webView);
@ -175,7 +173,7 @@ Item {
// Hook up callback for clara.io download from the marketplace. // Hook up callback for clara.io download from the marketplace.
Connections { Connections {
id: eventBridgeConnection id: eventBridgeConnection
target: null target: eventBridge
onWebEventReceived: { onWebEventReceived: {
if (message.slice(0, 17) === "CLARA.IO DOWNLOAD") { if (message.slice(0, 17) === "CLARA.IO DOWNLOAD") {
ApplicationInterface.addAssetToWorldFromURL(message.slice(18)); ApplicationInterface.addAssetToWorldFromURL(message.slice(18));
@ -184,10 +182,6 @@ Item {
} }
onLoaded: { onLoaded: {
if (loader.item.hasOwnProperty("eventBridge")) {
loader.item.eventBridge = eventBridge;
eventBridgeConnection.target = eventBridge
}
if (loader.item.hasOwnProperty("sendToScript")) { if (loader.item.hasOwnProperty("sendToScript")) {
loader.item.sendToScript.connect(tabletRoot.sendToScript); loader.item.sendToScript.connect(tabletRoot.sendToScript);
} }

View file

@ -18,7 +18,6 @@ Windows.ScrollingWindow {
id: tabletRoot id: tabletRoot
objectName: "tabletRoot" objectName: "tabletRoot"
property string username: "Unknown user" property string username: "Unknown user"
property var eventBridge;
property var rootMenu; property var rootMenu;
property string subMenu: "" property string subMenu: ""
@ -93,7 +92,7 @@ Windows.ScrollingWindow {
// Hook up callback for clara.io download from the marketplace. // Hook up callback for clara.io download from the marketplace.
Connections { Connections {
id: eventBridgeConnection id: eventBridgeConnection
target: null target: eventBridge
onWebEventReceived: { onWebEventReceived: {
if (message.slice(0, 17) === "CLARA.IO DOWNLOAD") { if (message.slice(0, 17) === "CLARA.IO DOWNLOAD") {
ApplicationInterface.addAssetToWorldFromURL(message.slice(18)); ApplicationInterface.addAssetToWorldFromURL(message.slice(18));
@ -102,10 +101,6 @@ Windows.ScrollingWindow {
} }
onLoaded: { onLoaded: {
if (loader.item.hasOwnProperty("eventBridge")) {
loader.item.eventBridge = eventBridge;
eventBridgeConnection.target = eventBridge
}
if (loader.item.hasOwnProperty("sendToScript")) { if (loader.item.hasOwnProperty("sendToScript")) {
loader.item.sendToScript.connect(tabletRoot.sendToScript); loader.item.sendToScript.connect(tabletRoot.sendToScript);
} }

View file

@ -27,8 +27,6 @@ Item {
property bool keyboardRaised: false property bool keyboardRaised: false
property bool punctuationMode: false property bool punctuationMode: false
property alias eventBridge: eventBridgeWrapper.eventBridge
anchors.fill: parent anchors.fill: parent
BaseWebView { BaseWebView {
@ -43,14 +41,6 @@ Item {
bottom: footer.top bottom: footer.top
} }
QtObject {
id: eventBridgeWrapper
WebChannel.id: "eventBridgeWrapper"
property var eventBridge;
}
webChannel.registeredObjects: [eventBridgeWrapper]
// Create a global EventBridge object for raiseAndLowerKeyboard. // Create a global EventBridge object for raiseAndLowerKeyboard.
WebEngineScript { WebEngineScript {
id: createGlobalEventBridge id: createGlobalEventBridge
@ -68,6 +58,11 @@ Item {
} }
userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard ] userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard ]
Component.onCompleted: {
webChannel.registerObject("eventBridge", eventBridge);
webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper);
}
} }
Rectangle { Rectangle {

View file

@ -114,6 +114,7 @@
#include <render/RenderFetchCullSortTask.h> #include <render/RenderFetchCullSortTask.h>
#include <RenderDeferredTask.h> #include <RenderDeferredTask.h>
#include <RenderForwardTask.h> #include <RenderForwardTask.h>
#include <RenderViewTask.h>
#include <ResourceCache.h> #include <ResourceCache.h>
#include <ResourceRequest.h> #include <ResourceRequest.h>
#include <SandboxUtils.h> #include <SandboxUtils.h>
@ -1866,15 +1867,9 @@ void Application::initializeGL() {
// Set up the render engine // Set up the render engine
render::CullFunctor cullFunctor = LODManager::shouldRender; render::CullFunctor cullFunctor = LODManager::shouldRender;
_renderEngine->addJob<RenderShadowTask>("RenderShadowTask", cullFunctor);
const auto items = _renderEngine->addJob<RenderFetchCullSortTask>("FetchCullSort", cullFunctor);
assert(items.canCast<RenderFetchCullSortTask::Output>());
static const QString RENDER_FORWARD = "HIFI_RENDER_FORWARD"; static const QString RENDER_FORWARD = "HIFI_RENDER_FORWARD";
if (QProcessEnvironment::systemEnvironment().contains(RENDER_FORWARD)) { bool isDeferred = !QProcessEnvironment::systemEnvironment().contains(RENDER_FORWARD);
_renderEngine->addJob<RenderForwardTask>("Forward", items); _renderEngine->addJob<RenderViewTask>("RenderMainView", cullFunctor, isDeferred);
} else {
_renderEngine->addJob<RenderDeferredTask>("RenderDeferredTask", items);
}
_renderEngine->load(); _renderEngine->load();
_renderEngine->registerScene(_main3DScene); _renderEngine->registerScene(_main3DScene);
@ -4126,10 +4121,10 @@ void Application::updateMyAvatarLookAtPosition() {
} }
} else { } else {
// I am not looking at anyone else, so just look forward // I am not looking at anyone else, so just look forward
if (isHMD) { auto headPose = myAvatar->getHeadControllerPoseInSensorFrame();
glm::mat4 worldHeadMat = myAvatar->getSensorToWorldMatrix() * if (headPose.isValid()) {
myAvatar->getHeadControllerPoseInSensorFrame().getMatrix(); glm::mat4 worldHeadMat = myAvatar->getSensorToWorldMatrix() * headPose.getMatrix();
lookAtSpot = transformPoint(worldHeadMat, glm::vec3(0.0f, 0.0f, -TREE_SCALE)); lookAtSpot = transformPoint(worldHeadMat, glm::vec3(0.0f, 0.0f, TREE_SCALE));
} else { } else {
lookAtSpot = myAvatar->getHead()->getEyePosition() + lookAtSpot = myAvatar->getHead()->getEyePosition() +
(myAvatar->getHead()->getFinalOrientationInWorldFrame() * glm::vec3(0.0f, 0.0f, -TREE_SCALE)); (myAvatar->getHead()->getFinalOrientationInWorldFrame() * glm::vec3(0.0f, 0.0f, -TREE_SCALE));
@ -5076,7 +5071,7 @@ namespace render {
template <> const ItemKey payloadGetKey(const WorldBoxRenderData::Pointer& stuff) { return ItemKey::Builder::opaqueShape(); } template <> const ItemKey payloadGetKey(const WorldBoxRenderData::Pointer& stuff) { return ItemKey::Builder::opaqueShape(); }
template <> const Item::Bound payloadGetBound(const WorldBoxRenderData::Pointer& stuff) { return Item::Bound(); } template <> const Item::Bound payloadGetBound(const WorldBoxRenderData::Pointer& stuff) { return Item::Bound(); }
template <> void payloadRender(const WorldBoxRenderData::Pointer& stuff, RenderArgs* args) { template <> void payloadRender(const WorldBoxRenderData::Pointer& stuff, RenderArgs* args) {
if (args->_renderMode != RenderArgs::MIRROR_RENDER_MODE && Menu::getInstance()->isOptionChecked(MenuOption::WorldAxes)) { if (Menu::getInstance()->isOptionChecked(MenuOption::WorldAxes)) {
PerformanceTimer perfTimer("worldBox"); PerformanceTimer perfTimer("worldBox");
auto& batch = *args->_batch; auto& batch = *args->_batch;

View file

@ -21,15 +21,35 @@
#include "MainWindow.h" #include "MainWindow.h"
#include "Menu.h" #include "Menu.h"
#include "AvatarBookmarks.h" #include "AvatarBookmarks.h"
#include "InterfaceLogging.h"
#include <QtQuick/QQuickWindow> #include <QtQuick/QQuickWindow>
AvatarBookmarks::AvatarBookmarks() { AvatarBookmarks::AvatarBookmarks() {
_bookmarksFilename = QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/" + AVATARBOOKMARKS_FILENAME; _bookmarksFilename = PathUtils::getAppDataPath() + "/" + AVATARBOOKMARKS_FILENAME;
readFromFile(); readFromFile();
} }
void AvatarBookmarks::readFromFile() {
// migrate old avatarbookmarks.json, used to be in 'local' folder on windows
QString oldConfigPath = QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/" + AVATARBOOKMARKS_FILENAME;
QFile oldConfig(oldConfigPath);
// I imagine that in a year from now, this code for migrating (as well as the two lines above)
// may be removed since all bookmarks should have been migrated by then
// - Robbie Uvanni (6.8.2017)
if (oldConfig.exists()) {
if (QDir().rename(oldConfigPath, _bookmarksFilename)) {
qCDebug(interfaceapp) << "Successfully migrated" << AVATARBOOKMARKS_FILENAME;
} else {
qCDebug(interfaceapp) << "Failed to migrate" << AVATARBOOKMARKS_FILENAME;
}
}
Bookmarks::readFromFile();
}
void AvatarBookmarks::setupMenus(Menu* menubar, MenuWrapper* menu) { void AvatarBookmarks::setupMenus(Menu* menubar, MenuWrapper* menu) {
// Add menus/actions // Add menus/actions
auto bookmarkAction = menubar->addActionToQMenuAndActionHash(menu, MenuOption::BookmarkAvatar); auto bookmarkAction = menubar->addActionToQMenuAndActionHash(menu, MenuOption::BookmarkAvatar);

View file

@ -29,6 +29,7 @@ public slots:
protected: protected:
void addBookmarkToMenu(Menu* menubar, const QString& name, const QString& address) override; void addBookmarkToMenu(Menu* menubar, const QString& name, const QString& address) override;
void readFromFile();
private: private:
const QString AVATARBOOKMARKS_FILENAME = "avatarbookmarks.json"; const QString AVATARBOOKMARKS_FILENAME = "avatarbookmarks.json";

View file

@ -73,7 +73,7 @@ CrashHandler::Action CrashHandler::promptUserForAction(bool showCrashMessage) {
layout->addWidget(label); layout->addWidget(label);
QRadioButton* option1 = new QRadioButton("Reset all my settings"); QRadioButton* option1 = new QRadioButton("Reset all my settings");
QRadioButton* option2 = new QRadioButton("Reset my settings but retain avatar info."); QRadioButton* option2 = new QRadioButton("Reset my settings but keep essential info");
QRadioButton* option3 = new QRadioButton("Continue with my current settings"); QRadioButton* option3 = new QRadioButton("Continue with my current settings");
option3->setChecked(true); option3->setChecked(true);
layout->addWidget(option1); layout->addWidget(option1);
@ -95,7 +95,7 @@ CrashHandler::Action CrashHandler::promptUserForAction(bool showCrashMessage) {
return CrashHandler::DELETE_INTERFACE_INI; return CrashHandler::DELETE_INTERFACE_INI;
} }
if (option2->isChecked()) { if (option2->isChecked()) {
return CrashHandler::RETAIN_AVATAR_INFO; return CrashHandler::RETAIN_IMPORTANT_INFO;
} }
} }
@ -104,7 +104,7 @@ CrashHandler::Action CrashHandler::promptUserForAction(bool showCrashMessage) {
} }
void CrashHandler::handleCrash(CrashHandler::Action action) { void CrashHandler::handleCrash(CrashHandler::Action action) {
if (action != CrashHandler::DELETE_INTERFACE_INI && action != CrashHandler::RETAIN_AVATAR_INFO) { if (action != CrashHandler::DELETE_INTERFACE_INI && action != CrashHandler::RETAIN_IMPORTANT_INFO) {
// CrashHandler::DO_NOTHING or unexpected value // CrashHandler::DO_NOTHING or unexpected value
return; return;
} }
@ -116,12 +116,15 @@ void CrashHandler::handleCrash(CrashHandler::Action action) {
const QString DISPLAY_NAME_KEY = "displayName"; const QString DISPLAY_NAME_KEY = "displayName";
const QString FULL_AVATAR_URL_KEY = "fullAvatarURL"; const QString FULL_AVATAR_URL_KEY = "fullAvatarURL";
const QString FULL_AVATAR_MODEL_NAME_KEY = "fullAvatarModelName"; const QString FULL_AVATAR_MODEL_NAME_KEY = "fullAvatarModelName";
const QString TUTORIAL_COMPLETE_FLAG_KEY = "tutorialComplete";
QString displayName; QString displayName;
QUrl fullAvatarURL; QUrl fullAvatarURL;
QString fullAvatarModelName; QString fullAvatarModelName;
QUrl address; QUrl address;
bool tutorialComplete = false;
if (action == CrashHandler::RETAIN_AVATAR_INFO) { if (action == CrashHandler::RETAIN_IMPORTANT_INFO) {
// Read avatar info // Read avatar info
// Location and orientation // Location and orientation
@ -135,6 +138,9 @@ void CrashHandler::handleCrash(CrashHandler::Action action) {
fullAvatarURL = settings.value(FULL_AVATAR_URL_KEY).toUrl(); fullAvatarURL = settings.value(FULL_AVATAR_URL_KEY).toUrl();
fullAvatarModelName = settings.value(FULL_AVATAR_MODEL_NAME_KEY).toString(); fullAvatarModelName = settings.value(FULL_AVATAR_MODEL_NAME_KEY).toString();
settings.endGroup(); settings.endGroup();
// Tutorial complete
tutorialComplete = settings.value(TUTORIAL_COMPLETE_FLAG_KEY).toBool();
} }
// Delete Interface.ini // Delete Interface.ini
@ -143,7 +149,7 @@ void CrashHandler::handleCrash(CrashHandler::Action action) {
settingsFile.remove(); settingsFile.remove();
} }
if (action == CrashHandler::RETAIN_AVATAR_INFO) { if (action == CrashHandler::RETAIN_IMPORTANT_INFO) {
// Write avatar info // Write avatar info
// Location and orientation // Location and orientation
@ -157,6 +163,9 @@ void CrashHandler::handleCrash(CrashHandler::Action action) {
settings.setValue(FULL_AVATAR_URL_KEY, fullAvatarURL); settings.setValue(FULL_AVATAR_URL_KEY, fullAvatarURL);
settings.setValue(FULL_AVATAR_MODEL_NAME_KEY, fullAvatarModelName); settings.setValue(FULL_AVATAR_MODEL_NAME_KEY, fullAvatarModelName);
settings.endGroup(); settings.endGroup();
// Tutorial complete
settings.setValue(TUTORIAL_COMPLETE_FLAG_KEY, tutorialComplete);
} }
} }

View file

@ -22,7 +22,7 @@ public:
private: private:
enum Action { enum Action {
DELETE_INTERFACE_INI, DELETE_INTERFACE_INI,
RETAIN_AVATAR_INFO, RETAIN_IMPORTANT_INFO,
DO_NOTHING DO_NOTHING
}; };

View file

@ -794,6 +794,77 @@ controller::Pose MyAvatar::getRightHandTipPose() const {
return pose; return pose;
} }
glm::vec3 MyAvatar::worldToJointPoint(const glm::vec3& position, const int jointIndex) const {
glm::vec3 jointPos = getPosition();//default value if no or invalid joint specified
glm::quat jointRot = getRotation();//default value if no or invalid joint specified
if (jointIndex != -1) {
if (_skeletonModel->getJointPositionInWorldFrame(jointIndex, jointPos)) {
_skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRot);
} else {
qWarning() << "Invalid joint index specified: " << jointIndex;
}
}
glm::vec3 modelOffset = position - jointPos;
glm::vec3 jointSpacePosition = glm::inverse(jointRot) * modelOffset;
return jointSpacePosition;
}
glm::vec3 MyAvatar::worldToJointDirection(const glm::vec3& worldDir, const int jointIndex) const {
glm::quat jointRot = getRotation();//default value if no or invalid joint specified
if ((jointIndex != -1) && (!_skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRot))) {
qWarning() << "Invalid joint index specified: " << jointIndex;
}
glm::vec3 jointSpaceDir = glm::inverse(jointRot) * worldDir;
return jointSpaceDir;
}
glm::quat MyAvatar::worldToJointRotation(const glm::quat& worldRot, const int jointIndex) const {
glm::quat jointRot = getRotation();//default value if no or invalid joint specified
if ((jointIndex != -1) && (!_skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRot))) {
qWarning() << "Invalid joint index specified: " << jointIndex;
}
glm::quat jointSpaceRot = glm::inverse(jointRot) * worldRot;
return jointSpaceRot;
}
glm::vec3 MyAvatar::jointToWorldPoint(const glm::vec3& jointSpacePos, const int jointIndex) const {
glm::vec3 jointPos = getPosition();//default value if no or invalid joint specified
glm::quat jointRot = getRotation();//default value if no or invalid joint specified
if (jointIndex != -1) {
if (_skeletonModel->getJointPositionInWorldFrame(jointIndex, jointPos)) {
_skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRot);
} else {
qWarning() << "Invalid joint index specified: " << jointIndex;
}
}
glm::vec3 worldOffset = jointRot * jointSpacePos;
glm::vec3 worldPos = jointPos + worldOffset;
return worldPos;
}
glm::vec3 MyAvatar::jointToWorldDirection(const glm::vec3& jointSpaceDir, const int jointIndex) const {
glm::quat jointRot = getRotation();//default value if no or invalid joint specified
if ((jointIndex != -1) && (!_skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRot))) {
qWarning() << "Invalid joint index specified: " << jointIndex;
}
glm::vec3 worldDir = jointRot * jointSpaceDir;
return worldDir;
}
glm::quat MyAvatar::jointToWorldRotation(const glm::quat& jointSpaceRot, const int jointIndex) const {
glm::quat jointRot = getRotation();//default value if no or invalid joint specified
if ((jointIndex != -1) && (!_skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRot))) {
qWarning() << "Invalid joint index specified: " << jointIndex;
}
glm::quat worldRot = jointRot * jointSpaceRot;
return worldRot;
}
// virtual // virtual
void MyAvatar::render(RenderArgs* renderArgs) { void MyAvatar::render(RenderArgs* renderArgs) {
// don't render if we've been asked to disable local rendering // don't render if we've been asked to disable local rendering
@ -2664,8 +2735,8 @@ bool MyAvatar::FollowHelper::shouldActivateVertical(const MyAvatar& myAvatar, co
return (offset.y > CYLINDER_TOP) || (offset.y < CYLINDER_BOTTOM); return (offset.y > CYLINDER_TOP) || (offset.y < CYLINDER_BOTTOM);
} }
void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix, bool hasDriveInput) { void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix,
_desiredBodyMatrix = desiredBodyMatrix; const glm::mat4& currentBodyMatrix, bool hasDriveInput) {
if (myAvatar.getHMDLeanRecenterEnabled()) { if (myAvatar.getHMDLeanRecenterEnabled()) {
if (!isActive(Rotation) && shouldActivateRotation(myAvatar, desiredBodyMatrix, currentBodyMatrix)) { if (!isActive(Rotation) && shouldActivateRotation(myAvatar, desiredBodyMatrix, currentBodyMatrix)) {
@ -2679,7 +2750,7 @@ void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat
} }
} }
glm::mat4 desiredWorldMatrix = myAvatar.getSensorToWorldMatrix() * _desiredBodyMatrix; glm::mat4 desiredWorldMatrix = myAvatar.getSensorToWorldMatrix() * desiredBodyMatrix;
glm::mat4 currentWorldMatrix = myAvatar.getSensorToWorldMatrix() * currentBodyMatrix; glm::mat4 currentWorldMatrix = myAvatar.getSensorToWorldMatrix() * currentBodyMatrix;
AnimPose followWorldPose(currentWorldMatrix); AnimPose followWorldPose(currentWorldMatrix);

View file

@ -378,6 +378,15 @@ public:
Q_INVOKABLE controller::Pose getLeftHandTipPose() const; Q_INVOKABLE controller::Pose getLeftHandTipPose() const;
Q_INVOKABLE controller::Pose getRightHandTipPose() const; Q_INVOKABLE controller::Pose getRightHandTipPose() const;
// world-space to avatar-space rigconversion functions
Q_INVOKABLE glm::vec3 worldToJointPoint(const glm::vec3& position, const int jointIndex = -1) const;
Q_INVOKABLE glm::vec3 worldToJointDirection(const glm::vec3& direction, const int jointIndex = -1) const;
Q_INVOKABLE glm::quat worldToJointRotation(const glm::quat& rotation, const int jointIndex = -1) const;
Q_INVOKABLE glm::vec3 jointToWorldPoint(const glm::vec3& position, const int jointIndex = -1) const;
Q_INVOKABLE glm::vec3 jointToWorldDirection(const glm::vec3& direction, const int jointIndex = -1) const;
Q_INVOKABLE glm::quat jointToWorldRotation(const glm::quat& rotation, const int jointIndex = -1) const;
AvatarWeakPointer getLookAtTargetAvatar() const { return _lookAtTargetAvatar; } AvatarWeakPointer getLookAtTargetAvatar() const { return _lookAtTargetAvatar; }
void updateLookAtTargetAvatar(); void updateLookAtTargetAvatar();
void clearLookAtTargetAvatar(); void clearLookAtTargetAvatar();
@ -702,7 +711,6 @@ private:
Vertical, Vertical,
NumFollowTypes NumFollowTypes
}; };
glm::mat4 _desiredBodyMatrix;
float _timeRemaining[NumFollowTypes]; float _timeRemaining[NumFollowTypes];
void deactivate(); void deactivate();

View file

@ -9,7 +9,9 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
// //
#include "AudioClient.h" #include <AudioClient.h>
#include <AudioClientLogging.h>
#include "AudioDeviceScriptingInterface.h" #include "AudioDeviceScriptingInterface.h"
#include "SettingsScriptingInterface.h" #include "SettingsScriptingInterface.h"
@ -44,17 +46,23 @@ AudioDeviceScriptingInterface::AudioDeviceScriptingInterface(): QAbstractListMod
onDeviceChanged(); onDeviceChanged();
//set up previously saved device //set up previously saved device
SettingsScriptingInterface* settings = SettingsScriptingInterface::getInstance(); SettingsScriptingInterface* settings = SettingsScriptingInterface::getInstance();
const QString inDevice = settings->getValue("audio_input_device").toString(); const QString inDevice = settings->getValue("audio_input_device", _currentInputDevice).toString();
if (inDevice != _currentInputDevice) { if (inDevice != _currentInputDevice) {
qCDebug(audioclient) << __FUNCTION__ << "about to call setInputDeviceAsync() device: [" << inDevice << "] _currentInputDevice:" << _currentInputDevice;
setInputDeviceAsync(inDevice); setInputDeviceAsync(inDevice);
} }
const QString outDevice = settings->getValue("audio_output_device").toString();
// If the audio_output_device setting is not available, use the _currentOutputDevice
auto outDevice = settings->getValue("audio_output_device", _currentOutputDevice).toString();
if (outDevice != _currentOutputDevice) { if (outDevice != _currentOutputDevice) {
qCDebug(audioclient) << __FUNCTION__ << "about to call setOutputDeviceAsync() outDevice: [" << outDevice << "] _currentOutputDevice:" << _currentOutputDevice;
setOutputDeviceAsync(outDevice); setOutputDeviceAsync(outDevice);
} }
} }
bool AudioDeviceScriptingInterface::setInputDevice(const QString& deviceName) { bool AudioDeviceScriptingInterface::setInputDevice(const QString& deviceName) {
qCDebug(audioclient) << __FUNCTION__ << "deviceName:" << deviceName;
bool result; bool result;
QMetaObject::invokeMethod(DependencyManager::get<AudioClient>().data(), "switchInputToAudioDevice", QMetaObject::invokeMethod(DependencyManager::get<AudioClient>().data(), "switchInputToAudioDevice",
Qt::BlockingQueuedConnection, Qt::BlockingQueuedConnection,
@ -64,6 +72,9 @@ bool AudioDeviceScriptingInterface::setInputDevice(const QString& deviceName) {
} }
bool AudioDeviceScriptingInterface::setOutputDevice(const QString& deviceName) { bool AudioDeviceScriptingInterface::setOutputDevice(const QString& deviceName) {
qCDebug(audioclient) << __FUNCTION__ << "deviceName:" << deviceName;
bool result; bool result;
QMetaObject::invokeMethod(DependencyManager::get<AudioClient>().data(), "switchOutputToAudioDevice", QMetaObject::invokeMethod(DependencyManager::get<AudioClient>().data(), "switchOutputToAudioDevice",
Qt::BlockingQueuedConnection, Qt::BlockingQueuedConnection,
@ -86,8 +97,10 @@ bool AudioDeviceScriptingInterface::setDeviceFromMenu(const QString& deviceMenuN
for (ScriptingAudioDeviceInfo di: _devices) { for (ScriptingAudioDeviceInfo di: _devices) {
if (mode == di.mode && deviceMenuName.contains(di.name)) { if (mode == di.mode && deviceMenuName.contains(di.name)) {
if (mode == QAudio::AudioOutput) { if (mode == QAudio::AudioOutput) {
qCDebug(audioclient) << __FUNCTION__ << "about to call setOutputDeviceAsync() device: [" << di.name << "]";
setOutputDeviceAsync(di.name); setOutputDeviceAsync(di.name);
} else { } else {
qCDebug(audioclient) << __FUNCTION__ << "about to call setInputDeviceAsync() device: [" << di.name << "]";
setInputDeviceAsync(di.name); setInputDeviceAsync(di.name);
} }
return true; return true;
@ -98,12 +111,26 @@ bool AudioDeviceScriptingInterface::setDeviceFromMenu(const QString& deviceMenuN
} }
void AudioDeviceScriptingInterface::setInputDeviceAsync(const QString& deviceName) { void AudioDeviceScriptingInterface::setInputDeviceAsync(const QString& deviceName) {
qCDebug(audioclient) << __FUNCTION__ << "deviceName:" << deviceName;
if (deviceName.isEmpty()) {
qCDebug(audioclient) << __FUNCTION__ << "attempt to set empty deviceName:" << deviceName << "... ignoring!";
return;
}
QMetaObject::invokeMethod(DependencyManager::get<AudioClient>().data(), "switchInputToAudioDevice", QMetaObject::invokeMethod(DependencyManager::get<AudioClient>().data(), "switchInputToAudioDevice",
Qt::QueuedConnection, Qt::QueuedConnection,
Q_ARG(const QString&, deviceName)); Q_ARG(const QString&, deviceName));
} }
void AudioDeviceScriptingInterface::setOutputDeviceAsync(const QString& deviceName) { void AudioDeviceScriptingInterface::setOutputDeviceAsync(const QString& deviceName) {
qCDebug(audioclient) << __FUNCTION__ << "deviceName:" << deviceName;
if (deviceName.isEmpty()) {
qCDebug(audioclient) << __FUNCTION__ << "attempt to set empty deviceName:" << deviceName << "... ignoring!";
return;
}
QMetaObject::invokeMethod(DependencyManager::get<AudioClient>().data(), "switchOutputToAudioDevice", QMetaObject::invokeMethod(DependencyManager::get<AudioClient>().data(), "switchOutputToAudioDevice",
Qt::QueuedConnection, Qt::QueuedConnection,
Q_ARG(const QString&, deviceName)); Q_ARG(const QString&, deviceName));
@ -241,8 +268,11 @@ void AudioDeviceScriptingInterface::onCurrentInputDeviceChanged(const QString& n
void AudioDeviceScriptingInterface::onCurrentOutputDeviceChanged(const QString& name) void AudioDeviceScriptingInterface::onCurrentOutputDeviceChanged(const QString& name)
{ {
currentDeviceUpdate(name, QAudio::AudioOutput); currentDeviceUpdate(name, QAudio::AudioOutput);
// FIXME - this is kinda janky to set the setting on the result of a signal
//we got a signal that device changed. Save it now //we got a signal that device changed. Save it now
SettingsScriptingInterface* settings = SettingsScriptingInterface::getInstance(); SettingsScriptingInterface* settings = SettingsScriptingInterface::getInstance();
qCDebug(audioclient) << __FUNCTION__ << "about to call settings->setValue('audio_output_device', name); name:" << name;
settings->setValue("audio_output_device", name); settings->setValue("audio_output_device", name);
emit currentOutputDeviceChanged(name); emit currentOutputDeviceChanged(name);
} }

View file

@ -166,7 +166,10 @@ AudioClient::AudioClient() :
connect(&_receivedAudioStream, &MixedProcessedAudioStream::processSamples, connect(&_receivedAudioStream, &MixedProcessedAudioStream::processSamples,
this, &AudioClient::processReceivedSamples, Qt::DirectConnection); this, &AudioClient::processReceivedSamples, Qt::DirectConnection);
connect(this, &AudioClient::changeDevice, this, [=](const QAudioDeviceInfo& outputDeviceInfo) { switchOutputToAudioDevice(outputDeviceInfo); }); connect(this, &AudioClient::changeDevice, this, [=](const QAudioDeviceInfo& outputDeviceInfo) {
qCDebug(audioclient) << "got AudioClient::changeDevice signal, about to call switchOutputToAudioDevice() outputDeviceInfo: [" << outputDeviceInfo.deviceName() << "]";
switchOutputToAudioDevice(outputDeviceInfo);
});
connect(&_receivedAudioStream, &InboundAudioStream::mismatchedAudioCodec, this, &AudioClient::handleMismatchAudioFormat); connect(&_receivedAudioStream, &InboundAudioStream::mismatchedAudioCodec, this, &AudioClient::handleMismatchAudioFormat);
@ -379,7 +382,8 @@ QAudioDeviceInfo defaultAudioDeviceForMode(QAudio::Mode mode) {
CoUninitialize(); CoUninitialize();
} }
qCDebug(audioclient) << "[" << deviceName << "] [" << getNamedAudioDeviceForMode(mode, deviceName).deviceName() << "]"; qCDebug(audioclient) << "defaultAudioDeviceForMode mode: " << (mode == QAudio::AudioOutput ? "Output" : "Input")
<< " [" << deviceName << "] [" << getNamedAudioDeviceForMode(mode, deviceName).deviceName() << "]";
return getNamedAudioDeviceForMode(mode, deviceName); return getNamedAudioDeviceForMode(mode, deviceName);
#endif #endif
@ -555,8 +559,12 @@ void AudioClient::start() {
} }
void AudioClient::stop() { void AudioClient::stop() {
// "switch" to invalid devices in order to shut down the state // "switch" to invalid devices in order to shut down the state
qCDebug(audioclient) << "AudioClient::stop(), about to call switchInputToAudioDevice(null)";
switchInputToAudioDevice(QAudioDeviceInfo()); switchInputToAudioDevice(QAudioDeviceInfo());
qCDebug(audioclient) << "AudioClient::stop(), about to call switchOutputToAudioDevice(null)";
switchOutputToAudioDevice(QAudioDeviceInfo()); switchOutputToAudioDevice(QAudioDeviceInfo());
} }
@ -748,12 +756,12 @@ QVector<QString> AudioClient::getDeviceNames(QAudio::Mode mode) {
} }
bool AudioClient::switchInputToAudioDevice(const QString& inputDeviceName) { bool AudioClient::switchInputToAudioDevice(const QString& inputDeviceName) {
qCDebug(audioclient) << "[" << inputDeviceName << "] [" << getNamedAudioDeviceForMode(QAudio::AudioInput, inputDeviceName).deviceName() << "]"; qCDebug(audioclient) << "switchInputToAudioDevice [" << inputDeviceName << "] [" << getNamedAudioDeviceForMode(QAudio::AudioInput, inputDeviceName).deviceName() << "]";
return switchInputToAudioDevice(getNamedAudioDeviceForMode(QAudio::AudioInput, inputDeviceName)); return switchInputToAudioDevice(getNamedAudioDeviceForMode(QAudio::AudioInput, inputDeviceName));
} }
bool AudioClient::switchOutputToAudioDevice(const QString& outputDeviceName) { bool AudioClient::switchOutputToAudioDevice(const QString& outputDeviceName) {
qCDebug(audioclient) << "[" << outputDeviceName << "] [" << getNamedAudioDeviceForMode(QAudio::AudioOutput, outputDeviceName).deviceName() << "]"; qCDebug(audioclient) << "switchOutputToAudioDevice [" << outputDeviceName << "] [" << getNamedAudioDeviceForMode(QAudio::AudioOutput, outputDeviceName).deviceName() << "]";
return switchOutputToAudioDevice(getNamedAudioDeviceForMode(QAudio::AudioOutput, outputDeviceName)); return switchOutputToAudioDevice(getNamedAudioDeviceForMode(QAudio::AudioOutput, outputDeviceName));
} }
@ -1298,6 +1306,7 @@ void AudioClient::setIsStereoInput(bool isStereoInput) {
} }
// change in channel count for desired input format, restart the input device // change in channel count for desired input format, restart the input device
qCDebug(audioclient) << __FUNCTION__ << "about to call switchInputToAudioDevice:" << _inputAudioDeviceName;
switchInputToAudioDevice(_inputAudioDeviceName); switchInputToAudioDevice(_inputAudioDeviceName);
} }
} }
@ -1334,6 +1343,7 @@ void AudioClient::outputFormatChanged() {
} }
bool AudioClient::switchInputToAudioDevice(const QAudioDeviceInfo& inputDeviceInfo) { bool AudioClient::switchInputToAudioDevice(const QAudioDeviceInfo& inputDeviceInfo) {
qCDebug(audioclient) << __FUNCTION__ << "inputDeviceInfo: [" << inputDeviceInfo.deviceName() << "]";
bool supportedFormat = false; bool supportedFormat = false;
// cleanup any previously initialized device // cleanup any previously initialized device
@ -1448,6 +1458,8 @@ void AudioClient::outputNotify() {
} }
bool AudioClient::switchOutputToAudioDevice(const QAudioDeviceInfo& outputDeviceInfo) { bool AudioClient::switchOutputToAudioDevice(const QAudioDeviceInfo& outputDeviceInfo) {
qCDebug(audioclient) << "AudioClient::switchOutputToAudioDevice() outputDeviceInfo: [" << outputDeviceInfo.deviceName() << "]";
bool supportedFormat = false; bool supportedFormat = false;
Lock localAudioLock(_localAudioMutex); Lock localAudioLock(_localAudioMutex);
@ -1582,7 +1594,11 @@ bool AudioClient::switchOutputToAudioDevice(const QAudioDeviceInfo& outputDevice
} }
int AudioClient::setOutputBufferSize(int numFrames, bool persist) { int AudioClient::setOutputBufferSize(int numFrames, bool persist) {
qCDebug(audioclient) << __FUNCTION__ << "numFrames:" << numFrames << "persist:" << persist;
numFrames = std::min(std::max(numFrames, MIN_BUFFER_FRAMES), MAX_BUFFER_FRAMES); numFrames = std::min(std::max(numFrames, MIN_BUFFER_FRAMES), MAX_BUFFER_FRAMES);
qCDebug(audioclient) << __FUNCTION__ << "clamped numFrames:" << numFrames << "_sessionOutputBufferSizeFrames:" << _sessionOutputBufferSizeFrames;
if (numFrames != _sessionOutputBufferSizeFrames) { if (numFrames != _sessionOutputBufferSizeFrames) {
qCInfo(audioclient, "Audio output buffer set to %d frames", numFrames); qCInfo(audioclient, "Audio output buffer set to %d frames", numFrames);
_sessionOutputBufferSizeFrames = numFrames; _sessionOutputBufferSizeFrames = numFrames;
@ -1594,6 +1610,7 @@ int AudioClient::setOutputBufferSize(int numFrames, bool persist) {
// The buffer size can't be adjusted after QAudioOutput::start() has been called, so // The buffer size can't be adjusted after QAudioOutput::start() has been called, so
// recreate the device by switching to the default. // recreate the device by switching to the default.
QAudioDeviceInfo outputDeviceInfo = defaultAudioDeviceForMode(QAudio::AudioOutput); QAudioDeviceInfo outputDeviceInfo = defaultAudioDeviceForMode(QAudio::AudioOutput);
qCDebug(audioclient) << __FUNCTION__ << "about to send changeDevice signal outputDeviceInfo: [" << outputDeviceInfo.deviceName() << "]";
emit changeDevice(outputDeviceInfo); // On correct thread, please, as setOutputBufferSize can be called from main thread. emit changeDevice(outputDeviceInfo); // On correct thread, please, as setOutputBufferSize can be called from main thread.
} }
} }

View file

@ -221,6 +221,12 @@ void HmdDisplayPlugin::internalPresent() {
float shiftLeftBy = getLeftCenterPixel() - (sourceSize.x / 2); float shiftLeftBy = getLeftCenterPixel() - (sourceSize.x / 2);
float newWidth = sourceSize.x - shiftLeftBy; float newWidth = sourceSize.x - shiftLeftBy;
// Experimentally adjusted the region presented in preview to avoid seeing the masked pixels and recenter the center...
static float SCALE_WIDTH = 0.9f;
static float SCALE_OFFSET = 2.0f;
newWidth *= SCALE_WIDTH;
shiftLeftBy *= SCALE_OFFSET;
const unsigned int RATIO_Y = 9; const unsigned int RATIO_Y = 9;
const unsigned int RATIO_X = 16; const unsigned int RATIO_X = 16;
glm::uvec2 originalClippedSize { newWidth, newWidth * RATIO_Y / RATIO_X }; glm::uvec2 originalClippedSize { newWidth, newWidth * RATIO_Y / RATIO_X };

View file

@ -605,7 +605,7 @@ bool RenderableModelEntityItem::findDetailedRayIntersection(const glm::vec3& ori
QString extraInfo; QString extraInfo;
return _model->findRayIntersectionAgainstSubMeshes(origin, direction, distance, return _model->findRayIntersectionAgainstSubMeshes(origin, direction, distance,
face, surfaceNormal, extraInfo, precisionPicking, precisionPicking); face, surfaceNormal, extraInfo, precisionPicking, false);
} }
void RenderableModelEntityItem::getCollisionGeometryResource() { void RenderableModelEntityItem::getCollisionGeometryResource() {

View file

@ -460,17 +460,11 @@ FBXLight extractLight(const FBXNode& object) {
} }
QByteArray fileOnUrl(const QByteArray& filepath, const QString& url) { QByteArray fileOnUrl(const QByteArray& filepath, const QString& url) {
QString path = QFileInfo(url).path(); // in order to match the behaviour when loading models from remote URLs
QByteArray filename = filepath; // we assume that all external textures are right beside the loaded model
QFileInfo checkFile(path + "/" + filepath); // ignoring any relative paths or absolute paths inside of models
// check if the file exists at the RelativeFilename return filepath.mid(filepath.lastIndexOf('/') + 1);
if (!(checkFile.exists() && checkFile.isFile())) {
// if not, assume it is in the fbx directory
filename = filename.mid(filename.lastIndexOf('/') + 1);
}
return filename;
} }
FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QString& url) { FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QString& url) {

View file

@ -277,6 +277,23 @@ QString getEventBridgeJavascript() {
return javaScriptToInject; return javaScriptToInject;
} }
class EventBridgeWrapper : public QObject {
Q_OBJECT
Q_PROPERTY(QObject* eventBridge READ getEventBridge CONSTANT);
public:
EventBridgeWrapper(QObject* eventBridge, QObject* parent = nullptr) : QObject(parent), _eventBridge(eventBridge) {
}
QObject* getEventBridge() {
return _eventBridge;
}
private:
QObject* _eventBridge;
};
QQmlEngine* acquireEngine(QQuickWindow* window) { QQmlEngine* acquireEngine(QQuickWindow* window) {
Q_ASSERT(QThread::currentThread() == qApp->thread()); Q_ASSERT(QThread::currentThread() == qApp->thread());
@ -430,7 +447,6 @@ OffscreenQmlSurface::~OffscreenQmlSurface() {
_canvas->deleteLater(); _canvas->deleteLater();
_rootItem->deleteLater(); _rootItem->deleteLater();
_qmlComponent->deleteLater();
_quickWindow->deleteLater(); _quickWindow->deleteLater();
releaseEngine(); releaseEngine();
} }
@ -473,11 +489,12 @@ void OffscreenQmlSurface::create(QOpenGLContext* shareContext) {
_qmlContext = new QQmlContext(qmlEngine->rootContext()); _qmlContext = new QQmlContext(qmlEngine->rootContext());
_qmlContext->setContextProperty("offscreenWindow", QVariant::fromValue(getWindow())); _qmlContext->setContextProperty("offscreenWindow", QVariant::fromValue(getWindow()));
_qmlContext->setContextProperty("globalEventBridge", this); _qmlContext->setContextProperty("eventBridge", this);
_qmlContext->setContextProperty("webEntity", this); _qmlContext->setContextProperty("webEntity", this);
_qmlComponent = new QQmlComponent(qmlEngine); // FIXME Compatibility mechanism for existing HTML and JS that uses eventBridgeWrapper
// Find a way to flag older scripts using this mechanism and wanr that this is deprecated
_qmlContext->setContextProperty("eventBridgeWrapper", new EventBridgeWrapper(this, _qmlContext));
if (!_canvas->makeCurrent()) { if (!_canvas->makeCurrent()) {
qWarning("Failed to make context current for QML Renderer"); qWarning("Failed to make context current for QML Renderer");
@ -577,71 +594,79 @@ void OffscreenQmlSurface::setBaseUrl(const QUrl& baseUrl) {
_qmlContext->setBaseUrl(baseUrl); _qmlContext->setBaseUrl(baseUrl);
} }
QObject* OffscreenQmlSurface::load(const QUrl& qmlSource, std::function<void(QQmlContext*, QObject*)> f) { QObject* OffscreenQmlSurface::load(const QUrl& qmlSource, bool createNewContext, std::function<void(QQmlContext*, QObject*)> f) {
// Synchronous loading may take a while; restart the deadlock timer // Synchronous loading may take a while; restart the deadlock timer
QMetaObject::invokeMethod(qApp, "updateHeartbeat", Qt::DirectConnection); QMetaObject::invokeMethod(qApp, "updateHeartbeat", Qt::DirectConnection);
if ((qmlSource.isRelative() && !qmlSource.isEmpty()) || qmlSource.scheme() == QLatin1String("file")) { QQmlContext* targetContext = _qmlContext;
_qmlComponent->loadUrl(_qmlContext->resolvedUrl(qmlSource), QQmlComponent::PreferSynchronous); if (_rootItem && createNewContext) {
} else { targetContext = new QQmlContext(targetContext);
_qmlComponent->loadUrl(qmlSource, QQmlComponent::PreferSynchronous);
} }
QUrl finalQmlSource = qmlSource;
if ((qmlSource.isRelative() && !qmlSource.isEmpty()) || qmlSource.scheme() == QLatin1String("file")) {
finalQmlSource = _qmlContext->resolvedUrl(qmlSource);
}
if (_qmlComponent->isLoading()) { auto qmlComponent = new QQmlComponent(_qmlContext->engine(), finalQmlSource, QQmlComponent::PreferSynchronous);
connect(_qmlComponent, &QQmlComponent::statusChanged, this, if (qmlComponent->isLoading()) {
[this, f](QQmlComponent::Status){ connect(qmlComponent, &QQmlComponent::statusChanged, this,
finishQmlLoad(f); [this, qmlComponent, targetContext, f](QQmlComponent::Status) {
}); finishQmlLoad(qmlComponent, targetContext, f);
});
return nullptr; return nullptr;
} }
return finishQmlLoad(f); return finishQmlLoad(qmlComponent, targetContext, f);
}
QObject* OffscreenQmlSurface::loadInNewContext(const QUrl& qmlSource, std::function<void(QQmlContext*, QObject*)> f) {
return load(qmlSource, true, f);
}
QObject* OffscreenQmlSurface::load(const QUrl& qmlSource, std::function<void(QQmlContext*, QObject*)> f) {
return load(qmlSource, false, f);
} }
void OffscreenQmlSurface::clearCache() { void OffscreenQmlSurface::clearCache() {
_qmlContext->engine()->clearComponentCache(); _qmlContext->engine()->clearComponentCache();
} }
QObject* OffscreenQmlSurface::finishQmlLoad(std::function<void(QQmlContext*, QObject*)> f) { QObject* OffscreenQmlSurface::finishQmlLoad(QQmlComponent* qmlComponent, QQmlContext* qmlContext, std::function<void(QQmlContext*, QObject*)> f) {
#if 0 disconnect(qmlComponent, &QQmlComponent::statusChanged, this, 0);
if (!_rootItem) { if (qmlComponent->isError()) {
QQmlComponent component(_qmlContext->engine()); for (const auto& error : qmlComponent->errors()) {
component.setData(R"QML( qCWarning(glLogging) << error.url() << error.line() << error;
import QtQuick 2.0
import QtWebChannel 1.0
Item { Component.onCompleted: globalEventBridge.WebChannel.id = "globalEventBridge"; }
)QML", QUrl());
QObject *helper = component.create(_qmlContext);
qDebug() << "Created helper";
}
#endif
disconnect(_qmlComponent, &QQmlComponent::statusChanged, this, 0);
if (_qmlComponent->isError()) {
QList<QQmlError> errorList = _qmlComponent->errors();
foreach(const QQmlError& error, errorList) {
qWarning() << error.url() << error.line() << error;
} }
qmlComponent->deleteLater();
return nullptr; return nullptr;
} }
QObject* newObject = _qmlComponent->beginCreate(_qmlContext); QObject* newObject = qmlComponent->beginCreate(qmlContext);
if (_qmlComponent->isError()) { if (qmlComponent->isError()) {
QList<QQmlError> errorList = _qmlComponent->errors(); for (const auto& error : qmlComponent->errors()) {
foreach(const QQmlError& error, errorList)
qCWarning(glLogging) << error.url() << error.line() << error; qCWarning(glLogging) << error.url() << error.line() << error;
}
if (!_rootItem) { if (!_rootItem) {
qFatal("Unable to finish loading QML root"); qFatal("Unable to finish loading QML root");
} }
qmlComponent->deleteLater();
return nullptr; return nullptr;
} }
_qmlContext->engine()->setObjectOwnership(this, QQmlEngine::CppOwnership); qmlContext->engine()->setObjectOwnership(this, QQmlEngine::CppOwnership);
newObject->setProperty("eventBridge", QVariant::fromValue(this)); f(qmlContext, newObject);
f(_qmlContext, newObject); QObject* eventBridge = qmlContext->contextProperty("eventBridge").value<QObject*>();
_qmlComponent->completeCreate(); if (qmlContext != _qmlContext && eventBridge && eventBridge != this) {
// FIXME Compatibility mechanism for existing HTML and JS that uses eventBridgeWrapper
// Find a way to flag older scripts using this mechanism and wanr that this is deprecated
qmlContext->setContextProperty("eventBridgeWrapper", new EventBridgeWrapper(eventBridge, qmlContext));
}
qmlComponent->completeCreate();
qmlComponent->deleteLater();
// All quick items should be focusable // All quick items should be focusable

View file

@ -48,6 +48,8 @@ public:
void resize(const QSize& size, bool forceResize = false); void resize(const QSize& size, bool forceResize = false);
QSize size() const; QSize size() const;
Q_INVOKABLE QObject* load(const QUrl& qmlSource, bool createNewContext, std::function<void(QQmlContext*, QObject*)> f = [](QQmlContext*, QObject*) {});
Q_INVOKABLE QObject* loadInNewContext(const QUrl& qmlSource, std::function<void(QQmlContext*, QObject*)> f = [](QQmlContext*, QObject*) {});
Q_INVOKABLE QObject* load(const QUrl& qmlSource, std::function<void(QQmlContext*, QObject*)> f = [](QQmlContext*, QObject*) {}); Q_INVOKABLE QObject* load(const QUrl& qmlSource, std::function<void(QQmlContext*, QObject*)> f = [](QQmlContext*, QObject*) {});
Q_INVOKABLE QObject* load(const QString& qmlSourceFile, std::function<void(QQmlContext*, QObject*)> f = [](QQmlContext*, QObject*) {}) { Q_INVOKABLE QObject* load(const QString& qmlSourceFile, std::function<void(QQmlContext*, QObject*)> f = [](QQmlContext*, QObject*) {}) {
return load(QUrl(qmlSourceFile), f); return load(QUrl(qmlSourceFile), f);
@ -118,7 +120,7 @@ protected:
void setFocusText(bool newFocusText); void setFocusText(bool newFocusText);
private: private:
QObject* finishQmlLoad(std::function<void(QQmlContext*, QObject*)> f); QObject* finishQmlLoad(QQmlComponent* qmlComponent, QQmlContext* qmlContext, std::function<void(QQmlContext*, QObject*)> f);
QPointF mapWindowToUi(const QPointF& sourcePosition, QObject* sourceObject); QPointF mapWindowToUi(const QPointF& sourcePosition, QObject* sourceObject);
void setupFbo(); void setupFbo();
bool allowNewFrame(uint8_t fps); bool allowNewFrame(uint8_t fps);
@ -134,7 +136,6 @@ private:
QQuickWindow* _quickWindow { nullptr }; QQuickWindow* _quickWindow { nullptr };
QMyQuickRenderControl* _renderControl{ nullptr }; QMyQuickRenderControl* _renderControl{ nullptr };
QQmlContext* _qmlContext { nullptr }; QQmlContext* _qmlContext { nullptr };
QQmlComponent* _qmlComponent { nullptr };
QQuickItem* _rootItem { nullptr }; QQuickItem* _rootItem { nullptr };
OffscreenGLCanvas* _canvas { nullptr }; OffscreenGLCanvas* _canvas { nullptr };

View file

@ -63,11 +63,17 @@ void GLBackend::do_clearFramebuffer(const Batch& batch, size_t paramOffset) {
int useScissor = batch._params[paramOffset + 0]._int; int useScissor = batch._params[paramOffset + 0]._int;
GLuint glmask = 0; GLuint glmask = 0;
bool restoreStencilMask = false;
uint8_t cacheStencilMask = 0xFF;
if (masks & Framebuffer::BUFFER_STENCIL) { if (masks & Framebuffer::BUFFER_STENCIL) {
glClearStencil(stencil); glClearStencil(stencil);
glmask |= GL_STENCIL_BUFFER_BIT; glmask |= GL_STENCIL_BUFFER_BIT;
// TODO: we will probably need to also check the write mask of stencil like we do
// for depth buffer, but as would say a famous Fez owner "We'll cross that bridge when we come to it" cacheStencilMask = _pipeline._stateCache.stencilActivation.getWriteMaskFront();
if (cacheStencilMask != 0xFF) {
restoreStencilMask = true;
glStencilMask( 0xFF);
}
} }
bool restoreDepthMask = false; bool restoreDepthMask = false;
@ -121,6 +127,11 @@ void GLBackend::do_clearFramebuffer(const Batch& batch, size_t paramOffset) {
glDisable(GL_SCISSOR_TEST); glDisable(GL_SCISSOR_TEST);
} }
// Restore Stencil write mask
if (restoreStencilMask) {
glStencilMask(cacheStencilMask);
}
// Restore write mask meaning turn back off // Restore write mask meaning turn back off
if (restoreDepthMask) { if (restoreDepthMask) {
glDepthMask(GL_FALSE); glDepthMask(GL_FALSE);

View file

@ -20,6 +20,10 @@
#include <DependencyManager.h> #include <DependencyManager.h>
#include "AddressManager.h" #include "AddressManager.h"
UserActivityLogger::UserActivityLogger() {
_timer.start();
}
UserActivityLogger& UserActivityLogger::getInstance() { UserActivityLogger& UserActivityLogger::getInstance() {
static UserActivityLogger sharedInstance; static UserActivityLogger sharedInstance;
return sharedInstance; return sharedInstance;
@ -43,6 +47,12 @@ void UserActivityLogger::logAction(QString action, QJsonObject details, JSONCall
actionPart.setBody(QByteArray().append(action)); actionPart.setBody(QByteArray().append(action));
multipart->append(actionPart); multipart->append(actionPart);
// Log the local-time that this event was logged
QHttpPart elapsedPart;
elapsedPart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data; name=\"elapsed_ms\"");
elapsedPart.setBody(QString::number(_timer.elapsed()).toLocal8Bit());
multipart->append(elapsedPart);
// If there are action details, add them to the multipart // If there are action details, add them to the multipart
if (!details.isEmpty()) { if (!details.isEmpty()) {
QHttpPart detailsPart; QHttpPart detailsPart;

View file

@ -18,6 +18,7 @@
#include <QString> #include <QString>
#include <QJsonObject> #include <QJsonObject>
#include <QNetworkReply> #include <QNetworkReply>
#include <QElapsedTimer>
#include <SettingHandle.h> #include <SettingHandle.h>
#include "AddressManager.h" #include "AddressManager.h"
@ -51,8 +52,10 @@ private slots:
void requestError(QNetworkReply& errorReply); void requestError(QNetworkReply& errorReply);
private: private:
UserActivityLogger() {}; UserActivityLogger();
Setting::Handle<bool> _disabled { "UserActivityLoggerDisabled", false }; Setting::Handle<bool> _disabled { "UserActivityLoggerDisabled", false };
QElapsedTimer _timer;
}; };
#endif // hifi_UserActivityLogger_h #endif // hifi_UserActivityLogger_h

View file

@ -56,7 +56,7 @@ PacketVersion versionForPacketType(PacketType packetType) {
case PacketType::AvatarData: case PacketType::AvatarData:
case PacketType::BulkAvatarData: case PacketType::BulkAvatarData:
case PacketType::KillAvatar: case PacketType::KillAvatar:
return static_cast<PacketVersion>(AvatarMixerPacketVersion::AvatarIdentitySequenceId); return static_cast<PacketVersion>(AvatarMixerPacketVersion::MannequinDefaultAvatar);
case PacketType::MessagesData: case PacketType::MessagesData:
return static_cast<PacketVersion>(MessageDataVersion::TextOrBinaryData); return static_cast<PacketVersion>(MessageDataVersion::TextOrBinaryData);
case PacketType::ICEServerHeartbeat: case PacketType::ICEServerHeartbeat:

View file

@ -236,7 +236,8 @@ enum class AvatarMixerPacketVersion : PacketVersion {
AvatarAsChildFixes, AvatarAsChildFixes,
StickAndBallDefaultAvatar, StickAndBallDefaultAvatar,
IdentityPacketsIncludeUpdateTime, IdentityPacketsIncludeUpdateTime,
AvatarIdentitySequenceId AvatarIdentitySequenceId,
MannequinDefaultAvatar
}; };
enum class DomainConnectRequestVersion : PacketVersion { enum class DomainConnectRequestVersion : PacketVersion {

View file

@ -110,10 +110,6 @@ void Antialiasing::run(const render::RenderContextPointer& renderContext, const
RenderArgs* args = renderContext->args; RenderArgs* args = renderContext->args;
if (args->_renderMode == RenderArgs::MIRROR_RENDER_MODE) {
return;
}
gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { gpu::doInBatch(args->_context, [&](gpu::Batch& batch) {
batch.enableStereo(false); batch.enableStereo(false);
batch.setViewportTransform(args->_viewport); batch.setViewportTransform(args->_viewport);

View file

@ -505,9 +505,6 @@ void RenderDeferredSetup::run(const render::RenderContextPointer& renderContext,
// Framebuffer copy operations cannot function as multipass stereo operations. // Framebuffer copy operations cannot function as multipass stereo operations.
batch.enableStereo(false); batch.enableStereo(false);
// perform deferred lighting, rendering to free fbo
auto framebufferCache = DependencyManager::get<FramebufferCache>();
auto textureCache = DependencyManager::get<TextureCache>(); auto textureCache = DependencyManager::get<TextureCache>();
auto deferredLightingEffect = DependencyManager::get<DeferredLightingEffect>(); auto deferredLightingEffect = DependencyManager::get<DeferredLightingEffect>();

View file

@ -69,7 +69,7 @@ std::vector<vec3> polygon() {
std::vector<vec3> result; std::vector<vec3> result;
result.reserve(SIDES); result.reserve(SIDES);
double angleIncrement = 2.0 * M_PI / SIDES; double angleIncrement = 2.0 * M_PI / SIDES;
for (size_t i = 0; i < SIDES; ++i) { for (size_t i = 0; i < SIDES; i++) {
double angle = (double)i * angleIncrement; double angle = (double)i * angleIncrement;
result.push_back(vec3{ cos(angle) * 0.5, 0.0, sin(angle) * 0.5 }); result.push_back(vec3{ cos(angle) * 0.5, 0.0, sin(angle) * 0.5 });
} }
@ -172,20 +172,20 @@ void setupFlatShape(GeometryCache::ShapeData& shapeData, const geometry::Solid<N
vertices.reserve(N * faceCount * 2); vertices.reserve(N * faceCount * 2);
solidIndices.reserve(faceIndexCount * faceCount); solidIndices.reserve(faceIndexCount * faceCount);
for (size_t f = 0; f < faceCount; ++f) { for (size_t f = 0; f < faceCount; f++) {
const Face<N>& face = shape.faces[f]; const Face<N>& face = shape.faces[f];
// Compute the face normal // Compute the face normal
vec3 faceNormal = shape.getFaceNormal(f); vec3 faceNormal = shape.getFaceNormal(f);
// Create the vertices for the face // Create the vertices for the face
for (Index i = 0; i < N; ++i) { for (Index i = 0; i < N; i++) {
Index originalIndex = face[i]; Index originalIndex = face[i];
vertices.push_back(shape.vertices[originalIndex]); vertices.push_back(shape.vertices[originalIndex]);
vertices.push_back(faceNormal); vertices.push_back(faceNormal);
} }
// Create the wire indices for unseen edges // Create the wire indices for unseen edges
for (Index i = 0; i < N; ++i) { for (Index i = 0; i < N; i++) {
Index a = i; Index a = i;
Index b = (i + 1) % N; Index b = (i + 1) % N;
auto token = indexToken(face[a], face[b]); auto token = indexToken(face[a], face[b]);
@ -197,7 +197,7 @@ void setupFlatShape(GeometryCache::ShapeData& shapeData, const geometry::Solid<N
} }
// Create the solid face indices // Create the solid face indices
for (Index i = 0; i < N - 2; ++i) { for (Index i = 0; i < N - 2; i++) {
solidIndices.push_back(0 + baseVertex); solidIndices.push_back(0 + baseVertex);
solidIndices.push_back(i + 1 + baseVertex); solidIndices.push_back(i + 1 + baseVertex);
solidIndices.push_back(i + 2 + baseVertex); solidIndices.push_back(i + 2 + baseVertex);
@ -229,10 +229,10 @@ void setupSmoothShape(GeometryCache::ShapeData& shapeData, const geometry::Solid
solidIndices.reserve(faceIndexCount * faceCount); solidIndices.reserve(faceIndexCount * faceCount);
for (size_t f = 0; f < faceCount; ++f) { for (size_t f = 0; f < faceCount; f++) {
const Face<N>& face = shape.faces[f]; const Face<N>& face = shape.faces[f];
// Create the wire indices for unseen edges // Create the wire indices for unseen edges
for (Index i = 0; i < N; ++i) { for (Index i = 0; i < N; i++) {
Index a = face[i]; Index a = face[i];
Index b = face[(i + 1) % N]; Index b = face[(i + 1) % N];
auto token = indexToken(a, b); auto token = indexToken(a, b);
@ -244,7 +244,7 @@ void setupSmoothShape(GeometryCache::ShapeData& shapeData, const geometry::Solid
} }
// Create the solid face indices // Create the solid face indices
for (Index i = 0; i < N - 2; ++i) { for (Index i = 0; i < N - 2; i++) {
solidIndices.push_back(face[i] + baseVertex); solidIndices.push_back(face[i] + baseVertex);
solidIndices.push_back(face[i + 1] + baseVertex); solidIndices.push_back(face[i + 1] + baseVertex);
solidIndices.push_back(face[i + 2] + baseVertex); solidIndices.push_back(face[i + 2] + baseVertex);
@ -256,23 +256,30 @@ void setupSmoothShape(GeometryCache::ShapeData& shapeData, const geometry::Solid
} }
template <uint32_t N> template <uint32_t N>
void extrudePolygon(GeometryCache::ShapeData& shapeData, gpu::BufferPointer& vertexBuffer, gpu::BufferPointer& indexBuffer) { void extrudePolygon(GeometryCache::ShapeData& shapeData, gpu::BufferPointer& vertexBuffer, gpu::BufferPointer& indexBuffer, bool isConical = false) {
using namespace geometry; using namespace geometry;
Index baseVertex = (Index)(vertexBuffer->getSize() / SHAPE_VERTEX_STRIDE); Index baseVertex = (Index)(vertexBuffer->getSize() / SHAPE_VERTEX_STRIDE);
VertexVector vertices; VertexVector vertices;
IndexVector solidIndices, wireIndices; IndexVector solidIndices, wireIndices;
// Top and bottom faces // Top (if not conical) and bottom faces
std::vector<vec3> shape = polygon<N>(); std::vector<vec3> shape = polygon<N>();
for (const vec3& v : shape) { if (isConical) {
vertices.push_back(vec3(v.x, 0.5f, v.z)); for (uint32_t i = 0; i < N; i++) {
vertices.push_back(vec3(0, 1, 0)); vertices.push_back(vec3(0.0f, 0.5f, 0.0f));
vertices.push_back(vec3(0.0f, 1.0f, 0.0f));
}
} else {
for (const vec3& v : shape) {
vertices.push_back(vec3(v.x, 0.5f, v.z));
vertices.push_back(vec3(0.0f, 1.0f, 0.0f));
}
} }
for (const vec3& v : shape) { for (const vec3& v : shape) {
vertices.push_back(vec3(v.x, -0.5f, v.z)); vertices.push_back(vec3(v.x, -0.5f, v.z));
vertices.push_back(vec3(0, -1, 0)); vertices.push_back(vec3(0.0f, -1.0f, 0.0f));
} }
for (uint32_t i = 2; i < N; ++i) { for (uint32_t i = 2; i < N; i++) {
solidIndices.push_back(baseVertex + 0); solidIndices.push_back(baseVertex + 0);
solidIndices.push_back(baseVertex + i); solidIndices.push_back(baseVertex + i);
solidIndices.push_back(baseVertex + i - 1); solidIndices.push_back(baseVertex + i - 1);
@ -280,7 +287,7 @@ void extrudePolygon(GeometryCache::ShapeData& shapeData, gpu::BufferPointer& ver
solidIndices.push_back(baseVertex + i + N - 1); solidIndices.push_back(baseVertex + i + N - 1);
solidIndices.push_back(baseVertex + i + N); solidIndices.push_back(baseVertex + i + N);
} }
for (uint32_t i = 1; i <= N; ++i) { for (uint32_t i = 1; i <= N; i++) {
wireIndices.push_back(baseVertex + (i % N)); wireIndices.push_back(baseVertex + (i % N));
wireIndices.push_back(baseVertex + i - 1); wireIndices.push_back(baseVertex + i - 1);
wireIndices.push_back(baseVertex + (i % N) + N); wireIndices.push_back(baseVertex + (i % N) + N);
@ -290,12 +297,12 @@ void extrudePolygon(GeometryCache::ShapeData& shapeData, gpu::BufferPointer& ver
// Now do the sides // Now do the sides
baseVertex += 2 * N; baseVertex += 2 * N;
for (uint32_t i = 0; i < N; ++i) { for (uint32_t i = 0; i < N; i++) {
vec3 left = shape[i]; vec3 left = shape[i];
vec3 right = shape[(i + 1) % N]; vec3 right = shape[(i + 1) % N];
vec3 normal = glm::normalize(left + right); vec3 normal = glm::normalize(left + right);
vec3 topLeft = vec3(left.x, 0.5f, left.z); vec3 topLeft = (isConical ? vec3(0.0f, 0.5f, 0.0f) : vec3(left.x, 0.5f, left.z));
vec3 topRight = vec3(right.x, 0.5f, right.z); vec3 topRight = (isConical ? vec3(0.0f, 0.5f, 0.0f) : vec3(right.x, 0.5f, right.z));
vec3 bottomLeft = vec3(left.x, -0.5f, left.z); vec3 bottomLeft = vec3(left.x, -0.5f, left.z);
vec3 bottomRight = vec3(right.x, -0.5f, right.z); vec3 bottomRight = vec3(right.x, -0.5f, right.z);
@ -325,6 +332,41 @@ void extrudePolygon(GeometryCache::ShapeData& shapeData, gpu::BufferPointer& ver
shapeData.setupIndices(indexBuffer, solidIndices, wireIndices); shapeData.setupIndices(indexBuffer, solidIndices, wireIndices);
} }
void drawCircle(GeometryCache::ShapeData& shapeData, gpu::BufferPointer& vertexBuffer, gpu::BufferPointer& indexBuffer) {
// Draw a circle with radius 1/4th the size of the bounding box
using namespace geometry;
Index baseVertex = (Index)(vertexBuffer->getSize() / SHAPE_VERTEX_STRIDE);
VertexVector vertices;
IndexVector solidIndices, wireIndices;
const int NUM_CIRCLE_VERTICES = 64;
std::vector<vec3> shape = polygon<NUM_CIRCLE_VERTICES>();
for (const vec3& v : shape) {
vertices.push_back(vec3(v.x, 0.0f, v.z));
vertices.push_back(vec3(0.0f, 0.0f, 0.0f));
}
for (uint32_t i = 2; i < NUM_CIRCLE_VERTICES; i++) {
solidIndices.push_back(baseVertex + 0);
solidIndices.push_back(baseVertex + i);
solidIndices.push_back(baseVertex + i - 1);
solidIndices.push_back(baseVertex + NUM_CIRCLE_VERTICES);
solidIndices.push_back(baseVertex + i + NUM_CIRCLE_VERTICES - 1);
solidIndices.push_back(baseVertex + i + NUM_CIRCLE_VERTICES);
}
for (uint32_t i = 1; i <= NUM_CIRCLE_VERTICES; i++) {
wireIndices.push_back(baseVertex + (i % NUM_CIRCLE_VERTICES));
wireIndices.push_back(baseVertex + i - 1);
wireIndices.push_back(baseVertex + (i % NUM_CIRCLE_VERTICES) + NUM_CIRCLE_VERTICES);
wireIndices.push_back(baseVertex + (i - 1) + NUM_CIRCLE_VERTICES);
}
shapeData.setupVertices(vertexBuffer, vertices);
shapeData.setupIndices(indexBuffer, solidIndices, wireIndices);
}
// FIXME solids need per-face vertices, but smooth shaded // FIXME solids need per-face vertices, but smooth shaded
// components do not. Find a way to support using draw elements // components do not. Find a way to support using draw elements
// or draw arrays as appropriate // or draw arrays as appropriate
@ -357,8 +399,8 @@ void GeometryCache::buildShapes() {
Index baseVertex = (Index)(_shapeVertices->getSize() / SHAPE_VERTEX_STRIDE); Index baseVertex = (Index)(_shapeVertices->getSize() / SHAPE_VERTEX_STRIDE);
ShapeData& shapeData = _shapes[Line]; ShapeData& shapeData = _shapes[Line];
shapeData.setupVertices(_shapeVertices, VertexVector { shapeData.setupVertices(_shapeVertices, VertexVector {
vec3(-0.5, 0, 0), vec3(-0.5f, 0, 0), vec3(-0.5f, 0.0f, 0.0f), vec3(-0.5f, 0.0f, 0.0f),
vec3(0.5f, 0, 0), vec3(0.5f, 0, 0) vec3(0.5f, 0.0f, 0.0f), vec3(0.5f, 0.0f, 0.0f)
}); });
IndexVector wireIndices; IndexVector wireIndices;
// Only two indices // Only two indices
@ -367,20 +409,22 @@ void GeometryCache::buildShapes() {
shapeData.setupIndices(_shapeIndices, IndexVector(), wireIndices); shapeData.setupIndices(_shapeIndices, IndexVector(), wireIndices);
} }
// Not implememented yet:
//Triangle, //Triangle,
extrudePolygon<3>(_shapes[Triangle], _shapeVertices, _shapeIndices); extrudePolygon<3>(_shapes[Triangle], _shapeVertices, _shapeIndices);
//Hexagon, //Hexagon,
extrudePolygon<6>(_shapes[Hexagon], _shapeVertices, _shapeIndices); extrudePolygon<6>(_shapes[Hexagon], _shapeVertices, _shapeIndices);
//Octagon, //Octagon,
extrudePolygon<8>(_shapes[Octagon], _shapeVertices, _shapeIndices); extrudePolygon<8>(_shapes[Octagon], _shapeVertices, _shapeIndices);
//Quad,
//Circle,
//Torus,
//Cone,
//Cylinder, //Cylinder,
extrudePolygon<64>(_shapes[Cylinder], _shapeVertices, _shapeIndices);
//Cone,
extrudePolygon<64>(_shapes[Cone], _shapeVertices, _shapeIndices, true);
//Circle
drawCircle(_shapes[Circle], _shapeVertices, _shapeIndices);
// Not implememented yet:
//Quad,
//Torus,
} }
gpu::Stream::FormatPointer& getSolidStreamFormat() { gpu::Stream::FormatPointer& getSolidStreamFormat() {
@ -597,7 +641,7 @@ void GeometryCache::updateVertices(int id, const QVector<glm::vec2>& points, con
auto pointCount = points.size(); auto pointCount = points.size();
auto colorCount = colors.size(); auto colorCount = colors.size();
int compactColor = 0; int compactColor = 0;
for (auto i = 0; i < pointCount; ++i) { for (auto i = 0; i < pointCount; i++) {
const auto& point = points[i]; const auto& point = points[i];
*(vertex++) = point.x; *(vertex++) = point.x;
*(vertex++) = point.y; *(vertex++) = point.y;
@ -674,7 +718,7 @@ void GeometryCache::updateVertices(int id, const QVector<glm::vec3>& points, con
const glm::vec3 NORMAL(0.0f, 0.0f, 1.0f); const glm::vec3 NORMAL(0.0f, 0.0f, 1.0f);
auto pointCount = points.size(); auto pointCount = points.size();
auto colorCount = colors.size(); auto colorCount = colors.size();
for (auto i = 0; i < pointCount; ++i) { for (auto i = 0; i < pointCount; i++) {
const glm::vec3& point = points[i]; const glm::vec3& point = points[i];
if (i < colorCount) { if (i < colorCount) {
const glm::vec4& color = colors[i]; const glm::vec4& color = colors[i];

View file

@ -142,8 +142,8 @@ public:
Dodecahedron, Dodecahedron,
Icosahedron, Icosahedron,
Torus, // not yet implemented Torus, // not yet implemented
Cone, // not yet implemented Cone,
Cylinder, // not yet implemented Cylinder,
NUM_SHAPES, NUM_SHAPES,
}; };

View file

@ -152,9 +152,9 @@ public:
int numInputLights { 0 }; int numInputLights { 0 };
int numClusteredLights { 0 }; int numClusteredLights { 0 };
void setNumClusteredLightReferences(int numRefs) { numClusteredLightReferences = numRefs; emit dirty(); } void setNumClusteredLightReferences(int numRefs) { numClusteredLightReferences = numRefs; }
void setNumInputLights(int numLights) { numInputLights = numLights; emit dirty(); } void setNumInputLights(int numLights) { numInputLights = numLights; }
void setNumClusteredLights(int numLights) { numClusteredLights = numLights; emit dirty(); } void setNumClusteredLights(int numLights) { numClusteredLights = numLights; }
int numSceneLights { 0 }; int numSceneLights { 0 };
int numFreeSceneLights { 0 }; int numFreeSceneLights { 0 };

View file

@ -48,6 +48,7 @@ void RenderShadowMap::run(const render::RenderContextPointer& renderContext,
RenderArgs* args = renderContext->args; RenderArgs* args = renderContext->args;
gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { gpu::doInBatch(args->_context, [&](gpu::Batch& batch) {
args->_batch = &batch; args->_batch = &batch;
batch.enableStereo(false);
glm::ivec4 viewport{0, 0, fbo->getWidth(), fbo->getHeight()}; glm::ivec4 viewport{0, 0, fbo->getWidth(), fbo->getHeight()};
batch.setViewportTransform(viewport); batch.setViewportTransform(viewport);
@ -114,7 +115,7 @@ void RenderShadowTask::build(JobModel& task, const render::Varying& input, rende
skinProgram, state); skinProgram, state);
} }
const auto cachedMode = task.addJob<RenderShadowSetup>("Setup"); const auto cachedMode = task.addJob<RenderShadowSetup>("ShadowSetup");
// CPU jobs: // CPU jobs:
// Fetch and cull the items from the scene // Fetch and cull the items from the scene
@ -129,7 +130,7 @@ void RenderShadowTask::build(JobModel& task, const render::Varying& input, rende
// GPU jobs: Render to shadow map // GPU jobs: Render to shadow map
task.addJob<RenderShadowMap>("RenderShadowMap", sortedShapes, shapePlumber); task.addJob<RenderShadowMap>("RenderShadowMap", sortedShapes, shapePlumber);
task.addJob<RenderShadowTeardown>("Teardown", cachedMode); task.addJob<RenderShadowTeardown>("ShadowTeardown", cachedMode);
} }
void RenderShadowTask::configure(const Config& configuration) { void RenderShadowTask::configure(const Config& configuration) {

View file

@ -0,0 +1,33 @@
//
// RenderViewTask.cpp
// render-utils/src/
//
// Created by Sam Gateau on 5/25/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
//
#include "RenderViewTask.h"
#include "RenderShadowTask.h"
#include "RenderDeferredTask.h"
#include "RenderForwardTask.h"
void RenderViewTask::build(JobModel& task, const render::Varying& input, render::Varying& output, render::CullFunctor cullFunctor, bool isDeferred) {
// auto items = input.get<Input>();
task.addJob<RenderShadowTask>("RenderShadowTask", cullFunctor);
const auto items = task.addJob<RenderFetchCullSortTask>("FetchCullSort", cullFunctor);
assert(items.canCast<RenderFetchCullSortTask::Output>());
if (isDeferred) {
task.addJob<RenderDeferredTask>("RenderDeferredTask", items);
} else {
task.addJob<RenderForwardTask>("Forward", items);
}
}

View file

@ -0,0 +1,31 @@
//
// RenderViewTask.h
// render-utils/src/
//
// Created by Sam Gateau on 5/25/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
//
#pragma once
#ifndef hifi_RenderViewTask_h
#define hifi_RenderViewTask_h
#include <render/Engine.h>
#include <render/RenderFetchCullSortTask.h>
class RenderViewTask {
public:
using Input = RenderFetchCullSortTask::Output;
using JobModel = render::Task::ModelI<RenderViewTask, Input>;
RenderViewTask() {}
void build(JobModel& task, const render::Varying& inputs, render::Varying& outputs, render::CullFunctor cullFunctor, bool isDeferred);
};
#endif // hifi_RenderViewTask_h

View file

@ -14,6 +14,8 @@
#include "../RenderUtilsLogging.h" #include "../RenderUtilsLogging.h"
#include "FontFamilies.h" #include "FontFamilies.h"
static std::mutex fontMutex;
struct TextureVertex { struct TextureVertex {
glm::vec2 pos; glm::vec2 pos;
glm::vec2 tex; glm::vec2 tex;
@ -56,6 +58,7 @@ Font::Pointer Font::load(QIODevice& fontFile) {
} }
Font::Pointer Font::load(const QString& family) { Font::Pointer Font::load(const QString& family) {
std::lock_guard<std::mutex> lock(fontMutex);
if (!LOADED_FONTS.contains(family)) { if (!LOADED_FONTS.contains(family)) {
static const QString SDFF_COURIER_PRIME_FILENAME{ ":/CourierPrime.sdff" }; static const QString SDFF_COURIER_PRIME_FILENAME{ ":/CourierPrime.sdff" };

View file

@ -31,10 +31,10 @@ public:
const glm::vec4* color, EffectType effectType, const glm::vec4* color, EffectType effectType,
const glm::vec2& bound, bool layered = false); const glm::vec2& bound, bool layered = false);
static Pointer load(QIODevice& fontFile);
static Pointer load(const QString& family); static Pointer load(const QString& family);
private: private:
static Pointer load(QIODevice& fontFile);
QStringList tokenizeForWrapping(const QString& str) const; QStringList tokenizeForWrapping(const QString& str) const;
QStringList splitLines(const QString& str) const; QStringList splitLines(const QString& str) const;
glm::vec2 computeTokenExtent(const QString& str) const; glm::vec2 computeTokenExtent(const QString& str) const;

View file

@ -63,6 +63,4 @@ void EngineStats::run(const RenderContextPointer& renderContext) {
config->frameSetPipelineCount = _gpuStats._PSNumSetPipelines; config->frameSetPipelineCount = _gpuStats._PSNumSetPipelines;
config->frameSetInputFormatCount = _gpuStats._ISNumFormatChanges; config->frameSetInputFormatCount = _gpuStats._ISNumFormatChanges;
config->emitDirty();
} }

View file

@ -34,6 +34,7 @@ void TaskConfig::connectChildConfig(QConfigPointer childConfig, const std::strin
if (childConfig->metaObject()->indexOfSignal(DIRTY_SIGNAL) != -1) { if (childConfig->metaObject()->indexOfSignal(DIRTY_SIGNAL) != -1) {
// Connect dirty->refresh if defined // Connect dirty->refresh if defined
QObject::connect(childConfig.get(), SIGNAL(dirty()), this, SLOT(refresh())); QObject::connect(childConfig.get(), SIGNAL(dirty()), this, SLOT(refresh()));
QObject::connect(childConfig.get(), SIGNAL(dirtyEnabled()), this, SLOT(refresh()));
} }
} }
@ -50,6 +51,7 @@ void TaskConfig::transferChildrenConfigs(QConfigPointer source) {
if (child->metaObject()->indexOfSignal(DIRTY_SIGNAL) != -1) { if (child->metaObject()->indexOfSignal(DIRTY_SIGNAL) != -1) {
// Connect dirty->refresh if defined // Connect dirty->refresh if defined
QObject::connect(child, SIGNAL(dirty()), this, SLOT(refresh())); QObject::connect(child, SIGNAL(dirty()), this, SLOT(refresh()));
QObject::connect(child, SIGNAL(dirtyEnabled()), this, SLOT(refresh()));
} }
} }
} }

View file

@ -89,7 +89,7 @@ protected:
class JobConfig : public QObject { class JobConfig : public QObject {
Q_OBJECT Q_OBJECT
Q_PROPERTY(double cpuRunTime READ getCPURunTime NOTIFY newStats()) //ms Q_PROPERTY(double cpuRunTime READ getCPURunTime NOTIFY newStats()) //ms
Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled) Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled NOTIFY dirtyEnabled())
double _msCPURunTime{ 0.0 }; double _msCPURunTime{ 0.0 };
public: public:
@ -99,7 +99,7 @@ public:
JobConfig(bool enabled) : alwaysEnabled{ false }, enabled{ enabled } {} JobConfig(bool enabled) : alwaysEnabled{ false }, enabled{ enabled } {}
bool isEnabled() { return alwaysEnabled || enabled; } bool isEnabled() { return alwaysEnabled || enabled; }
void setEnabled(bool enable) { enabled = alwaysEnabled || enable; } void setEnabled(bool enable) { enabled = alwaysEnabled || enable; emit dirtyEnabled(); }
bool alwaysEnabled{ true }; bool alwaysEnabled{ true };
bool enabled{ true }; bool enabled{ true };
@ -121,6 +121,7 @@ public slots:
signals: signals:
void loaded(); void loaded();
void newStats(); void newStats();
void dirtyEnabled();
}; };
class TaskConfig : public JobConfig { class TaskConfig : public JobConfig {

View file

@ -170,6 +170,7 @@ protected:
std::string _name = ""; std::string _name = "";
}; };
// A task is a specialized job to run a collection of other jobs // A task is a specialized job to run a collection of other jobs
// It can be created on any type T by aliasing the type JobModel in the class T // It can be created on any type T by aliasing the type JobModel in the class T
// using JobModel = Task::Model<T> // using JobModel = Task::Model<T>

View file

@ -540,7 +540,7 @@ void TabletProxy::gotoWebScreen(const QString& url, const QString& injectedJavaS
QObject* TabletProxy::addButton(const QVariant& properties) { QObject* TabletProxy::addButton(const QVariant& properties) {
auto tabletButtonProxy = QSharedPointer<TabletButtonProxy>(new TabletButtonProxy(properties.toMap())); auto tabletButtonProxy = QSharedPointer<TabletButtonProxy>(new TabletButtonProxy(properties.toMap()));
std::lock_guard<std::mutex> guard(_tabletMutex); std::unique_lock<std::mutex> guard(_tabletMutex);
_tabletButtonProxies.push_back(tabletButtonProxy); _tabletButtonProxies.push_back(tabletButtonProxy);
if (!_toolbarMode && _qmlTabletRoot) { if (!_toolbarMode && _qmlTabletRoot) {
auto tablet = getQmlTablet(); auto tablet = getQmlTablet();
@ -550,7 +550,6 @@ QObject* TabletProxy::addButton(const QVariant& properties) {
qCCritical(scriptengine) << "Could not find tablet in TabletRoot.qml"; qCCritical(scriptengine) << "Could not find tablet in TabletRoot.qml";
} }
} else if (_toolbarMode) { } else if (_toolbarMode) {
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>(); auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
QObject* toolbarProxy = tabletScriptingInterface->getSystemToolbarProxy(); QObject* toolbarProxy = tabletScriptingInterface->getSystemToolbarProxy();
@ -559,6 +558,8 @@ QObject* TabletProxy::addButton(const QVariant& properties) {
connectionType = Qt::BlockingQueuedConnection; connectionType = Qt::BlockingQueuedConnection;
} }
guard.unlock();
// copy properties from tablet button proxy to toolbar button proxy. // copy properties from tablet button proxy to toolbar button proxy.
QObject* toolbarButtonProxy = nullptr; QObject* toolbarButtonProxy = nullptr;
bool hasResult = QMetaObject::invokeMethod(toolbarProxy, "addButton", connectionType, Q_RETURN_ARG(QObject*, toolbarButtonProxy), Q_ARG(QVariant, tabletButtonProxy->getProperties())); bool hasResult = QMetaObject::invokeMethod(toolbarProxy, "addButton", connectionType, Q_RETURN_ARG(QObject*, toolbarButtonProxy), Q_ARG(QVariant, tabletButtonProxy->getProperties()));
@ -576,31 +577,38 @@ bool TabletProxy::onHomeScreen() {
} }
void TabletProxy::removeButton(QObject* tabletButtonProxy) { void TabletProxy::removeButton(QObject* tabletButtonProxy) {
std::lock_guard<std::mutex> guard(_tabletMutex); std::unique_lock<std::mutex> guard(_tabletMutex);
auto tablet = getQmlTablet(); auto tablet = getQmlTablet();
if (!tablet) { if (!tablet) {
qCCritical(scriptengine) << "Could not find tablet in TabletRoot.qml"; qCCritical(scriptengine) << "Could not find tablet in TabletRoot.qml";
} }
auto iter = std::find(_tabletButtonProxies.begin(), _tabletButtonProxies.end(), tabletButtonProxy); QSharedPointer<TabletButtonProxy> buttonProxy;
if (iter != _tabletButtonProxies.end()) { {
if (!_toolbarMode && _qmlTabletRoot) { auto iter = std::find(_tabletButtonProxies.begin(), _tabletButtonProxies.end(), tabletButtonProxy);
(*iter)->setQmlButton(nullptr); if (iter == _tabletButtonProxies.end()) {
if (tablet) { qCWarning(scriptengine) << "TabletProxy::removeButton() could not find button " << tabletButtonProxy;
QMetaObject::invokeMethod(tablet, "removeButtonProxy", Qt::AutoConnection, Q_ARG(QVariant, (*iter)->getProperties())); return;
}
} else if (_toolbarMode) {
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
QObject* toolbarProxy = tabletScriptingInterface->getSystemToolbarProxy();
// remove button from toolbarProxy
QMetaObject::invokeMethod(toolbarProxy, "removeButton", Qt::AutoConnection, Q_ARG(QVariant, (*iter)->getUuid().toString()));
(*iter)->setToolbarButtonProxy(nullptr);
} }
buttonProxy = *iter;
_tabletButtonProxies.erase(iter); _tabletButtonProxies.erase(iter);
} else { }
qCWarning(scriptengine) << "TabletProxy::removeButton() could not find button " << tabletButtonProxy;
if (!_toolbarMode && _qmlTabletRoot) {
buttonProxy->setQmlButton(nullptr);
if (tablet) {
guard.unlock();
QMetaObject::invokeMethod(tablet, "removeButtonProxy", Qt::AutoConnection, Q_ARG(QVariant, buttonProxy->getProperties()));
}
} else if (_toolbarMode) {
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
QObject* toolbarProxy = tabletScriptingInterface->getSystemToolbarProxy();
// remove button from toolbarProxy
guard.unlock();
QMetaObject::invokeMethod(toolbarProxy, "removeButton", Qt::AutoConnection, Q_ARG(QVariant, buttonProxy->getUuid().toString()));
buttonProxy->setToolbarButtonProxy(nullptr);
} }
} }

View file

@ -104,9 +104,9 @@ void QmlWindowClass::initQml(QVariantMap properties) {
Q_ASSERT(invokeResult); Q_ASSERT(invokeResult);
} else { } else {
// Build the event bridge and wrapper on the main thread // Build the event bridge and wrapper on the main thread
offscreenUi->load(qmlSource(), [&](QQmlContext* context, QObject* object) { offscreenUi->loadInNewContext(qmlSource(), [&](QQmlContext* context, QObject* object) {
_qmlWindow = object; _qmlWindow = object;
_qmlWindow->setProperty("eventBridge", QVariant::fromValue(this)); context->setContextProperty("eventBridge", this);
context->engine()->setObjectOwnership(this, QQmlEngine::CppOwnership); context->engine()->setObjectOwnership(this, QQmlEngine::CppOwnership);
context->engine()->setObjectOwnership(object, QQmlEngine::CppOwnership); context->engine()->setObjectOwnership(object, QQmlEngine::CppOwnership);
if (properties.contains(TITLE_PROPERTY)) { if (properties.contains(TITLE_PROPERTY)) {

View file

@ -10,10 +10,10 @@ if (WIN32)
# we're using static GLEW, so define GLEW_STATIC # we're using static GLEW, so define GLEW_STATIC
add_definitions(-DGLEW_STATIC) add_definitions(-DGLEW_STATIC)
set(TARGET_NAME openvr) set(TARGET_NAME openvr)
setup_hifi_plugin(OpenGL Script Qml Widgets) setup_hifi_plugin(OpenGL Script Qml Widgets Multimedia)
link_hifi_libraries(shared gl networking controllers ui link_hifi_libraries(shared gl networking controllers ui
plugins display-plugins ui-plugins input-plugins script-engine plugins display-plugins ui-plugins input-plugins script-engine
render-utils model gpu gpu-gl render model-networking fbx ktx image procedural) audio-client render-utils model gpu gpu-gl render model-networking fbx ktx image procedural)
include_hifi_library_headers(octree) include_hifi_library_headers(octree)
@ -21,4 +21,5 @@ if (WIN32)
find_package(OpenVR REQUIRED) find_package(OpenVR REQUIRED)
target_include_directories(${TARGET_NAME} PRIVATE ${OPENVR_INCLUDE_DIRS}) target_include_directories(${TARGET_NAME} PRIVATE ${OPENVR_INCLUDE_DIRS})
target_link_libraries(${TARGET_NAME} ${OPENVR_LIBRARIES}) target_link_libraries(${TARGET_NAME} ${OPENVR_LIBRARIES})
target_link_libraries(${TARGET_NAME} Winmm.lib)
endif() endif()

View file

@ -7,6 +7,9 @@
// //
#include "OpenVrDisplayPlugin.h" #include "OpenVrDisplayPlugin.h"
// Odd ordering of header is required to avoid 'macro redinition warnings'
#include <AudioClient.h>
#include <QtCore/QThread> #include <QtCore/QThread>
#include <QtCore/QLoggingCategory> #include <QtCore/QLoggingCategory>
#include <QtCore/QFileInfo> #include <QtCore/QFileInfo>
@ -713,3 +716,30 @@ bool OpenVrDisplayPlugin::isKeyboardVisible() {
int OpenVrDisplayPlugin::getRequiredThreadCount() const { int OpenVrDisplayPlugin::getRequiredThreadCount() const {
return Parent::getRequiredThreadCount() + (_threadedSubmit ? 1 : 0); return Parent::getRequiredThreadCount() + (_threadedSubmit ? 1 : 0);
} }
QString OpenVrDisplayPlugin::getPreferredAudioInDevice() const {
QString device = getVrSettingString(vr::k_pch_audio_Section, vr::k_pch_audio_OnPlaybackDevice_String);
if (!device.isEmpty()) {
static const WCHAR INIT = 0;
size_t size = device.size() + 1;
std::vector<WCHAR> deviceW;
deviceW.assign(size, INIT);
device.toWCharArray(deviceW.data());
device = AudioClient::friendlyNameForAudioDevice(deviceW.data());
}
return device;
}
QString OpenVrDisplayPlugin::getPreferredAudioOutDevice() const {
QString device = getVrSettingString(vr::k_pch_audio_Section, vr::k_pch_audio_OnRecordDevice_String);
if (!device.isEmpty()) {
static const WCHAR INIT = 0;
size_t size = device.size() + 1;
std::vector<WCHAR> deviceW;
deviceW.assign(size, INIT);
device.toWCharArray(deviceW.data());
device = AudioClient::friendlyNameForAudioDevice(deviceW.data());
}
return device;
}

View file

@ -58,6 +58,9 @@ public:
// Possibly needs an additional thread for VR submission // Possibly needs an additional thread for VR submission
int getRequiredThreadCount() const override; int getRequiredThreadCount() const override;
QString getPreferredAudioInDevice() const override;
QString getPreferredAudioOutDevice() const override;
protected: protected:
bool internalActivate() override; bool internalActivate() override;
void internalDeactivate() override; void internalDeactivate() override;

View file

@ -72,6 +72,21 @@ bool openVrSupported() {
return (enableDebugOpenVR || !isOculusPresent()) && vr::VR_IsHmdPresent(); return (enableDebugOpenVR || !isOculusPresent()) && vr::VR_IsHmdPresent();
} }
QString getVrSettingString(const char* section, const char* setting) {
QString result;
static const uint32_t BUFFER_SIZE = 1024;
static char BUFFER[BUFFER_SIZE];
vr::IVRSettings * vrSettings = vr::VRSettings();
if (vrSettings) {
vr::EVRSettingsError error = vr::VRSettingsError_None;
vrSettings->GetString(vr::k_pch_audio_Section, vr::k_pch_audio_OnPlaybackDevice_String, BUFFER, BUFFER_SIZE, &error);
if (error == vr::VRSettingsError_None) {
result = BUFFER;
}
}
return result;
}
vr::IVRSystem* acquireOpenVrSystem() { vr::IVRSystem* acquireOpenVrSystem() {
bool hmdPresent = vr::VR_IsHmdPresent(); bool hmdPresent = vr::VR_IsHmdPresent();
if (hmdPresent) { if (hmdPresent) {
@ -82,6 +97,7 @@ vr::IVRSystem* acquireOpenVrSystem() {
#endif #endif
vr::EVRInitError eError = vr::VRInitError_None; vr::EVRInitError eError = vr::VRInitError_None;
activeHmd = vr::VR_Init(&eError, vr::VRApplication_Scene); activeHmd = vr::VR_Init(&eError, vr::VRApplication_Scene);
#if DEV_BUILD #if DEV_BUILD
qCDebug(displayplugins) << "OpenVR display: HMD is " << activeHmd << " error is " << eError; qCDebug(displayplugins) << "OpenVR display: HMD is " << activeHmd << " error is " << eError;
#endif #endif

View file

@ -25,6 +25,7 @@ bool openVrQuitRequested();
void enableOpenVrKeyboard(PluginContainer* container); void enableOpenVrKeyboard(PluginContainer* container);
void disableOpenVrKeyboard(); void disableOpenVrKeyboard();
bool isOpenVrKeyboardShown(); bool isOpenVrKeyboardShown();
QString getVrSettingString(const char* section, const char* setting);
template<typename F> template<typename F>

View file

@ -123,15 +123,18 @@ bool ViveControllerManager::isSupported() const {
bool ViveControllerManager::activate() { bool ViveControllerManager::activate() {
InputPlugin::activate(); InputPlugin::activate();
_container->addMenu(MENU_PATH);
_container->addMenuItem(PluginType::INPUT_PLUGIN, MENU_PATH, RENDER_CONTROLLERS,
[this] (bool clicked) { this->setRenderControllers(clicked); },
true, true);
if (!_system) { if (!_system) {
_system = acquireOpenVrSystem(); _system = acquireOpenVrSystem();
} }
Q_ASSERT(_system);
if (!_system) {
return false;
}
_container->addMenu(MENU_PATH);
_container->addMenuItem(PluginType::INPUT_PLUGIN, MENU_PATH, RENDER_CONTROLLERS,
[this](bool clicked) { this->setRenderControllers(clicked); },
true, true);
enableOpenVrKeyboard(_container); enableOpenVrKeyboard(_container);

View file

@ -0,0 +1,127 @@
var AVATAR_SELF_ID = "{00000000-0000-0000-0000-000000000001}";
var debugSphereBaseProperties = {
type: "Sphere",
dimensions: { x: 0.2, y: 0.2, z: 0.2 },
dynamic: false,
collisionless: true,
gravity: { x: 0, y: 0, z: 0 },
lifetime: 10.0,
userData: "{ \"grabbableKey\": { \"grabbable\": false, \"kinematic\": false } }"
};
var debugBoxBaseProperties = {
type: "Box",
dimensions: { x: 0.2, y: 0.2, z: 0.2 },
dynamic: false,
collisionless: true,
gravity: { x: 0, y: 0, z: 0 },
lifetime: 10.0,
userData: "{ \"grabbableKey\": { \"grabbable\": false, \"kinematic\": false } }"
};
//jointToWorldPoint
// create sphere for finger on left hand
// each frame, calculate world position of finger, with some offset
// update sphere to match this position
var jointToWorldPointTest_sphereEntity;
function jointToWorldPointTest() {
var jointIndex = MyAvatar.getJointIndex("LeftHandPinky4");
var jointOffset_WorldSpace = { x: 0.1, y: 0, z: 0 };
var worldPos = MyAvatar.jointToWorldPoint(jointOffset_WorldSpace, jointIndex);
var jointSphereProps = Object.create(debugSphereBaseProperties);
jointSphereProps.name = "jointToWorldPointTest_Sphere";
jointSphereProps.color = { blue: 240, green: 150, red: 150 };
jointSphereProps.position = worldPos;
jointToWorldPointTest_sphereEntity = Entities.addEntity(jointSphereProps);
}
function jointToWorldPointTest_update(deltaTime) {
var jointIndex = MyAvatar.getJointIndex("LeftHandPinky4");
var jointOffset_WorldSpace = { x: 0.1, y: 0, z: 0 };
var worldPos = MyAvatar.jointToWorldPoint(jointOffset_WorldSpace, jointIndex);
var newProperties = { position: worldPos };
Entities.editEntity(jointToWorldPointTest_sphereEntity, newProperties);
}
//jointToWorldDirection
// create line in world space
// each frame calculate world space direction of players head z axis
// update line to match
var jointToWorldDirectionTest_lineEntity;
function jointToWorldDirectionTest() {
var jointIndex = MyAvatar.getJointIndex("Head");
var avatarPos = MyAvatar.getJointPosition(jointIndex);
var jointDir = { x: 1, y: 0, z: 1 };
var worldDir = MyAvatar.jointToWorldDirection(jointDir, jointIndex);
print(worldDir.x);
print(worldDir.y);
print(worldDir.z);
jointToWorldDirectionTest_lineEntity = Entities.addEntity({
type: "Line",
color: {red: 250, green: 0, blue: 0},
dimensions: {x: 5, y: 5, z: 5},
lifetime: 10.0,
linePoints: [{
x: 0,
y: 0,
z: 0
}, worldDir
],
position : avatarPos,
});
}
function jointToWorldDirection_update(deltaTime) {
var jointIndex = MyAvatar.getJointIndex("Head");
var avatarPos = MyAvatar.getJointPosition(jointIndex);
var jointDir = { x: 1, y: 0, z: 0 };
var worldDir = MyAvatar.jointToWorldDirection(jointDir, jointIndex);
var newProperties = {
linePoints: [{
x: 0,
y: 0,
z: 0
}, worldDir
],
position : avatarPos
};
Entities.editEntity(jointToWorldDirectionTest_lineEntity, newProperties);
}
//jointToWorldRotation
// create box in world space
// each frame calculate world space rotation of players head
// update box rotation to match
var jointToWorldRotationTest_boxEntity;
function jointToWorldRotationTest() {
var jointIndex = MyAvatar.getJointIndex("Head");
var jointPosition_WorldSpace = MyAvatar.getJointPosition(jointIndex);
var jointRot = MyAvatar.getJointRotation(jointIndex);
var jointRot_WorldSpace = MyAvatar.jointToWorldRotation(jointRot, jointIndex);
var boxProps = Object.create(debugBoxBaseProperties);
boxProps.name = "jointToWorldRotationTest_Box";
boxProps.color = { blue: 250, green: 250, red: 250 };
boxProps.position = jointPosition_WorldSpace;
boxProps.rotation = jointRot_WorldSpace;
jointToWorldRotationTest_boxEntity = Entities.addEntity(boxProps);
}
function jointToWorldRotationTest_update(deltaTime) {
var jointIndex = MyAvatar.getJointIndex("Head");
var jointPosition_WorldSpace = MyAvatar.getJointPosition(jointIndex);
var jointRot = MyAvatar.getJointRotation(jointIndex);
var jointRot_WorldSpace = MyAvatar.jointToWorldRotation(jointRot, jointIndex);
var newProperties = { position: jointPosition_WorldSpace, rotation: jointRot_WorldSpace };
Entities.editEntity(jointToWorldRotationTest_boxEntity, newProperties);
}
jointToWorldPointTest();
Script.update.connect(jointToWorldPointTest_update);
jointToWorldDirectionTest();
Script.update.connect(jointToWorldDirection_update);
jointToWorldRotationTest();
Script.update.connect(jointToWorldRotationTest_update);

View file

@ -0,0 +1,134 @@
var AVATAR_SELF_ID = "{00000000-0000-0000-0000-000000000001}";
var debugSphereBaseProperties = {
type: "Sphere",
dimensions: { x: 0.2, y: 0.2, z: 0.2 },
dynamic: false,
collisionless: true,
gravity: { x: 0, y: 0, z: 0 },
lifetime: 10.0,
userData: "{ \"grabbableKey\": { \"grabbable\": false, \"kinematic\": false } }"
};
var debugBoxBaseProperties = {
type: "Box",
dimensions: { x: 0.2, y: 0.2, z: 0.2 },
dynamic: false,
collisionless: true,
gravity: { x: 0, y: 0, z: 0 },
lifetime: 10.0,
userData: "{ \"grabbableKey\": { \"grabbable\": false, \"kinematic\": false } }"
};
//worldToJointPoint
// calculate position offset from joint using getJointPosition
// pass through worldToJointPoint to get offset in joint space of players joint
// create a blue sphere and attach it to players joint using the joint space offset
// The two spheres should appear in the same place, but the blue sphere will follow the avatar
function worldToJointPointTest() {
var jointIndex = MyAvatar.getJointIndex("LeftHandPinky4");
var avatarPos = MyAvatar.position;
var jointPosition_WorldSpace = MyAvatar.getJointPosition(jointIndex);
var jointOffset_WorldSpace = { x: 0.1, y: 0, z: 0 };
var jointPosition_WorldSpaceOffset = Vec3.sum(jointPosition_WorldSpace, jointOffset_WorldSpace);
var jointPosition_JointSpaceOffset = MyAvatar.worldToJointPoint(jointPosition_WorldSpaceOffset, jointIndex);
var jointSphereProps = Object.create(debugSphereBaseProperties);
jointSphereProps.name = "worldToJointPointTest_Joint";
jointSphereProps.color = { blue: 240, green: 150, red: 150 };
jointSphereProps.localPosition = jointPosition_JointSpaceOffset;
jointSphereProps.parentID = AVATAR_SELF_ID;
jointSphereProps.parentJointIndex = jointIndex;
Entities.addEntity(jointSphereProps);
var worldSphereProps = Object.create(debugSphereBaseProperties);
worldSphereProps.name = "worldToJointPointTest_World";
worldSphereProps.color = { blue: 150, green: 250, red: 150 };
worldSphereProps.position = jointPosition_WorldSpaceOffset;
Entities.addEntity(worldSphereProps);
}
//worldToJointDirection
// create line and attach to avatars head
// each frame calculate direction of world x axis in joint space of players head
// update arrow orientation to match
var worldToJointDirectionTest_lineEntity;
function worldToJointDirectionTest() {
var jointIndex = MyAvatar.getJointIndex("Head");
var jointPosition_WorldSpace = MyAvatar.getJointPosition(jointIndex);
var jointOffset_WorldSpace = { x: 0, y: 0, z: 0 };
var jointPosition_WorldSpaceOffset = Vec3.sum(jointPosition_WorldSpace, jointOffset_WorldSpace);
var jointPosition_JointSpaceOffset = MyAvatar.worldToJointPoint(jointPosition_WorldSpaceOffset, jointIndex);
var worldDir = { x: 1, y: 0, z: 0 };
var avatarDir = MyAvatar.worldToJointDirection(worldDir, jointIndex);
worldToJointDirectionTest_lineEntity = Entities.addEntity({
type: "Line",
color: {red: 200, green: 250, blue: 0},
dimensions: {x: 5, y: 5, z: 5},
lifetime: 10.0,
linePoints: [{
x: 0,
y: 0,
z: 0
}, avatarDir
],
localPosition : jointOffset_WorldSpace,
parentID : AVATAR_SELF_ID,
parentJointIndex : jointIndex
});
}
function worldToJointDirectionTest_update(deltaTime) {
var jointIndex = MyAvatar.getJointIndex("Head");
var worldDir = { x: 1, y: 0, z: 0 };
var avatarDir = MyAvatar.worldToJointDirection(worldDir, jointIndex);
var newProperties = { linePoints: [{
x: 0,
y: 0,
z: 0
}, avatarDir
]};
Entities.editEntity(worldToJointDirectionTest_lineEntity, newProperties);
}
//worldToJointRotation
// create box and parent to some player joint
// convert world identity rotation to joint space rotation
// each frame, update box with new orientation
var worldToJointRotationTest_boxEntity;
function worldToJointRotationTest() {
var jointIndex = MyAvatar.getJointIndex("RightHandPinky4");
var avatarPos = MyAvatar.position;
var jointPosition_WorldSpace = MyAvatar.getJointPosition(jointIndex);
var jointOffset_WorldSpace = { x: 0.0, y: 0, z: 0 };
var jointPosition_WorldSpaceOffset = Vec3.sum(jointPosition_WorldSpace, jointOffset_WorldSpace);
var jointPosition_JointSpaceOffset = MyAvatar.worldToJointPoint(jointPosition_WorldSpaceOffset, jointIndex);
var jointBoxProps = Object.create(debugBoxBaseProperties);
jointBoxProps.name = "worldToJointRotationTest_Box";
jointBoxProps.color = { blue: 0, green: 0, red: 250 };
jointBoxProps.localPosition = jointPosition_JointSpaceOffset;
jointBoxProps.parentID = AVATAR_SELF_ID;
jointBoxProps.parentJointIndex = jointIndex;
worldToJointRotationTest_boxEntity = Entities.addEntity(jointBoxProps);
}
function worldToJointRotationTest_update(deltaTime) {
var jointIndex = MyAvatar.getJointIndex("RightHandPinky4");
var worldRot = Quat.fromPitchYawRollDegrees(0,0,0);
var avatarRot = MyAvatar.worldToJointRotation(worldRot, jointIndex);
var newProperties = { localRotation: avatarRot };
Entities.editEntity(worldToJointRotationTest_boxEntity, newProperties);
}
worldToJointPointTest();
worldToJointDirectionTest();
worldToJointRotationTest();
Script.update.connect(worldToJointDirectionTest_update);
Script.update.connect(worldToJointRotationTest_update);

View file

@ -80,27 +80,7 @@ selectionManager.addEventListener(function () {
} }
var type = Entities.getEntityProperties(selectedEntityID, "type").type; var type = Entities.getEntityProperties(selectedEntityID, "type").type;
if (type === "ParticleEffect") { if (type === "ParticleEffect") {
// Destroy the old particles web view first selectParticleEntity(selectedEntityID);
particleExplorerTool.destroyWebView();
particleExplorerTool.createWebView();
var properties = Entities.getEntityProperties(selectedEntityID);
var particleData = {
messageType: "particle_settings",
currentProperties: properties
};
selectedParticleEntityID = selectedEntityID;
particleExplorerTool.setActiveParticleEntity(selectedParticleEntityID);
particleExplorerTool.webView.webEventReceived.connect(function (data) {
data = JSON.parse(data);
if (data.messageType === "page_loaded") {
particleExplorerTool.webView.emitScriptEvent(JSON.stringify(particleData));
}
});
// Switch to particle explorer
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
tablet.sendToQml({method: 'selectTab', params: {id: 'particle'}});
} else { } else {
needToDestroyParticleExplorer = true; needToDestroyParticleExplorer = true;
} }
@ -618,7 +598,6 @@ var toolBar = (function () {
that.toggle = function () { that.toggle = function () {
that.setActive(!isActive); that.setActive(!isActive);
activeButton.editProperties({isActive: isActive});
if (!isActive) { if (!isActive) {
tablet.gotoHomeScreen(); tablet.gotoHomeScreen();
} }
@ -642,6 +621,8 @@ var toolBar = (function () {
enabled: active enabled: active
})); }));
isActive = active; isActive = active;
activeButton.editProperties({isActive: isActive});
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
if (!isActive) { if (!isActive) {
@ -1372,7 +1353,7 @@ function parentSelectedEntities() {
} }
}); });
if(parentCheck) { if (parentCheck) {
Window.notify("Entities parented"); Window.notify("Entities parented");
}else { }else {
Window.notify("Entities are already parented to last"); Window.notify("Entities are already parented to last");
@ -1906,11 +1887,11 @@ var PropertiesTool = function (opts) {
} }
pushCommandForSelections(); pushCommandForSelections();
selectionManager._update(); selectionManager._update();
} else if(data.type === 'parent') { } else if (data.type === 'parent') {
parentSelectedEntities(); parentSelectedEntities();
} else if(data.type === 'unparent') { } else if (data.type === 'unparent') {
unparentSelectedEntities(); unparentSelectedEntities();
} else if(data.type === 'saveUserData'){ } else if (data.type === 'saveUserData'){
//the event bridge and json parsing handle our avatar id string differently. //the event bridge and json parsing handle our avatar id string differently.
var actualID = data.id.split('"')[1]; var actualID = data.id.split('"')[1];
Entities.editEntity(actualID, data.properties); Entities.editEntity(actualID, data.properties);
@ -2202,6 +2183,10 @@ var selectedParticleEntityID = null;
function selectParticleEntity(entityID) { function selectParticleEntity(entityID) {
var properties = Entities.getEntityProperties(entityID); var properties = Entities.getEntityProperties(entityID);
if (properties.emitOrientation) {
properties.emitOrientation = Quat.safeEulerAngles(properties.emitOrientation);
}
var particleData = { var particleData = {
messageType: "particle_settings", messageType: "particle_settings",
currentProperties: properties currentProperties: properties
@ -2211,6 +2196,7 @@ function selectParticleEntity(entityID) {
selectedParticleEntity = entityID; selectedParticleEntity = entityID;
particleExplorerTool.setActiveParticleEntity(entityID); particleExplorerTool.setActiveParticleEntity(entityID);
particleExplorerTool.webView.emitScriptEvent(JSON.stringify(particleData)); particleExplorerTool.webView.emitScriptEvent(JSON.stringify(particleData));
// Switch to particle explorer // Switch to particle explorer
@ -2228,7 +2214,7 @@ entityListTool.webView.webEventReceived.connect(function (data) {
if (data.type === 'parent') { if (data.type === 'parent') {
parentSelectedEntities(); parentSelectedEntities();
} else if(data.type === 'unparent') { } else if (data.type === 'unparent') {
unparentSelectedEntities(); unparentSelectedEntities();
} else if (data.type === "selectionUpdate") { } else if (data.type === "selectionUpdate") {
var ids = data.entityIds; var ids = data.entityIds;
@ -2249,4 +2235,3 @@ entityListTool.webView.webEventReceived.connect(function (data) {
}); });
}()); // END LOCAL_SCOPE }()); // END LOCAL_SCOPE

View file

@ -610,6 +610,7 @@ hr {
.dropdown dl[dropped="true"] { .dropdown dl[dropped="true"] {
color: #404040; color: #404040;
background: linear-gradient(#afafaf, #afafaf); background: linear-gradient(#afafaf, #afafaf);
z-index: 998;
} }
.dropdown dt { .dropdown dt {
@ -657,7 +658,8 @@ hr {
font-family: FiraSans-SemiBold; font-family: FiraSans-SemiBold;
font-size: 15px; font-size: 15px;
color: #404040; color: #404040;
background-color: #afafaf background-color: #afafaf;
z-index: 999;
} }
.dropdown li:hover { .dropdown li:hover {
background-color: #00b4ef; background-color: #00b4ef;

View file

@ -503,7 +503,7 @@ div.jsoneditor-contextmenu-root {
div.jsoneditor-contextmenu { div.jsoneditor-contextmenu {
position: absolute; position: absolute;
box-sizing: content-box; box-sizing: content-box;
z-index: 99999; z-index: 998;
} }
div.jsoneditor-contextmenu ul, div.jsoneditor-contextmenu ul,

View file

@ -51,6 +51,9 @@
<option value="Hexagon">Hexagon</option> <option value="Hexagon">Hexagon</option>
<option value="Triangle">Triangle</option> <option value="Triangle">Triangle</option>
<option value="Octagon">Octagon</option> <option value="Octagon">Octagon</option>
<option value="Cylinder">Cylinder</option>
<option value="Cone">Cone</option>
<option value="Circle">Circle</option>
</select> </select>
</div> </div>
<div class="property text"> <div class="property text">

View file

@ -13,7 +13,7 @@ var WebChannel;
openEventBridge = function(callback) { openEventBridge = function(callback) {
WebChannel = new QWebChannel(qt.webChannelTransport, function (channel) { WebChannel = new QWebChannel(qt.webChannelTransport, function (channel) {
EventBridge = WebChannel.objects.eventBridgeWrapper.eventBridge; EventBridge = WebChannel.objects.eventBridge;
callback(EventBridge); callback(EventBridge);
}); });
} }

View file

@ -43,7 +43,7 @@ var LOCAL_TABLET_MODEL_PATH = Script.resourcesPath() + "meshes/tablet-with-home-
// returns object with two fields: // returns object with two fields:
// * position - position in front of the user // * position - position in front of the user
// * rotation - rotation of entity so it faces the user. // * rotation - rotation of entity so it faces the user.
function calcSpawnInfo(hand, height) { function calcSpawnInfo(hand, tabletHeight) {
var finalPosition; var finalPosition;
var headPos = (HMD.active && Camera.mode === "first person") ? HMD.position : Camera.position; var headPos = (HMD.active && Camera.mode === "first person") ? HMD.position : Camera.position;
@ -53,30 +53,35 @@ function calcSpawnInfo(hand, height) {
hand = NO_HANDS; hand = NO_HANDS;
} }
var handController = null;
if (HMD.active && hand !== NO_HANDS) { if (HMD.active && hand !== NO_HANDS) {
var handController = getControllerWorldLocation(hand, true); handController = getControllerWorldLocation(hand, true);
}
var TABLET_UP_OFFSET = 0.1; if (handController && handController.valid) {
var TABLET_FORWARD_OFFSET = 0.1; // Orient tablet per hand pitch and yaw.
var normal = Vec3.multiplyQbyV(handController.rotation, {x: 0, y: -1, z: 0}); // Angle it back similar to holding it like a book.
var pitch = Math.asin(normal.y); // Move tablet up so that hand is at bottom.
var MAX_PITCH = Math.PI / 4; // Move tablet back so that hand is in front.
if (pitch < -MAX_PITCH) {
pitch = -MAX_PITCH; var position = handController.position;
} else if (pitch > MAX_PITCH) { var rotation = handController.rotation;
pitch = MAX_PITCH;
if (hand === Controller.Standard.LeftHand) {
rotation = Quat.multiply(rotation, Quat.fromPitchYawRollDegrees(0, 90, 0));
} else {
rotation = Quat.multiply(rotation, Quat.fromPitchYawRollDegrees(0, -90, 0));
} }
var normal = Vec3.multiplyQbyV(rotation, Vec3.UNIT_NEG_Y);
var lookAt = Quat.lookAt(Vec3.ZERO, normal, Vec3.multiplyQbyV(MyAvatar.orientation, Vec3.UNIT_Y));
var TABLET_RAKE_ANGLE = 30;
rotation = Quat.multiply(Quat.angleAxis(TABLET_RAKE_ANGLE, Vec3.multiplyQbyV(lookAt, Vec3.UNIT_X)), lookAt);
// rebuild normal from pitch and heading. var RELATIVE_SPAWN_OFFSET = { x: 0, y: 0.4, z: 0.05 };
var heading = Math.atan2(normal.z, normal.x); position = Vec3.sum(position, Vec3.multiplyQbyV(rotation, Vec3.multiply(tabletHeight, RELATIVE_SPAWN_OFFSET)));
normal = {x: Math.cos(heading), y: Math.sin(pitch), z: Math.sin(heading)};
var position = Vec3.sum(handController.position, {x: 0, y: TABLET_UP_OFFSET, z: 0});
var rotation = Quat.lookAt({x: 0, y: 0, z: 0}, normal, Y_AXIS);
var offset = Vec3.multiplyQbyV(rotation, {x: 0, y: height / 2, z: TABLET_FORWARD_OFFSET});
return { return {
position: Vec3.sum(offset, position), position: position,
rotation: rotation rotation: rotation
}; };
} else { } else {

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,680 @@
/* global window, document, print, alert, console,setTimeout, clearTimeout, _ $ */
/* eslint no-console: 0 */
/**
UI Builder V1.0
Created by Matti 'Menithal' Lahtinen
24/5/2017
Copyright 2017 High Fidelity, Inc.
This can eventually be expanded to all of Edit, for now, starting
with Particles Only.
This is created for the sole purpose of streamliming the bridge, and to simplify
the logic between an inputfield in WebView and Entities in High Fidelity.
We also do not need anything as heavy as jquery or any other platform,
as we are mostly only building for QT (while, all the other JS frameworks usually do alot of polyfilling)
Available Types:
JSONInputField - Accepts JSON input, once one presses Save, it will be propegated.
Button- A Button that listens for a custom event as defined by callback
Boolean - Creates a checkbox that the user can either check or uncheck
SliderFloat - Creates a slider (with input) that has Float values from min to max.
Default is min 0, max 1
SliderInteger - Creates a slider (with input) that has a Integer value from min to max.
Default is min 1, max 10000
SliderRadian - Creates a slider (with input) that has Float values in degrees,
that are converted to radians. default is min 0, max Math.PI.
Texture - Creates a Image with an url input field that points to texture.
If image cannot form, show "cannot find image"
VecQuaternion - Creates a 3D Vector field that converts to quaternions.
Checkbox exists to show quaternions instead.
Color - Create field color button, that when pressed, opens the color picker.
Vector - Create a 3D Vector field that has one to one correspondence.
The script will use this structure to build a UI that is connected The
id fields within High Fidelity
This should make editing, and everything related much more simpler to maintain,
and If there is any changes to either the Entities or properties of
**/
var RADIANS_PER_DEGREE = Math.PI / 180;
var roundFloat = function (input, round) {
round = round ? round : 1000;
var sanitizedInput;
if (typeof input === "string") {
sanitizedInput = parseFloat(input);
} else {
sanitizedInput = input;
}
return Math.round(sanitizedInput * round) / round;
};
function HifiEntityUI(parent) {
this.parent = parent;
var self = this;
this.webBridgeSync = _.debounce(function (id, val) {
if (self.EventBridge) {
var sendPackage = {};
sendPackage[id] = val;
self.submitChanges(sendPackage);
}
}, 125);
}
HifiEntityUI.prototype = {
setOnSelect: function (callback) {
this.onSelect = callback;
},
submitChanges: function (structure) {
var message = {
messageType: "settings_update",
updatedSettings: structure
};
this.EventBridge.emitWebEvent(JSON.stringify(message));
},
setUI: function (structure) {
this.structure = structure;
},
disableFields: function () {
var fields = document.getElementsByTagName("input");
for (var i = 0; i < fields.length; i++) {
if (fields[i].getAttribute("type") !== "button") {
fields[i].value = "";
}
fields[i].setAttribute("disabled", true);
}
var textures = document.getElementsByTagName("img");
for (i = 0; i < textures.length; i++) {
textures[i].src = "";
}
textures = document.getElementsByClassName("with-texture");
for (i = 0; i < textures.length; i++) {
textures[i].classList.remove("with-textures");
textures[i].classList.add("no-texture");
}
var textareas = document.getElementsByTagName("textarea");
for (var x = 0; x < textareas.length; x++) {
textareas[x].remove();
}
},
getSettings: function () {
var self = this;
var json = {};
var keys = Object.keys(self.builtRows);
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
var el = self.builtRows[key];
if (el.className.indexOf("checkbox") !== -1) {
json[key] = document.getElementById(key)
.checked ? true : false;
} else if (el.className.indexOf("vector-section") !== -1) {
var vector = {};
if (el.className.indexOf("rgb") !== -1) {
var red = document.getElementById(key + "-red");
var blue = document.getElementById(key + "-blue");
var green = document.getElementById(key + "-green");
vector.red = red.value;
vector.blue = blue.value;
vector.green = green.value;
} else if (el.className.indexOf("pyr") !== -1) {
var p = document.getElementById(key + "-Pitch");
var y = document.getElementById(key + "-Yaw");
var r = document.getElementById(key + "-Roll");
vector.x = p.value;
vector.y = y.value;
vector.z = r.value;
} else {
var x = document.getElementById(key + "-x");
var ey = document.getElementById(key + "-y");
var z = document.getElementById(key + "-z");
vector.x = x.value;
vector.y = ey.value;
vector.z = z.value;
}
json[key] = vector;
} else if (el.className.length > 0) {
json[key] = document.getElementById(key)
.value;
}
}
return json;
},
fillFields: function (currentProperties) {
var self = this;
var fields = document.getElementsByTagName("input");
if (!currentProperties.locked) {
for (var i = 0; i < fields.length; i++) {
fields[i].removeAttribute("disabled");
}
}
if (self.onSelect) {
self.onSelect();
}
var keys = Object.keys(currentProperties);
for (var e in keys) {
if (keys.hasOwnProperty(e)) {
var value = keys[e];
var property = currentProperties[value];
var field = self.builtRows[value];
if (field) {
var el = document.getElementById(value);
if (field.className.indexOf("radian") !== -1) {
el.value = property / RADIANS_PER_DEGREE;
el.onchange({
target: el
});
} else if (field.className.indexOf("range") !== -1 || field.className.indexOf("texture") !== -1) {
el.value = property;
el.onchange({
target: el
});
} else if (field.className.indexOf("checkbox") !== -1) {
if (property) {
el.setAttribute("checked", property);
} else {
el.removeAttribute("checked");
}
} else if (field.className.indexOf("vector-section") !== -1) {
if (field.className.indexOf("rgb") !== -1) {
var red = document.getElementById(value + "-red");
var blue = document.getElementById(value + "-blue");
var green = document.getElementById(value + "-green");
red.value = parseInt(property.red);
blue.value = parseInt(property.blue);
green.value = parseInt(property.green);
red.oninput({
target: red
});
} else if (field.className.indexOf("xyz") !== -1) {
var x = document.getElementById(value + "-x");
var y = document.getElementById(value + "-y");
var z = document.getElementById(value + "-z");
x.value = roundFloat(property.x, 100);
y.value = roundFloat(property.y, 100);
z.value = roundFloat(property.z, 100);
} else if (field.className.indexOf("pyr") !== -1) {
var pitch = document.getElementById(value + "-Pitch");
var yaw = document.getElementById(value + "-Yaw");
var roll = document.getElementById(value + "-Roll");
pitch.value = roundFloat(property.x, 100);
yaw.value = roundFloat(property.y, 100);
roll.value = roundFloat(property.z, 100);
}
}
}
}
}
},
connect: function (EventBridge) {
this.EventBridge = EventBridge;
var self = this;
EventBridge.emitWebEvent(JSON.stringify({
messageType: 'page_loaded'
}));
EventBridge.scriptEventReceived.connect(function (data) {
data = JSON.parse(data);
if (data.messageType === 'particle_settings') {
// Update settings
var currentProperties = data.currentProperties;
self.fillFields(currentProperties);
// Do expected property match with structure;
} else if (data.messageType === 'particle_close') {
self.disableFields();
}
});
},
build: function () {
var self = this;
var sections = Object.keys(this.structure);
this.builtRows = {};
sections.forEach(function (section, index) {
var properties = self.structure[section];
self.addSection(self.parent, section, properties, index);
});
},
addSection: function (parent, section, properties, index) {
var self = this;
var sectionDivHeader = document.createElement("div");
var title = document.createElement("label");
var dropDown = document.createElement("span");
dropDown.className = "arrow";
sectionDivHeader.className = "section-header";
title.innerHTML = section;
sectionDivHeader.appendChild(title);
sectionDivHeader.appendChild(dropDown);
var collapsed = index !== 0;
dropDown.innerHTML = collapsed ? "L" : "M";
sectionDivHeader.setAttribute("collapsed", collapsed);
parent.appendChild(sectionDivHeader);
var sectionDivBody = document.createElement("div");
sectionDivBody.className = "property-group";
var animationWrapper = document.createElement("div");
animationWrapper.className = "section-wrap";
for (var property in properties) {
if (properties.hasOwnProperty(property)) {
var builtRow = self.addElement(animationWrapper, properties[property]);
var id = properties[property].id;
if (id) {
self.builtRows[id] = builtRow;
}
}
}
sectionDivBody.appendChild(animationWrapper);
parent.appendChild(sectionDivBody);
_.defer(function () {
var height = (animationWrapper.clientHeight) + "px";
if (collapsed) {
sectionDivBody.classList.remove("visible");
sectionDivBody.style.maxHeight = "0px";
} else {
sectionDivBody.classList.add("visible");
sectionDivBody.style.maxHeight = height;
}
sectionDivHeader.onclick = function () {
collapsed = !collapsed;
if (collapsed) {
sectionDivBody.classList.remove("visible");
sectionDivBody.style.maxHeight = "0px";
} else {
sectionDivBody.classList.add("visible");
sectionDivBody.style.maxHeight = (animationWrapper.clientHeight) + "px";
}
// sectionDivBody.style.display = collapsed ? "none": "block";
dropDown.innerHTML = collapsed ? "L" : "M";
sectionDivHeader.setAttribute("collapsed", collapsed);
};
});
},
addLabel: function (parent, group) {
var label = document.createElement("label");
label.innerHTML = group.name;
parent.appendChild(label);
if (group.unit) {
var span = document.createElement("span");
span.innerHTML = group.unit;
span.className = "unit";
label.appendChild(span);
}
return label;
},
addVector: function (parent, group, labels, domArray) {
var self = this;
var inputs = labels ? labels : ["x", "y", "z"];
domArray = domArray ? domArray : [];
parent.id = group.id;
for (var index in inputs) {
var element = document.createElement("input");
element.setAttribute("type", "number");
element.className = inputs[index];
element.id = group.id + "-" + inputs[index];
if (group.defaultRange) {
if (group.defaultRange.min) {
element.setAttribute("min", group.defaultRange.min);
}
if (group.defaultRange.max) {
element.setAttribute("max", group.defaultRange.max);
}
if (group.defaultRange.step) {
element.setAttribute("step", group.defaultRange.step);
}
}
if (group.oninput) {
element.oninput = group.oninput;
} else {
element.oninput = function (event) {
self.webBridgeSync(group.id, {
x: domArray[0].value,
y: domArray[1].value,
z: domArray[2].value
});
};
}
element.onchange = element.oninput;
domArray.push(element);
}
this.addLabel(parent, group);
var className = "";
for (var i = 0; i < inputs.length; i++) {
className += inputs[i].charAt(0)
.toLowerCase();
}
parent.className += " property vector-section " + className;
// Add Tuple and the rest
var tupleContainer = document.createElement("div");
tupleContainer.className = "tuple";
for (var domIndex in domArray) {
var container = domArray[domIndex];
var div = document.createElement("div");
var label = document.createElement("label");
label.innerHTML = inputs[domIndex] + ":";
label.setAttribute("for", container.id);
div.appendChild(container);
div.appendChild(label);
tupleContainer.appendChild(div);
}
parent.appendChild(tupleContainer);
},
addVectorQuaternion: function (parent, group) {
this.addVector(parent, group, ["Pitch", "Yaw", "Roll"]);
},
addColorPicker: function (parent, group) {
var self = this;
var $colPickContainer = $('<div>', {
id: group.id,
class: "color-picker"
});
var updateColors = function (red, green, blue) {
$colPickContainer.css('background-color', "rgb(" +
red + "," +
green + "," +
blue + ")");
};
var inputs = ["red", "green", "blue"];
var domArray = [];
group.oninput = function (event) {
$colPickContainer.colpickSetColor(
{
r: domArray[0].value,
g: domArray[1].value,
b: domArray[2].value
},
true);
};
group.defaultRange = {
min: 0,
max: 255,
step: 1
};
parent.appendChild($colPickContainer[0]);
self.addVector(parent, group, inputs, domArray);
updateColors(domArray[0].value, domArray[1].value, domArray[2].value);
// Could probably write a custom one for this to completely write out jquery,
// but for now, using the same as earlier.
/* Color Picker Logic Here */
$colPickContainer.colpick({
colorScheme: 'dark',
layout: 'hex',
color: {
r: domArray[0].value,
g: domArray[1].value,
b: domArray[2].value
},
onChange: function (hsb, hex, rgb, el) {
updateColors(rgb.r, rgb.g, rgb.b);
domArray[0].value = rgb.r;
domArray[1].value = rgb.g;
domArray[2].value = rgb.b;
self.webBridgeSync(group.id, {
red: rgb.r,
green: rgb.g,
blue: rgb.b
});
},
onSubmit: function (hsb, hex, rgb, el) {
$(el)
.css('background-color', '#' + hex);
$(el)
.colpickHide();
domArray[0].value = rgb.r;
domArray[1].value = rgb.g;
domArray[2].value = rgb.b;
self.webBridgeSync(group.id, {
red: rgb.r,
green: rgb.g,
blue: rgb.b
});
}
});
},
addTextureField: function (parent, group) {
var self = this;
this.addLabel(parent, group);
parent.className += " property texture";
var textureImage = document.createElement("div");
var textureUrl = document.createElement("input");
textureUrl.setAttribute("type", "text");
textureUrl.id = group.id;
textureImage.className = "texture-image no-texture";
var image = document.createElement("img");
var imageLoad = _.debounce(function (url) {
if (url.length > 0) {
textureImage.classList.remove("no-texture");
textureImage.classList.add("with-texture");
image.src = url;
image.style.display = "block";
} else {
image.src = "";
image.style.display = "none";
textureImage.classList.add("no-texture");
}
self.webBridgeSync(group.id, url);
}, 250);
textureUrl.oninput = function (event) {
// Add throttle
var url = event.target.value;
imageLoad(url);
};
textureUrl.onchange = textureUrl.oninput;
textureImage.appendChild(image);
parent.appendChild(textureImage);
parent.appendChild(textureUrl);
},
addSlider: function (parent, group) {
var self = this;
this.addLabel(parent, group);
parent.className += " property range";
var container = document.createElement("div");
container.className = "slider-wrapper";
var slider = document.createElement("input");
slider.setAttribute("type", "range");
var inputField = document.createElement("input");
inputField.setAttribute("type", "number");
container.appendChild(slider);
container.appendChild(inputField);
parent.appendChild(container);
if (group.type === "SliderInteger") {
inputField.setAttribute("min", group.min !== undefined ? group.min : 0);
inputField.setAttribute("step", 1);
slider.setAttribute("min", group.min !== undefined ? group.min : 0);
slider.setAttribute("max", group.max !== undefined ? group.max : 10000);
slider.setAttribute("step", 1);
inputField.oninput = function (event) {
if (parseInt(event.target.value) > parseInt(slider.getAttribute("max")) && group.max !== 1) {
slider.setAttribute("max", event.target.value);
}
slider.value = event.target.value;
self.webBridgeSync(group.id, slider.value);
};
inputField.onchange = inputField.oninput;
slider.oninput = function (event) {
inputField.value = event.target.value;
self.webBridgeSync(group.id, slider.value);
};
inputField.id = group.id;
} else if (group.type === "SliderRadian") {
slider.setAttribute("min", group.min !== undefined ? group.min : 0);
slider.setAttribute("max", group.max !== undefined ? group.max : 180);
slider.setAttribute("step", 1);
parent.className += " radian";
inputField.setAttribute("min", (group.min !== undefined ? group.min : 0));
inputField.setAttribute("max", (group.max !== undefined ? group.max : 180));
inputField.oninput = function (event) {
slider.value = event.target.value;
self.webBridgeSync(group.id, slider.value * RADIANS_PER_DEGREE);
};
inputField.onchange = inputField.oninput;
inputField.id = group.id;
slider.oninput = function (event) {
if (event.target.value > 0) {
inputField.value = Math.floor(event.target.value);
} else {
inputField.value = Math.ceil(event.target.value);
}
self.webBridgeSync(group.id, slider.value * RADIANS_PER_DEGREE);
};
var degrees = document.createElement("label");
degrees.innerHTML = "&#176;";
degrees.style.fontSize = "1.4rem";
degrees.style.display = "inline";
degrees.style.verticalAlign = "top";
degrees.style.paddingLeft = "0.4rem";
container.appendChild(degrees);
} else {
// Must then be Float
inputField.setAttribute("min", group.min !== undefined ? group.min : 0);
slider.setAttribute("step", 0.01);
slider.setAttribute("min", group.min !== undefined ? group.min : 0);
slider.setAttribute("max", group.max !== undefined ? group.max : 1);
slider.setAttribute("step", 0.01);
inputField.oninput = function (event) {
if (parseFloat(event.target.value) > parseFloat(slider.getAttribute("max")) && group.max !== 1) {
slider.setAttribute("max", event.target.value);
}
slider.value = event.target.value;
self.webBridgeSync(group.id, slider.value);
// bind web sock update here.
};
inputField.onchange = inputField.oninput;
slider.oninput = function (event) {
inputField.value = event.target.value;
self.webBridgeSync(group.id, inputField.value);
};
inputField.id = group.id;
}
// UpdateBinding
},
addCheckBox: function (parent, group) {
var checkBox = document.createElement("input");
checkBox.setAttribute("type", "checkbox");
var self = this;
checkBox.onchange = function (event) {
self.webBridgeSync(group.id, event.target.checked);
};
checkBox.id = group.id;
parent.appendChild(checkBox);
var label = this.addLabel(parent, group);
label.setAttribute("for", checkBox.id);
parent.className += " property checkbox";
},
addElement: function (parent, group) {
var self = this;
var property = document.createElement("div");
property.id = group.id;
var row = document.createElement("div");
switch (group.type) {
case "Button":
var button = document.createElement("input");
button.setAttribute("type", "button");
button.id = group.id;
if (group.disabled) {
button.disabled = group.disabled;
}
button.className = group.class;
button.value = group.name;
button.onclick = group.callback;
parent.appendChild(button);
break;
case "Row":
var hr = document.createElement("hr");
hr.className = "splitter";
if (group.id) {
hr.id = group.id;
}
parent.appendChild(hr);
break;
case "Boolean":
self.addCheckBox(row, group);
parent.appendChild(row);
break;
case "SliderFloat":
case "SliderInteger":
case "SliderRadian":
self.addSlider(row, group);
parent.appendChild(row);
break;
case "Texture":
self.addTextureField(row, group);
parent.appendChild(row);
break;
case "Color":
self.addColorPicker(row, group);
parent.appendChild(row);
break;
case "Vector":
self.addVector(row, group);
parent.appendChild(row);
break;
case "VectorQuaternion":
self.addVectorQuaternion(row, group);
parent.appendChild(row);
break;
default:
console.log("not defined");
}
return row;
}
};

File diff suppressed because one or more lines are too long

View file

@ -5,8 +5,9 @@
// Created by James B. Pollack @imgntn on 9/26/2015 // Created by James B. Pollack @imgntn on 9/26/2015
// Copyright 2015 High Fidelity, Inc. // Copyright 2015 High Fidelity, Inc.
// //
// Loads dat.gui, underscore, and the app
// Quickly edit the aesthetics of a particle system. // Reworked by Menithal on 20/5/2017
// Using a custom built system for High Fidelity
// //
// Distributed under the Apache License, Version 2.0. // Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
@ -14,82 +15,28 @@
--> -->
<html> <html>
<head> <head>
<link rel="stylesheet" type="text/css" href="../html/css/colpick.css"> <link rel="stylesheet" type="text/css" href="../html/css/colpick.css">
<script type="text/javascript" src="dat.gui.min.js"></script> <script type="text/javascript" src="qrc:///qtwebchannel/qwebchannel.js"></script>
<script type="text/javascript" src="underscore-min.js"></script> <script type="text/javascript" src="../html/js/eventBridgeLoader.js"></script>
<script type="text/javascript" src="qrc:///qtwebchannel/qwebchannel.js"></script> <!---->
<script type="text/javascript" src="../html/js/eventBridgeLoader.js"></script> <script type="text/javascript" src="../html/js/jquery-2.1.4.min.js"></script>
<script type="text/javascript" src="../html/js/jquery-2.1.4.min.js"></script> <script type="text/javascript" src="../html/js/colpick.js"></script>
<script type="text/javascript" src="../html/js/colpick.js"></script>
<script type="text/javascript" src="particleExplorer.js"></script>
<script>
function loaded() {
// Disable right-click context menu which is not visible in the HMD and makes it seem like the app has locked
document.addEventListener("contextmenu", function (event) {
event.preventDefault();
}, false);
}
</script>
<style>
body{ <script type="text/javascript" src="underscore-min.js"></script>
background-color:black; <script type="text/javascript" src="hifi-entity-ui.js?v1"></script>
overflow-x: hidden;
}
#my-gui-container{ <link rel="stylesheet" type="text/css" href="../html/css/hifi-style.css">
<link rel="stylesheet" type="text/css" href="../html/css/edit-style.css">
} <link rel="stylesheet" type="text/css" href="particle-style.css">
.importer{
margin-bottom:4px;
}
.exported-props-section {
width: 50%;
margin: 0 auto;
}
#exported-props {
/* Set the margin-left and margin-right automatically set */
color: white;
white-space: pre-wrap; /* css-3 */
}
.color-box {
display: block;
width: 60%;
height: 21px;
margin-top: 4px;
float: left;
}
.color-box.highlight {
width: 13.5pt;
height: 13.5pt;
border: 1.5pt solid black;
}
::-webkit-input-placeholder {
text-align: center;
font-family: Helvetica
}
#importer-input{
width:90%;
line-height: 2;
margin-left:5%;
}
</style>
</head> </head>
<body onload="loaded();"> <body>
<div class="importer"> <div id="particle-explorer">
<input type='text' id="importer-input" placeholder="Import: Paste JSON here." onkeypress="handleInputKeyPress(event)"> <div class="section-header">
<div class = "exported-props-section"> <label> Particle Explorer </label>
<div id = "exported-props"></div> </div>
</div> <!-- This will be filled by the script! -->
<div id="my-gui-container"> </div>
</div> <div id="rem"></div>
<script type="text/javascript" src="particleExplorer.js"></script>
</body> </body>
</html> </html>

View file

@ -2,550 +2,410 @@
// particleExplorer.js // particleExplorer.js
// //
// Created by James B. Pollack @imgntn on 9/26/2015 // Created by James B. Pollack @imgntn on 9/26/2015
// Copyright 2015 High Fidelity, Inc. // Copyright 2017 High Fidelity, Inc.
//
// Reworked by Menithal on 20/5/2017
//
// Web app side of the App - contains GUI. // Web app side of the App - contains GUI.
// This is an example of a new, easy way to do two way bindings between dynamically created GUI and in-world entities. // This is an example of a new, easy way to do two way bindings between dynamically created GUI and in-world entities.
// //
// Distributed under the Apache License, Version 2.0. // Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
// //
/* global window, alert, EventBridge, dat, listenForSettingsUpdates, createVec3Folder, createQuatFolder, writeVec3ToInterface, writeDataToInterface, /* global HifiEntityUI, openEventBridge, console, EventBridge, document, window */
$, document, _, openEventBridge */ /* eslint no-console: 0, no-global-assign: 0 */
var Settings = function () { (function () {
this.exportSettings = function () {
// copyExportSettingsToClipboard();
showPreselectedPrompt();
};
this.importSettings = function () {
importSettings();
};
};
// 2-way bindings-aren't quite ready yet. see bottom of file. var root = document.getElementById("particle-explorer");
var AUTO_UPDATE = false;
var UPDATE_ALL_FREQUENCY = 100;
var controllers = []; window.onload = function () {
var colpickKeys = []; var ui = new HifiEntityUI(root);
var folders = []; var textarea = document.createElement("textarea");
var gui = null; var properties = "";
var settings = new Settings(); var menuStructure = {
var updateInterval; General: [
{
var active = false; type: "Row",
id: "export-import-field"
var currentInputField; },
var storedController; {
// CHANGE TO WHITELIST id: "show-properties-button",
var keysToAllow = [ name: "Show Properties",
'isEmitting', type: "Button",
'maxParticles', class: "blue",
'lifespan', disabled: true,
'emitRate', callback: function (event) {
'emitSpeed', var insertZone = document.getElementById("export-import-field");
'speedSpread', var json = ui.getSettings();
'emitOrientation', properties = JSON.stringify(json);
'emitDimensions', textarea.value = properties;
'polarStart', if (!insertZone.contains(textarea)) {
'polarFinish', insertZone.appendChild(textarea);
'azimuthStart', insertZone.parentNode.parentNode.style.maxHeight =
'azimuthFinish', insertZone.parentNode.clientHeight + "px";
'emitAcceleration', document.getElementById("export-properties-button").removeAttribute("disabled");
'accelerationSpread', textarea.onchange = function (e) {
'particleRadius', if (e.target.value !== properties) {
'radiusSpread', document.getElementById("import-properties-button").removeAttribute("disabled");
'radiusStart', }
'radiusFinish', };
'color', textarea.oninput = textarea.onchange;
'colorSpread', } else {
'colorStart', textarea.onchange = function () {};
'colorFinish', textarea.oninput = textarea.onchange;
'alpha', textarea.value = "";
'alphaSpread', textarea.remove();
'alphaStart', insertZone.parentNode.parentNode.style.maxHeight =
'alphaFinish', insertZone.parentNode.clientHeight + "px";
'emitterShouldTrail', document.getElementById("export-properties-button").setAttribute("disabled", true);
'textures' document.getElementById("import-properties-button").setAttribute("disabled", true);
]; }
}
var individualKeys = []; },
var vec3Keys = []; {
var quatKeys = []; id: "import-properties-button",
var colorKeys = []; name: "Import",
type: "Button",
window.onload = function () { class: "blue",
openEventBridge(function () { disabled: true,
var stringifiedData = JSON.stringify({ callback: function (event) {
messageType: 'page_loaded' ui.fillFields(JSON.parse(textarea.value));
ui.submitChanges(JSON.parse(textarea.value));
}
},
{
id: "export-properties-button",
name: "Export",
type: "Button",
class: "red",
disabled: true,
callback: function (event) {
textarea.select();
try {
var success = document.execCommand('copy');
if (!success) {
throw "Not success :(";
}
} catch (e) {
print("couldnt copy field");
}
}
},
{
type: "Row"
},
{
id: "isEmitting",
name: "Is Emitting",
type: "Boolean"
},
{
type: "Row"
},
{
id: "lifespan",
name: "Lifespan",
type: "SliderFloat",
min: 0.01,
max: 10
},
{
type: "Row"
},
{
id: "maxParticles",
name: "Max Particles",
type: "SliderInteger",
min: 1,
max: 10000
},
{
type: "Row"
},
{
id: "textures",
name: "Textures",
type: "Texture"
},
{
type: "Row"
}
],
Emit: [
{
id: "emitRate",
name: "Emit Rate",
type: "SliderInteger",
max: 1000,
min: 1
},
{
type: "Row"
},
{
id: "emitSpeed",
name: "Emit Speed",
type: "SliderFloat",
max: 5
},
{
type: "Row"
},
{
id: "emitDimensions",
name: "Emit Dimension",
type: "Vector",
defaultRange: {
min: 0,
step: 0.01
}
},
{
type: "Row"
},
{
id: "emitOrientation",
unit: "deg",
name: "Emit Orientation",
type: "VectorQuaternion",
defaultRange: {
min: 0,
step: 0.01
}
},
{
type: "Row"
},
{
id: "emitShouldTrail",
name: "Emit Should Trail",
type: "Boolean"
},
{
type: "Row"
}
],
Radius: [
{
id: "particleRadius",
name: "Particle Radius",
type: "SliderFloat",
max: 4.0
},
{
type: "Row"
},
{
id: "radiusSpread",
name: "Radius Spread",
type: "SliderFloat",
max: 4.0
},
{
type: "Row"
},
{
id: "radiusStart",
name: "Radius Start",
type: "SliderFloat",
max: 4.0
},
{
type: "Row"
},
{
id: "radiusFinish",
name: "Radius Finish",
type: "SliderFloat",
max: 4.0
},
{
type: "Row"
}
],
Color: [
{
id: "color",
name: "Color",
type: "Color",
defaultColor: {
red: 255,
green: 255,
blue: 255
}
},
{
type: "Row"
},
{
id: "colorSpread",
name: "Color Spread",
type: "Color",
defaultColor: {
red: 0,
green: 0,
blue: 0
}
},
{
type: "Row"
},
{
id: "colorStart",
name: "Color Start",
type: "Color",
defaultColor: {
red: 255,
green: 255,
blue: 255
}
},
{
type: "Row"
},
{
id: "colorFinish",
name: "Color Finish",
type: "Color",
defaultColor: {
red: 255,
green: 255,
blue: 255
}
},
{
type: "Row"
}
],
Acceleration: [
{
id: "emitAcceleration",
name: "Emit Acceleration",
type: "Vector",
defaultRange: {
step: 0.01
}
},
{
type: "Row"
},
{
id: "accelerationSpread",
name: "Acceleration Spread",
type: "Vector",
defaultRange: {
step: 0.01
}
},
{
type: "Row"
}
],
Alpha: [
{
id: "alpha",
name: "Alpha",
type: "SliderFloat"
},
{
type: "Row"
},
{
id: "alphaSpread",
name: "Alpha Spread",
type: "SliderFloat"
},
{
type: "Row"
},
{
id: "alphaStart",
name: "Alpha Start",
type: "SliderFloat"
},
{
type: "Row"
},
{
id: "alphaFinish",
name: "Alpha Finish",
type: "SliderFloat"
},
{
type: "Row"
}
],
Polar: [
{
id: "polarStart",
name: "Polar Start",
unit: "deg",
type: "SliderRadian"
},
{
type: "Row"
},
{
id: "polarFinish",
name: "Polar Finish",
unit: "deg",
type: "SliderRadian"
},
{
type: "Row"
}
],
Azimuth: [
{
id: "azimuthStart",
name: "Azimuth Start",
unit: "deg",
type: "SliderRadian",
min: -180,
max: 0
},
{
type: "Row"
},
{
id: "azimuthFinish",
name: "Azimuth Finish",
unit: "deg",
type: "SliderRadian"
},
{
type: "Row"
}
]
};
ui.setUI(menuStructure);
ui.setOnSelect(function () {
document.getElementById("show-properties-button").removeAttribute("disabled");
document.getElementById("export-properties-button").setAttribute("disabled", true);
document.getElementById("import-properties-button").setAttribute("disabled", true);
}); });
ui.build();
var overrideLoad = false;
if (openEventBridge === undefined) {
overrideLoad = true,
openEventBridge = function (callback) {
callback({
emitWebEvent: function () {},
submitChanges: function () {},
scriptEventReceived: {
connect: function () {
EventBridge.emitWebEvent( }
stringifiedData }
); });
};
listenForSettingsUpdates();
window.onresize = setGUIWidthToWindowWidth;
});
};
function loadGUI() {
// whether or not to autoplace
gui = new dat.GUI({
autoPlace: false
});
// if not autoplacing, put gui in a custom container
if (gui.autoPlace === false) {
var customContainer = document.getElementById('my-gui-container');
customContainer.appendChild(gui.domElement);
}
// presets for the GUI itself. a little confusing and import/export is mostly what we want to do at the moment.
// gui.remember(settings);
colpickKeys = [];
var keys = _.keys(settings);
_.each(keys, function(key) {
var shouldAllow = _.contains(keysToAllow, key);
if (shouldAllow) {
var subKeys = _.keys(settings[key]);
var hasX = _.contains(subKeys, 'x');
var hasY = _.contains(subKeys, 'y');
var hasZ = _.contains(subKeys, 'z');
var hasW = _.contains(subKeys, 'w');
var hasRed = _.contains(subKeys, 'red');
var hasGreen = _.contains(subKeys, 'green');
var hasBlue = _.contains(subKeys, 'blue');
if ((hasX && hasY && hasZ) && hasW === false) {
vec3Keys.push(key);
} else if (hasX && hasY && hasZ && hasW) {
quatKeys.push(key);
} else if (hasRed || hasGreen || hasBlue) {
colorKeys.push(key);
} else {
individualKeys.push(key);
}
} }
}); openEventBridge(function (EventBridge) {
ui.connect(EventBridge);
// alphabetize our keys
individualKeys.sort();
vec3Keys.sort();
quatKeys.sort();
colorKeys.sort();
// add to gui in the order they should appear
gui.add(settings, 'importSettings');
gui.add(settings, 'exportSettings');
addIndividualKeys();
addFolders();
// set the gui width to match the web window width
gui.width = window.innerWidth;
// 2-way binding stuff
// if (AUTO_UPDATE) {
// setInterval(manuallyUpdateDisplay, UPDATE_ALL_FREQUENCY);
// registerDOMElementsForListenerBlocking();
// }
}
function addIndividualKeys() {
_.each(individualKeys, function(key) {
// temporary patch for not crashing when this goes below 0
var controller;
if (key.indexOf('emitRate') > -1) {
controller = gui.add(settings, key).min(0);
} else {
controller = gui.add(settings, key);
}
// 2-way - need to fix not being able to input exact values if constantly listening
// controller.listen();
// keep track of our controller
controllers.push(controller);
// hook into change events for this gui controller
controller.onChange(function(value) {
// Fires on every change, drag, keypress, etc.
writeDataToInterface(this.property, value);
}); });
if (overrideLoad) {
}); openEventBridge();
}
function addFolders() {
_.each(colorKeys, function(key) {
createColorPicker(key);
});
_.each(vec3Keys, function(key) {
createVec3Folder(key);
});
_.each(quatKeys, function(key) {
createQuatFolder(key);
});
}
function createColorPicker(key) {
var colorObject = settings[key];
// Embed colpick's color picker into dat.GUI
var name = document.createElement('span');
name.className = 'property-name';
name.innerHTML = key;
var container = document.createElement('div');
container.appendChild(name);
var $colPickContainer = $('<div>', {
id: key.toString(),
class: "color-box"
});
$colPickContainer.css('background-color', "rgb(" + colorObject.red + "," + colorObject.green + "," + colorObject.blue + ")");
container.appendChild($colPickContainer[0]);
var $li = $('<li>', { class: 'cr object color' });
$li.append(container);
gui.__ul.appendChild($li[0]);
gui.onResize();
$colPickContainer.colpick({
colorScheme: 'dark',
layout: 'hex',
color: { r: colorObject.red, g: colorObject.green, b: colorObject.blue },
onSubmit: function (hsb, hex, rgb, el) {
$(el).css('background-color', '#' + hex);
$(el).colpickHide();
var obj = {};
obj[key] = { red: rgb.r, green: rgb.g, blue: rgb.b };
writeVec3ToInterface(obj);
} }
});
colpickKeys.push(key);
}
function createVec3Folder(category) {
var folder = gui.addFolder(category);
folder.add(settings[category], 'x').step(0.1).onChange(function(value) {
// Fires when a controller loses focus.
var obj = {};
obj[category] = {};
obj[category][this.property] = value;
obj[category].y = settings[category].y;
obj[category].z = settings[category].z;
writeVec3ToInterface(obj);
});
folder.add(settings[category], 'y').step(0.1).onChange(function(value) {
// Fires when a controller loses focus.
var obj = {};
obj[category] = {};
obj[category].x = settings[category].x;
obj[category][this.property] = value;
obj[category].z = settings[category].z;
writeVec3ToInterface(obj);
});
folder.add(settings[category], 'z').step(0.1).onChange(function(value) {
// Fires when a controller loses focus.
var obj = {};
obj[category] = {};
obj[category].y = settings[category].y;
obj[category].x = settings[category].x;
obj[category][this.property] = value;
writeVec3ToInterface(obj);
});
folders.push(folder);
folder.open();
}
function createQuatFolder(category) {
var folder = gui.addFolder(category);
folder.add(settings[category], 'x').step(0.1).onChange(function(value) {
// Fires when a controller loses focus.
var obj = {};
obj[category] = {};
obj[category][this.property] = value;
obj[category].y = settings[category].y;
obj[category].z = settings[category].z;
obj[category].w = settings[category].w;
writeVec3ToInterface(obj);
});
folder.add(settings[category], 'y').step(0.1).onChange(function(value) {
// Fires when a controller loses focus.
var obj = {};
obj[category] = {};
obj[category].x = settings[category].x;
obj[category][this.property] = value;
obj[category].z = settings[category].z;
obj[category].w = settings[category].w;
writeVec3ToInterface(obj);
});
folder.add(settings[category], 'z').step(0.1).onChange(function(value) {
// Fires when a controller loses focus.
var obj = {};
obj[category] = {};
obj[category].x = settings[category].x;
obj[category].y = settings[category].y;
obj[category][this.property] = value;
obj[category].w = settings[category].w;
writeVec3ToInterface(obj);
});
folder.add(settings[category], 'w').step(0.1).onChange(function(value) {
// Fires when a controller loses focus.
var obj = {};
obj[category] = {};
obj[category].x = settings[category].x;
obj[category].y = settings[category].y;
obj[category].z = settings[category].z;
obj[category][this.property] = value;
writeVec3ToInterface(obj);
});
folders.push(folder);
folder.open();
}
function convertColorObjectToArray(colorObject) {
var colorArray = [];
_.each(colorObject, function(singleColor) {
colorArray.push(singleColor);
});
return colorArray;
}
function convertColorArrayToObject(colorArray) {
var colorObject = {
red: colorArray[0],
green: colorArray[1],
blue: colorArray[2]
}; };
})();
return colorObject;
}
function writeDataToInterface(property, value) {
var data = {};
data[property] = value;
var sendData = {
messageType: "settings_update",
updatedSettings: data
};
var stringifiedData = JSON.stringify(sendData);
EventBridge.emitWebEvent(stringifiedData);
}
function writeVec3ToInterface(obj) {
var sendData = {
messageType: "settings_update",
updatedSettings: obj
};
var stringifiedData = JSON.stringify(sendData);
EventBridge.emitWebEvent(stringifiedData);
}
function listenForSettingsUpdates() {
EventBridge.scriptEventReceived.connect(function(data) {
data = JSON.parse(data);
if (data.messageType === 'particle_settings') {
_.each(data.currentProperties, function(value, key) {
settings[key] = {};
settings[key] = value;
});
if (gui) {
manuallyUpdateDisplay();
} else {
loadGUI();
}
if (!active) {
// gui.toggleHide();
gui.closed = false;
}
active = true;
} else if (data.messageType === "particle_close") {
// none of this seems to work.
// if (active) {
// gui.toggleHide();
// }
active = false;
gui.closed = true;
}
});
}
function manuallyUpdateDisplay() {
// Iterate over all controllers
// this is expensive, write a method for indiviudal controllers and use it when the value is different than a cached value, perhaps.
var i;
for (i in gui.__controllers) {
gui.__controllers[i].updateDisplay();
}
// Update color pickers
for (i in colpickKeys) {
var color = settings[colpickKeys[i]];
var $object = $('#' + colpickKeys[i]);
$object.css('background-color', "rgb(" + color.red + "," + color.green + "," + color.blue + ")");
$object.colpickSetColor({ r: color.red, g: color.green, b: color.blue }, true);
}
}
function setGUIWidthToWindowWidth() {
if (gui !== null) {
gui.width = window.innerWidth;
}
}
function handleInputKeyPress(e) {
if (e.keyCode === 13) {
importSettings();
}
return false;
}
function importSettings() {
var importInput = document.getElementById('importer-input');
try {
var importedSettings = JSON.parse(importInput.value);
var keys = _.keys(importedSettings);
_.each(keys, function(key) {
var shouldAllow = _.contains(keysToAllow, key);
if (!shouldAllow) {
return;
}
settings[key] = importedSettings[key];
});
writeVec3ToInterface(settings);
manuallyUpdateDisplay();
} catch (e) {
alert('Not properly formatted JSON');
}
}
function prepareSettingsForExport() {
var keys = _.keys(settings);
var exportSettings = {};
_.each(keys, function(key) {
var shouldAllow = _.contains(keysToAllow, key);
if (!shouldAllow) {
return;
}
if (key.indexOf('color') > -1) {
var colorObject = convertColorArrayToObject(settings[key]);
settings[key] = colorObject;
}
exportSettings[key] = settings[key];
});
return JSON.stringify(exportSettings, null, 4);
}
function showPreselectedPrompt() {
var elem = document.getElementById("exported-props");
var exportSettings = prepareSettingsForExport();
elem.innerHTML = "";
var buttonnode = document.createElement('input');
buttonnode.setAttribute('type', 'button');
buttonnode.setAttribute('value', 'close');
elem.appendChild(document.createTextNode("COPY THE BELOW FIELD TO CLIPBOARD:"));
elem.appendChild(document.createElement("br"));
var textAreaNode = document.createElement("textarea");
textAreaNode.value = exportSettings;
elem.appendChild(textAreaNode);
elem.appendChild(document.createElement("br"));
elem.appendChild(buttonnode);
buttonnode.onclick = function() {
console.log("click");
elem.innerHTML = "";
};
// window.alert("Ctrl-C to copy, then Enter.", prepareSettingsForExport());
}
function removeContainerDomElement() {
var elem = document.getElementById("my-gui-container");
elem.parentNode.removeChild(elem);
}
function removeListenerFromGUI(key) {
_.each(gui.__listening, function(controller, index) {
if (controller.property === key) {
storedController = controller;
gui.__listening.splice(index, 1);
}
});
}
// the section below is to try to work at achieving two way bindings;
function addListenersBackToGUI() {
gui.__listening.push(storedController);
storedController = null;
}
function registerDOMElementsForListenerBlocking() {
_.each(gui.__controllers, function(controller) {
var input = controller.domElement.childNodes[0];
input.addEventListener('focus', function() {
console.log('INPUT ELEMENT GOT FOCUS!' + controller.property);
removeListenerFromGUI(controller.property);
});
});
_.each(gui.__controllers, function(controller) {
var input = controller.domElement.childNodes[0];
input.addEventListener('blur', function() {
console.log('INPUT ELEMENT GOT BLUR!' + controller.property);
addListenersBackToGUI();
});
});
// also listen to inputs inside of folders
_.each(gui.__folders, function(folder) {
_.each(folder.__controllers, function(controller) {
var input = controller.__input;
input.addEventListener('focus', function() {
console.log('FOLDER ELEMENT GOT FOCUS!' + controller.property);
});
});
});
}

View file

@ -9,14 +9,13 @@
// Distributed under the Apache License, Version 2.0. // Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
// //
/*global window, alert, EventBridge, dat, listenForSettingsUpdates,createVec3Folder,createQuatFolder,writeVec3ToInterface,writeDataToInterface*/ /* global window, alert, ParticleExplorerTool, EventBridge, dat, listenForSettingsUpdates,createVec3Folder,createQuatFolder,writeVec3ToInterface,writeDataToInterface*/
var PARTICLE_EXPLORER_HTML_URL = Script.resolvePath('particleExplorer.html'); var PARTICLE_EXPLORER_HTML_URL = Script.resolvePath('particleExplorer.html');
ParticleExplorerTool = function() { ParticleExplorerTool = function() {
var that = {}; var that = {};
that.createWebView = function() { that.createWebView = function() {
that.webView = Tablet.getTablet("com.highfidelity.interface.tablet.system"); that.webView = Tablet.getTablet("com.highfidelity.interface.tablet.system");
that.webView.setVisible = function(value) {}; that.webView.setVisible = function(value) {};
@ -38,6 +37,9 @@ ParticleExplorerTool = function() {
that.webEventReceived = function(data) { that.webEventReceived = function(data) {
var data = JSON.parse(data); var data = JSON.parse(data);
if (data.messageType === "settings_update") { if (data.messageType === "settings_update") {
if (data.updatedSettings.emitOrientation) {
data.updatedSettings.emitOrientation = Quat.fromVec3Degrees(data.updatedSettings.emitOrientation);
}
Entities.editEntity(that.activeParticleEntity, data.updatedSettings); Entities.editEntity(that.activeParticleEntity, data.updatedSettings);
} }
} }

Some files were not shown because too many files have changed in this diff Show more