diff --git a/interface/resources/config/render.json b/interface/resources/config/render.json index 414d94e11e..b5b72d7d07 100644 --- a/interface/resources/config/render.json +++ b/interface/resources/config/render.json @@ -1,14 +1,16 @@ { - "RenderShadowTask": { - "Enabled": { - "enabled": true - } - }, - "RenderDeferredTask": { - "AmbientOcclusion": { + "RenderMainView": { + "RenderShadowTask": { "Enabled": { "enabled": true } + }, + "RenderDeferredTask": { + "AmbientOcclusion": { + "Enabled": { + "enabled": true + } + } } } } diff --git a/interface/resources/html/createGlobalEventBridge.js b/interface/resources/html/createGlobalEventBridge.js index 027d6fe8db..4a0de464c3 100644 --- a/interface/resources/html/createGlobalEventBridge.js +++ b/interface/resources/html/createGlobalEventBridge.js @@ -32,7 +32,7 @@ var EventBridge; var webChannel = new QWebChannel(qt.webChannelTransport, function (channel) { // replace the TempEventBridge with the real one. var tempEventBridge = EventBridge; - EventBridge = channel.objects.eventBridgeWrapper.eventBridge; + EventBridge = channel.objects.eventBridge; tempEventBridge._callbacks.forEach(function (callback) { EventBridge.scriptEventReceived.connect(callback); }); diff --git a/interface/resources/meshes/Jointy3/Jointy3.fbx b/interface/resources/meshes/Jointy3/Jointy3.fbx deleted file mode 100644 index 9df7b17eac..0000000000 Binary files a/interface/resources/meshes/Jointy3/Jointy3.fbx and /dev/null differ diff --git a/interface/resources/meshes/defaultAvatar_full.fst b/interface/resources/meshes/defaultAvatar_full.fst index 3abbaf9ff7..eb8e356196 100644 --- a/interface/resources/meshes/defaultAvatar_full.fst +++ b/interface/resources/meshes/defaultAvatar_full.fst @@ -1,85 +1,89 @@ -name = Jointy3 +name = mannequin type = body+head scale = 1 -filename = Jointy3/Jointy3.fbx -texdir = Jointy3/textures +filename = mannequin/mannequin.baked.fbx +joint = jointEyeLeft = LeftEye +joint = jointRightHand = RightHand +joint = jointHead = Head +joint = jointEyeRight = RightEye +joint = jointLean = Spine joint = jointNeck = Neck joint = jointLeftHand = LeftHand -joint = jointEyeRight = RightEye -joint = jointHead = Head -joint = jointRightHand = RightHand joint = jointRoot = Hips -joint = jointLean = Spine -joint = jointEyeLeft = LeftEye freeJoint = LeftArm freeJoint = LeftForeArm freeJoint = RightArm freeJoint = RightForeArm -jointIndex = RightHand = 17 -jointIndex = LeftHandIndex3 = 56 -jointIndex = Hips = 0 -jointIndex = LeftHandRing2 = 47 -jointIndex = LeftHandThumb3 = 60 -jointIndex = RightShoulder = 14 -jointIndex = RightHandRing1 = 30 -jointIndex = RightHandRing3 = 32 -jointIndex = LeftHandPinky4 = 45 -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 +bs = EyeBlink_L = blink = 1 +bs = JawOpen = mouth_Open = 1 +bs = LipsFunnel = Oo = 1 +bs = BrowsU_L = brow_Up = 1 +jointIndex = RightHandIndex2 = 27 +jointIndex = LeftHandIndex2 = 51 +jointIndex = RightUpLeg = 6 +jointIndex = RightToe_End = 10 +jointIndex = RightEye = 65 jointIndex = LeftHandPinky1 = 42 -jointIndex = RightHandThumb1 = 18 -jointIndex = LeftHandIndex4 = 57 -jointIndex = LeftHandMiddle3 = 52 -jointIndex = RightHandIndex3 = 24 -jointIndex = Spine1 = 12 +jointIndex = RightHandRing1 = 22 +jointIndex = face = 67 +jointIndex = LeftUpLeg = 1 +jointIndex = LeftHand = 41 +jointIndex = LeftHandMiddle1 = 58 +jointIndex = LeftHandIndex1 = 50 +jointIndex = LeftEye = 64 +jointIndex = RightHandIndex1 = 26 +jointIndex = LeftHandPinky4 = 45 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 diff --git a/interface/resources/meshes/mannequin/Eyes.ktx b/interface/resources/meshes/mannequin/Eyes.ktx new file mode 100755 index 0000000000..4da922936b Binary files /dev/null and b/interface/resources/meshes/mannequin/Eyes.ktx differ diff --git a/interface/resources/meshes/mannequin/lambert1_Base_Color.ktx b/interface/resources/meshes/mannequin/lambert1_Base_Color.ktx new file mode 100755 index 0000000000..fcca382445 Binary files /dev/null and b/interface/resources/meshes/mannequin/lambert1_Base_Color.ktx differ diff --git a/interface/resources/meshes/mannequin/lambert1_Normal_OpenGL.ktx b/interface/resources/meshes/mannequin/lambert1_Normal_OpenGL.ktx new file mode 100755 index 0000000000..3ae717c5d7 Binary files /dev/null and b/interface/resources/meshes/mannequin/lambert1_Normal_OpenGL.ktx differ diff --git a/interface/resources/meshes/mannequin/lambert1_Roughness.ktx b/interface/resources/meshes/mannequin/lambert1_Roughness.ktx new file mode 100755 index 0000000000..fe9b42a54b Binary files /dev/null and b/interface/resources/meshes/mannequin/lambert1_Roughness.ktx differ diff --git a/interface/resources/meshes/mannequin/mannequin.baked.fbx b/interface/resources/meshes/mannequin/mannequin.baked.fbx new file mode 100755 index 0000000000..b405b4cfbb Binary files /dev/null and b/interface/resources/meshes/mannequin/mannequin.baked.fbx differ diff --git a/interface/resources/qml/Browser.qml b/interface/resources/qml/Browser.qml index 4f7639dd0e..55927fda24 100644 --- a/interface/resources/qml/Browser.qml +++ b/interface/resources/qml/Browser.qml @@ -21,8 +21,6 @@ ScrollingWindow { property alias url: webview.url property alias webView: webview - property alias eventBridge: eventBridgeWrapper.eventBridge - signal loadingChanged(int status) x: 100 @@ -210,17 +208,6 @@ ScrollingWindow { url: "https://highfidelity.com/" 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. WebEngineScript { id: createGlobalEventBridge @@ -267,6 +254,8 @@ ScrollingWindow { } Component.onCompleted: { + webChannel.registerObject("eventBridge", eventBridge); + webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper); desktop.initWebviewProfileHandlers(webview.profile); } } diff --git a/interface/resources/qml/QmlWebWindow.qml b/interface/resources/qml/QmlWebWindow.qml index d40b1595ba..d2daf0fa1d 100644 --- a/interface/resources/qml/QmlWebWindow.qml +++ b/interface/resources/qml/QmlWebWindow.qml @@ -26,15 +26,8 @@ Windows.ScrollingWindow { // Don't destroy on close... otherwise the JS/C++ will have a dangling pointer destroyOnCloseButton: false property alias source: webview.url - property alias eventBridge: eventBridgeWrapper.eventBridge; 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, // but not having this here results in spurious warnings about a // missing signal @@ -70,7 +63,6 @@ Windows.ScrollingWindow { url: "about:blank" anchors.fill: parent focus: true - webChannel.registeredObjects: [eventBridgeWrapper] property string userScriptUrl: "" @@ -107,6 +99,8 @@ Windows.ScrollingWindow { } Component.onCompleted: { + webChannel.registerObject("eventBridge", eventBridge); + webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper); eventBridge.webEventReceived.connect(onWebEventReceived); } } diff --git a/interface/resources/qml/QmlWindow.qml b/interface/resources/qml/QmlWindow.qml index ac18d36ce6..9ef151b32e 100644 --- a/interface/resources/qml/QmlWindow.qml +++ b/interface/resources/qml/QmlWindow.qml @@ -30,15 +30,6 @@ Windows.Window { property bool keyboardRaised: 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: { if (dynamicContent) { dynamicContent.destroy(); diff --git a/interface/resources/qml/TabletBrowser.qml b/interface/resources/qml/TabletBrowser.qml index d89aa8626f..c3d879c513 100644 --- a/interface/resources/qml/TabletBrowser.qml +++ b/interface/resources/qml/TabletBrowser.qml @@ -18,7 +18,6 @@ Item { property variant permissionsBar: {'securityOrigin':'none','feature':'none'} property alias url: webview.url property WebEngineView webView: webview - property alias eventBridge: eventBridgeWrapper.eventBridge property bool canGoBack: webview.canGoBack property bool canGoForward: webview.canGoForward @@ -32,12 +31,6 @@ Item { webview.profile = profile; } - QtObject { - id: eventBridgeWrapper - WebChannel.id: "eventBridgeWrapper" - property var eventBridge; - } - WebEngineView { id: webview objectName: "webEngineView" @@ -78,9 +71,10 @@ Item { property string newUrl: "" - webChannel.registeredObjects: [eventBridgeWrapper] - Component.onCompleted: { + webChannel.registerObject("eventBridge", eventBridge); + webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper); + // Ensure the JS from the web-engine makes it to our logging webview.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) { console.log("Web Entity JS message: " + sourceID + " " + lineNumber + " " + message); diff --git a/interface/resources/qml/ToolWindow.qml b/interface/resources/qml/ToolWindow.qml index 9c0b0a8c1a..b1120058f9 100644 --- a/interface/resources/qml/ToolWindow.qml +++ b/interface/resources/qml/ToolWindow.qml @@ -79,15 +79,11 @@ ScrollingWindow { id: webView anchors.fill: parent enabled: false - property alias eventBridgeWrapper: eventBridgeWrapper - - QtObject { - id: eventBridgeWrapper - WebChannel.id: "eventBridgeWrapper" - property var eventBridge + Component.onCompleted: { + webChannel.registerObject("eventBridge", eventBridge); + webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper); } - webChannel.registeredObjects: [eventBridgeWrapper] onEnabledChanged: toolWindow.updateVisiblity() } } @@ -251,12 +247,9 @@ ScrollingWindow { tab.enabled = true; tab.originalUrl = properties.source; - var eventBridge = properties.eventBridge; - var result = tab.item; result.enabled = true; tabView.tabCount++; - result.eventBridgeWrapper.eventBridge = eventBridge; result.url = properties.source; return result; } diff --git a/interface/resources/qml/controls/TabletWebScreen.qml b/interface/resources/qml/controls/TabletWebScreen.qml index 0b265f6fbb..68f8226e21 100644 --- a/interface/resources/qml/controls/TabletWebScreen.qml +++ b/interface/resources/qml/controls/TabletWebScreen.qml @@ -6,7 +6,6 @@ import "../controls-uit" as HiFiControls Item { property alias url: root.url property alias scriptURL: root.userScriptUrl - property alias eventBridge: eventBridgeWrapper.eventBridge property alias canGoBack: root.canGoBack; property var goBack: root.goBack; property alias urlTag: root.urlTag @@ -22,12 +21,6 @@ Item { } */ - QtObject { - id: eventBridgeWrapper - WebChannel.id: "eventBridgeWrapper" - property var eventBridge; - } - property alias viewProfile: root.profile WebEngineView { @@ -71,10 +64,11 @@ Item { userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard, userScript ] property string newUrl: "" - - webChannel.registeredObjects: [eventBridgeWrapper] + Component.onCompleted: { + webChannel.registerObject("eventBridge", eventBridge); + webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper); // Ensure the JS from the web-engine makes it to our logging root.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) { console.log("Web Entity JS message: " + sourceID + " " + lineNumber + " " + message); diff --git a/interface/resources/qml/controls/TabletWebView.qml b/interface/resources/qml/controls/TabletWebView.qml index 3b23cbc19e..0a5a68717e 100644 --- a/interface/resources/qml/controls/TabletWebView.qml +++ b/interface/resources/qml/controls/TabletWebView.qml @@ -17,7 +17,6 @@ Item { property int headerHeight: 70 property string url property string scriptURL - property alias eventBridge: eventBridgeWrapper.eventBridge property bool keyboardEnabled: false property bool keyboardRaised: false property bool punctuationMode: false @@ -135,12 +134,6 @@ Item { loadUrl(url); } - QtObject { - id: eventBridgeWrapper - WebChannel.id: "eventBridgeWrapper" - property var eventBridge; - } - WebEngineView { id: webview objectName: "webEngineView" @@ -182,9 +175,9 @@ Item { property string newUrl: "" - webChannel.registeredObjects: [eventBridgeWrapper] - Component.onCompleted: { + webChannel.registerObject("eventBridge", eventBridge); + webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper); // Ensure the JS from the web-engine makes it to our logging webview.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) { console.log("Web Entity JS message: " + sourceID + " " + lineNumber + " " + message); diff --git a/interface/resources/qml/controls/WebView.qml b/interface/resources/qml/controls/WebView.qml index d08562eea3..38136c7eec 100644 --- a/interface/resources/qml/controls/WebView.qml +++ b/interface/resources/qml/controls/WebView.qml @@ -6,7 +6,6 @@ import "../controls-uit" as HiFiControls Item { property alias url: root.url property alias scriptURL: root.userScriptUrl - property alias eventBridge: eventBridgeWrapper.eventBridge property alias canGoBack: root.canGoBack; property var goBack: root.goBack; property alias urlTag: root.urlTag @@ -22,12 +21,6 @@ Item { } */ - QtObject { - id: eventBridgeWrapper - WebChannel.id: "eventBridgeWrapper" - property var eventBridge; - } - property alias viewProfile: root.profile WebEngineView { @@ -72,9 +65,9 @@ Item { property string newUrl: "" - webChannel.registeredObjects: [eventBridgeWrapper] - Component.onCompleted: { + webChannel.registerObject("eventBridge", eventBridge); + webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper); // Ensure the JS from the web-engine makes it to our logging root.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) { console.log("Web Entity JS message: " + sourceID + " " + lineNumber + " " + message); diff --git a/interface/resources/qml/dialogs/TabletLoginDialog.qml b/interface/resources/qml/dialogs/TabletLoginDialog.qml index 30c3e678b4..36ca480b24 100644 --- a/interface/resources/qml/dialogs/TabletLoginDialog.qml +++ b/interface/resources/qml/dialogs/TabletLoginDialog.qml @@ -20,7 +20,6 @@ TabletModalWindow { id: loginDialogRoot objectName: "LoginDialog" - property var eventBridge; signal sendToScript(var message); property bool isHMD: false property bool gotoPreviousApp: false; diff --git a/interface/resources/qml/dialogs/preferences/AvatarBrowser.qml b/interface/resources/qml/dialogs/preferences/AvatarBrowser.qml index 652e02b6b9..e2a012ad46 100644 --- a/interface/resources/qml/dialogs/preferences/AvatarBrowser.qml +++ b/interface/resources/qml/dialogs/preferences/AvatarBrowser.qml @@ -24,8 +24,6 @@ Window { resizable: true modality: Qt.ApplicationModal - property alias eventBridge: eventBridgeWrapper.eventBridge - Item { anchors.fill: parent @@ -45,16 +43,6 @@ Window { 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. WebEngineScript { id: createGlobalEventBridge @@ -73,6 +61,10 @@ Window { userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard ] + Component.onCompleted: { + webChannel.registerObject("eventBridge", eventBridge); + webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper); + } } Keyboard { diff --git a/interface/resources/qml/dialogs/preferences/AvatarPreference.qml b/interface/resources/qml/dialogs/preferences/AvatarPreference.qml index bcc5a1d9e6..b27827d9d7 100644 --- a/interface/resources/qml/dialogs/preferences/AvatarPreference.qml +++ b/interface/resources/qml/dialogs/preferences/AvatarPreference.qml @@ -116,9 +116,7 @@ Preference { Component { id: tabletAvatarBrowserBuilder; - TabletAvatarBrowser { - eventBridge: tabletRoot.eventBridge - } + TabletAvatarBrowser { } } } diff --git a/interface/resources/qml/hifi/Audio.qml b/interface/resources/qml/hifi/Audio.qml index 66760ff290..48de891733 100644 --- a/interface/resources/qml/hifi/Audio.qml +++ b/interface/resources/qml/hifi/Audio.qml @@ -31,7 +31,6 @@ Rectangle { HifiConstants { id: hifi; } objectName: "AudioWindow" - property var eventBridge; property string title: "Audio Options" signal sendToScript(var message); @@ -161,7 +160,12 @@ Rectangle { text.text: devicename onCheckBoxClicked: { 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 onCheckBoxClicked: { 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."); + } + } } } diff --git a/interface/resources/qml/hifi/Pal.qml b/interface/resources/qml/hifi/Pal.qml index 2d6b21b219..250015bab9 100644 --- a/interface/resources/qml/hifi/Pal.qml +++ b/interface/resources/qml/hifi/Pal.qml @@ -44,7 +44,6 @@ Rectangle { property var activeTab: "nearbyTab"; property bool currentlyEditingDisplayName: false property bool punctuationMode: false; - property var eventBridge; HifiConstants { id: hifi; } @@ -1043,7 +1042,6 @@ Rectangle { } // Keyboard HifiControls.TabletWebView { - eventBridge: pal.eventBridge; id: userInfoViewer; anchors { top: parent.top; diff --git a/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml b/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml index 6e0263787b..47c9af1f57 100644 --- a/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml +++ b/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml @@ -24,7 +24,6 @@ Rectangle { property string title: "Asset Browser" property bool keyboardRaised: false - property var eventBridge; signal sendToScript(var message); property bool isHMD: false diff --git a/interface/resources/qml/hifi/dialogs/TabletDCDialog.qml b/interface/resources/qml/hifi/dialogs/TabletDCDialog.qml index b33b957e15..0f363d1be9 100644 --- a/interface/resources/qml/hifi/dialogs/TabletDCDialog.qml +++ b/interface/resources/qml/hifi/dialogs/TabletDCDialog.qml @@ -20,7 +20,6 @@ Rectangle { id: root objectName: "DCConectionTiming" - property var eventBridge; signal sendToScript(var message); property bool isHMD: false diff --git a/interface/resources/qml/hifi/dialogs/TabletDebugWindow.qml b/interface/resources/qml/hifi/dialogs/TabletDebugWindow.qml index d4bbe0af04..22e9dc07a2 100644 --- a/interface/resources/qml/hifi/dialogs/TabletDebugWindow.qml +++ b/interface/resources/qml/hifi/dialogs/TabletDebugWindow.qml @@ -19,7 +19,6 @@ Rectangle { id: root objectName: "DebugWindow" - property var eventBridge; property var title: "Debug Window" property bool isHMD: false diff --git a/interface/resources/qml/hifi/dialogs/TabletEntityStatistics.qml b/interface/resources/qml/hifi/dialogs/TabletEntityStatistics.qml index 35ee58be0c..da295917a0 100644 --- a/interface/resources/qml/hifi/dialogs/TabletEntityStatistics.qml +++ b/interface/resources/qml/hifi/dialogs/TabletEntityStatistics.qml @@ -20,7 +20,6 @@ Rectangle { id: root objectName: "EntityStatistics" - property var eventBridge; signal sendToScript(var message); property bool isHMD: false diff --git a/interface/resources/qml/hifi/dialogs/TabletLODTools.qml b/interface/resources/qml/hifi/dialogs/TabletLODTools.qml index 26e9759e0d..2291a42bf6 100644 --- a/interface/resources/qml/hifi/dialogs/TabletLODTools.qml +++ b/interface/resources/qml/hifi/dialogs/TabletLODTools.qml @@ -20,7 +20,6 @@ Rectangle { id: root objectName: "LODTools" - property var eventBridge; signal sendToScript(var message); property bool isHMD: false diff --git a/interface/resources/qml/hifi/dialogs/TabletRunningScripts.qml b/interface/resources/qml/hifi/dialogs/TabletRunningScripts.qml index d826b40ad1..11643ae1f1 100644 --- a/interface/resources/qml/hifi/dialogs/TabletRunningScripts.qml +++ b/interface/resources/qml/hifi/dialogs/TabletRunningScripts.qml @@ -23,7 +23,6 @@ Rectangle { property string title: "Running Scripts" HifiConstants { id: hifi } signal sendToScript(var message); - property var eventBridge; property var scripts: ScriptDiscoveryService; property var scriptsModel: scripts.scriptsModelFilter property var runningScriptsModel: ListModel { } diff --git a/interface/resources/qml/hifi/tablet/Edit.qml b/interface/resources/qml/hifi/tablet/Edit.qml index ea31eb26d8..e2e8c4362e 100644 --- a/interface/resources/qml/hifi/tablet/Edit.qml +++ b/interface/resources/qml/hifi/tablet/Edit.qml @@ -7,14 +7,12 @@ StackView { objectName: "stack" initialItem: Qt.resolvedUrl('EditTabView.qml') - property var eventBridge; signal sendToScript(var message); HifiConstants { id: hifi } function pushSource(path) { editRoot.push(Qt.resolvedUrl(path)); - editRoot.currentItem.eventBridge = editRoot.eventBridge; editRoot.currentItem.sendToScript.connect(editRoot.sendToScript); } diff --git a/interface/resources/qml/hifi/tablet/EditTabView.qml b/interface/resources/qml/hifi/tablet/EditTabView.qml index d084f1c7b3..e4a20a0316 100644 --- a/interface/resources/qml/hifi/tablet/EditTabView.qml +++ b/interface/resources/qml/hifi/tablet/EditTabView.qml @@ -181,7 +181,6 @@ TabView { WebView { id: entityListToolWebView url: "../../../../../scripts/system/html/entityList.html" - eventBridge: editRoot.eventBridge anchors.fill: parent enabled: true } @@ -196,7 +195,6 @@ TabView { WebView { id: entityPropertiesWebView url: "../../../../../scripts/system/html/entityProperties.html" - eventBridge: editRoot.eventBridge anchors.fill: parent enabled: true } @@ -211,7 +209,6 @@ TabView { WebView { id: gridControlsWebView url: "../../../../../scripts/system/html/gridControls.html" - eventBridge: editRoot.eventBridge anchors.fill: parent enabled: true } @@ -226,7 +223,6 @@ TabView { WebView { id: particleExplorerWebView url: "../../../../../scripts/system/particle_explorer/particleExplorer.html" - eventBridge: editRoot.eventBridge anchors.fill: parent enabled: true } @@ -289,7 +285,7 @@ TabView { editTabView.currentIndex = id; } else { console.warn('Attempt to switch to invalid tab:', id); - } + } } else if (typeof id === 'string'){ switch (id.toLowerCase()) { case 'create': diff --git a/interface/resources/qml/hifi/tablet/InputRecorder.qml b/interface/resources/qml/hifi/tablet/InputRecorder.qml index 76b122d07d..292deb751e 100644 --- a/interface/resources/qml/hifi/tablet/InputRecorder.qml +++ b/interface/resources/qml/hifi/tablet/InputRecorder.qml @@ -18,7 +18,6 @@ import "../../dialogs" Rectangle { id: inputRecorder - property var eventBridge; HifiConstants { id: hifi } signal sendToScript(var message); color: hifi.colors.baseGray; diff --git a/interface/resources/qml/hifi/tablet/NewModelDialog.qml b/interface/resources/qml/hifi/tablet/NewModelDialog.qml index 2d9d121209..5040c043f4 100644 --- a/interface/resources/qml/hifi/tablet/NewModelDialog.qml +++ b/interface/resources/qml/hifi/tablet/NewModelDialog.qml @@ -20,7 +20,6 @@ Rectangle { // height: parent.height HifiConstants { id: hifi } color: hifi.colors.baseGray; - property var eventBridge; signal sendToScript(var message); property bool keyboardEnabled: false property bool punctuationMode: false diff --git a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml index 7159b078ee..073f143dbe 100644 --- a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml +++ b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml @@ -29,7 +29,6 @@ StackView { initialItem: addressBarDialog width: parent !== null ? parent.width : undefined height: parent !== null ? parent.height : undefined - property var eventBridge; property int cardWidth: 212; property int cardHeight: 152; property string metaverseBase: addressBarDialog.metaverseServerUrl + "/api/v1/"; @@ -80,7 +79,6 @@ StackView { var card = tabletWebView.createObject(); card.url = addressBarDialog.metaverseServerUrl + targetString; card.parentStackItem = root; - card.eventBridge = root.eventBridge; root.push(card); return; } diff --git a/interface/resources/qml/hifi/tablet/TabletAttachmentsDialog.qml b/interface/resources/qml/hifi/tablet/TabletAttachmentsDialog.qml index 634c9d41ec..19548365aa 100644 --- a/interface/resources/qml/hifi/tablet/TabletAttachmentsDialog.qml +++ b/interface/resources/qml/hifi/tablet/TabletAttachmentsDialog.qml @@ -25,7 +25,6 @@ Item { property bool keyboardRaised: false property bool punctuationMode: false - property var eventBridge; signal sendToScript(var message); anchors.fill: parent diff --git a/interface/resources/qml/hifi/tablet/TabletAudioPreferences.qml b/interface/resources/qml/hifi/tablet/TabletAudioPreferences.qml index 2046071e4c..b30e741be9 100644 --- a/interface/resources/qml/hifi/tablet/TabletAudioPreferences.qml +++ b/interface/resources/qml/hifi/tablet/TabletAudioPreferences.qml @@ -19,7 +19,6 @@ StackView { objectName: "stack" property string title: "Audio Settings" - property var eventBridge; signal sendToScript(var message); function pushSource(path) { diff --git a/interface/resources/qml/hifi/tablet/TabletAvatarPreferences.qml b/interface/resources/qml/hifi/tablet/TabletAvatarPreferences.qml index 75973f32ae..94fb29c6a1 100644 --- a/interface/resources/qml/hifi/tablet/TabletAvatarPreferences.qml +++ b/interface/resources/qml/hifi/tablet/TabletAvatarPreferences.qml @@ -19,7 +19,6 @@ StackView { objectName: "stack" property string title: "Avatar Settings" - property var eventBridge; signal sendToScript(var message); function pushSource(path) { diff --git a/interface/resources/qml/hifi/tablet/TabletGeneralPreferences.qml b/interface/resources/qml/hifi/tablet/TabletGeneralPreferences.qml index 17d3f1b959..fe043f6ac7 100644 --- a/interface/resources/qml/hifi/tablet/TabletGeneralPreferences.qml +++ b/interface/resources/qml/hifi/tablet/TabletGeneralPreferences.qml @@ -19,7 +19,6 @@ StackView { objectName: "stack" property string title: "General Settings" property alias gotoPreviousApp: root.gotoPreviousApp; - property var eventBridge; signal sendToScript(var message); function pushSource(path) { diff --git a/interface/resources/qml/hifi/tablet/TabletGraphicsPreferences.qml b/interface/resources/qml/hifi/tablet/TabletGraphicsPreferences.qml index 95ee2c3a72..25b5be05f2 100644 --- a/interface/resources/qml/hifi/tablet/TabletGraphicsPreferences.qml +++ b/interface/resources/qml/hifi/tablet/TabletGraphicsPreferences.qml @@ -19,7 +19,6 @@ StackView { objectName: "stack" property string title: "Graphics Settings" - property var eventBridge; signal sendToScript(var message); function pushSource(path) { diff --git a/interface/resources/qml/hifi/tablet/TabletLodPreferences.qml b/interface/resources/qml/hifi/tablet/TabletLodPreferences.qml index 6f38fee8b9..b502c26245 100644 --- a/interface/resources/qml/hifi/tablet/TabletLodPreferences.qml +++ b/interface/resources/qml/hifi/tablet/TabletLodPreferences.qml @@ -19,7 +19,6 @@ StackView { objectName: "stack" property string title: "LOD Settings" - property var eventBridge; signal sendToScript(var message); function pushSource(path) { diff --git a/interface/resources/qml/hifi/tablet/TabletMenu.qml b/interface/resources/qml/hifi/tablet/TabletMenu.qml index 62b61d129b..457fe84c3a 100644 --- a/interface/resources/qml/hifi/tablet/TabletMenu.qml +++ b/interface/resources/qml/hifi/tablet/TabletMenu.qml @@ -21,7 +21,6 @@ FocusScope { property var point: Qt.point(50, 50); TabletMenuStack { id: menuPopperUpper } property string subMenu: "" - property var eventBridge; signal sendToScript(var message); Rectangle { diff --git a/interface/resources/qml/hifi/tablet/TabletMenuStack.qml b/interface/resources/qml/hifi/tablet/TabletMenuStack.qml index bacc11228e..2fd33e9cbc 100644 --- a/interface/resources/qml/hifi/tablet/TabletMenuStack.qml +++ b/interface/resources/qml/hifi/tablet/TabletMenuStack.qml @@ -49,7 +49,6 @@ Item { function pushSource(path) { d.push(Qt.resolvedUrl(path)); - d.currentItem.eventBridge = tabletMenu.eventBridge d.currentItem.sendToScript.connect(tabletMenu.sendToScript); d.currentItem.focus = true; d.currentItem.forceActiveFocus(); diff --git a/interface/resources/qml/hifi/tablet/TabletNetworkingPreferences.qml b/interface/resources/qml/hifi/tablet/TabletNetworkingPreferences.qml index 7184d91044..91d6140fc3 100644 --- a/interface/resources/qml/hifi/tablet/TabletNetworkingPreferences.qml +++ b/interface/resources/qml/hifi/tablet/TabletNetworkingPreferences.qml @@ -19,7 +19,6 @@ StackView { objectName: "stack" property var title: "Networking Settings" - property var eventBridge; signal sendToScript(var message); function pushSource(path) { diff --git a/interface/resources/qml/hifi/tablet/TabletRoot.qml b/interface/resources/qml/hifi/tablet/TabletRoot.qml index e7654d9ff1..b6cdce0853 100644 --- a/interface/resources/qml/hifi/tablet/TabletRoot.qml +++ b/interface/resources/qml/hifi/tablet/TabletRoot.qml @@ -8,7 +8,6 @@ Item { id: tabletRoot objectName: "tabletRoot" property string username: "Unknown user" - property var eventBridge; property var rootMenu; property var openModal: null; property var openMessage: null; @@ -111,7 +110,6 @@ Item { function openBrowserWindow(request, profile) { var component = Qt.createComponent("../../controls/TabletWebView.qml"); var newWindow = component.createObject(tabletRoot); - newWindow.eventBridge = tabletRoot.eventBridge; newWindow.remove = true; newWindow.profile = profile; request.openIn(newWindow.webView); @@ -175,7 +173,7 @@ Item { // Hook up callback for clara.io download from the marketplace. Connections { id: eventBridgeConnection - target: null + target: eventBridge onWebEventReceived: { if (message.slice(0, 17) === "CLARA.IO DOWNLOAD") { ApplicationInterface.addAssetToWorldFromURL(message.slice(18)); @@ -184,10 +182,6 @@ Item { } onLoaded: { - if (loader.item.hasOwnProperty("eventBridge")) { - loader.item.eventBridge = eventBridge; - eventBridgeConnection.target = eventBridge - } if (loader.item.hasOwnProperty("sendToScript")) { loader.item.sendToScript.connect(tabletRoot.sendToScript); } diff --git a/interface/resources/qml/hifi/tablet/WindowRoot.qml b/interface/resources/qml/hifi/tablet/WindowRoot.qml index ee8dbbff59..12f302d60a 100644 --- a/interface/resources/qml/hifi/tablet/WindowRoot.qml +++ b/interface/resources/qml/hifi/tablet/WindowRoot.qml @@ -18,7 +18,6 @@ Windows.ScrollingWindow { id: tabletRoot objectName: "tabletRoot" property string username: "Unknown user" - property var eventBridge; property var rootMenu; property string subMenu: "" @@ -93,7 +92,7 @@ Windows.ScrollingWindow { // Hook up callback for clara.io download from the marketplace. Connections { id: eventBridgeConnection - target: null + target: eventBridge onWebEventReceived: { if (message.slice(0, 17) === "CLARA.IO DOWNLOAD") { ApplicationInterface.addAssetToWorldFromURL(message.slice(18)); @@ -102,10 +101,6 @@ Windows.ScrollingWindow { } onLoaded: { - if (loader.item.hasOwnProperty("eventBridge")) { - loader.item.eventBridge = eventBridge; - eventBridgeConnection.target = eventBridge - } if (loader.item.hasOwnProperty("sendToScript")) { loader.item.sendToScript.connect(tabletRoot.sendToScript); } diff --git a/interface/resources/qml/hifi/tablet/tabletWindows/preferences/TabletAvatarBrowser.qml b/interface/resources/qml/hifi/tablet/tabletWindows/preferences/TabletAvatarBrowser.qml index 029cf7d46b..cab76bf818 100644 --- a/interface/resources/qml/hifi/tablet/tabletWindows/preferences/TabletAvatarBrowser.qml +++ b/interface/resources/qml/hifi/tablet/tabletWindows/preferences/TabletAvatarBrowser.qml @@ -27,8 +27,6 @@ Item { property bool keyboardRaised: false property bool punctuationMode: false - property alias eventBridge: eventBridgeWrapper.eventBridge - anchors.fill: parent BaseWebView { @@ -43,14 +41,6 @@ Item { bottom: footer.top } - QtObject { - id: eventBridgeWrapper - WebChannel.id: "eventBridgeWrapper" - property var eventBridge; - } - - webChannel.registeredObjects: [eventBridgeWrapper] - // Create a global EventBridge object for raiseAndLowerKeyboard. WebEngineScript { id: createGlobalEventBridge @@ -68,6 +58,11 @@ Item { } userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard ] + + Component.onCompleted: { + webChannel.registerObject("eventBridge", eventBridge); + webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper); + } } Rectangle { diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index b949110a2a..9ce6cc9b25 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -114,6 +114,7 @@ #include #include #include +#include #include #include #include @@ -1866,15 +1867,9 @@ void Application::initializeGL() { // Set up the render engine render::CullFunctor cullFunctor = LODManager::shouldRender; - _renderEngine->addJob("RenderShadowTask", cullFunctor); - const auto items = _renderEngine->addJob("FetchCullSort", cullFunctor); - assert(items.canCast()); static const QString RENDER_FORWARD = "HIFI_RENDER_FORWARD"; - if (QProcessEnvironment::systemEnvironment().contains(RENDER_FORWARD)) { - _renderEngine->addJob("Forward", items); - } else { - _renderEngine->addJob("RenderDeferredTask", items); - } + bool isDeferred = !QProcessEnvironment::systemEnvironment().contains(RENDER_FORWARD); + _renderEngine->addJob("RenderMainView", cullFunctor, isDeferred); _renderEngine->load(); _renderEngine->registerScene(_main3DScene); @@ -4126,10 +4121,10 @@ void Application::updateMyAvatarLookAtPosition() { } } else { // I am not looking at anyone else, so just look forward - if (isHMD) { - glm::mat4 worldHeadMat = myAvatar->getSensorToWorldMatrix() * - myAvatar->getHeadControllerPoseInSensorFrame().getMatrix(); - lookAtSpot = transformPoint(worldHeadMat, glm::vec3(0.0f, 0.0f, -TREE_SCALE)); + auto headPose = myAvatar->getHeadControllerPoseInSensorFrame(); + if (headPose.isValid()) { + glm::mat4 worldHeadMat = myAvatar->getSensorToWorldMatrix() * headPose.getMatrix(); + lookAtSpot = transformPoint(worldHeadMat, glm::vec3(0.0f, 0.0f, TREE_SCALE)); } else { lookAtSpot = myAvatar->getHead()->getEyePosition() + (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 Item::Bound payloadGetBound(const WorldBoxRenderData::Pointer& stuff) { return Item::Bound(); } 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"); auto& batch = *args->_batch; diff --git a/interface/src/AvatarBookmarks.cpp b/interface/src/AvatarBookmarks.cpp index 5cdfc8213f..db2a240b92 100644 --- a/interface/src/AvatarBookmarks.cpp +++ b/interface/src/AvatarBookmarks.cpp @@ -21,15 +21,35 @@ #include "MainWindow.h" #include "Menu.h" - #include "AvatarBookmarks.h" +#include "InterfaceLogging.h" + #include AvatarBookmarks::AvatarBookmarks() { - _bookmarksFilename = QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/" + AVATARBOOKMARKS_FILENAME; + _bookmarksFilename = PathUtils::getAppDataPath() + "/" + AVATARBOOKMARKS_FILENAME; 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) { // Add menus/actions auto bookmarkAction = menubar->addActionToQMenuAndActionHash(menu, MenuOption::BookmarkAvatar); diff --git a/interface/src/AvatarBookmarks.h b/interface/src/AvatarBookmarks.h index 725af88b0d..dc5a0aee6e 100644 --- a/interface/src/AvatarBookmarks.h +++ b/interface/src/AvatarBookmarks.h @@ -29,6 +29,7 @@ public slots: protected: void addBookmarkToMenu(Menu* menubar, const QString& name, const QString& address) override; + void readFromFile(); private: const QString AVATARBOOKMARKS_FILENAME = "avatarbookmarks.json"; diff --git a/interface/src/CrashHandler.cpp b/interface/src/CrashHandler.cpp index 19e887eb6e..f7d71de3fe 100644 --- a/interface/src/CrashHandler.cpp +++ b/interface/src/CrashHandler.cpp @@ -73,7 +73,7 @@ CrashHandler::Action CrashHandler::promptUserForAction(bool showCrashMessage) { layout->addWidget(label); 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"); option3->setChecked(true); layout->addWidget(option1); @@ -95,7 +95,7 @@ CrashHandler::Action CrashHandler::promptUserForAction(bool showCrashMessage) { return CrashHandler::DELETE_INTERFACE_INI; } 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) { - 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 return; } @@ -116,12 +116,15 @@ void CrashHandler::handleCrash(CrashHandler::Action action) { const QString DISPLAY_NAME_KEY = "displayName"; const QString FULL_AVATAR_URL_KEY = "fullAvatarURL"; const QString FULL_AVATAR_MODEL_NAME_KEY = "fullAvatarModelName"; + const QString TUTORIAL_COMPLETE_FLAG_KEY = "tutorialComplete"; + QString displayName; QUrl fullAvatarURL; QString fullAvatarModelName; QUrl address; + bool tutorialComplete = false; - if (action == CrashHandler::RETAIN_AVATAR_INFO) { + if (action == CrashHandler::RETAIN_IMPORTANT_INFO) { // Read avatar info // Location and orientation @@ -135,6 +138,9 @@ void CrashHandler::handleCrash(CrashHandler::Action action) { fullAvatarURL = settings.value(FULL_AVATAR_URL_KEY).toUrl(); fullAvatarModelName = settings.value(FULL_AVATAR_MODEL_NAME_KEY).toString(); settings.endGroup(); + + // Tutorial complete + tutorialComplete = settings.value(TUTORIAL_COMPLETE_FLAG_KEY).toBool(); } // Delete Interface.ini @@ -143,7 +149,7 @@ void CrashHandler::handleCrash(CrashHandler::Action action) { settingsFile.remove(); } - if (action == CrashHandler::RETAIN_AVATAR_INFO) { + if (action == CrashHandler::RETAIN_IMPORTANT_INFO) { // Write avatar info // Location and orientation @@ -157,6 +163,9 @@ void CrashHandler::handleCrash(CrashHandler::Action action) { settings.setValue(FULL_AVATAR_URL_KEY, fullAvatarURL); settings.setValue(FULL_AVATAR_MODEL_NAME_KEY, fullAvatarModelName); settings.endGroup(); + + // Tutorial complete + settings.setValue(TUTORIAL_COMPLETE_FLAG_KEY, tutorialComplete); } } diff --git a/interface/src/CrashHandler.h b/interface/src/CrashHandler.h index da2e1575db..bff8bba6dd 100644 --- a/interface/src/CrashHandler.h +++ b/interface/src/CrashHandler.h @@ -22,7 +22,7 @@ public: private: enum Action { DELETE_INTERFACE_INI, - RETAIN_AVATAR_INFO, + RETAIN_IMPORTANT_INFO, DO_NOTHING }; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index bc621543e3..24a25f314d 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -794,6 +794,77 @@ controller::Pose MyAvatar::getRightHandTipPose() const { 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 void MyAvatar::render(RenderArgs* renderArgs) { // 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); } -void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix, bool hasDriveInput) { - _desiredBodyMatrix = desiredBodyMatrix; +void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, + const glm::mat4& currentBodyMatrix, bool hasDriveInput) { if (myAvatar.getHMDLeanRecenterEnabled()) { 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; AnimPose followWorldPose(currentWorldMatrix); diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 3e2581382d..cfe66eb10e 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -378,6 +378,15 @@ public: Q_INVOKABLE controller::Pose getLeftHandTipPose() 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; } void updateLookAtTargetAvatar(); void clearLookAtTargetAvatar(); @@ -702,7 +711,6 @@ private: Vertical, NumFollowTypes }; - glm::mat4 _desiredBodyMatrix; float _timeRemaining[NumFollowTypes]; void deactivate(); diff --git a/interface/src/scripting/AudioDeviceScriptingInterface.cpp b/interface/src/scripting/AudioDeviceScriptingInterface.cpp index 05168b0d4c..d22f948344 100644 --- a/interface/src/scripting/AudioDeviceScriptingInterface.cpp +++ b/interface/src/scripting/AudioDeviceScriptingInterface.cpp @@ -9,7 +9,9 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include "AudioClient.h" +#include +#include + #include "AudioDeviceScriptingInterface.h" #include "SettingsScriptingInterface.h" @@ -44,17 +46,23 @@ AudioDeviceScriptingInterface::AudioDeviceScriptingInterface(): QAbstractListMod onDeviceChanged(); //set up previously saved device 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) { + qCDebug(audioclient) << __FUNCTION__ << "about to call setInputDeviceAsync() device: [" << inDevice << "] _currentInputDevice:" << _currentInputDevice; 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) { + qCDebug(audioclient) << __FUNCTION__ << "about to call setOutputDeviceAsync() outDevice: [" << outDevice << "] _currentOutputDevice:" << _currentOutputDevice; setOutputDeviceAsync(outDevice); } } bool AudioDeviceScriptingInterface::setInputDevice(const QString& deviceName) { + qCDebug(audioclient) << __FUNCTION__ << "deviceName:" << deviceName; + bool result; QMetaObject::invokeMethod(DependencyManager::get().data(), "switchInputToAudioDevice", Qt::BlockingQueuedConnection, @@ -64,6 +72,9 @@ bool AudioDeviceScriptingInterface::setInputDevice(const QString& deviceName) { } bool AudioDeviceScriptingInterface::setOutputDevice(const QString& deviceName) { + + qCDebug(audioclient) << __FUNCTION__ << "deviceName:" << deviceName; + bool result; QMetaObject::invokeMethod(DependencyManager::get().data(), "switchOutputToAudioDevice", Qt::BlockingQueuedConnection, @@ -86,8 +97,10 @@ bool AudioDeviceScriptingInterface::setDeviceFromMenu(const QString& deviceMenuN for (ScriptingAudioDeviceInfo di: _devices) { if (mode == di.mode && deviceMenuName.contains(di.name)) { if (mode == QAudio::AudioOutput) { + qCDebug(audioclient) << __FUNCTION__ << "about to call setOutputDeviceAsync() device: [" << di.name << "]"; setOutputDeviceAsync(di.name); } else { + qCDebug(audioclient) << __FUNCTION__ << "about to call setInputDeviceAsync() device: [" << di.name << "]"; setInputDeviceAsync(di.name); } return true; @@ -98,12 +111,26 @@ bool AudioDeviceScriptingInterface::setDeviceFromMenu(const QString& deviceMenuN } 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().data(), "switchInputToAudioDevice", Qt::QueuedConnection, Q_ARG(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().data(), "switchOutputToAudioDevice", Qt::QueuedConnection, Q_ARG(const QString&, deviceName)); @@ -241,8 +268,11 @@ void AudioDeviceScriptingInterface::onCurrentInputDeviceChanged(const QString& n void AudioDeviceScriptingInterface::onCurrentOutputDeviceChanged(const QString& name) { 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 SettingsScriptingInterface* settings = SettingsScriptingInterface::getInstance(); + qCDebug(audioclient) << __FUNCTION__ << "about to call settings->setValue('audio_output_device', name); name:" << name; settings->setValue("audio_output_device", name); emit currentOutputDeviceChanged(name); } diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 6caa4fb159..2f812724c5 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -166,7 +166,10 @@ AudioClient::AudioClient() : connect(&_receivedAudioStream, &MixedProcessedAudioStream::processSamples, 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); @@ -379,7 +382,8 @@ QAudioDeviceInfo defaultAudioDeviceForMode(QAudio::Mode mode) { 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); #endif @@ -555,8 +559,12 @@ void AudioClient::start() { } void AudioClient::stop() { + // "switch" to invalid devices in order to shut down the state + qCDebug(audioclient) << "AudioClient::stop(), about to call switchInputToAudioDevice(null)"; switchInputToAudioDevice(QAudioDeviceInfo()); + + qCDebug(audioclient) << "AudioClient::stop(), about to call switchOutputToAudioDevice(null)"; switchOutputToAudioDevice(QAudioDeviceInfo()); } @@ -748,12 +756,12 @@ QVector AudioClient::getDeviceNames(QAudio::Mode mode) { } 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)); } 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)); } @@ -1298,6 +1306,7 @@ void AudioClient::setIsStereoInput(bool isStereoInput) { } // change in channel count for desired input format, restart the input device + qCDebug(audioclient) << __FUNCTION__ << "about to call switchInputToAudioDevice:" << _inputAudioDeviceName; switchInputToAudioDevice(_inputAudioDeviceName); } } @@ -1334,6 +1343,7 @@ void AudioClient::outputFormatChanged() { } bool AudioClient::switchInputToAudioDevice(const QAudioDeviceInfo& inputDeviceInfo) { + qCDebug(audioclient) << __FUNCTION__ << "inputDeviceInfo: [" << inputDeviceInfo.deviceName() << "]"; bool supportedFormat = false; // cleanup any previously initialized device @@ -1448,6 +1458,8 @@ void AudioClient::outputNotify() { } bool AudioClient::switchOutputToAudioDevice(const QAudioDeviceInfo& outputDeviceInfo) { + qCDebug(audioclient) << "AudioClient::switchOutputToAudioDevice() outputDeviceInfo: [" << outputDeviceInfo.deviceName() << "]"; + bool supportedFormat = false; Lock localAudioLock(_localAudioMutex); @@ -1582,7 +1594,11 @@ bool AudioClient::switchOutputToAudioDevice(const QAudioDeviceInfo& outputDevice } 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); + qCDebug(audioclient) << __FUNCTION__ << "clamped numFrames:" << numFrames << "_sessionOutputBufferSizeFrames:" << _sessionOutputBufferSizeFrames; + if (numFrames != _sessionOutputBufferSizeFrames) { qCInfo(audioclient, "Audio output buffer set to %d frames", 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 // recreate the device by switching to the default. 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. } } diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp index cab96c258b..08c8d4f754 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp @@ -221,6 +221,12 @@ void HmdDisplayPlugin::internalPresent() { float shiftLeftBy = getLeftCenterPixel() - (sourceSize.x / 2); 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_X = 16; glm::uvec2 originalClippedSize { newWidth, newWidth * RATIO_Y / RATIO_X }; diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index eab0e3011e..e89646d838 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -605,7 +605,7 @@ bool RenderableModelEntityItem::findDetailedRayIntersection(const glm::vec3& ori QString extraInfo; return _model->findRayIntersectionAgainstSubMeshes(origin, direction, distance, - face, surfaceNormal, extraInfo, precisionPicking, precisionPicking); + face, surfaceNormal, extraInfo, precisionPicking, false); } void RenderableModelEntityItem::getCollisionGeometryResource() { diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp index 236daf6443..6d4f586c52 100644 --- a/libraries/fbx/src/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -460,17 +460,11 @@ FBXLight extractLight(const FBXNode& object) { } QByteArray fileOnUrl(const QByteArray& filepath, const QString& url) { - QString path = QFileInfo(url).path(); - QByteArray filename = filepath; - QFileInfo checkFile(path + "/" + filepath); + // in order to match the behaviour when loading models from remote URLs + // we assume that all external textures are right beside the loaded model + // ignoring any relative paths or absolute paths inside of models - // check if the file exists at the RelativeFilename - if (!(checkFile.exists() && checkFile.isFile())) { - // if not, assume it is in the fbx directory - filename = filename.mid(filename.lastIndexOf('/') + 1); - } - - return filename; + return filepath.mid(filepath.lastIndexOf('/') + 1); } FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QString& url) { diff --git a/libraries/gl/src/gl/OffscreenQmlSurface.cpp b/libraries/gl/src/gl/OffscreenQmlSurface.cpp index 65c311424f..9dcc1d7991 100644 --- a/libraries/gl/src/gl/OffscreenQmlSurface.cpp +++ b/libraries/gl/src/gl/OffscreenQmlSurface.cpp @@ -277,6 +277,23 @@ QString getEventBridgeJavascript() { 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) { Q_ASSERT(QThread::currentThread() == qApp->thread()); @@ -430,7 +447,6 @@ OffscreenQmlSurface::~OffscreenQmlSurface() { _canvas->deleteLater(); _rootItem->deleteLater(); - _qmlComponent->deleteLater(); _quickWindow->deleteLater(); releaseEngine(); } @@ -473,11 +489,12 @@ void OffscreenQmlSurface::create(QOpenGLContext* shareContext) { _qmlContext = new QQmlContext(qmlEngine->rootContext()); _qmlContext->setContextProperty("offscreenWindow", QVariant::fromValue(getWindow())); - _qmlContext->setContextProperty("globalEventBridge", this); + _qmlContext->setContextProperty("eventBridge", 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()) { qWarning("Failed to make context current for QML Renderer"); @@ -577,71 +594,79 @@ void OffscreenQmlSurface::setBaseUrl(const QUrl& baseUrl) { _qmlContext->setBaseUrl(baseUrl); } -QObject* OffscreenQmlSurface::load(const QUrl& qmlSource, std::function f) { +QObject* OffscreenQmlSurface::load(const QUrl& qmlSource, bool createNewContext, std::function f) { // Synchronous loading may take a while; restart the deadlock timer QMetaObject::invokeMethod(qApp, "updateHeartbeat", Qt::DirectConnection); - if ((qmlSource.isRelative() && !qmlSource.isEmpty()) || qmlSource.scheme() == QLatin1String("file")) { - _qmlComponent->loadUrl(_qmlContext->resolvedUrl(qmlSource), QQmlComponent::PreferSynchronous); - } else { - _qmlComponent->loadUrl(qmlSource, QQmlComponent::PreferSynchronous); + QQmlContext* targetContext = _qmlContext; + if (_rootItem && createNewContext) { + targetContext = new QQmlContext(targetContext); } + QUrl finalQmlSource = qmlSource; + if ((qmlSource.isRelative() && !qmlSource.isEmpty()) || qmlSource.scheme() == QLatin1String("file")) { + finalQmlSource = _qmlContext->resolvedUrl(qmlSource); + } - if (_qmlComponent->isLoading()) { - connect(_qmlComponent, &QQmlComponent::statusChanged, this, - [this, f](QQmlComponent::Status){ - finishQmlLoad(f); - }); + auto qmlComponent = new QQmlComponent(_qmlContext->engine(), finalQmlSource, QQmlComponent::PreferSynchronous); + if (qmlComponent->isLoading()) { + connect(qmlComponent, &QQmlComponent::statusChanged, this, + [this, qmlComponent, targetContext, f](QQmlComponent::Status) { + finishQmlLoad(qmlComponent, targetContext, f); + }); return nullptr; } - return finishQmlLoad(f); + return finishQmlLoad(qmlComponent, targetContext, f); +} + +QObject* OffscreenQmlSurface::loadInNewContext(const QUrl& qmlSource, std::function f) { + return load(qmlSource, true, f); +} + +QObject* OffscreenQmlSurface::load(const QUrl& qmlSource, std::function f) { + return load(qmlSource, false, f); } void OffscreenQmlSurface::clearCache() { _qmlContext->engine()->clearComponentCache(); } -QObject* OffscreenQmlSurface::finishQmlLoad(std::function f) { -#if 0 - if (!_rootItem) { - QQmlComponent component(_qmlContext->engine()); - component.setData(R"QML( -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 errorList = _qmlComponent->errors(); - foreach(const QQmlError& error, errorList) { - qWarning() << error.url() << error.line() << error; +QObject* OffscreenQmlSurface::finishQmlLoad(QQmlComponent* qmlComponent, QQmlContext* qmlContext, std::function f) { + disconnect(qmlComponent, &QQmlComponent::statusChanged, this, 0); + if (qmlComponent->isError()) { + for (const auto& error : qmlComponent->errors()) { + qCWarning(glLogging) << error.url() << error.line() << error; } + qmlComponent->deleteLater(); return nullptr; } - QObject* newObject = _qmlComponent->beginCreate(_qmlContext); - if (_qmlComponent->isError()) { - QList errorList = _qmlComponent->errors(); - foreach(const QQmlError& error, errorList) + QObject* newObject = qmlComponent->beginCreate(qmlContext); + if (qmlComponent->isError()) { + for (const auto& error : qmlComponent->errors()) { qCWarning(glLogging) << error.url() << error.line() << error; + } if (!_rootItem) { qFatal("Unable to finish loading QML root"); } + qmlComponent->deleteLater(); return nullptr; } - _qmlContext->engine()->setObjectOwnership(this, QQmlEngine::CppOwnership); - newObject->setProperty("eventBridge", QVariant::fromValue(this)); + qmlContext->engine()->setObjectOwnership(this, QQmlEngine::CppOwnership); + f(qmlContext, newObject); - f(_qmlContext, newObject); - _qmlComponent->completeCreate(); + QObject* eventBridge = qmlContext->contextProperty("eventBridge").value(); + 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 diff --git a/libraries/gl/src/gl/OffscreenQmlSurface.h b/libraries/gl/src/gl/OffscreenQmlSurface.h index 2a078d2b4f..ae81ae48b4 100644 --- a/libraries/gl/src/gl/OffscreenQmlSurface.h +++ b/libraries/gl/src/gl/OffscreenQmlSurface.h @@ -48,6 +48,8 @@ public: void resize(const QSize& size, bool forceResize = false); QSize size() const; + Q_INVOKABLE QObject* load(const QUrl& qmlSource, bool createNewContext, std::function f = [](QQmlContext*, QObject*) {}); + Q_INVOKABLE QObject* loadInNewContext(const QUrl& qmlSource, std::function f = [](QQmlContext*, QObject*) {}); Q_INVOKABLE QObject* load(const QUrl& qmlSource, std::function f = [](QQmlContext*, QObject*) {}); Q_INVOKABLE QObject* load(const QString& qmlSourceFile, std::function f = [](QQmlContext*, QObject*) {}) { return load(QUrl(qmlSourceFile), f); @@ -118,7 +120,7 @@ protected: void setFocusText(bool newFocusText); private: - QObject* finishQmlLoad(std::function f); + QObject* finishQmlLoad(QQmlComponent* qmlComponent, QQmlContext* qmlContext, std::function f); QPointF mapWindowToUi(const QPointF& sourcePosition, QObject* sourceObject); void setupFbo(); bool allowNewFrame(uint8_t fps); @@ -134,7 +136,6 @@ private: QQuickWindow* _quickWindow { nullptr }; QMyQuickRenderControl* _renderControl{ nullptr }; QQmlContext* _qmlContext { nullptr }; - QQmlComponent* _qmlComponent { nullptr }; QQuickItem* _rootItem { nullptr }; OffscreenGLCanvas* _canvas { nullptr }; diff --git a/libraries/gpu-gl/src/gpu/gl/GLBackendOutput.cpp b/libraries/gpu-gl/src/gpu/gl/GLBackendOutput.cpp index 1e6691538b..5c6a18d7af 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLBackendOutput.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLBackendOutput.cpp @@ -63,11 +63,17 @@ void GLBackend::do_clearFramebuffer(const Batch& batch, size_t paramOffset) { int useScissor = batch._params[paramOffset + 0]._int; GLuint glmask = 0; + bool restoreStencilMask = false; + uint8_t cacheStencilMask = 0xFF; if (masks & Framebuffer::BUFFER_STENCIL) { glClearStencil(stencil); 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; @@ -121,6 +127,11 @@ void GLBackend::do_clearFramebuffer(const Batch& batch, size_t paramOffset) { glDisable(GL_SCISSOR_TEST); } + // Restore Stencil write mask + if (restoreStencilMask) { + glStencilMask(cacheStencilMask); + } + // Restore write mask meaning turn back off if (restoreDepthMask) { glDepthMask(GL_FALSE); diff --git a/libraries/networking/src/UserActivityLogger.cpp b/libraries/networking/src/UserActivityLogger.cpp index 28117c0933..0cfd1e09e7 100644 --- a/libraries/networking/src/UserActivityLogger.cpp +++ b/libraries/networking/src/UserActivityLogger.cpp @@ -20,6 +20,10 @@ #include #include "AddressManager.h" +UserActivityLogger::UserActivityLogger() { + _timer.start(); +} + UserActivityLogger& UserActivityLogger::getInstance() { static UserActivityLogger sharedInstance; return sharedInstance; @@ -42,6 +46,12 @@ void UserActivityLogger::logAction(QString action, QJsonObject details, JSONCall actionPart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data; name=\"action_name\""); actionPart.setBody(QByteArray().append(action)); 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 (!details.isEmpty()) { diff --git a/libraries/networking/src/UserActivityLogger.h b/libraries/networking/src/UserActivityLogger.h index 9fad498b86..179e8e6e66 100644 --- a/libraries/networking/src/UserActivityLogger.h +++ b/libraries/networking/src/UserActivityLogger.h @@ -18,6 +18,7 @@ #include #include #include +#include #include #include "AddressManager.h" @@ -51,8 +52,10 @@ private slots: void requestError(QNetworkReply& errorReply); private: - UserActivityLogger() {}; + UserActivityLogger(); Setting::Handle _disabled { "UserActivityLoggerDisabled", false }; + + QElapsedTimer _timer; }; #endif // hifi_UserActivityLogger_h diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index 9d970fa318..d59da2f726 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -56,7 +56,7 @@ PacketVersion versionForPacketType(PacketType packetType) { case PacketType::AvatarData: case PacketType::BulkAvatarData: case PacketType::KillAvatar: - return static_cast(AvatarMixerPacketVersion::AvatarIdentitySequenceId); + return static_cast(AvatarMixerPacketVersion::MannequinDefaultAvatar); case PacketType::MessagesData: return static_cast(MessageDataVersion::TextOrBinaryData); case PacketType::ICEServerHeartbeat: diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 2cc3a2c42e..fa1151e0a6 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -236,7 +236,8 @@ enum class AvatarMixerPacketVersion : PacketVersion { AvatarAsChildFixes, StickAndBallDefaultAvatar, IdentityPacketsIncludeUpdateTime, - AvatarIdentitySequenceId + AvatarIdentitySequenceId, + MannequinDefaultAvatar }; enum class DomainConnectRequestVersion : PacketVersion { diff --git a/libraries/render-utils/src/AntialiasingEffect.cpp b/libraries/render-utils/src/AntialiasingEffect.cpp index 139f1ae091..f7881b0333 100644 --- a/libraries/render-utils/src/AntialiasingEffect.cpp +++ b/libraries/render-utils/src/AntialiasingEffect.cpp @@ -110,10 +110,6 @@ void Antialiasing::run(const render::RenderContextPointer& renderContext, const RenderArgs* args = renderContext->args; - if (args->_renderMode == RenderArgs::MIRROR_RENDER_MODE) { - return; - } - gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { batch.enableStereo(false); batch.setViewportTransform(args->_viewport); diff --git a/libraries/render-utils/src/DeferredLightingEffect.cpp b/libraries/render-utils/src/DeferredLightingEffect.cpp index 36a9401d00..4b3ee9fec7 100644 --- a/libraries/render-utils/src/DeferredLightingEffect.cpp +++ b/libraries/render-utils/src/DeferredLightingEffect.cpp @@ -504,10 +504,7 @@ void RenderDeferredSetup::run(const render::RenderContextPointer& renderContext, { // Framebuffer copy operations cannot function as multipass stereo operations. batch.enableStereo(false); - - // perform deferred lighting, rendering to free fbo - auto framebufferCache = DependencyManager::get(); - + auto textureCache = DependencyManager::get(); auto deferredLightingEffect = DependencyManager::get(); diff --git a/libraries/render-utils/src/GeometryCache.cpp b/libraries/render-utils/src/GeometryCache.cpp index cd87cfdb3d..dcf90012c1 100644 --- a/libraries/render-utils/src/GeometryCache.cpp +++ b/libraries/render-utils/src/GeometryCache.cpp @@ -69,7 +69,7 @@ std::vector polygon() { std::vector result; result.reserve(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; 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& face = shape.faces[f]; // Compute the face normal vec3 faceNormal = shape.getFaceNormal(f); // Create the vertices for the face - for (Index i = 0; i < N; ++i) { + for (Index i = 0; i < N; i++) { Index originalIndex = face[i]; vertices.push_back(shape.vertices[originalIndex]); vertices.push_back(faceNormal); } // 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 b = (i + 1) % N; auto token = indexToken(face[a], face[b]); @@ -197,7 +197,7 @@ void setupFlatShape(GeometryCache::ShapeData& shapeData, const geometry::Solid& face = shape.faces[f]; // 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 b = face[(i + 1) % N]; auto token = indexToken(a, b); @@ -244,7 +244,7 @@ void setupSmoothShape(GeometryCache::ShapeData& shapeData, const geometry::Solid } // 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 + 1] + baseVertex); solidIndices.push_back(face[i + 2] + baseVertex); @@ -256,23 +256,30 @@ void setupSmoothShape(GeometryCache::ShapeData& shapeData, const geometry::Solid } template -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; Index baseVertex = (Index)(vertexBuffer->getSize() / SHAPE_VERTEX_STRIDE); VertexVector vertices; IndexVector solidIndices, wireIndices; - // Top and bottom faces + // Top (if not conical) and bottom faces std::vector shape = polygon(); - for (const vec3& v : shape) { - vertices.push_back(vec3(v.x, 0.5f, v.z)); - vertices.push_back(vec3(0, 1, 0)); + if (isConical) { + for (uint32_t i = 0; i < N; i++) { + 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) { 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 + i); 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); } - 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 - 1); wireIndices.push_back(baseVertex + (i % N) + N); @@ -290,12 +297,12 @@ void extrudePolygon(GeometryCache::ShapeData& shapeData, gpu::BufferPointer& ver // Now do the sides baseVertex += 2 * N; - for (uint32_t i = 0; i < N; ++i) { + for (uint32_t i = 0; i < N; i++) { vec3 left = shape[i]; vec3 right = shape[(i + 1) % N]; vec3 normal = glm::normalize(left + right); - vec3 topLeft = vec3(left.x, 0.5f, left.z); - vec3 topRight = vec3(right.x, 0.5f, right.z); + vec3 topLeft = (isConical ? vec3(0.0f, 0.5f, 0.0f) : vec3(left.x, 0.5f, left.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 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); } +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 shape = polygon(); + 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 // components do not. Find a way to support using draw elements // or draw arrays as appropriate @@ -357,8 +399,8 @@ void GeometryCache::buildShapes() { Index baseVertex = (Index)(_shapeVertices->getSize() / SHAPE_VERTEX_STRIDE); ShapeData& shapeData = _shapes[Line]; shapeData.setupVertices(_shapeVertices, VertexVector { - vec3(-0.5, 0, 0), vec3(-0.5f, 0, 0), - vec3(0.5f, 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.0f, 0.0f), vec3(0.5f, 0.0f, 0.0f) }); IndexVector wireIndices; // Only two indices @@ -367,20 +409,22 @@ void GeometryCache::buildShapes() { shapeData.setupIndices(_shapeIndices, IndexVector(), wireIndices); } - // Not implememented yet: - //Triangle, extrudePolygon<3>(_shapes[Triangle], _shapeVertices, _shapeIndices); //Hexagon, extrudePolygon<6>(_shapes[Hexagon], _shapeVertices, _shapeIndices); //Octagon, extrudePolygon<8>(_shapes[Octagon], _shapeVertices, _shapeIndices); - - //Quad, - //Circle, - //Torus, - //Cone, //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() { @@ -597,7 +641,7 @@ void GeometryCache::updateVertices(int id, const QVector& points, con auto pointCount = points.size(); auto colorCount = colors.size(); int compactColor = 0; - for (auto i = 0; i < pointCount; ++i) { + for (auto i = 0; i < pointCount; i++) { const auto& point = points[i]; *(vertex++) = point.x; *(vertex++) = point.y; @@ -674,7 +718,7 @@ void GeometryCache::updateVertices(int id, const QVector& points, con const glm::vec3 NORMAL(0.0f, 0.0f, 1.0f); auto pointCount = points.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]; if (i < colorCount) { const glm::vec4& color = colors[i]; diff --git a/libraries/render-utils/src/GeometryCache.h b/libraries/render-utils/src/GeometryCache.h index e0a610a095..9853269280 100644 --- a/libraries/render-utils/src/GeometryCache.h +++ b/libraries/render-utils/src/GeometryCache.h @@ -142,8 +142,8 @@ public: Dodecahedron, Icosahedron, Torus, // not yet implemented - Cone, // not yet implemented - Cylinder, // not yet implemented + Cone, + Cylinder, NUM_SHAPES, }; diff --git a/libraries/render-utils/src/LightClusters.h b/libraries/render-utils/src/LightClusters.h index 105d6fb139..f495dabebb 100644 --- a/libraries/render-utils/src/LightClusters.h +++ b/libraries/render-utils/src/LightClusters.h @@ -152,9 +152,9 @@ public: int numInputLights { 0 }; int numClusteredLights { 0 }; - void setNumClusteredLightReferences(int numRefs) { numClusteredLightReferences = numRefs; emit dirty(); } - void setNumInputLights(int numLights) { numInputLights = numLights; emit dirty(); } - void setNumClusteredLights(int numLights) { numClusteredLights = numLights; emit dirty(); } + void setNumClusteredLightReferences(int numRefs) { numClusteredLightReferences = numRefs; } + void setNumInputLights(int numLights) { numInputLights = numLights; } + void setNumClusteredLights(int numLights) { numClusteredLights = numLights; } int numSceneLights { 0 }; int numFreeSceneLights { 0 }; diff --git a/libraries/render-utils/src/RenderShadowTask.cpp b/libraries/render-utils/src/RenderShadowTask.cpp index d7ec087174..ddb64bc69e 100644 --- a/libraries/render-utils/src/RenderShadowTask.cpp +++ b/libraries/render-utils/src/RenderShadowTask.cpp @@ -48,6 +48,7 @@ void RenderShadowMap::run(const render::RenderContextPointer& renderContext, RenderArgs* args = renderContext->args; gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { args->_batch = &batch; + batch.enableStereo(false); glm::ivec4 viewport{0, 0, fbo->getWidth(), fbo->getHeight()}; batch.setViewportTransform(viewport); @@ -114,7 +115,7 @@ void RenderShadowTask::build(JobModel& task, const render::Varying& input, rende skinProgram, state); } - const auto cachedMode = task.addJob("Setup"); + const auto cachedMode = task.addJob("ShadowSetup"); // CPU jobs: // 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 task.addJob("RenderShadowMap", sortedShapes, shapePlumber); - task.addJob("Teardown", cachedMode); + task.addJob("ShadowTeardown", cachedMode); } void RenderShadowTask::configure(const Config& configuration) { diff --git a/libraries/render-utils/src/RenderViewTask.cpp b/libraries/render-utils/src/RenderViewTask.cpp new file mode 100644 index 0000000000..fceaf7b5b9 --- /dev/null +++ b/libraries/render-utils/src/RenderViewTask.cpp @@ -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(); + + task.addJob("RenderShadowTask", cullFunctor); + + const auto items = task.addJob("FetchCullSort", cullFunctor); + assert(items.canCast()); + + if (isDeferred) { + task.addJob("RenderDeferredTask", items); + } else { + task.addJob("Forward", items); + } +} + diff --git a/libraries/render-utils/src/RenderViewTask.h b/libraries/render-utils/src/RenderViewTask.h new file mode 100644 index 0000000000..eb61f56eab --- /dev/null +++ b/libraries/render-utils/src/RenderViewTask.h @@ -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 +#include + + +class RenderViewTask { +public: + using Input = RenderFetchCullSortTask::Output; + using JobModel = render::Task::ModelI; + + RenderViewTask() {} + + void build(JobModel& task, const render::Varying& inputs, render::Varying& outputs, render::CullFunctor cullFunctor, bool isDeferred); + +}; + + +#endif // hifi_RenderViewTask_h diff --git a/libraries/render-utils/src/text/Font.cpp b/libraries/render-utils/src/text/Font.cpp index c6a7da3a1a..f51a779066 100644 --- a/libraries/render-utils/src/text/Font.cpp +++ b/libraries/render-utils/src/text/Font.cpp @@ -14,6 +14,8 @@ #include "../RenderUtilsLogging.h" #include "FontFamilies.h" +static std::mutex fontMutex; + struct TextureVertex { glm::vec2 pos; glm::vec2 tex; @@ -56,6 +58,7 @@ Font::Pointer Font::load(QIODevice& fontFile) { } Font::Pointer Font::load(const QString& family) { + std::lock_guard lock(fontMutex); if (!LOADED_FONTS.contains(family)) { static const QString SDFF_COURIER_PRIME_FILENAME{ ":/CourierPrime.sdff" }; diff --git a/libraries/render-utils/src/text/Font.h b/libraries/render-utils/src/text/Font.h index 5b6b4f2a43..2b61f19492 100644 --- a/libraries/render-utils/src/text/Font.h +++ b/libraries/render-utils/src/text/Font.h @@ -31,10 +31,10 @@ public: const glm::vec4* color, EffectType effectType, const glm::vec2& bound, bool layered = false); - static Pointer load(QIODevice& fontFile); static Pointer load(const QString& family); private: + static Pointer load(QIODevice& fontFile); QStringList tokenizeForWrapping(const QString& str) const; QStringList splitLines(const QString& str) const; glm::vec2 computeTokenExtent(const QString& str) const; diff --git a/libraries/render/src/render/EngineStats.cpp b/libraries/render/src/render/EngineStats.cpp index 9e45be5dbd..ae1467ac0f 100644 --- a/libraries/render/src/render/EngineStats.cpp +++ b/libraries/render/src/render/EngineStats.cpp @@ -63,6 +63,4 @@ void EngineStats::run(const RenderContextPointer& renderContext) { config->frameSetPipelineCount = _gpuStats._PSNumSetPipelines; config->frameSetInputFormatCount = _gpuStats._ISNumFormatChanges; - - config->emitDirty(); } diff --git a/libraries/render/src/task/Config.cpp b/libraries/render/src/task/Config.cpp index cb2c4f1e3c..0e630311f6 100644 --- a/libraries/render/src/task/Config.cpp +++ b/libraries/render/src/task/Config.cpp @@ -34,6 +34,7 @@ void TaskConfig::connectChildConfig(QConfigPointer childConfig, const std::strin if (childConfig->metaObject()->indexOfSignal(DIRTY_SIGNAL) != -1) { // Connect dirty->refresh if defined 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) { // Connect dirty->refresh if defined QObject::connect(child, SIGNAL(dirty()), this, SLOT(refresh())); + QObject::connect(child, SIGNAL(dirtyEnabled()), this, SLOT(refresh())); } } } diff --git a/libraries/render/src/task/Config.h b/libraries/render/src/task/Config.h index c78a3f3bfe..40a3abbd18 100644 --- a/libraries/render/src/task/Config.h +++ b/libraries/render/src/task/Config.h @@ -89,7 +89,7 @@ protected: class JobConfig : public QObject { Q_OBJECT 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 }; public: @@ -99,7 +99,7 @@ public: JobConfig(bool enabled) : alwaysEnabled{ false }, enabled{ 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 enabled{ true }; @@ -121,6 +121,7 @@ public slots: signals: void loaded(); void newStats(); + void dirtyEnabled(); }; class TaskConfig : public JobConfig { diff --git a/libraries/render/src/task/Task.h b/libraries/render/src/task/Task.h index ed335150a7..f76ba92546 100644 --- a/libraries/render/src/task/Task.h +++ b/libraries/render/src/task/Task.h @@ -170,6 +170,7 @@ protected: std::string _name = ""; }; + // 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 // using JobModel = Task::Model diff --git a/libraries/script-engine/src/TabletScriptingInterface.cpp b/libraries/script-engine/src/TabletScriptingInterface.cpp index 644f1e6f0c..dadf436ea5 100644 --- a/libraries/script-engine/src/TabletScriptingInterface.cpp +++ b/libraries/script-engine/src/TabletScriptingInterface.cpp @@ -540,7 +540,7 @@ void TabletProxy::gotoWebScreen(const QString& url, const QString& injectedJavaS QObject* TabletProxy::addButton(const QVariant& properties) { auto tabletButtonProxy = QSharedPointer(new TabletButtonProxy(properties.toMap())); - std::lock_guard guard(_tabletMutex); + std::unique_lock guard(_tabletMutex); _tabletButtonProxies.push_back(tabletButtonProxy); if (!_toolbarMode && _qmlTabletRoot) { auto tablet = getQmlTablet(); @@ -550,7 +550,6 @@ QObject* TabletProxy::addButton(const QVariant& properties) { qCCritical(scriptengine) << "Could not find tablet in TabletRoot.qml"; } } else if (_toolbarMode) { - auto tabletScriptingInterface = DependencyManager::get(); QObject* toolbarProxy = tabletScriptingInterface->getSystemToolbarProxy(); @@ -559,6 +558,8 @@ QObject* TabletProxy::addButton(const QVariant& properties) { connectionType = Qt::BlockingQueuedConnection; } + guard.unlock(); + // copy properties from tablet button proxy to toolbar button proxy. QObject* toolbarButtonProxy = nullptr; 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) { - std::lock_guard guard(_tabletMutex); + std::unique_lock guard(_tabletMutex); auto tablet = getQmlTablet(); if (!tablet) { qCCritical(scriptengine) << "Could not find tablet in TabletRoot.qml"; } - auto iter = std::find(_tabletButtonProxies.begin(), _tabletButtonProxies.end(), tabletButtonProxy); - if (iter != _tabletButtonProxies.end()) { - if (!_toolbarMode && _qmlTabletRoot) { - (*iter)->setQmlButton(nullptr); - if (tablet) { - QMetaObject::invokeMethod(tablet, "removeButtonProxy", Qt::AutoConnection, Q_ARG(QVariant, (*iter)->getProperties())); - } - } else if (_toolbarMode) { - auto tabletScriptingInterface = DependencyManager::get(); - QObject* toolbarProxy = tabletScriptingInterface->getSystemToolbarProxy(); - - // remove button from toolbarProxy - QMetaObject::invokeMethod(toolbarProxy, "removeButton", Qt::AutoConnection, Q_ARG(QVariant, (*iter)->getUuid().toString())); - (*iter)->setToolbarButtonProxy(nullptr); + QSharedPointer buttonProxy; + { + auto iter = std::find(_tabletButtonProxies.begin(), _tabletButtonProxies.end(), tabletButtonProxy); + if (iter == _tabletButtonProxies.end()) { + qCWarning(scriptengine) << "TabletProxy::removeButton() could not find button " << tabletButtonProxy; + return; } + buttonProxy = *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(); + 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); } } diff --git a/libraries/ui/src/QmlWindowClass.cpp b/libraries/ui/src/QmlWindowClass.cpp index c0e94058ae..58d39448ac 100644 --- a/libraries/ui/src/QmlWindowClass.cpp +++ b/libraries/ui/src/QmlWindowClass.cpp @@ -104,9 +104,9 @@ void QmlWindowClass::initQml(QVariantMap properties) { Q_ASSERT(invokeResult); } else { // 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->setProperty("eventBridge", QVariant::fromValue(this)); + context->setContextProperty("eventBridge", this); context->engine()->setObjectOwnership(this, QQmlEngine::CppOwnership); context->engine()->setObjectOwnership(object, QQmlEngine::CppOwnership); if (properties.contains(TITLE_PROPERTY)) { diff --git a/plugins/openvr/CMakeLists.txt b/plugins/openvr/CMakeLists.txt index af65f3dbf7..6a95ef6d76 100644 --- a/plugins/openvr/CMakeLists.txt +++ b/plugins/openvr/CMakeLists.txt @@ -10,10 +10,10 @@ if (WIN32) # we're using static GLEW, so define GLEW_STATIC add_definitions(-DGLEW_STATIC) 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 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) @@ -21,4 +21,5 @@ if (WIN32) find_package(OpenVR REQUIRED) target_include_directories(${TARGET_NAME} PRIVATE ${OPENVR_INCLUDE_DIRS}) target_link_libraries(${TARGET_NAME} ${OPENVR_LIBRARIES}) + target_link_libraries(${TARGET_NAME} Winmm.lib) endif() diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.cpp b/plugins/openvr/src/OpenVrDisplayPlugin.cpp index 8105de7a13..15fb7d72c9 100644 --- a/plugins/openvr/src/OpenVrDisplayPlugin.cpp +++ b/plugins/openvr/src/OpenVrDisplayPlugin.cpp @@ -7,6 +7,9 @@ // #include "OpenVrDisplayPlugin.h" +// Odd ordering of header is required to avoid 'macro redinition warnings' +#include + #include #include #include @@ -713,3 +716,30 @@ bool OpenVrDisplayPlugin::isKeyboardVisible() { int OpenVrDisplayPlugin::getRequiredThreadCount() const { 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 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 deviceW; + deviceW.assign(size, INIT); + device.toWCharArray(deviceW.data()); + device = AudioClient::friendlyNameForAudioDevice(deviceW.data()); + } + return device; +} + diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.h b/plugins/openvr/src/OpenVrDisplayPlugin.h index 01e02c9892..a1bbed8754 100644 --- a/plugins/openvr/src/OpenVrDisplayPlugin.h +++ b/plugins/openvr/src/OpenVrDisplayPlugin.h @@ -58,6 +58,9 @@ public: // Possibly needs an additional thread for VR submission int getRequiredThreadCount() const override; + QString getPreferredAudioInDevice() const override; + QString getPreferredAudioOutDevice() const override; + protected: bool internalActivate() override; void internalDeactivate() override; diff --git a/plugins/openvr/src/OpenVrHelpers.cpp b/plugins/openvr/src/OpenVrHelpers.cpp index d9db757b2f..7e287a16c3 100644 --- a/plugins/openvr/src/OpenVrHelpers.cpp +++ b/plugins/openvr/src/OpenVrHelpers.cpp @@ -72,6 +72,21 @@ bool openVrSupported() { 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() { bool hmdPresent = vr::VR_IsHmdPresent(); if (hmdPresent) { @@ -82,6 +97,7 @@ vr::IVRSystem* acquireOpenVrSystem() { #endif vr::EVRInitError eError = vr::VRInitError_None; activeHmd = vr::VR_Init(&eError, vr::VRApplication_Scene); + #if DEV_BUILD qCDebug(displayplugins) << "OpenVR display: HMD is " << activeHmd << " error is " << eError; #endif diff --git a/plugins/openvr/src/OpenVrHelpers.h b/plugins/openvr/src/OpenVrHelpers.h index f00cd9e117..f4253899a2 100644 --- a/plugins/openvr/src/OpenVrHelpers.h +++ b/plugins/openvr/src/OpenVrHelpers.h @@ -25,6 +25,7 @@ bool openVrQuitRequested(); void enableOpenVrKeyboard(PluginContainer* container); void disableOpenVrKeyboard(); bool isOpenVrKeyboardShown(); +QString getVrSettingString(const char* section, const char* setting); template diff --git a/plugins/openvr/src/ViveControllerManager.cpp b/plugins/openvr/src/ViveControllerManager.cpp index b5fa7cadad..7330daf228 100644 --- a/plugins/openvr/src/ViveControllerManager.cpp +++ b/plugins/openvr/src/ViveControllerManager.cpp @@ -123,15 +123,18 @@ bool ViveControllerManager::isSupported() const { bool ViveControllerManager::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) { _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); diff --git a/scripts/developer/tests/avatarToWorldTests.js b/scripts/developer/tests/avatarToWorldTests.js new file mode 100644 index 0000000000..c6e23fc81b --- /dev/null +++ b/scripts/developer/tests/avatarToWorldTests.js @@ -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); diff --git a/scripts/developer/tests/worldToAvatarTests.js b/scripts/developer/tests/worldToAvatarTests.js new file mode 100644 index 0000000000..6f0b19dc2d --- /dev/null +++ b/scripts/developer/tests/worldToAvatarTests.js @@ -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); diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 1348734f53..a3583e7808 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -12,7 +12,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -/* global Script, SelectionDisplay, LightOverlayManager, CameraManager, Grid, GridTool, EntityListTool, Vec3, SelectionManager, Overlays, OverlayWebWindow, UserActivityLogger, +/* global Script, SelectionDisplay, LightOverlayManager, CameraManager, Grid, GridTool, EntityListTool, Vec3, SelectionManager, Overlays, OverlayWebWindow, UserActivityLogger, Settings, Entities, Tablet, Toolbars, Messages, Menu, Camera, progressDialog, tooltip, MyAvatar, Quat, Controller, Clipboard, HMD, UndoStack, ParticleExplorerTool */ (function() { // BEGIN LOCAL_SCOPE @@ -80,27 +80,7 @@ selectionManager.addEventListener(function () { } var type = Entities.getEntityProperties(selectedEntityID, "type").type; if (type === "ParticleEffect") { - // Destroy the old particles web view first - 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'}}); + selectParticleEntity(selectedEntityID); } else { needToDestroyParticleExplorer = true; } @@ -218,7 +198,7 @@ function hideMarketplace() { // } function adjustPositionPerBoundingBox(position, direction, registration, dimensions, orientation) { - // Adjust the position such that the bounding box (registration, dimenions, and orientation) lies behind the original + // Adjust the position such that the bounding box (registration, dimenions, and orientation) lies behind the original // position in the given direction. var CORNERS = [ { x: 0, y: 0, z: 0 }, @@ -618,7 +598,6 @@ var toolBar = (function () { that.toggle = function () { that.setActive(!isActive); - activeButton.editProperties({isActive: isActive}); if (!isActive) { tablet.gotoHomeScreen(); } @@ -642,6 +621,8 @@ var toolBar = (function () { enabled: active })); isActive = active; + activeButton.editProperties({isActive: isActive}); + var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); if (!isActive) { @@ -1372,7 +1353,7 @@ function parentSelectedEntities() { } }); - if(parentCheck) { + if (parentCheck) { Window.notify("Entities parented"); }else { Window.notify("Entities are already parented to last"); @@ -1574,7 +1555,7 @@ function importSVO(importURL) { var properties = Entities.getEntityProperties(pastedEntityIDs[0], ["position", "dimensions", "registrationPoint"]); var position = Vec3.sum(deltaPosition, properties.position); - position = grid.snapToSurface(grid.snapToGrid(position, false, properties.dimensions, + position = grid.snapToSurface(grid.snapToGrid(position, false, properties.dimensions, properties.registrationPoint), properties.dimensions, properties.registrationPoint); deltaPosition = Vec3.subtract(position, properties.position); } @@ -1906,11 +1887,11 @@ var PropertiesTool = function (opts) { } pushCommandForSelections(); selectionManager._update(); - } else if(data.type === 'parent') { + } else if (data.type === 'parent') { parentSelectedEntities(); - } else if(data.type === 'unparent') { + } else if (data.type === 'unparent') { unparentSelectedEntities(); - } else if(data.type === 'saveUserData'){ + } else if (data.type === 'saveUserData'){ //the event bridge and json parsing handle our avatar id string differently. var actualID = data.id.split('"')[1]; Entities.editEntity(actualID, data.properties); @@ -2202,6 +2183,10 @@ var selectedParticleEntityID = null; function selectParticleEntity(entityID) { var properties = Entities.getEntityProperties(entityID); + + if (properties.emitOrientation) { + properties.emitOrientation = Quat.safeEulerAngles(properties.emitOrientation); + } var particleData = { messageType: "particle_settings", currentProperties: properties @@ -2211,6 +2196,7 @@ function selectParticleEntity(entityID) { selectedParticleEntity = entityID; particleExplorerTool.setActiveParticleEntity(entityID); + particleExplorerTool.webView.emitScriptEvent(JSON.stringify(particleData)); // Switch to particle explorer @@ -2228,7 +2214,7 @@ entityListTool.webView.webEventReceived.connect(function (data) { if (data.type === 'parent') { parentSelectedEntities(); - } else if(data.type === 'unparent') { + } else if (data.type === 'unparent') { unparentSelectedEntities(); } else if (data.type === "selectionUpdate") { var ids = data.entityIds; @@ -2249,4 +2235,3 @@ entityListTool.webView.webEventReceived.connect(function (data) { }); }()); // END LOCAL_SCOPE - diff --git a/scripts/system/html/css/edit-style.css b/scripts/system/html/css/edit-style.css index fcb1815ca4..40f3d682cd 100644 --- a/scripts/system/html/css/edit-style.css +++ b/scripts/system/html/css/edit-style.css @@ -610,6 +610,7 @@ hr { .dropdown dl[dropped="true"] { color: #404040; background: linear-gradient(#afafaf, #afafaf); + z-index: 998; } .dropdown dt { @@ -657,7 +658,8 @@ hr { font-family: FiraSans-SemiBold; font-size: 15px; color: #404040; - background-color: #afafaf + background-color: #afafaf; + z-index: 999; } .dropdown li:hover { background-color: #00b4ef; diff --git a/scripts/system/html/css/hifi-style.css b/scripts/system/html/css/hifi-style.css index ec6cd1a402..e1e4f67723 100644 --- a/scripts/system/html/css/hifi-style.css +++ b/scripts/system/html/css/hifi-style.css @@ -172,4 +172,4 @@ input[type=radio]:active + label > span > span{ } .blueButton:disabled { background-image: linear-gradient(#FFFFFF, #AFAFAF); -} \ No newline at end of file +} diff --git a/scripts/system/html/css/jsoneditor.css b/scripts/system/html/css/jsoneditor.css index 4d63380a62..ce83b45ff3 100644 --- a/scripts/system/html/css/jsoneditor.css +++ b/scripts/system/html/css/jsoneditor.css @@ -503,7 +503,7 @@ div.jsoneditor-contextmenu-root { div.jsoneditor-contextmenu { position: absolute; box-sizing: content-box; - z-index: 99999; + z-index: 998; } div.jsoneditor-contextmenu ul, diff --git a/scripts/system/html/entityProperties.html b/scripts/system/html/entityProperties.html index 35accdd0df..bf65bdad32 100644 --- a/scripts/system/html/entityProperties.html +++ b/scripts/system/html/entityProperties.html @@ -51,6 +51,9 @@ + + +
diff --git a/scripts/system/html/js/eventBridgeLoader.js b/scripts/system/html/js/eventBridgeLoader.js index 0e95345b40..411780853b 100644 --- a/scripts/system/html/js/eventBridgeLoader.js +++ b/scripts/system/html/js/eventBridgeLoader.js @@ -13,7 +13,7 @@ var WebChannel; openEventBridge = function(callback) { WebChannel = new QWebChannel(qt.webChannelTransport, function (channel) { - EventBridge = WebChannel.objects.eventBridgeWrapper.eventBridge; + EventBridge = WebChannel.objects.eventBridge; callback(EventBridge); }); } diff --git a/scripts/system/libraries/WebTablet.js b/scripts/system/libraries/WebTablet.js index 8c3f5d03bb..b429a9f3ae 100644 --- a/scripts/system/libraries/WebTablet.js +++ b/scripts/system/libraries/WebTablet.js @@ -43,7 +43,7 @@ var LOCAL_TABLET_MODEL_PATH = Script.resourcesPath() + "meshes/tablet-with-home- // returns object with two fields: // * position - position in front of the user // * rotation - rotation of entity so it faces the user. -function calcSpawnInfo(hand, height) { +function calcSpawnInfo(hand, tabletHeight) { var finalPosition; var headPos = (HMD.active && Camera.mode === "first person") ? HMD.position : Camera.position; @@ -53,30 +53,35 @@ function calcSpawnInfo(hand, height) { hand = NO_HANDS; } + var handController = null; if (HMD.active && hand !== NO_HANDS) { - var handController = getControllerWorldLocation(hand, true); + handController = getControllerWorldLocation(hand, true); + } - var TABLET_UP_OFFSET = 0.1; - var TABLET_FORWARD_OFFSET = 0.1; - var normal = Vec3.multiplyQbyV(handController.rotation, {x: 0, y: -1, z: 0}); - var pitch = Math.asin(normal.y); - var MAX_PITCH = Math.PI / 4; - if (pitch < -MAX_PITCH) { - pitch = -MAX_PITCH; - } else if (pitch > MAX_PITCH) { - pitch = MAX_PITCH; + if (handController && handController.valid) { + // Orient tablet per hand pitch and yaw. + // Angle it back similar to holding it like a book. + // Move tablet up so that hand is at bottom. + // Move tablet back so that hand is in front. + + var position = handController.position; + var rotation = handController.rotation; + + 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 heading = Math.atan2(normal.z, normal.x); - 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}); + var RELATIVE_SPAWN_OFFSET = { x: 0, y: 0.4, z: 0.05 }; + position = Vec3.sum(position, Vec3.multiplyQbyV(rotation, Vec3.multiply(tabletHeight, RELATIVE_SPAWN_OFFSET))); return { - position: Vec3.sum(offset, position), + position: position, rotation: rotation }; } else { diff --git a/scripts/system/particle_explorer/dat.gui.min.js b/scripts/system/particle_explorer/dat.gui.min.js deleted file mode 100644 index 8ea141a966..0000000000 --- a/scripts/system/particle_explorer/dat.gui.min.js +++ /dev/null @@ -1,95 +0,0 @@ -/** - * dat-gui JavaScript Controller Library - * http://code.google.com/p/dat-gui - * - * Copyright 2011 Data Arts Team, Google Creative Lab - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - */ -var dat=dat||{};dat.gui=dat.gui||{};dat.utils=dat.utils||{};dat.controllers=dat.controllers||{};dat.dom=dat.dom||{};dat.color=dat.color||{};dat.utils.css=function(){return{load:function(f,a){a=a||document;var d=a.createElement("link");d.type="text/css";d.rel="stylesheet";d.href=f;a.getElementsByTagName("head")[0].appendChild(d)},inject:function(f,a){a=a||document;var d=document.createElement("style");d.type="text/css";d.innerHTML=f;a.getElementsByTagName("head")[0].appendChild(d)}}}(); -dat.utils.common=function(){var f=Array.prototype.forEach,a=Array.prototype.slice;return{BREAK:{},extend:function(d){this.each(a.call(arguments,1),function(a){for(var c in a)this.isUndefined(a[c])||(d[c]=a[c])},this);return d},defaults:function(d){this.each(a.call(arguments,1),function(a){for(var c in a)this.isUndefined(d[c])&&(d[c]=a[c])},this);return d},compose:function(){var d=a.call(arguments);return function(){for(var e=a.call(arguments),c=d.length-1;0<=c;c--)e=[d[c].apply(this,e)];return e[0]}}, -each:function(a,e,c){if(a)if(f&&a.forEach&&a.forEach===f)a.forEach(e,c);else if(a.length===a.length+0)for(var b=0,p=a.length;bthis.__max&&(a=this.__max);void 0!==this.__step&&0!=a%this.__step&&(a=Math.round(a/this.__step)*this.__step);return e.superclass.prototype.setValue.call(this,a)},min:function(a){this.__min=a;return this},max:function(a){this.__max=a;return this},step:function(a){this.__impliedStep=this.__step=a;this.__precision=d(a);return this}});return e}(dat.controllers.Controller,dat.utils.common); -dat.controllers.NumberControllerBox=function(f,a,d){var e=function(c,b,f){function q(){var a=parseFloat(n.__input.value);d.isNaN(a)||n.setValue(a)}function l(a){var b=u-a.clientY;n.setValue(n.getValue()+b*n.__impliedStep);u=a.clientY}function r(){a.unbind(window,"mousemove",l);a.unbind(window,"mouseup",r)}this.__truncationSuspended=!1;e.superclass.call(this,c,b,f);var n=this,u;this.__input=document.createElement("input");this.__input.setAttribute("type","text");a.bind(this.__input,"change",q);a.bind(this.__input, -"blur",function(){q();n.__onFinishChange&&n.__onFinishChange.call(n,n.getValue())});a.bind(this.__input,"mousedown",function(b){a.bind(window,"mousemove",l);a.bind(window,"mouseup",r);u=b.clientY});a.bind(this.__input,"keydown",function(a){13===a.keyCode&&(n.__truncationSuspended=!0,this.blur(),n.__truncationSuspended=!1)});this.updateDisplay();this.domElement.appendChild(this.__input)};e.superclass=f;d.extend(e.prototype,f.prototype,{updateDisplay:function(){var a=this.__input,b;if(this.__truncationSuspended)b= -this.getValue();else{b=this.getValue();var d=Math.pow(10,this.__precision);b=Math.round(b*d)/d}a.value=b;return e.superclass.prototype.updateDisplay.call(this)}});return e}(dat.controllers.NumberController,dat.dom.dom,dat.utils.common); -dat.controllers.NumberControllerSlider=function(f,a,d,e,c){function b(a,b,c,e,d){return e+(a-b)/(c-b)*(d-e)}var p=function(c,e,d,f,u){function A(c){c.preventDefault();var e=a.getOffset(k.__background),d=a.getWidth(k.__background);k.setValue(b(c.clientX,e.left,e.left+d,k.__min,k.__max));return!1}function g(){a.unbind(window,"mousemove",A);a.unbind(window,"mouseup",g);k.__onFinishChange&&k.__onFinishChange.call(k,k.getValue())}p.superclass.call(this,c,e,{min:d,max:f,step:u});var k=this;this.__background= -document.createElement("div");this.__foreground=document.createElement("div");a.bind(this.__background,"mousedown",function(b){a.bind(window,"mousemove",A);a.bind(window,"mouseup",g);A(b)});a.addClass(this.__background,"slider");a.addClass(this.__foreground,"slider-fg");this.updateDisplay();this.__background.appendChild(this.__foreground);this.domElement.appendChild(this.__background)};p.superclass=f;p.useDefaultStyles=function(){d.inject(c)};e.extend(p.prototype,f.prototype,{updateDisplay:function(){var a= -(this.getValue()-this.__min)/(this.__max-this.__min);this.__foreground.style.width=100*a+"%";return p.superclass.prototype.updateDisplay.call(this)}});return p}(dat.controllers.NumberController,dat.dom.dom,dat.utils.css,dat.utils.common,"/**\n * dat-gui JavaScript Controller Library\n * http://code.google.com/p/dat-gui\n *\n * Copyright 2011 Data Arts Team, Google Creative Lab\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n */\n\n.slider {\n box-shadow: inset 0 2px 4px rgba(0,0,0,0.15);\n height: 1em;\n border-radius: 1em;\n background-color: #eee;\n padding: 0 0.5em;\n overflow: hidden;\n}\n\n.slider-fg {\n padding: 1px 0 2px 0;\n background-color: #aaa;\n height: 1em;\n margin-left: -0.5em;\n padding-right: 0.5em;\n border-radius: 1em 0 0 1em;\n}\n\n.slider-fg:after {\n display: inline-block;\n border-radius: 1em;\n background-color: #fff;\n border: 1px solid #aaa;\n content: '';\n float: right;\n margin-right: -1em;\n margin-top: -1px;\n height: 0.9em;\n width: 0.9em;\n}"); -dat.controllers.FunctionController=function(f,a,d){var e=function(c,b,d){e.superclass.call(this,c,b);var f=this;this.__button=document.createElement("div");this.__button.innerHTML=void 0===d?"Fire":d;a.bind(this.__button,"click",function(a){a.preventDefault();f.fire();return!1});a.addClass(this.__button,"button");this.domElement.appendChild(this.__button)};e.superclass=f;d.extend(e.prototype,f.prototype,{fire:function(){this.__onChange&&this.__onChange.call(this);this.getValue().call(this.object); -this.__onFinishChange&&this.__onFinishChange.call(this,this.getValue())}});return e}(dat.controllers.Controller,dat.dom.dom,dat.utils.common); -dat.controllers.BooleanController=function(f,a,d){var e=function(c,b){e.superclass.call(this,c,b);var d=this;this.__prev=this.getValue();this.__checkbox=document.createElement("input");this.__checkbox.setAttribute("type","checkbox");a.bind(this.__checkbox,"change",function(){d.setValue(!d.__prev)},!1);this.domElement.appendChild(this.__checkbox);this.updateDisplay()};e.superclass=f;d.extend(e.prototype,f.prototype,{setValue:function(a){a=e.superclass.prototype.setValue.call(this,a);this.__onFinishChange&& -this.__onFinishChange.call(this,this.getValue());this.__prev=this.getValue();return a},updateDisplay:function(){!0===this.getValue()?(this.__checkbox.setAttribute("checked","checked"),this.__checkbox.checked=!0):this.__checkbox.checked=!1;return e.superclass.prototype.updateDisplay.call(this)}});return e}(dat.controllers.Controller,dat.dom.dom,dat.utils.common); -dat.color.toString=function(f){return function(a){if(1==a.a||f.isUndefined(a.a)){for(a=a.hex.toString(16);6>a.length;)a="0"+a;return"#"+a}return"rgba("+Math.round(a.r)+","+Math.round(a.g)+","+Math.round(a.b)+","+a.a+")"}}(dat.utils.common); -dat.color.interpret=function(f,a){var d,e,c=[{litmus:a.isString,conversions:{THREE_CHAR_HEX:{read:function(a){a=a.match(/^#([A-F0-9])([A-F0-9])([A-F0-9])$/i);return null===a?!1:{space:"HEX",hex:parseInt("0x"+a[1].toString()+a[1].toString()+a[2].toString()+a[2].toString()+a[3].toString()+a[3].toString())}},write:f},SIX_CHAR_HEX:{read:function(a){a=a.match(/^#([A-F0-9]{6})$/i);return null===a?!1:{space:"HEX",hex:parseInt("0x"+a[1].toString())}},write:f},CSS_RGB:{read:function(a){a=a.match(/^rgb\(\s*(.+)\s*,\s*(.+)\s*,\s*(.+)\s*\)/); -return null===a?!1:{space:"RGB",r:parseFloat(a[1]),g:parseFloat(a[2]),b:parseFloat(a[3])}},write:f},CSS_RGBA:{read:function(a){a=a.match(/^rgba\(\s*(.+)\s*,\s*(.+)\s*,\s*(.+)\s*\,\s*(.+)\s*\)/);return null===a?!1:{space:"RGB",r:parseFloat(a[1]),g:parseFloat(a[2]),b:parseFloat(a[3]),a:parseFloat(a[4])}},write:f}}},{litmus:a.isNumber,conversions:{HEX:{read:function(a){return{space:"HEX",hex:a,conversionName:"HEX"}},write:function(a){return a.hex}}}},{litmus:a.isArray,conversions:{RGB_ARRAY:{read:function(a){return 3!= -a.length?!1:{space:"RGB",r:a[0],g:a[1],b:a[2]}},write:function(a){return[a.r,a.g,a.b]}},RGBA_ARRAY:{read:function(a){return 4!=a.length?!1:{space:"RGB",r:a[0],g:a[1],b:a[2],a:a[3]}},write:function(a){return[a.r,a.g,a.b,a.a]}}}},{litmus:a.isObject,conversions:{RGBA_OBJ:{read:function(b){return a.isNumber(b.r)&&a.isNumber(b.g)&&a.isNumber(b.b)&&a.isNumber(b.a)?{space:"RGB",r:b.r,g:b.g,b:b.b,a:b.a}:!1},write:function(a){return{r:a.r,g:a.g,b:a.b,a:a.a}}},RGB_OBJ:{read:function(b){return a.isNumber(b.r)&& -a.isNumber(b.g)&&a.isNumber(b.b)?{space:"RGB",r:b.r,g:b.g,b:b.b}:!1},write:function(a){return{r:a.r,g:a.g,b:a.b}}},HSVA_OBJ:{read:function(b){return a.isNumber(b.h)&&a.isNumber(b.s)&&a.isNumber(b.v)&&a.isNumber(b.a)?{space:"HSV",h:b.h,s:b.s,v:b.v,a:b.a}:!1},write:function(a){return{h:a.h,s:a.s,v:a.v,a:a.a}}},HSV_OBJ:{read:function(b){return a.isNumber(b.h)&&a.isNumber(b.s)&&a.isNumber(b.v)?{space:"HSV",h:b.h,s:b.s,v:b.v}:!1},write:function(a){return{h:a.h,s:a.s,v:a.v}}}}}];return function(){e=!1; -var b=1\n\n Here\'s the new load parameter for your GUI\'s constructor:\n\n \n\n
\n\n Automatically save\n values to localStorage on exit.\n\n
The values saved to localStorage will\n override those passed to dat.GUI\'s constructor. This makes it\n easier to work incrementally, but localStorage is fragile,\n and your friends may not see the same values you do.\n \n
\n \n
\n\n
', -".dg {\n /** Clear list styles */\n /* Auto-place container */\n /* Auto-placed GUI's */\n /* Line items that don't contain folders. */\n /** Folder names */\n /** Hides closed items */\n /** Controller row */\n /** Name-half (left) */\n /** Controller-half (right) */\n /** Controller placement */\n /** Shorter number boxes when slider is present. */\n /** Ensure the entire boolean and function row shows a hand */ }\n .dg ul {\n list-style: none;\n margin: 0;\n padding: 0;\n width: 100%;\n clear: both; }\n .dg.ac {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n height: 0;\n z-index: 0; }\n .dg:not(.ac) .main {\n /** Exclude mains in ac so that we don't hide close button */\n overflow: hidden; }\n .dg.main {\n -webkit-transition: opacity 0.1s linear;\n -o-transition: opacity 0.1s linear;\n -moz-transition: opacity 0.1s linear;\n transition: opacity 0.1s linear; }\n .dg.main.taller-than-window {\n overflow-y: auto; }\n .dg.main.taller-than-window .close-button {\n opacity: 1;\n /* TODO, these are style notes */\n margin-top: -1px;\n border-top: 1px solid #2c2c2c; }\n .dg.main ul.closed .close-button {\n opacity: 1 !important; }\n .dg.main:hover .close-button,\n .dg.main .close-button.drag {\n opacity: 1; }\n .dg.main .close-button {\n /*opacity: 0;*/\n -webkit-transition: opacity 0.1s linear;\n -o-transition: opacity 0.1s linear;\n -moz-transition: opacity 0.1s linear;\n transition: opacity 0.1s linear;\n border: 0;\n position: absolute;\n line-height: 19px;\n height: 20px;\n /* TODO, these are style notes */\n cursor: pointer;\n text-align: center;\n background-color: #000; }\n .dg.main .close-button:hover {\n background-color: #111; }\n .dg.a {\n float: right;\n margin-right: 15px;\n overflow-x: hidden; }\n .dg.a.has-save > ul {\n margin-top: 27px; }\n .dg.a.has-save > ul.closed {\n margin-top: 0; }\n .dg.a .save-row {\n position: fixed;\n top: 0;\n z-index: 1002; }\n .dg li {\n -webkit-transition: height 0.1s ease-out;\n -o-transition: height 0.1s ease-out;\n -moz-transition: height 0.1s ease-out;\n transition: height 0.1s ease-out; }\n .dg li:not(.folder) {\n cursor: auto;\n height: 27px;\n line-height: 27px;\n overflow: hidden;\n padding: 0 4px 0 5px; }\n .dg li.folder {\n padding: 0;\n border-left: 4px solid rgba(0, 0, 0, 0); }\n .dg li.title {\n cursor: pointer;\n margin-left: -4px; }\n .dg .closed li:not(.title),\n .dg .closed ul li,\n .dg .closed ul li > * {\n height: 0;\n overflow: hidden;\n border: 0; }\n .dg .cr {\n clear: both;\n padding-left: 3px;\n height: 27px; }\n .dg .property-name {\n cursor: default;\n float: left;\n clear: left;\n width: 40%;\n overflow: hidden;\n text-overflow: ellipsis; }\n .dg .c {\n float: left;\n width: 60%; }\n .dg .c input[type=text] {\n border: 0;\n margin-top: 4px;\n padding: 3px;\n width: 100%;\n float: right; }\n .dg .has-slider input[type=text] {\n width: 30%;\n /*display: none;*/\n margin-left: 0; }\n .dg .slider {\n float: left;\n width: 66%;\n margin-left: -5px;\n margin-right: 0;\n height: 19px;\n margin-top: 4px; }\n .dg .slider-fg {\n height: 100%; }\n .dg .c input[type=checkbox] {\n margin-top: 9px; }\n .dg .c select {\n margin-top: 5px; }\n .dg .cr.function,\n .dg .cr.function .property-name,\n .dg .cr.function *,\n .dg .cr.boolean,\n .dg .cr.boolean * {\n cursor: pointer; }\n .dg .selector {\n display: none;\n position: absolute;\n margin-left: -9px;\n margin-top: 23px;\n z-index: 10; }\n .dg .c:hover .selector,\n .dg .selector.drag {\n display: block; }\n .dg li.save-row {\n padding: 0; }\n .dg li.save-row .button {\n display: inline-block;\n padding: 0px 6px; }\n .dg.dialogue {\n background-color: #222;\n width: 460px;\n padding: 15px;\n font-size: 13px;\n line-height: 15px; }\n\n/* TODO Separate style and structure */\n#dg-new-constructor {\n padding: 10px;\n color: #222;\n font-family: Monaco, monospace;\n font-size: 10px;\n border: 0;\n resize: none;\n box-shadow: inset 1px 1px 1px #888;\n word-wrap: break-word;\n margin: 12px 0;\n display: block;\n width: 440px;\n overflow-y: scroll;\n height: 100px;\n position: relative; }\n\n#dg-local-explain {\n display: none;\n font-size: 11px;\n line-height: 17px;\n border-radius: 3px;\n background-color: #333;\n padding: 8px;\n margin-top: 10px; }\n #dg-local-explain code {\n font-size: 10px; }\n\n#dat-gui-save-locally {\n display: none; }\n\n/** Main type */\n.dg {\n color: #eee;\n font: 11px 'Lucida Grande', sans-serif;\n text-shadow: 0 -1px 0 #111;\n /** Auto place */\n /* Controller row,
  • */\n /** Controllers */ }\n .dg.main {\n /** Scrollbar */ }\n .dg.main::-webkit-scrollbar {\n width: 5px;\n background: #1a1a1a; }\n .dg.main::-webkit-scrollbar-corner {\n height: 0;\n display: none; }\n .dg.main::-webkit-scrollbar-thumb {\n border-radius: 5px;\n background: #676767; }\n .dg li:not(.folder) {\n background: #1a1a1a;\n border-bottom: 1px solid #2c2c2c; }\n .dg li.save-row {\n line-height: 25px;\n background: #dad5cb;\n border: 0; }\n .dg li.save-row select {\n margin-left: 5px;\n width: 108px; }\n .dg li.save-row .button {\n margin-left: 5px;\n margin-top: 1px;\n border-radius: 2px;\n font-size: 9px;\n line-height: 7px;\n padding: 4px 4px 5px 4px;\n background: #c5bdad;\n color: #fff;\n text-shadow: 0 1px 0 #b0a58f;\n box-shadow: 0 -1px 0 #b0a58f;\n cursor: pointer; }\n .dg li.save-row .button.gears {\n background: #c5bdad url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAsAAAANCAYAAAB/9ZQ7AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQJJREFUeNpiYKAU/P//PwGIC/ApCABiBSAW+I8AClAcgKxQ4T9hoMAEUrxx2QSGN6+egDX+/vWT4e7N82AMYoPAx/evwWoYoSYbACX2s7KxCxzcsezDh3evFoDEBYTEEqycggWAzA9AuUSQQgeYPa9fPv6/YWm/Acx5IPb7ty/fw+QZblw67vDs8R0YHyQhgObx+yAJkBqmG5dPPDh1aPOGR/eugW0G4vlIoTIfyFcA+QekhhHJhPdQxbiAIguMBTQZrPD7108M6roWYDFQiIAAv6Aow/1bFwXgis+f2LUAynwoIaNcz8XNx3Dl7MEJUDGQpx9gtQ8YCueB+D26OECAAQDadt7e46D42QAAAABJRU5ErkJggg==) 2px 1px no-repeat;\n height: 7px;\n width: 8px; }\n .dg li.save-row .button:hover {\n background-color: #bab19e;\n box-shadow: 0 -1px 0 #b0a58f; }\n .dg li.folder {\n border-bottom: 0; }\n .dg li.title {\n padding-left: 16px;\n background: black url(data:image/gif;base64,R0lGODlhBQAFAJEAAP////Pz8////////yH5BAEAAAIALAAAAAAFAAUAAAIIlI+hKgFxoCgAOw==) 6px 10px no-repeat;\n cursor: pointer;\n border-bottom: 1px solid rgba(255, 255, 255, 0.2); }\n .dg .closed li.title {\n background-image: url(data:image/gif;base64,R0lGODlhBQAFAJEAAP////Pz8////////yH5BAEAAAIALAAAAAAFAAUAAAIIlGIWqMCbWAEAOw==); }\n .dg .cr.boolean {\n border-left: 3px solid #806787; }\n .dg .cr.function {\n border-left: 3px solid #e61d5f; }\n .dg .cr.number {\n border-left: 3px solid #2fa1d6; }\n .dg .cr.number input[type=text] {\n color: #2fa1d6; }\n .dg .cr.string {\n border-left: 3px solid #1ed36f; }\n .dg .cr.string input[type=text] {\n color: #1ed36f; }\n .dg .cr.function:hover, .dg .cr.boolean:hover {\n background: #111; }\n .dg .c input[type=text] {\n background: #303030;\n outline: none; }\n .dg .c input[type=text]:hover {\n background: #3c3c3c; }\n .dg .c input[type=text]:focus {\n background: #494949;\n color: #fff; }\n .dg .c .slider {\n background: #303030;\n cursor: ew-resize; }\n .dg .c .slider-fg {\n background: #2fa1d6; }\n .dg .c .slider:hover {\n background: #3c3c3c; }\n .dg .c .slider:hover .slider-fg {\n background: #44abda; }\n", -dat.controllers.factory=function(f,a,d,e,c,b,p){return function(q,l,r,n){var u=q[l];if(p.isArray(r)||p.isObject(r))return new f(q,l,r);if(p.isNumber(u))return p.isNumber(r)&&p.isNumber(n)?new d(q,l,r,n):new a(q,l,{min:r,max:n});if(p.isString(u))return new e(q,l);if(p.isFunction(u))return new c(q,l,"");if(p.isBoolean(u))return new b(q,l)}}(dat.controllers.OptionController,dat.controllers.NumberControllerBox,dat.controllers.NumberControllerSlider,dat.controllers.StringController=function(f,a,d){var e= -function(c,b){function d(){f.setValue(f.__input.value)}e.superclass.call(this,c,b);var f=this;this.__input=document.createElement("input");this.__input.setAttribute("type","text");a.bind(this.__input,"keyup",d);a.bind(this.__input,"change",d);a.bind(this.__input,"blur",function(){f.__onFinishChange&&f.__onFinishChange.call(f,f.getValue())});a.bind(this.__input,"keydown",function(a){13===a.keyCode&&this.blur()});this.updateDisplay();this.domElement.appendChild(this.__input)};e.superclass=f;d.extend(e.prototype, -f.prototype,{updateDisplay:function(){a.isActive(this.__input)||(this.__input.value=this.getValue());return e.superclass.prototype.updateDisplay.call(this)}});return e}(dat.controllers.Controller,dat.dom.dom,dat.utils.common),dat.controllers.FunctionController,dat.controllers.BooleanController,dat.utils.common),dat.controllers.Controller,dat.controllers.BooleanController,dat.controllers.FunctionController,dat.controllers.NumberControllerBox,dat.controllers.NumberControllerSlider,dat.controllers.OptionController, -dat.controllers.ColorController=function(f,a,d,e,c){function b(a,b,d,e){a.style.background="";c.each(l,function(c){a.style.cssText+="background: "+c+"linear-gradient("+b+", "+d+" 0%, "+e+" 100%); "})}function p(a){a.style.background="";a.style.cssText+="background: -moz-linear-gradient(top, #ff0000 0%, #ff00ff 17%, #0000ff 34%, #00ffff 50%, #00ff00 67%, #ffff00 84%, #ff0000 100%);";a.style.cssText+="background: -webkit-linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);"; -a.style.cssText+="background: -o-linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);";a.style.cssText+="background: -ms-linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);";a.style.cssText+="background: linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);"}var q=function(f,n){function u(b){v(b);a.bind(window,"mousemove",v);a.bind(window, -"mouseup",l)}function l(){a.unbind(window,"mousemove",v);a.unbind(window,"mouseup",l)}function g(){var a=e(this.value);!1!==a?(t.__color.__state=a,t.setValue(t.__color.toOriginal())):this.value=t.__color.toString()}function k(){a.unbind(window,"mousemove",w);a.unbind(window,"mouseup",k)}function v(b){b.preventDefault();var c=a.getWidth(t.__saturation_field),d=a.getOffset(t.__saturation_field),e=(b.clientX-d.left+document.body.scrollLeft)/c;b=1-(b.clientY-d.top+document.body.scrollTop)/c;1 -b&&(b=0);1e&&(e=0);t.__color.v=b;t.__color.s=e;t.setValue(t.__color.toOriginal());return!1}function w(b){b.preventDefault();var c=a.getHeight(t.__hue_field),d=a.getOffset(t.__hue_field);b=1-(b.clientY-d.top+document.body.scrollTop)/c;1b&&(b=0);t.__color.h=360*b;t.setValue(t.__color.toOriginal());return!1}q.superclass.call(this,f,n);this.__color=new d(this.getValue());this.__temp=new d(0);var t=this;this.domElement=document.createElement("div");a.makeSelectable(this.domElement,!1); -this.__selector=document.createElement("div");this.__selector.className="selector";this.__saturation_field=document.createElement("div");this.__saturation_field.className="saturation-field";this.__field_knob=document.createElement("div");this.__field_knob.className="field-knob";this.__field_knob_border="2px solid ";this.__hue_knob=document.createElement("div");this.__hue_knob.className="hue-knob";this.__hue_field=document.createElement("div");this.__hue_field.className="hue-field";this.__input=document.createElement("input"); -this.__input.type="text";this.__input_textShadow="0 1px 1px ";a.bind(this.__input,"keydown",function(a){13===a.keyCode&&g.call(this)});a.bind(this.__input,"blur",g);a.bind(this.__selector,"mousedown",function(b){a.addClass(this,"drag").bind(window,"mouseup",function(b){a.removeClass(t.__selector,"drag")})});var y=document.createElement("div");c.extend(this.__selector.style,{width:"122px",height:"102px",padding:"3px",backgroundColor:"#222",boxShadow:"0px 1px 3px rgba(0,0,0,0.3)"});c.extend(this.__field_knob.style, -{position:"absolute",width:"12px",height:"12px",border:this.__field_knob_border+(.5>this.__color.v?"#fff":"#000"),boxShadow:"0px 1px 3px rgba(0,0,0,0.5)",borderRadius:"12px",zIndex:1});c.extend(this.__hue_knob.style,{position:"absolute",width:"15px",height:"2px",borderRight:"4px solid #fff",zIndex:1});c.extend(this.__saturation_field.style,{width:"100px",height:"100px",border:"1px solid #555",marginRight:"3px",display:"inline-block",cursor:"pointer"});c.extend(y.style,{width:"100%",height:"100%", -background:"none"});b(y,"top","rgba(0,0,0,0)","#000");c.extend(this.__hue_field.style,{width:"15px",height:"100px",display:"inline-block",border:"1px solid #555",cursor:"ns-resize"});p(this.__hue_field);c.extend(this.__input.style,{outline:"none",textAlign:"center",color:"#fff",border:0,fontWeight:"bold",textShadow:this.__input_textShadow+"rgba(0,0,0,0.7)"});a.bind(this.__saturation_field,"mousedown",u);a.bind(this.__field_knob,"mousedown",u);a.bind(this.__hue_field,"mousedown",function(b){w(b);a.bind(window, -"mousemove",w);a.bind(window,"mouseup",k)});this.__saturation_field.appendChild(y);this.__selector.appendChild(this.__field_knob);this.__selector.appendChild(this.__saturation_field);this.__selector.appendChild(this.__hue_field);this.__hue_field.appendChild(this.__hue_knob);this.domElement.appendChild(this.__input);this.domElement.appendChild(this.__selector);this.updateDisplay()};q.superclass=f;c.extend(q.prototype,f.prototype,{updateDisplay:function(){var a=e(this.getValue());if(!1!==a){var f=!1; -c.each(d.COMPONENTS,function(b){if(!c.isUndefined(a[b])&&!c.isUndefined(this.__color.__state[b])&&a[b]!==this.__color.__state[b])return f=!0,{}},this);f&&c.extend(this.__color.__state,a)}c.extend(this.__temp.__state,this.__color.__state);this.__temp.a=1;var l=.5>this.__color.v||.5a&&(a+=1);return{h:360*a,s:c/b,v:b/255}},rgb_to_hex:function(a,d,e){a=this.hex_with_component(0,2,a);a=this.hex_with_component(a,1,d);return a=this.hex_with_component(a,0,e)},component_from_hex:function(a,d){return a>>8*d&255},hex_with_component:function(a,d,e){return e<<(f=8*d)|a&~(255< 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 = $('
    ', { + 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 = "°"; + 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; + } +}; diff --git a/scripts/system/particle_explorer/particle-style.css b/scripts/system/particle_explorer/particle-style.css new file mode 100644 index 0000000000..e8b71fdba0 --- /dev/null +++ b/scripts/system/particle_explorer/particle-style.css @@ -0,0 +1,124 @@ +/* +// particle-style.css +// +// Created by Matti 'Menithal' Lahtinen on 21 May 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 +*/ + + +.property-group { + max-height: 0; + -webkit-transition: max-height 0.15s ease-out; + transition: max-height 0.15s ease-out; + overflow: hidden; +} +.property-group.visible { + transition: max-height 0.25s ease-in; +} +.section-wrap { + width: 100%; +} +.property { + padding: 0.4rem 0; + margin: 0; +} +.property.checkbox { + margin: 0; +} +.property.range label{ + display: block; +} + +input[type="button"] { + margin: 0.4rem; + min-width: 6rem; +} +input[type="text"] { + margin: 0; +} +.property.range input[type=number]{ + margin-left: 0.8rem; + width: 5.4rem; + height: 1.8rem; +} +input[type=range] { + -webkit-appearance: none; + background: #2e2e2e; + height: 1.8rem; + border-radius: 1rem; +} +input[type=range]::-webkit-slider-thumb { + -webkit-appearance:none; + width: 0.6rem; + height: 1.8rem; + padding:0; + margin: 0; + background-color: #696969; + border-radius: 1rem; +} +input[type=range]::-webkit-slider-thumb:hover { + background-color: white; +} +input[type=range]:focus { /*#252525*/ + outline: none; +} +.tuple label { + text-transform: capitalize; +} +.slider-wrapper { + display: table; + padding: 0.4rem 0; +} +hr.splitter{ + width: 100%; + padding: 0.2rem 0 0 0; + margin: 0; + position: relative; + clear: both; +} +hr.splitter:last-of-type{ + padding:0; +} +#rem { + height: 1rem; + width: 1rem; +} +.property { + min-height: 2rem; +} +.property.vector-section{ + + width: 24rem; +} + +.property.texture { + display: block; +} +.property.texture input{ + margin: 0.4rem 0; +} +.texture-image img{ + padding: 0; + margin: 0; + width: 100%; + height: 100%; + display: none; +} +.texture-image { + display: block; + position: relative; + background-repeat: no-repeat; + background-position: center; + background-size: 100% 100%; + margin-top: 0.4rem; + height:128px; + width: 128px; + background-image: url('data:image/png;base64,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'); +} + +.texture-image.no-texture{ + background-image: url('data:image/png;base64,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'); +} diff --git a/scripts/system/particle_explorer/particleExplorer.html b/scripts/system/particle_explorer/particleExplorer.html index d0d86d79da..0f014e9fa8 100644 --- a/scripts/system/particle_explorer/particleExplorer.html +++ b/scripts/system/particle_explorer/particleExplorer.html @@ -1,95 +1,42 @@ +// + --> - - - - - - - - - - + + + - -
    - -
    -
    -
    -
    -
    - + +
    +
    + +
    + +
    +
    + diff --git a/scripts/system/particle_explorer/particleExplorer.js b/scripts/system/particle_explorer/particleExplorer.js index 5f66fe7ae6..ca6a873b73 100644 --- a/scripts/system/particle_explorer/particleExplorer.js +++ b/scripts/system/particle_explorer/particleExplorer.js @@ -2,550 +2,410 @@ // particleExplorer.js // // 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. -// 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. // 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, - $, document, _, openEventBridge */ +/* global HifiEntityUI, openEventBridge, console, EventBridge, document, window */ +/* eslint no-console: 0, no-global-assign: 0 */ -var Settings = function () { - this.exportSettings = function () { - // copyExportSettingsToClipboard(); - showPreselectedPrompt(); - }; - this.importSettings = function () { - importSettings(); - }; -}; +(function () { -// 2-way bindings-aren't quite ready yet. see bottom of file. -var AUTO_UPDATE = false; -var UPDATE_ALL_FREQUENCY = 100; + var root = document.getElementById("particle-explorer"); -var controllers = []; -var colpickKeys = []; -var folders = []; -var gui = null; -var settings = new Settings(); -var updateInterval; - -var active = false; - -var currentInputField; -var storedController; -// CHANGE TO WHITELIST -var keysToAllow = [ - 'isEmitting', - 'maxParticles', - 'lifespan', - 'emitRate', - 'emitSpeed', - 'speedSpread', - 'emitOrientation', - 'emitDimensions', - 'polarStart', - 'polarFinish', - 'azimuthStart', - 'azimuthFinish', - 'emitAcceleration', - 'accelerationSpread', - 'particleRadius', - 'radiusSpread', - 'radiusStart', - 'radiusFinish', - 'color', - 'colorSpread', - 'colorStart', - 'colorFinish', - 'alpha', - 'alphaSpread', - 'alphaStart', - 'alphaFinish', - 'emitterShouldTrail', - 'textures' -]; - -var individualKeys = []; -var vec3Keys = []; -var quatKeys = []; -var colorKeys = []; - -window.onload = function () { - openEventBridge(function () { - var stringifiedData = JSON.stringify({ - messageType: 'page_loaded' + window.onload = function () { + var ui = new HifiEntityUI(root); + var textarea = document.createElement("textarea"); + var properties = ""; + var menuStructure = { + General: [ + { + type: "Row", + id: "export-import-field" + }, + { + id: "show-properties-button", + name: "Show Properties", + type: "Button", + class: "blue", + disabled: true, + callback: function (event) { + var insertZone = document.getElementById("export-import-field"); + var json = ui.getSettings(); + properties = JSON.stringify(json); + textarea.value = properties; + if (!insertZone.contains(textarea)) { + insertZone.appendChild(textarea); + insertZone.parentNode.parentNode.style.maxHeight = + insertZone.parentNode.clientHeight + "px"; + document.getElementById("export-properties-button").removeAttribute("disabled"); + textarea.onchange = function (e) { + if (e.target.value !== properties) { + document.getElementById("import-properties-button").removeAttribute("disabled"); + } + }; + textarea.oninput = textarea.onchange; + } else { + textarea.onchange = function () {}; + textarea.oninput = textarea.onchange; + textarea.value = ""; + textarea.remove(); + insertZone.parentNode.parentNode.style.maxHeight = + insertZone.parentNode.clientHeight + "px"; + document.getElementById("export-properties-button").setAttribute("disabled", true); + document.getElementById("import-properties-button").setAttribute("disabled", true); + } + } + }, + { + id: "import-properties-button", + name: "Import", + type: "Button", + class: "blue", + disabled: true, + callback: function (event) { + 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); - } + } + } + }); + }; } - }); - - // 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); + openEventBridge(function (EventBridge) { + ui.connect(EventBridge); }); - - }); -} - -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 = $('
    ', { - id: key.toString(), - class: "color-box" - }); - $colPickContainer.css('background-color', "rgb(" + colorObject.red + "," + colorObject.green + "," + colorObject.blue + ")"); - container.appendChild($colPickContainer[0]); - - var $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); + if (overrideLoad) { + openEventBridge(); } - }); - - 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); - }); - }); - }); -} +})(); diff --git a/scripts/system/particle_explorer/particleExplorerTool.js b/scripts/system/particle_explorer/particleExplorerTool.js index b3db475ab0..d85fc169b1 100644 --- a/scripts/system/particle_explorer/particleExplorerTool.js +++ b/scripts/system/particle_explorer/particleExplorerTool.js @@ -4,23 +4,22 @@ // Created by Eric Levin on 2/15/16 // Copyright 2016 High Fidelity, Inc. // Adds particleExplorer tool to the edit panel when a user selects a particle entity from the edit tool window -// 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. // 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'); ParticleExplorerTool = function() { var that = {}; - that.createWebView = function() { that.webView = Tablet.getTablet("com.highfidelity.interface.tablet.system"); that.webView.setVisible = function(value) {}; - that.webView.webEventReceived.connect(that.webEventReceived); + that.webView.webEventReceived.connect(that.webEventReceived); } that.destroyWebView = function() { @@ -38,6 +37,9 @@ ParticleExplorerTool = function() { that.webEventReceived = function(data) { var data = JSON.parse(data); if (data.messageType === "settings_update") { + if (data.updatedSettings.emitOrientation) { + data.updatedSettings.emitOrientation = Quat.fromVec3Degrees(data.updatedSettings.emitOrientation); + } Entities.editEntity(that.activeParticleEntity, data.updatedSettings); } } diff --git a/tools/oven/src/BakerCLI.cpp b/tools/oven/src/BakerCLI.cpp index 7bdd221514..14eb9de150 100644 --- a/tools/oven/src/BakerCLI.cpp +++ b/tools/oven/src/BakerCLI.cpp @@ -48,7 +48,7 @@ void BakerCLI::bakeFile(QUrl inputUrl, const QString outputPath) { _baker->moveToThread(qApp->getNextWorkerThread()); } else { qCDebug(model_baking) << "Failed to determine baker type for file" << inputUrl; - return; + QApplication::exit(1); } // invoke the bake method on the baker thread @@ -60,5 +60,5 @@ void BakerCLI::bakeFile(QUrl inputUrl, const QString outputPath) { void BakerCLI::handleFinishedBaker() { qCDebug(model_baking) << "Finished baking file."; - QApplication::quit(); + QApplication::exit(_baker.get()->hasErrors()); } \ No newline at end of file