diff --git a/BUILD_WIN.md b/BUILD_WIN.md index 48530b9351..2d6074cd77 100644 --- a/BUILD_WIN.md +++ b/BUILD_WIN.md @@ -31,7 +31,7 @@ If you do not wish to use the Python installation bundled with Visual Studio, yo ### Step 2. Installing CMake -Download and install the latest version of CMake 3.9. +Download and install the latest version of CMake 3.14. Download the file named win64-x64 Installer from the [CMake Website](https://cmake.org/download/). You can access the installer on this [3.14 Version page](https://cmake.org/files/v3.14/). During installation, make sure to check "Add CMake to system PATH for all users" when prompted. diff --git a/CMakeLists.txt b/CMakeLists.txt index 1f6cffb7c1..9876a0d7ef 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -139,6 +139,7 @@ option(BUILD_MANUAL_TESTS "Build manual tests" ${BUILD_MANUAL_TESTS_OPTION}) option(BUILD_TOOLS "Build tools" ${BUILD_TOOLS_OPTION}) option(BUILD_INSTALLER "Build installer" ${BUILD_INSTALLER_OPTION}) option(USE_GLES "Use OpenGL ES" ${GLES_OPTION}) +option(USE_KHR_ROBUSTNESS "Use KHR_robustness" OFF) option(DISABLE_QML "Disable QML" ${DISABLE_QML_OPTION}) option(DISABLE_KTX_CACHE "Disable KTX Cache" OFF) option( @@ -149,6 +150,10 @@ option( set(PLATFORM_QT_GL OpenGL) +if (USE_KHR_ROBUSTNESS) + add_definitions(-DUSE_KHR_ROBUSTNESS) +endif() + if (USE_GLES) add_definitions(-DUSE_GLES) add_definitions(-DGPU_POINTER_STORAGE_SHARED) diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index 709c5810e2..6aaa348f6c 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -127,7 +127,8 @@ void DomainGatekeeper::processConnectRequestPacket(QSharedPointergetSenderSockAddr() << "with hardware address" << nodeConnection.hardwareAddress - << "and machine fingerprint" << nodeConnection.machineFingerprint; + << "and machine fingerprint" << nodeConnection.machineFingerprint + << "sysinfo" << nodeConnection.SystemInfo; } } diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 358b05222c..c3a4a94c7c 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -739,6 +739,10 @@ void DomainServer::setupNodeListAndAssignments() { connect(nodeList.data(), &LimitedNodeList::nodeAdded, this, &DomainServer::nodeAdded); connect(nodeList.data(), &LimitedNodeList::nodeKilled, this, &DomainServer::nodeKilled); + connect(nodeList.data(), &LimitedNodeList::localSockAddrChanged, this, + [this](const HifiSockAddr& localSockAddr) { + DependencyManager::get()->putLocalPortIntoSharedMemory(DOMAIN_SERVER_LOCAL_PORT_SMEM_KEY, this, localSockAddr.getPort()); + }); // register as the packet receiver for the types we want PacketReceiver& packetReceiver = nodeList->getPacketReceiver(); diff --git a/domain-server/src/NodeConnectionData.cpp b/domain-server/src/NodeConnectionData.cpp index b4aaacd749..5419014622 100644 --- a/domain-server/src/NodeConnectionData.cpp +++ b/domain-server/src/NodeConnectionData.cpp @@ -36,6 +36,13 @@ NodeConnectionData NodeConnectionData::fromDataStream(QDataStream& dataStream, c // now the machine fingerprint dataStream >> newHeader.machineFingerprint; + // and the operating system type + QByteArray compressedSystemInfo; + dataStream >> compressedSystemInfo; + if (!compressedSystemInfo.isEmpty()) { + newHeader.SystemInfo = qUncompress(compressedSystemInfo); + } + dataStream >> newHeader.connectReason; dataStream >> newHeader.previousConnectionUpTime; diff --git a/domain-server/src/NodeConnectionData.h b/domain-server/src/NodeConnectionData.h index 23eceb0dca..3833f5afd3 100644 --- a/domain-server/src/NodeConnectionData.h +++ b/domain-server/src/NodeConnectionData.h @@ -31,9 +31,9 @@ public: QString placeName; QString hardwareAddress; QUuid machineFingerprint; + QString SystemInfo; quint32 connectReason; quint64 previousConnectionUpTime; - QByteArray protocolVersion; }; diff --git a/interface/resources/qml/AnimStats.qml b/interface/resources/qml/AnimStats.qml index d06a0484cb..70f2e37927 100644 --- a/interface/resources/qml/AnimStats.qml +++ b/interface/resources/qml/AnimStats.qml @@ -53,6 +53,9 @@ Item { StatText { text: root.recenterText } + StatText { + text: root.overrideJointText + } StatText { text: "Anim Vars:--------------------------------------------------------------------------------" } @@ -98,6 +101,9 @@ Item { StatText { text: root.sittingText } + StatText { + text: root.flowText + } StatText { text: "State Machines:---------------------------------------------------------------------------" } @@ -131,6 +137,9 @@ Item { StatText { text: root.walkingText } + StatText { + text: root.networkGraphText + } StatText { text: "Alpha Values:--------------------------------------------------------------------------" } diff --git a/interface/resources/qml/Stats.qml b/interface/resources/qml/Stats.qml index 2c991aa9dd..23aa256cdc 100644 --- a/interface/resources/qml/Stats.qml +++ b/interface/resources/qml/Stats.qml @@ -340,10 +340,10 @@ Item { text: "GPU: " + root.gpuFrameTime.toFixed(1) + " ms" } StatText { - text: "GPU (Per pixel): " + root.gpuFrameTimePerPixel.toFixed(5) + " ns/pp" + text: "GPU (Per pixel): " + root.gpuFrameTimePerPixel.toFixed(1) + " ns/pp" } StatText { - text: "GPU frame size: " + root.gpuFrameSize.x + " x " + root.gpuFrameSize.y + text: "GPU frame size: " + root.gpuFrameSize.x.toFixed(0) + " x " + root.gpuFrameSize.y.toFixed(0) } StatText { text: "LOD Target: " + root.lodTargetFramerate + " Hz Angle: " + root.lodAngle + " deg" diff --git a/interface/resources/qml/controls/qmldir b/interface/resources/qml/controls/qmldir new file mode 100644 index 0000000000..6d7cf458cd --- /dev/null +++ b/interface/resources/qml/controls/qmldir @@ -0,0 +1,25 @@ +module controls +Button 1.0 Button.qml +ButtonAwesome 1.0 ButtonAwesome.qml +CheckBox 1.0 CheckBox.qml +ComboBox 1.0 ComboBox.qml +FlickableWebViewCore 1.0 FlickableWebViewCore.qml +FontAwesome 1.0 FontAwesome.qml +Player 1.0 Player.qml +RadioButton 1.0 RadioButton.qml +Slider 1.0 Slider.qml +Spacer 1.0 Spacer.qml +SpinBox 1.0 SpinBox.qml +TabletWebButton 1.0 TabletWebButton.qml +TabletWebScreen 1.0 TabletWebScreen.qml +TabletWebView 1.0 TabletWebView.qml +Text 1.0 Text.qml +TextAndSlider 1.0 TextAndSlider.qml +TextAndSpinBox 1.0 TextAndSpinBox.qml +TextArea 1.0 TextArea.qml +TextEdit 1.0 TextEdit.qml +TextField 1.0 TextField.qml +TextHeader 1.0 TextHeader.qml +TextInput 1.0 TextInput.qml +TextInputAndButton 1.0 TextInputAndButton.qml +WebView 1.0 WebView.qml \ No newline at end of file diff --git a/interface/resources/qml/hifi/dialogs/qmldir b/interface/resources/qml/hifi/dialogs/qmldir new file mode 100644 index 0000000000..4075738e33 --- /dev/null +++ b/interface/resources/qml/hifi/dialogs/qmldir @@ -0,0 +1,18 @@ +module dialogs +AboutDialog 1.0 AboutDialog.qml +AdvancedPreferencesDialog 1.0 AdvancedPreferencesDialog.qml +AudioBuffers 1.0 AudioBuffers.qml +AvatarPreferencesDialog 1.0 AvatarPreferencesDialog.qml +GeneralPreferencesDialog 1.0 GeneralPreferencesDialog.qml +LodPreferencesDialog 1.0 LodPreferencesDialog.qml +ModelBrowserDialog 1.0 ModelBrowserDialog.qml +NetworkingPreferencesDialog 1.0 NetworkingPreferencesDialog.qml +RunningScripts 1.0 RunningScripts.qml +TabletAboutDialog 1.0 TabletAboutDialog.qml +TabletAssetServer 1.0 TabletAssetServer.qml +TabletDCDialog 1.0 TabletDCDialog.qml +TabletDebugWindow 1.0 TabletDebugWindow.qml +TabletEntityStatistics 1.0 TabletEntityStatistics.qml +TabletEntityStatisticsItem 1.0 TabletEntityStatisticsItem.qml +TabletLODTools 1.0 TabletLODTools.qml +TabletRunningScripts 1.0 TabletRunningScripts.qml \ No newline at end of file diff --git a/interface/resources/qml/hifi/simplifiedUI/avatarApp/components/DisplayNameHeader.qml b/interface/resources/qml/hifi/simplifiedUI/avatarApp/components/DisplayNameHeader.qml index a6be398e53..becbd8ca61 100644 --- a/interface/resources/qml/hifi/simplifiedUI/avatarApp/components/DisplayNameHeader.qml +++ b/interface/resources/qml/hifi/simplifiedUI/avatarApp/components/DisplayNameHeader.qml @@ -94,6 +94,7 @@ Item { text: MyAvatar.sessionDisplayName === "" ? MyAvatar.displayName : MyAvatar.sessionDisplayName maximumLength: 256 clip: true + selectByMouse: true anchors.fill: parent onEditingFinished: { if (MyAvatar.displayName !== text) { diff --git a/interface/resources/qml/hifi/simplifiedUI/inputDeviceButton/InputDeviceButton.qml b/interface/resources/qml/hifi/simplifiedUI/inputDeviceButton/InputDeviceButton.qml index c546af218b..15f4c42d39 100644 --- a/interface/resources/qml/hifi/simplifiedUI/inputDeviceButton/InputDeviceButton.qml +++ b/interface/resources/qml/hifi/simplifiedUI/inputDeviceButton/InputDeviceButton.qml @@ -33,6 +33,7 @@ Rectangle { readonly property string unmutedIcon: "images/mic-unmute-i.svg" readonly property string mutedIcon: "images/mic-mute-i.svg" readonly property string pushToTalkIcon: "images/mic-ptt-i.svg" + readonly property string pushToTalkMutedIcon: "images/mic-ptt-mute-i.svg" readonly property string clippingIcon: "images/mic-clip-i.svg" readonly property string gatedIcon: "images/mic-gate-i.svg" @@ -48,18 +49,6 @@ Rectangle { } } - opacity: 0.7 - - onLevelChanged: { - var rectOpacity = (muted && (level >= userSpeakingLevel)) ? 1.0 : 0.7; - if (pushToTalk && !pushingToTalk) { - rectOpacity = (mouseArea.containsMouse) ? 1.0 : 0.7; - } else if (mouseArea.containsMouse && rectOpacity != 1.0) { - rectOpacity = 1.0; - } - micBar.opacity = rectOpacity; - } - color: "#00000000" MouseArea { @@ -116,82 +105,84 @@ Rectangle { Item { id: icon anchors.verticalCenter: parent.verticalCenter - anchors.right: parent.horizontalCenter + anchors.horizontalCenter: parent.horizontalCenter anchors.rightMargin: 2 - width: 13 - height: 21 + width: pushToTalk ? 16 : (muted ? 20 : 16) + height: 22 Item { anchors.fill: parent - opacity: mouseArea.containsMouse ? 1.0 : 0.7 Image { id: image + visible: false source: (pushToTalk) ? pushToTalkIcon : muted ? mutedIcon : clipping ? clippingIcon : gated ? gatedIcon : unmutedIcon anchors.fill: parent - fillMode: Image.PreserveAspectFit } ColorOverlay { + opacity: mouseArea.containsMouse ? 1.0 : 0.7 + visible: level === 0 || micBar.muted || micBar.clipping id: imageOverlay anchors { fill: image } source: image - color: pushToTalk ? (pushingToTalk ? colors.unmutedColor : colors.mutedColor) : colors.icon + color: pushToTalk ? (pushingToTalk ? colors.icon : colors.mutedColor) : colors.icon + } + + OpacityMask { + id: bar + visible: level > 0 && !micBar.muted && !micBar.clipping + anchors.fill: meterGradient + source: meterGradient + maskSource: image + } + + LinearGradient { + id: meterGradient + anchors { fill: parent } + visible: false + start: Qt.point(0, 0) + end: Qt.point(0, parent.height) + rotation: 180 + gradient: Gradient { + GradientStop { + position: 1.0 + color: colors.greenStart + } + GradientStop { + position: 0.5 + color: colors.greenEnd + } + GradientStop { + position: 0.0 + color: colors.yellow + } + } } } - } - Item { - id: bar - - anchors.verticalCenter: parent.verticalCenter - anchors.left: parent.horizontalCenter - anchors.leftMargin: 2 - - width: 4 - height: 21 - - Rectangle { // base - id: baseBar - radius: 4 - anchors { fill: parent } - color: colors.gutter - } - - Rectangle { // mask - id: mask - height: micBar.muted ? parent.height : parent.height * level - color: micBar.muted ? colors.mutedColor : "white" + Item { width: parent.width - radius: 5 - anchors { - bottom: parent.bottom - bottomMargin: 0 - left: parent.left - leftMargin: 0 + height: parent.height - parent.height * level + anchors.top: parent.top + anchors.left: parent.left + clip:true + Image { + id: maskImage + visible: false + source: (pushToTalk) ? pushToTalkIcon : muted ? mutedIcon : + clipping ? clippingIcon : gated ? gatedIcon : unmutedIcon + anchors.top: parent.top + anchors.left: parent.left + width: parent.width + height: parent.parent.height } - } - - LinearGradient { - anchors { fill: mask } - visible: mask.visible && !micBar.muted - source: mask - start: Qt.point(0, 0) - end: Qt.point(0, bar.height) - rotation: 180 - gradient: Gradient { - GradientStop { - position: 0.0 - color: colors.greenStart - } - GradientStop { - position: 0.5 - color: colors.greenEnd - } - GradientStop { - position: 1.0 - color: colors.yellow - } + + ColorOverlay { + visible: level > 0 && !micBar.muted && !micBar.clipping + anchors { fill: maskImage } + source: maskImage + color: "#b2b2b2" } } } diff --git a/interface/resources/qml/hifi/simplifiedUI/inputDeviceButton/images/mic-clip-i.svg b/interface/resources/qml/hifi/simplifiedUI/inputDeviceButton/images/mic-clip-i.svg index 8b694c7f3d..f16b9e1a56 100644 --- a/interface/resources/qml/hifi/simplifiedUI/inputDeviceButton/images/mic-clip-i.svg +++ b/interface/resources/qml/hifi/simplifiedUI/inputDeviceButton/images/mic-clip-i.svg @@ -1,20 +1,14 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - + + + + + + + + diff --git a/interface/resources/qml/hifi/simplifiedUI/inputDeviceButton/images/mic-gate-i.svg b/interface/resources/qml/hifi/simplifiedUI/inputDeviceButton/images/mic-gate-i.svg index ac70ce66cb..56e0e1df57 100644 --- a/interface/resources/qml/hifi/simplifiedUI/inputDeviceButton/images/mic-gate-i.svg +++ b/interface/resources/qml/hifi/simplifiedUI/inputDeviceButton/images/mic-gate-i.svg @@ -1,13 +1,11 @@ - - - - - - image/svg+xml - - - - - - + + + + diff --git a/interface/resources/qml/hifi/simplifiedUI/inputDeviceButton/images/mic-mute-a.svg b/interface/resources/qml/hifi/simplifiedUI/inputDeviceButton/images/mic-mute-a.svg deleted file mode 100644 index eb36c2dd55..0000000000 --- a/interface/resources/qml/hifi/simplifiedUI/inputDeviceButton/images/mic-mute-a.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - image/svg+xml - - - - - - - diff --git a/interface/resources/qml/hifi/simplifiedUI/inputDeviceButton/images/mic-mute-i.svg b/interface/resources/qml/hifi/simplifiedUI/inputDeviceButton/images/mic-mute-i.svg index ebca81f370..a00b7f9a9c 100644 --- a/interface/resources/qml/hifi/simplifiedUI/inputDeviceButton/images/mic-mute-i.svg +++ b/interface/resources/qml/hifi/simplifiedUI/inputDeviceButton/images/mic-mute-i.svg @@ -1,13 +1,13 @@ - - - - - - image/svg+xml - - - - - - + + + + diff --git a/interface/resources/qml/hifi/simplifiedUI/inputDeviceButton/images/mic-ptt-a.svg b/interface/resources/qml/hifi/simplifiedUI/inputDeviceButton/images/mic-ptt-a.svg deleted file mode 100644 index 3ce7c0ca51..0000000000 --- a/interface/resources/qml/hifi/simplifiedUI/inputDeviceButton/images/mic-ptt-a.svg +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - image/svg+xml - - mic-ptt-a - - - - - - - mic-ptt-a - - diff --git a/interface/resources/qml/hifi/simplifiedUI/inputDeviceButton/images/mic-ptt-i.svg b/interface/resources/qml/hifi/simplifiedUI/inputDeviceButton/images/mic-ptt-i.svg index 3bf1f1bf9e..7dd65c96a0 100644 --- a/interface/resources/qml/hifi/simplifiedUI/inputDeviceButton/images/mic-ptt-i.svg +++ b/interface/resources/qml/hifi/simplifiedUI/inputDeviceButton/images/mic-ptt-i.svg @@ -1,8 +1,16 @@ - - -image/svg+xml - - + + + + diff --git a/interface/resources/qml/hifi/simplifiedUI/inputDeviceButton/images/mic-ptt-mute-i.svg b/interface/resources/qml/hifi/simplifiedUI/inputDeviceButton/images/mic-ptt-mute-i.svg new file mode 100644 index 0000000000..fef3e533b4 --- /dev/null +++ b/interface/resources/qml/hifi/simplifiedUI/inputDeviceButton/images/mic-ptt-mute-i.svg @@ -0,0 +1,18 @@ + + + + + diff --git a/interface/resources/qml/hifi/simplifiedUI/inputDeviceButton/images/mic-unmute-a.svg b/interface/resources/qml/hifi/simplifiedUI/inputDeviceButton/images/mic-unmute-a.svg deleted file mode 100644 index 0bd0b0c238..0000000000 --- a/interface/resources/qml/hifi/simplifiedUI/inputDeviceButton/images/mic-unmute-a.svg +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - diff --git a/interface/resources/qml/hifi/simplifiedUI/inputDeviceButton/images/mic-unmute-i.svg b/interface/resources/qml/hifi/simplifiedUI/inputDeviceButton/images/mic-unmute-i.svg index 121873dd1b..dd8bfa087f 100644 --- a/interface/resources/qml/hifi/simplifiedUI/inputDeviceButton/images/mic-unmute-i.svg +++ b/interface/resources/qml/hifi/simplifiedUI/inputDeviceButton/images/mic-unmute-i.svg @@ -1,13 +1,9 @@ - - - - - - image/svg+xml - - - - - - + + + + diff --git a/interface/resources/qml/hifi/simplifiedUI/settingsApp/SettingsApp.qml b/interface/resources/qml/hifi/simplifiedUI/settingsApp/SettingsApp.qml index 1f48d1d753..a9199ff5f1 100644 --- a/interface/resources/qml/hifi/simplifiedUI/settingsApp/SettingsApp.qml +++ b/interface/resources/qml/hifi/simplifiedUI/settingsApp/SettingsApp.qml @@ -91,7 +91,20 @@ Rectangle { Component { id: highlightBar Rectangle { + width: tabListView.currentItem.width + height: tabListView.currentItem.height color: simplifiedUI.colors.darkBackground + x: tabListView.currentItem.x + Behavior on x { + SmoothedAnimation { + duration: 250 + } + } + Behavior on width { + SmoothedAnimation { + duration: 250 + } + } } } @@ -104,6 +117,7 @@ Rectangle { orientation: ListView.Horizontal model: tabListModel highlight: highlightBar + highlightFollowsCurrentItem: false interactive: contentItem.width > width delegate: Item { visible: model.tabTitle !== "Dev" || (model.tabTitle === "Dev" && root.developerModeEnabled) diff --git a/interface/resources/qml/hifi/simplifiedUI/settingsApp/about/About.qml b/interface/resources/qml/hifi/simplifiedUI/settingsApp/about/About.qml index 76ab762a6b..3e3758e7a8 100644 --- a/interface/resources/qml/hifi/simplifiedUI/settingsApp/about/About.qml +++ b/interface/resources/qml/hifi/simplifiedUI/settingsApp/about/About.qml @@ -122,12 +122,22 @@ Flickable { } HifiStylesUit.GraphikRegular { - text: "CPU: " + PlatformInfo.getCPUBrand() + text: "CPU:" Layout.maximumWidth: parent.width height: paintedHeight size: 16 color: simplifiedUI.colors.text.white wrapMode: Text.Wrap + + Component.onCompleted: { + var cpu = JSON.parse(PlatformInfo.getCPU(0)); + var cpuModel = cpu.model; + if (cpuModel.length === 0) { + cpuModel = "Unknown"; + } + + text = "CPU: " + cpuModel; + } } HifiStylesUit.GraphikRegular { @@ -158,12 +168,22 @@ Flickable { } HifiStylesUit.GraphikRegular { - text: "GPU: " + PlatformInfo.getGraphicsCardType() + text: "GPU: " Layout.maximumWidth: parent.width height: paintedHeight size: 16 color: simplifiedUI.colors.text.white wrapMode: Text.Wrap + + Component.onCompleted: { + var gpu = JSON.parse(PlatformInfo.getGPU(0)); + var gpuModel = gpu.model; + if (gpuModel.length === 0) { + gpuModel = "Unknown"; + } + + text = "GPU: " + gpuModel; + } } HifiStylesUit.GraphikRegular { @@ -180,9 +200,11 @@ Flickable { width: 200 height: 32 text: "Copy to Clipboard" + temporaryText: "Copied!" onClicked: { Window.copyToClipboard(root.buildPlatformInfoTextToCopy()); + showTemporaryText(); } } } @@ -206,12 +228,29 @@ Flickable { textToCopy += "Computer Vendor/Model: " + computerVendor + "/" + computerModel + "\n"; textToCopy += "Profiled Platform Tier: " + PlatformInfo.getTierProfiled() + "\n"; textToCopy += "OS Type: " + PlatformInfo.getOperatingSystemType() + "\n"; - textToCopy += "CPU: " + PlatformInfo.getCPUBrand() + "\n"; + + var cpu = JSON.parse(PlatformInfo.getCPU(0)); + var cpuModel = cpu.model; + if (cpuModel.length === 0) { + cpuModel = "Unknown"; + } + + textToCopy += "CPU: " + cpuModel + "\n"; textToCopy += "# CPUs: " + PlatformInfo.getNumCPUs() + "\n"; textToCopy += "# CPU Cores: " + PlatformInfo.getNumLogicalCores() + "\n"; textToCopy += "RAM: " + PlatformInfo.getTotalSystemMemoryMB() + " MB\n"; - textToCopy += "GPU: " + PlatformInfo.getGraphicsCardType() + "\n"; - textToCopy += "VR Hand Controllers: " + (PlatformInfo.hasRiftControllers() ? "Rift" : (PlatformInfo.hasViveControllers() ? "Vive" : "None")); + + var gpu = JSON.parse(PlatformInfo.getGPU(0)); + var gpuModel = gpu.model; + if (gpuModel.length === 0) { + gpuModel = "Unknown"; + } + + textToCopy += "GPU: " + gpuModel + "\n"; + textToCopy += "VR Hand Controllers: " + (PlatformInfo.hasRiftControllers() ? "Rift" : (PlatformInfo.hasViveControllers() ? "Vive" : "None")) + "\n"; + + textToCopy += "\n**All Platform Info**\n"; + textToCopy += JSON.stringify(JSON.parse(PlatformInfo.getPlatform()), null, 4); return textToCopy; } diff --git a/interface/resources/qml/hifi/simplifiedUI/simplifiedControls/Button.qml b/interface/resources/qml/hifi/simplifiedUI/simplifiedControls/Button.qml index 313daab704..1d594a0d6d 100644 --- a/interface/resources/qml/hifi/simplifiedUI/simplifiedControls/Button.qml +++ b/interface/resources/qml/hifi/simplifiedUI/simplifiedControls/Button.qml @@ -16,6 +16,9 @@ import TabletScriptingInterface 1.0 Original.Button { id: root + // The two properties below are used when calling showTemporaryText() + property string originalText: "" + property string temporaryText: "" SimplifiedConstants.SimplifiedConstants { id: simplifiedUI @@ -103,4 +106,31 @@ Original.Button { horizontalAlignment: Text.AlignHCenter text: root.text } + + Timer { + id: showTemporaryTextTimer + interval: 1500 + repeat: false + running: false + + onTriggered: { + buttonText.text = root.originalText; + root.originalText = ""; + } + } + + function showTemporaryText() { + if (root.temporaryText === "") { + return; + } + + if (showTemporaryTextTimer.running) { + showTemporaryTextTimer.restart(); + return; + } + + root.originalText = buttonText.text; + buttonText.text = root.temporaryText; + showTemporaryTextTimer.start(); + } } diff --git a/interface/resources/qml/hifi/simplifiedUI/topBar/SimplifiedTopBar.qml b/interface/resources/qml/hifi/simplifiedUI/topBar/SimplifiedTopBar.qml index 42d53d3f79..61d2fd6634 100644 --- a/interface/resources/qml/hifi/simplifiedUI/topBar/SimplifiedTopBar.qml +++ b/interface/resources/qml/hifi/simplifiedUI/topBar/SimplifiedTopBar.qml @@ -141,7 +141,7 @@ Rectangle { id: avatarButtonContainer anchors.verticalCenter: parent.verticalCenter anchors.left: parent.left - anchors.leftMargin: 16 + anchors.leftMargin: 2 width: 48 height: width @@ -210,7 +210,7 @@ Rectangle { id: inputDeviceButton anchors.verticalCenter: parent.verticalCenter anchors.left: avatarButtonContainer.right - anchors.leftMargin: 6 + anchors.leftMargin: 2 width: 32 height: width } @@ -220,7 +220,7 @@ Rectangle { id: outputDeviceButtonContainer anchors.verticalCenter: parent.verticalCenter anchors.left: inputDeviceButton.right - anchors.leftMargin: 2 + anchors.leftMargin: 7 width: 32 height: width @@ -232,9 +232,8 @@ Rectangle { AudioScriptingInterface.systemInjectorGain === simplifiedUI.numericConstants.mutedValue source: outputDeviceButton.outputMuted ? "./images/outputDeviceMuted.svg" : "./images/outputDeviceLoud.svg" anchors.centerIn: parent - width: 20 - height: 20 - fillMode: Image.PreserveAspectFit + width: outputDeviceButton.outputMuted ? 25 : 26 + height: 22 visible: false } @@ -283,7 +282,7 @@ Rectangle { id: statusButton property string currentStatus anchors.centerIn: parent - width: 15 + width: 22 height: width radius: width/2 visible: false @@ -304,6 +303,21 @@ Rectangle { } } + Image { + id: statusIcon + source: statusButton.currentStatus === "available" ? "images/statusPresent.svg" : "images/statusAway.svg" + anchors.centerIn: parent + width: statusButton.currentStatus === "busy" ? 13 : 14 + height: statusButton.currentStatus === "busy" ? 2 : 10 + } + + ColorOverlay { + anchors.fill: statusIcon + opacity: statusButton.currentStatus ? (statusButtonMouseArea.containsMouse ? 1.0 : 0.7) : 0.7 + source: statusIcon + color: "#ffffff" + } + MouseArea { id: statusButtonMouseArea anchors.fill: parent @@ -329,8 +343,8 @@ Rectangle { id: hmdButtonContainer anchors.verticalCenter: parent.verticalCenter anchors.right: settingsButtonContainer.left - anchors.rightMargin: 14 - width: 32 + anchors.rightMargin: 8 + width: 48 height: width visible: false @@ -338,9 +352,8 @@ Rectangle { id: displayModeImage source: HMD.active ? "./images/desktopMode.svg" : "./images/vrMode.svg" anchors.centerIn: parent - width: 29 - height: 16 - fillMode: Image.PreserveAspectFit + width: HMD.active ? 25 : 43 + height: 22 visible: false } @@ -399,17 +412,16 @@ Rectangle { id: settingsButtonContainer anchors.verticalCenter: parent.verticalCenter anchors.right: parent.right - anchors.rightMargin: 16 - width: 32 + anchors.rightMargin: 3 + width: 36 height: width Image { id: settingsButtonImage source: "./images/settings.svg" anchors.centerIn: parent - width: 20 - height: 20 - fillMode: Image.PreserveAspectFit + width: 22 + height: 22 visible: false } diff --git a/interface/resources/qml/hifi/simplifiedUI/topBar/images/desktopMode.svg b/interface/resources/qml/hifi/simplifiedUI/topBar/images/desktopMode.svg index 8b04caca88..dfb1a1a662 100644 --- a/interface/resources/qml/hifi/simplifiedUI/topBar/images/desktopMode.svg +++ b/interface/resources/qml/hifi/simplifiedUI/topBar/images/desktopMode.svg @@ -1,13 +1,8 @@ - - - - - - image/svg+xml - - - - - - + + + + diff --git a/interface/resources/qml/hifi/simplifiedUI/topBar/images/outputDeviceLoud.svg b/interface/resources/qml/hifi/simplifiedUI/topBar/images/outputDeviceLoud.svg index ebd844c471..798ce62ccd 100644 --- a/interface/resources/qml/hifi/simplifiedUI/topBar/images/outputDeviceLoud.svg +++ b/interface/resources/qml/hifi/simplifiedUI/topBar/images/outputDeviceLoud.svg @@ -1,3 +1,14 @@ - - - + + + + + diff --git a/interface/resources/qml/hifi/simplifiedUI/topBar/images/outputDeviceMuted.svg b/interface/resources/qml/hifi/simplifiedUI/topBar/images/outputDeviceMuted.svg index 4188175c31..9b7e3ad613 100644 --- a/interface/resources/qml/hifi/simplifiedUI/topBar/images/outputDeviceMuted.svg +++ b/interface/resources/qml/hifi/simplifiedUI/topBar/images/outputDeviceMuted.svg @@ -1,14 +1,14 @@ - - - - - - image/svg+xml - - - - - - - + + + + diff --git a/interface/resources/qml/hifi/simplifiedUI/topBar/images/settings.svg b/interface/resources/qml/hifi/simplifiedUI/topBar/images/settings.svg index 04a031d498..910ef353e1 100644 --- a/interface/resources/qml/hifi/simplifiedUI/topBar/images/settings.svg +++ b/interface/resources/qml/hifi/simplifiedUI/topBar/images/settings.svg @@ -1,13 +1,17 @@ - - - - - - image/svg+xml - - - - - - + + + + diff --git a/interface/resources/qml/hifi/simplifiedUI/topBar/images/status.svg b/interface/resources/qml/hifi/simplifiedUI/topBar/images/status.svg deleted file mode 100644 index ebd844c471..0000000000 --- a/interface/resources/qml/hifi/simplifiedUI/topBar/images/status.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/interface/resources/qml/hifi/simplifiedUI/topBar/images/statusAway.svg b/interface/resources/qml/hifi/simplifiedUI/topBar/images/statusAway.svg new file mode 100644 index 0000000000..d5b4798d9e --- /dev/null +++ b/interface/resources/qml/hifi/simplifiedUI/topBar/images/statusAway.svg @@ -0,0 +1,9 @@ + + + + + + diff --git a/interface/resources/qml/hifi/simplifiedUI/topBar/images/statusPresent.svg b/interface/resources/qml/hifi/simplifiedUI/topBar/images/statusPresent.svg new file mode 100644 index 0000000000..af7649ba78 --- /dev/null +++ b/interface/resources/qml/hifi/simplifiedUI/topBar/images/statusPresent.svg @@ -0,0 +1,7 @@ + + + + + diff --git a/interface/resources/qml/hifi/simplifiedUI/topBar/images/vrMode.svg b/interface/resources/qml/hifi/simplifiedUI/topBar/images/vrMode.svg index 57b564813d..f19989c0b1 100644 --- a/interface/resources/qml/hifi/simplifiedUI/topBar/images/vrMode.svg +++ b/interface/resources/qml/hifi/simplifiedUI/topBar/images/vrMode.svg @@ -1,13 +1,10 @@ - - - - - - image/svg+xml - - - - - - + + + + diff --git a/interface/resources/qml/hifi/toolbars/qmldir b/interface/resources/qml/hifi/toolbars/qmldir new file mode 100644 index 0000000000..a27cdc0a4f --- /dev/null +++ b/interface/resources/qml/hifi/toolbars/qmldir @@ -0,0 +1,4 @@ +module toolbars +StateImage 1.0 StateImage.qml +Toolbar 1.0 Toolbar.qml +ToolbarButton 1.0 ToolbarButton.qml \ No newline at end of file diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index f9470782bf..1a0030bc12 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2712,6 +2712,7 @@ void Application::cleanupBeforeQuit() { // Clear any queued processing (I/O, FBX/OBJ/Texture parsing) QThreadPool::globalInstance()->clear(); + QThreadPool::globalInstance()->waitForDone(); DependencyManager::destroy(); @@ -2775,7 +2776,6 @@ void Application::cleanupBeforeQuit() { // destroy Audio so it and its threads have a chance to go down safely // this must happen after QML, as there are unexplained audio crashes originating in qtwebengine - QMetaObject::invokeMethod(DependencyManager::get().data(), "stop"); DependencyManager::destroy(); DependencyManager::destroy(); @@ -2935,8 +2935,10 @@ void Application::initializeGL() { #if !defined(DISABLE_QML) QStringList chromiumFlags; + // HACK: re-expose mic and camera to prevent crash on domain-change in chromium's media::FakeAudioInputStream::ReadAudioFromSource() // Bug 21993: disable microphone and camera input - chromiumFlags << "--use-fake-device-for-media-stream"; + //chromiumFlags << "--use-fake-device-for-media-stream"; + // Disable signed distance field font rendering on ATI/AMD GPUs, due to // https://highfidelity.manuscript.com/f/cases/13677/Text-showing-up-white-on-Marketplace-app std::string vendor{ (const char*)glGetString(GL_VENDOR) }; @@ -3741,18 +3743,6 @@ void Application::resizeGL() { DependencyManager::get()->setFrameBufferSize(fromGlm(renderSize)); } - auto renderResolutionScale = getRenderResolutionScale(); - if (displayPlugin->getRenderResolutionScale() != renderResolutionScale) { - auto renderConfig = _graphicsEngine.getRenderEngine()->getConfiguration(); - assert(renderConfig); - auto mainView = renderConfig->getConfig("RenderMainView.RenderDeferredTask"); - // mainView can be null if we're rendering in forward mode - if (mainView) { - mainView->setProperty("resolutionScale", renderResolutionScale); - } - displayPlugin->setRenderResolutionScale(renderResolutionScale); - } - // FIXME the aspect ratio for stereo displays is incorrect based on this. float aspectRatio = displayPlugin->getRecommendedAspectRatio(); _myCamera.setProjection(glm::perspective(glm::radians(_fieldOfView.get()), aspectRatio, @@ -8546,23 +8536,7 @@ void Application::shareSnapshot(const QString& path, const QUrl& href) { } float Application::getRenderResolutionScale() const { - auto menu = Menu::getInstance(); - if (!menu) { - return 1.0f; - } - if (menu->isOptionChecked(MenuOption::RenderResolutionOne)) { - return 1.0f; - } else if (menu->isOptionChecked(MenuOption::RenderResolutionTwoThird)) { - return 0.666f; - } else if (menu->isOptionChecked(MenuOption::RenderResolutionHalf)) { - return 0.5f; - } else if (menu->isOptionChecked(MenuOption::RenderResolutionThird)) { - return 0.333f; - } else if (menu->isOptionChecked(MenuOption::RenderResolutionQuarter)) { - return 0.25f; - } else { - return 1.0f; - } + return RenderScriptingInterface::getInstance()->getViewportResolutionScale(); } void Application::notifyPacketVersionMismatch() { diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index bb087c96d5..8bee8de8c3 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -382,28 +382,6 @@ Menu::Menu() { // Developer > Render > OpenVR threaded submit addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::OpenVrThreadedSubmit, 0, true); - // Developer > Render > Resolution - MenuWrapper* resolutionMenu = renderOptionsMenu->addMenu(MenuOption::RenderResolution); - QActionGroup* resolutionGroup = new QActionGroup(resolutionMenu); - resolutionGroup->setExclusive(true); - -#if defined(Q_OS_MAC) - resolutionGroup->addAction(addCheckableActionToQMenuAndActionHash(resolutionMenu, MenuOption::RenderResolutionOne, 0, false)); -#else - resolutionGroup->addAction(addCheckableActionToQMenuAndActionHash(resolutionMenu, MenuOption::RenderResolutionOne, 0, true)); -#endif - - resolutionGroup->addAction(addCheckableActionToQMenuAndActionHash(resolutionMenu, MenuOption::RenderResolutionTwoThird, 0, false)); - - #if defined(Q_OS_MAC) - resolutionGroup->addAction(addCheckableActionToQMenuAndActionHash(resolutionMenu, MenuOption::RenderResolutionHalf, 0, true)); -#else - resolutionGroup->addAction(addCheckableActionToQMenuAndActionHash(resolutionMenu, MenuOption::RenderResolutionHalf, 0, false)); -#endif - - resolutionGroup->addAction(addCheckableActionToQMenuAndActionHash(resolutionMenu, MenuOption::RenderResolutionThird, 0, false)); - resolutionGroup->addAction(addCheckableActionToQMenuAndActionHash(resolutionMenu, MenuOption::RenderResolutionQuarter, 0, false)); - //const QString = "Automatic Texture Memory"; //const QString = "64 MB"; //const QString = "256 MB"; diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 7c462e4e74..70687786a9 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -168,12 +168,6 @@ namespace MenuOption { const QString RenderMaxTexture4096MB = "4096 MB"; const QString RenderMaxTexture6144MB = "6144 MB"; const QString RenderMaxTexture8192MB = "8192 MB"; - const QString RenderResolution = "Scale Resolution"; - const QString RenderResolutionOne = "1"; - const QString RenderResolutionTwoThird = "2/3"; - const QString RenderResolutionHalf = "1/2"; - const QString RenderResolutionThird = "1/3"; - const QString RenderResolutionQuarter = "1/4"; const QString RenderSensorToWorldMatrix = "Show SensorToWorld Matrix"; const QString RenderIKTargets = "Show IK Targets"; const QString RenderIKConstraints = "Show IK Constraints"; diff --git a/interface/src/avatar/AvatarActionFarGrab.h b/interface/src/avatar/AvatarActionFarGrab.h index 97d4a6bb03..c9b8f6ff3c 100644 --- a/interface/src/avatar/AvatarActionFarGrab.h +++ b/interface/src/avatar/AvatarActionFarGrab.h @@ -19,13 +19,15 @@ * The "far-grab" {@link Entities.ActionType|ActionType} moves and rotates an entity to a target position and * orientation, optionally relative to another entity. Collisions between the entity and the user's avatar are disabled during * the far-grab. - * It has arguments in addition to the common {@link Entities.ActionArguments|ActionArguments}. + * It has arguments in addition to the common {@link Entities.ActionArguments|ActionArguments}: * * @typedef {object} Entities.ActionArguments-FarGrab + * @property {Uuid} otherID=null - If an entity ID, the targetPosition and targetRotation are + * relative to the entity's position and rotation. + * @property {Uuid} otherJointIndex=null - If a joint index in the otherID entity, the targetPosition + * and targetRotation are relative to the entity joint's position and rotation. * @property {Vec3} targetPosition=0,0,0 - The target position. * @property {Quat} targetRotation=0,0,0,1 - The target rotation. - * @property {Uuid} otherID=null - If an entity ID, the targetPosition and targetRotation are - * relative to this entity's position and rotation. * @property {number} linearTimeScale=3.4e+38 - Controls how long it takes for the entity's position to catch up with the * target position. The value is the time for the action to catch up to 1/e = 0.368 of the target value, where the action * is applied using an exponential decay. @@ -33,6 +35,7 @@ * target orientation. The value is the time for the action to catch up to 1/e = 0.368 of the target value, where the * action is applied using an exponential decay. */ +// The properties are per ObjectActionTractor. class AvatarActionFarGrab : public ObjectActionTractor { public: AvatarActionFarGrab(const QUuid& id, EntityItemPointer ownerEntity); diff --git a/interface/src/avatar/AvatarActionHold.cpp b/interface/src/avatar/AvatarActionHold.cpp index 5fb9c9a0ee..3ac9f63649 100644 --- a/interface/src/avatar/AvatarActionHold.cpp +++ b/interface/src/avatar/AvatarActionHold.cpp @@ -447,16 +447,16 @@ bool AvatarActionHold::updateArguments(QVariantMap arguments) { /**jsdoc * The "hold" {@link Entities.ActionType|ActionType} positions and rotates an entity relative to an avatar's hand. * Collisions between the entity and the user's avatar are disabled during the hold. - * It has arguments in addition to the common {@link Entities.ActionArguments|ActionArguments}. + * It has arguments in addition to the common {@link Entities.ActionArguments|ActionArguments}: * * @typedef {object} Entities.ActionArguments-Hold * @property {Uuid} holderID=MyAvatar.sessionUUID - The ID of the avatar holding the entity. + * @property {string} hand=right - The hand holding the entity: "left" or "right". * @property {Vec3} relativePosition=0,0,0 - The target position relative to the avatar's hand. * @property {Vec3} relativeRotation=0,0,0,1 - The target rotation relative to the avatar's hand. * @property {number} timeScale=3.4e+38 - Controls how long it takes for the entity's position and rotation to catch up with * the target. The value is the time for the action to catch up to 1/e = 0.368 of the target value, where the action is * applied using an exponential decay. - * @property {string} hand=right - The hand holding the entity: "left" or "right". * @property {boolean} kinematic=false - If true, the entity is made kinematic during the action; the entity won't * lag behind the hand but constraint actions such as "hinge" won't act properly. * @property {boolean} kinematicSetVelocity=false - If true and kinematic is true, the diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index aba74806c0..cce2af466d 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -6100,6 +6100,30 @@ QVariantList MyAvatar::getCollidingFlowJoints() { return result; } +int MyAvatar::getOverrideJointCount() const { + if (_skeletonModel) { + return _skeletonModel->getRig().getOverrideJointCount(); + } else { + return 0; + } +} + +bool MyAvatar::getFlowActive() const { + if (_skeletonModel) { + return _skeletonModel->getRig().getFlowActive(); + } else { + return false; + } +} + +bool MyAvatar::getNetworkGraphActive() const { + if (_skeletonModel) { + return _skeletonModel->getRig().getNetworkGraphActive(); + } else { + return false; + } +} + void MyAvatar::initFlowFromFST() { if (_skeletonModel->isLoaded()) { auto &flowData = _skeletonModel->getHFMModel().flowData; diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index f31e6e524e..d092122863 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -267,7 +267,7 @@ class MyAvatar : public Avatar { * @property {number} analogPlusWalkSpeed - The walk speed of your avatar for the "AnalogPlus" control scheme. *

Warning: Setting this value also sets the value of analogPlusSprintSpeed to twice * the value.

- * @property {number} analogPlusSprintSpeed - The sprint speed of your avatar for the "AnalogPlus" control scheme. + * @property {number} analogPlusSprintSpeed - The sprint (run) speed of your avatar for the "AnalogPlus" control scheme. * @property {MyAvatar.SitStandModelType} userRecenterModel - Controls avatar leaning and recentering behavior. * @property {number} isInSittingState - true if your avatar is sitting (avatar leaning is disabled, * recenntering is enabled), false if it is standing (avatar leaning is enabled, and avatar recenters if it @@ -1835,6 +1835,10 @@ public: */ Q_INVOKABLE QVariantList getCollidingFlowJoints(); + int getOverrideJointCount() const; + bool getFlowActive() const; + bool getNetworkGraphActive() const; + public slots: /**jsdoc @@ -2198,33 +2202,35 @@ signals: void audioListenerModeChanged(); /**jsdoc - * Notifies when the analogPlusWalkSpeed value is changed. + * Triggered when the walk speed set for the "AnalogPlus" control scheme changes. * @function MyAvatar.analogPlusWalkSpeedChanged - * @param {float} value - the new avatar walk speed + * @param {number} speed - The new walk speed set for the "AnalogPlus" control scheme. * @returns {Signal} */ void analogPlusWalkSpeedChanged(float value); /**jsdoc - * Notifies when the analogPlusSprintSpeed value is changed. + * Triggered when the sprint (run) speed set for the "AnalogPlus" control scheme changes. * @function MyAvatar.analogPlusSprintSpeedChanged - * @param {float} value - the new avatar sprint speed + * @param {number} speed - The new sprint speed set for the "AnalogPlus" control scheme. * @returns {Signal} */ void analogPlusSprintSpeedChanged(float value); /**jsdoc - * Notifies when the sprintSpeed value is changed. + * Triggered when the sprint (run) speed set for the current control scheme (see + * {@link MyAvatar.getControlScheme|getControlScheme}) changes. * @function MyAvatar.sprintSpeedChanged - * @param {float} value - the new avatar sprint speed + * @param {number} speed -The new sprint speed set for the current control scheme. * @returns {Signal} */ void sprintSpeedChanged(float value); /**jsdoc - * Notifies when the walkBackwardSpeed value is changed. + * Triggered when the walk backward speed set for the current control scheme (see + * {@link MyAvatar.getControlScheme|getControlScheme}) changes. * @function MyAvatar.walkBackwardSpeedChanged - * @param {float} value - the new avatar walk backward speed + * @param {number} speed - The new walk backward speed set for the current control scheme. * @returns {Signal} */ void walkBackwardSpeedChanged(float value); diff --git a/interface/src/scripting/PlatformInfoScriptingInterface.cpp b/interface/src/scripting/PlatformInfoScriptingInterface.cpp index 9a5a08503d..84c4d923d0 100644 --- a/interface/src/scripting/PlatformInfoScriptingInterface.cpp +++ b/interface/src/scripting/PlatformInfoScriptingInterface.cpp @@ -192,7 +192,7 @@ QString PlatformInfoScriptingInterface::getDisplay(int index) { } QString PlatformInfoScriptingInterface::getMemory() { - auto desc = platform::getMemory(0); + auto desc = platform::getMemory(); return QString(desc.dump().c_str()); } @@ -201,6 +201,10 @@ QString PlatformInfoScriptingInterface::getComputer() { return QString(desc.dump().c_str()); } +QString PlatformInfoScriptingInterface::getPlatform() { + auto desc = platform::getAll(); + return QString(desc.dump().c_str()); +} PlatformInfoScriptingInterface::PlatformTier PlatformInfoScriptingInterface::getTierProfiled() { return (PlatformInfoScriptingInterface::PlatformTier) platform::Profiler::profilePlatform(); diff --git a/interface/src/scripting/PlatformInfoScriptingInterface.h b/interface/src/scripting/PlatformInfoScriptingInterface.h index 476c5c5788..113509d6d9 100644 --- a/interface/src/scripting/PlatformInfoScriptingInterface.h +++ b/interface/src/scripting/PlatformInfoScriptingInterface.h @@ -51,6 +51,8 @@ public slots: * Gets the operating system type. * @function PlatformInfo.getOperatingSystemType * @returns {string} "WINDOWS", "MACOS", or "UNKNOWN". + * @deprecated This function is deprecated and will be removed. + * use getComputer()["OS"] instead */ QString getOperatingSystemType(); @@ -61,6 +63,10 @@ public slots: * @example Report the CPU being used. * print("CPU: " + PlatformInfo.getCPUBrand()); * // Example: Intel(R) Core(TM) i7-7820HK CPU @ 2.90GHz + * @deprecated This function is deprecated and will be removed. + * use getNumCPUs() to know the number of CPUs in the hardware, at least one is expected + * use getCPU(0)["vendor"] to get the brand of the vendor + * use getCPU(0)["model"] to get the model name of the cpu */ QString getCPUBrand(); @@ -68,6 +74,8 @@ public slots: * Gets the number of logical CPU cores. * @function PlatformInfo.getNumLogicalCores * @returns {number} The number of logical CPU cores. + * @deprecated This function is deprecated and will be removed. + * use getCPU(0)["numCores"] instead */ unsigned int getNumLogicalCores(); @@ -75,6 +83,8 @@ public slots: * Returns the total system memory in megabytes. * @function PlatformInfo.getTotalSystemMemoryMB * @returns {number} The total system memory in megabytes. + * @deprecated This function is deprecated and will be removed. + * use getMemory()["memTotal"] instead */ int getTotalSystemMemoryMB(); @@ -82,6 +92,10 @@ public slots: * Gets the graphics card type. * @function PlatformInfo.getGraphicsCardType * @returns {string} The graphics card type. + * @deprecated This function is deprecated and will be removed. + * use getNumGPUs() to know the number of GPUs in the hardware, at least one is expected + * use getGPU(0)["vendor"] to get the brand of the vendor + * use getGPU(0)["model"] to get the model name of the gpu */ QString getGraphicsCardType(); @@ -141,7 +155,7 @@ public slots: /**jsdoc * Get the description of the GPU at the index parameter * expected fields are: - * - gpuVendor... + * - vendor, model... * @param index The index of the GPU of the platform * @function PlatformInfo.getGPU * @returns {string} The GPU description json field @@ -183,6 +197,14 @@ public slots: */ QString getComputer(); + /**jsdoc + * Get the complete description of the Platform as an aggregated Json + * The expected object description is: + * { "computer": {...}, "memory": {...}, "cpus": [{...}, ...], "gpus": [{...}, ...], "displays": [{...}, ...] } + * @function PlatformInfo.getPlatform + * @returns {string} The Platform description json field + */ + QString getPlatform(); /**jsdoc * Get the Platform TIer profiled on startup of the Computer diff --git a/interface/src/scripting/RenderScriptingInterface.cpp b/interface/src/scripting/RenderScriptingInterface.cpp index 608f1d30e9..338a5ab883 100644 --- a/interface/src/scripting/RenderScriptingInterface.cpp +++ b/interface/src/scripting/RenderScriptingInterface.cpp @@ -30,14 +30,16 @@ void RenderScriptingInterface::loadSettings() { _shadowsEnabled = (_shadowsEnabledSetting.get()); _ambientOcclusionEnabled = (_ambientOcclusionEnabledSetting.get()); _antialiasingEnabled = (_antialiasingEnabledSetting.get()); + _viewportResolutionScale = (_viewportResolutionScaleSetting.get()); }); forceRenderMethod((RenderMethod)_renderMethod); forceShadowsEnabled(_shadowsEnabled); forceAmbientOcclusionEnabled(_ambientOcclusionEnabled); forceAntialiasingEnabled(_antialiasingEnabled); + forceViewportResolutionScale(_viewportResolutionScale); } -RenderScriptingInterface::RenderMethod RenderScriptingInterface::getRenderMethod() { +RenderScriptingInterface::RenderMethod RenderScriptingInterface::getRenderMethod() const { return (RenderMethod) _renderMethod; } @@ -64,7 +66,7 @@ QStringList RenderScriptingInterface::getRenderMethodNames() const { return refrenderMethodNames; } -bool RenderScriptingInterface::getShadowsEnabled() { +bool RenderScriptingInterface::getShadowsEnabled() const { return _shadowsEnabled; } @@ -88,7 +90,7 @@ void RenderScriptingInterface::forceShadowsEnabled(bool enabled) { }); } -bool RenderScriptingInterface::getAmbientOcclusionEnabled() { +bool RenderScriptingInterface::getAmbientOcclusionEnabled() const { return _ambientOcclusionEnabled; } @@ -112,7 +114,7 @@ void RenderScriptingInterface::forceAmbientOcclusionEnabled(bool enabled) { }); } -bool RenderScriptingInterface::getAntialiasingEnabled() { +bool RenderScriptingInterface::getAntialiasingEnabled() const { return _antialiasingEnabled; } @@ -145,3 +147,37 @@ void RenderScriptingInterface::forceAntialiasingEnabled(bool enabled) { } +float RenderScriptingInterface::getViewportResolutionScale() const { + return _viewportResolutionScale; +} + +void RenderScriptingInterface::setViewportResolutionScale(float scale) { + if (_viewportResolutionScale != scale) { + forceViewportResolutionScale(scale); + emit settingsChanged(); + } +} + +void RenderScriptingInterface::forceViewportResolutionScale(float scale) { + // just not negative values or zero + if (scale <= 0.f) { + return; + } + _renderSettingLock.withWriteLock([&] { + _viewportResolutionScale = (scale); + _viewportResolutionScaleSetting.set(scale); + + auto renderConfig = qApp->getRenderEngine()->getConfiguration(); + assert(renderConfig); + auto deferredView = renderConfig->getConfig("RenderMainView.RenderDeferredTask"); + // mainView can be null if we're rendering in forward mode + if (deferredView) { + deferredView->setProperty("resolutionScale", _viewportResolutionScale); + } + auto forwardView = renderConfig->getConfig("RenderMainView.RenderForwardTask"); + // mainView can be null if we're rendering in forward mode + if (forwardView) { + forwardView->setProperty("resolutionScale", _viewportResolutionScale); + } + }); +} diff --git a/interface/src/scripting/RenderScriptingInterface.h b/interface/src/scripting/RenderScriptingInterface.h index 39a88d4aad..9b96448c9d 100644 --- a/interface/src/scripting/RenderScriptingInterface.h +++ b/interface/src/scripting/RenderScriptingInterface.h @@ -29,6 +29,7 @@ class RenderScriptingInterface : public QObject { Q_PROPERTY(bool shadowsEnabled READ getShadowsEnabled WRITE setShadowsEnabled NOTIFY settingsChanged) Q_PROPERTY(bool ambientOcclusionEnabled READ getAmbientOcclusionEnabled WRITE setAmbientOcclusionEnabled NOTIFY settingsChanged) Q_PROPERTY(bool antialiasingEnabled READ getAntialiasingEnabled WRITE setAntialiasingEnabled NOTIFY settingsChanged) + Q_PROPERTY(float viewportResolutionScale READ getViewportResolutionScale WRITE setViewportResolutionScale NOTIFY settingsChanged) public: RenderScriptingInterface(); @@ -66,7 +67,7 @@ public slots: * @function Render.getRenderMethod * @returns {number} "DEFERRED" or "FORWARD" */ - RenderMethod getRenderMethod(); + RenderMethod getRenderMethod() const; /**jsdoc * Sets the current render method @@ -88,7 +89,7 @@ public slots: * @function Render.getShadowsEnabled * @returns {bool} true if shadows are enabled, otherwise false */ - bool getShadowsEnabled(); + bool getShadowsEnabled() const; /**jsdoc * Enables or disables shadows @@ -102,7 +103,7 @@ public slots: * @function Render.getAmbientOcclusionEnabled * @returns {bool} true if ambient occlusion is enabled, otherwise false */ - bool getAmbientOcclusionEnabled(); + bool getAmbientOcclusionEnabled() const; /**jsdoc * Enables or disables ambient occlusion @@ -116,7 +117,7 @@ public slots: * @function Render.getAntialiasingEnabled * @returns {bool} true if anti-aliasing is enabled, otherwise false */ - bool getAntialiasingEnabled(); + bool getAntialiasingEnabled() const; /**jsdoc * Enables or disables anti-aliasing @@ -130,14 +131,14 @@ public slots: * @function Render.getViewportResolutionScale * @returns {number} */ - // float getViewportResolutionScale(); + float getViewportResolutionScale() const; /**jsdoc * Sets the current viewport resolution scale * @function Render.setViewportResolutionScale * @param {number} resolutionScale - between epsilon and 1.0 */ - // void setViewportResolutionScale(float resolutionScale); + void setViewportResolutionScale(float resolutionScale); signals: void settingsChanged(); @@ -150,19 +151,22 @@ private: int _renderMethod{ RENDER_FORWARD ? render::Args::RenderMethod::FORWARD : render::Args::RenderMethod::DEFERRED }; bool _shadowsEnabled{ true }; bool _ambientOcclusionEnabled{ false }; - bool _antialiasingEnabled { true }; + bool _antialiasingEnabled{ true }; + float _viewportResolutionScale{ 1.0f }; // Actual settings saved on disk Setting::Handle _renderMethodSetting { "renderMethod", RENDER_FORWARD ? render::Args::RenderMethod::FORWARD : render::Args::RenderMethod::DEFERRED }; Setting::Handle _shadowsEnabledSetting { "shadowsEnabled", true }; Setting::Handle _ambientOcclusionEnabledSetting { "ambientOcclusionEnabled", false }; Setting::Handle _antialiasingEnabledSetting { "antialiasingEnabled", true }; + Setting::Handle _viewportResolutionScaleSetting { "viewportResolutionScale", 1.0f }; // Force assign both setting AND runtime value to the parameter value void forceRenderMethod(RenderMethod renderMethod); void forceShadowsEnabled(bool enabled); void forceAmbientOcclusionEnabled(bool enabled); void forceAntialiasingEnabled(bool enabled); + void forceViewportResolutionScale(float scale); static std::once_flag registry_flag; }; diff --git a/interface/src/scripting/TestScriptingInterface.h b/interface/src/scripting/TestScriptingInterface.h index 897f955a74..dffda3ece0 100644 --- a/interface/src/scripting/TestScriptingInterface.h +++ b/interface/src/scripting/TestScriptingInterface.h @@ -165,14 +165,14 @@ public slots: /**jsdoc * Set number of cycles texture size is required to be stable - * @function Entities.setMinimumGPUTextureMemStabilityCount + * @function Test.setMinimumGPUTextureMemStabilityCount * @param {number} count - Number of cycles to wait */ Q_INVOKABLE void setMinimumGPUTextureMemStabilityCount(int count); /**jsdoc * Check whether all textures have been loaded. - * @function Entities.isTextureLoadingComplete + * @function Test.isTextureLoadingComplete * @returns {boolean} true texture memory usage is not increasing */ Q_INVOKABLE bool isTextureLoadingComplete(); diff --git a/interface/src/ui/AnimStats.cpp b/interface/src/ui/AnimStats.cpp index 6317c069f4..d4696a8c04 100644 --- a/interface/src/ui/AnimStats.cpp +++ b/interface/src/ui/AnimStats.cpp @@ -94,6 +94,21 @@ void AnimStats::updateStats(bool force) { } emit walkingTextChanged(); + // print current overrideJointText + int overrideJointCount = myAvatar->getOverrideJointCount(); + _overrideJointText = QString("Override Joint Count: %1").arg(overrideJointCount); + emit overrideJointTextChanged(); + + // print current flowText + bool flowActive = myAvatar->getFlowActive(); + _flowText = QString("Flow: %1").arg(flowActive ? "enabled" : "disabled"); + emit flowTextChanged(); + + // print current networkGraphText + bool networkGraphActive = myAvatar->getNetworkGraphActive(); + _networkGraphText = QString("Network Graph: %1").arg(networkGraphActive ? "enabled" : "disabled"); + emit networkGraphTextChanged(); + // update animation debug alpha values QStringList newAnimAlphaValues; qint64 now = usecTimestampNow(); diff --git a/interface/src/ui/AnimStats.h b/interface/src/ui/AnimStats.h index 57d2a4f1a6..ccd7187d9b 100644 --- a/interface/src/ui/AnimStats.h +++ b/interface/src/ui/AnimStats.h @@ -25,6 +25,9 @@ class AnimStats : public QQuickItem { Q_PROPERTY(QString recenterText READ recenterText NOTIFY recenterTextChanged) Q_PROPERTY(QString sittingText READ sittingText NOTIFY sittingTextChanged) Q_PROPERTY(QString walkingText READ walkingText NOTIFY walkingTextChanged) + Q_PROPERTY(QString overrideJointText READ overrideJointText NOTIFY overrideJointTextChanged) + Q_PROPERTY(QString flowText READ flowText NOTIFY flowTextChanged) + Q_PROPERTY(QString networkGraphText READ networkGraphText NOTIFY networkGraphTextChanged) public: static AnimStats* getInstance(); @@ -43,6 +46,9 @@ public: QString recenterText() const { return _recenterText; } QString sittingText() const { return _sittingText; } QString walkingText() const { return _walkingText; } + QString overrideJointText() const { return _overrideJointText; } + QString flowText() const { return _flowText; } + QString networkGraphText() const { return _networkGraphText; } public slots: void forceUpdateStats() { updateStats(true); } @@ -58,6 +64,9 @@ signals: void recenterTextChanged(); void sittingTextChanged(); void walkingTextChanged(); + void overrideJointTextChanged(); + void flowTextChanged(); + void networkGraphTextChanged(); private: QStringList _animAlphaValues; @@ -76,6 +85,9 @@ private: QString _recenterText; QString _sittingText; QString _walkingText; + QString _overrideJointText; + QString _flowText; + QString _networkGraphText; }; #endif // hifi_AnimStats_h diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index e0505770f5..ce70e91128 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -14,10 +14,9 @@ #include #include #include -#include #include #include - +#include "scripting/RenderScriptingInterface.h" #include "Application.h" #include "DialogsManager.h" #include "LODManager.h" @@ -103,6 +102,22 @@ void setupPreferences() { preference->setItems(refreshRateProfiles); preferences->addPreference(preference); } + { + // Expose the Viewport Resolution Scale + auto getter = []()->float { + return RenderScriptingInterface::getInstance()->getViewportResolutionScale(); + }; + + auto setter = [](float value) { + RenderScriptingInterface::getInstance()->setViewportResolutionScale(value); + }; + + auto scaleSlider = new SliderPreference(GRAPHICS_QUALITY, "Resolution Scale", getter, setter); + scaleSlider->setMin(0.25f); + scaleSlider->setMax(1.0f); + scaleSlider->setStep(0.02f); + preferences->addPreference(scaleSlider); + } // UI static const QString UI_CATEGORY { "User Interface" }; diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp index dc9780adf5..8b9b9743f0 100644 --- a/interface/src/ui/Stats.cpp +++ b/interface/src/ui/Stats.cpp @@ -377,7 +377,7 @@ void Stats::updateStats(bool force) { auto displayPlugin = qApp->getActiveDisplayPlugin(); if (displayPlugin) { QVector2D dims(displayPlugin->getRecommendedRenderSize().x, displayPlugin->getRecommendedRenderSize().y); - dims *= displayPlugin->getRenderResolutionScale(); + dims *= qApp->getRenderResolutionScale(); STAT_UPDATE(gpuFrameSize, dims); STAT_UPDATE(gpuFrameTimePerPixel, (float)(gpuContext->getFrameTimerGPUAverage()*1000000.0 / double(dims.x()*dims.y()))); } diff --git a/launchers/darwin/CMakeLists.txt b/launchers/darwin/CMakeLists.txt index 4da675fcc9..a3fc0dc7c1 100644 --- a/launchers/darwin/CMakeLists.txt +++ b/launchers/darwin/CMakeLists.txt @@ -1,4 +1,5 @@ cmake_minimum_required(VERSION 3.0) +set(ENV{MACOSX_DEPLOYMENT_TARGET} 10.9) project(HQLauncher) set (CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules/") set(src_files @@ -26,6 +27,8 @@ set(src_files src/LatestBuildRequest.m src/OrganizationRequest.m src/OrganizationRequest.h + src/Interface.h + src/Interface.m src/ErrorViewController.h src/ErrorViewController.m src/Settings.h @@ -67,7 +70,7 @@ add_executable(${PROJECT_NAME} MACOSX_BUNDLE ${src_files}) set_target_properties(${PROJECT_NAME} PROPERTIES OUTPUT_NAME ${APP_NAME} MACOSX_BUNDLE_BUNDLE_NAME ${APP_NAME}) set_from_env(LAUNCHER_HMAC_SECRET LAUNCHER_HMAC_SECRET "") -if (LAUNCHER_HMAC_SECRET STREQUAL "") +if ("${LAUNCHER_HMAC_SECRET}" STREQUAL "") message(FATAL_ERROR "LAUNCHER_HMAC_SECRET is not set") endif() diff --git a/launchers/darwin/cmake/modules/MacOSXBundleInfo.plist.in b/launchers/darwin/cmake/modules/MacOSXBundleInfo.plist.in index 4c87bff3cf..3fe8e80f7a 100644 --- a/launchers/darwin/cmake/modules/MacOSXBundleInfo.plist.in +++ b/launchers/darwin/cmake/modules/MacOSXBundleInfo.plist.in @@ -32,6 +32,6 @@ CFBundleName ${MACOSX_BUNDLE_BUNDLE_NAME} CFBundleDisplayName - CFBundleName + ${MACOSX_BUNDLE_BUNDLE_NAME} diff --git a/launchers/darwin/nib/DisplayNameScreen.xib b/launchers/darwin/nib/DisplayNameScreen.xib index e56f2bf66b..3ba3deba53 100644 --- a/launchers/darwin/nib/DisplayNameScreen.xib +++ b/launchers/darwin/nib/DisplayNameScreen.xib @@ -69,8 +69,17 @@ + + + + + + + + + + + + + + + + + + + + + @@ -91,5 +112,14 @@ + + + + + + + + + diff --git a/launchers/darwin/src/DisplayNameScreen.m b/launchers/darwin/src/DisplayNameScreen.m index 7a402d792b..581eabc4ee 100644 --- a/launchers/darwin/src/DisplayNameScreen.m +++ b/launchers/darwin/src/DisplayNameScreen.m @@ -31,4 +31,9 @@ { [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"https://www.highfidelity.com/hq-support"]]; } + +- (IBAction)termsOfService:(id)sender +{ + [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"https://www.highfidelity.com/termsofservice"]]; +} @end diff --git a/launchers/darwin/src/Interface.h b/launchers/darwin/src/Interface.h new file mode 100644 index 0000000000..c142aeecf5 --- /dev/null +++ b/launchers/darwin/src/Interface.h @@ -0,0 +1,8 @@ +#import + +@interface Interface : NSObject + +-(id _Nonnull) initWith:(NSString * _Nonnull) aPathToInterface; +-(NSInteger) getVersion:(out NSError * _Nullable * _Nonnull) anError; + +@end diff --git a/launchers/darwin/src/Interface.m b/launchers/darwin/src/Interface.m new file mode 100644 index 0000000000..d02b66b581 --- /dev/null +++ b/launchers/darwin/src/Interface.m @@ -0,0 +1,90 @@ +#import "Interface.h" + +@implementation Interface +{ + NSString *pathTo; +} + +-(id) initWith:(NSString*)aPathToInterface +{ + [self init]; + self->pathTo = [NSString stringWithFormat:@"%@/Contents/MacOS/interface", aPathToInterface]; + return self; +} + +-(NSInteger) getVersion:(out NSError * _Nullable *) outError +{ + NSTask * interface = [[NSTask alloc] init]; + NSPipe * standardOut = [NSPipe pipe]; + + interface.launchPath = self->pathTo; + interface.arguments = @[ @"--version" ]; + interface.standardOutput = standardOut; + + NSLog(@"calling interface at %@", self->pathTo); + + NSError *error = nil; + [interface launch]; + [interface waitUntilExit]; + if (0 != [interface terminationStatus]) { + *outError = [NSError errorWithDomain:@"interface" + code:-1 + userInfo:@{NSUnderlyingErrorKey: error}]; + return 0; + } + + NSFileHandle * fh = [standardOut fileHandleForReading]; + NSData * data = [fh readDataToEndOfFile]; + NSString * output = [NSString stringWithUTF8String:[data bytes]]; + if (output == nil) { + NSDictionary * userInfo = @{ + NSLocalizedDescriptionKey: NSLocalizedString(@"Couldn't start interface", nil) + }; + *outError = [NSError errorWithDomain:@"interface" + code:-1 + userInfo:userInfo]; + return 0; + } + + // Interface returns the build version as a string like this: + // "Interface 33333-DEADBEEF". This code grabs the substring + // between "Interface " and the hyphon ("-") + NSRange start = [output rangeOfString:@"Interface "]; + if (start.length == 0) { + NSDictionary * userInfo = @{ + NSLocalizedDescriptionKey: NSLocalizedString(@"Couldn't read interface's version", nil) + }; + *outError = [NSError errorWithDomain:@"interface" + code:-2 + userInfo:userInfo]; + return 0; + } + NSRange end = [output rangeOfString:@"-"]; + if (end.length == 0) { + NSDictionary * userInfo = @{ + NSLocalizedDescriptionKey: NSLocalizedString(@"Couldn't read interface's version", nil) + }; + *outError = [NSError errorWithDomain:@"interface" + code:-2 + userInfo:userInfo]; + return 0; + } + NSRange subRange = {start.length, end.location - start.length}; + NSString * versionStr; + @try { + versionStr = [output substringWithRange:subRange]; + } + @catch (NSException *) { + NSDictionary * userInfo = @{ + NSLocalizedDescriptionKey: NSLocalizedString(@"Couldn't read interface's version", nil) + }; + *outError = [NSError errorWithDomain:@"interface" + code:-2 + userInfo:userInfo]; + return 0; + } + + return versionStr.integerValue; +} + +@end diff --git a/launchers/darwin/src/LatestBuildRequest.m b/launchers/darwin/src/LatestBuildRequest.m index 019637ed55..0c136dce3c 100644 --- a/launchers/darwin/src/LatestBuildRequest.m +++ b/launchers/darwin/src/LatestBuildRequest.m @@ -1,29 +1,58 @@ #import "LatestBuildRequest.h" #import "Launcher.h" #import "Settings.h" +#import "Interface.h" @implementation LatestBuildRequest +- (NSInteger) getCurrentVersion { + NSInteger currentVersion; + @try { + NSString* interfaceAppPath = [[Launcher.sharedLauncher getAppPath] stringByAppendingString:@"interface.app"]; + NSError * error = nil; + Interface * interface = [[Interface alloc] initWith:interfaceAppPath]; + currentVersion = [interface getVersion:&error]; + if (currentVersion == 0 && error != nil) { + NSLog(@"can't get version from interface, falling back to settings: %@", error); + currentVersion = [Settings.sharedSettings latestBuildVersion]; + } + } @catch (NSException *exception) { + NSLog(@"an exception was thrown: %@", exception); + currentVersion = [Settings.sharedSettings latestBuildVersion]; + } + return currentVersion; +} + - (void) requestLatestBuildInfo { NSMutableURLRequest *request = [NSMutableURLRequest new]; [request setURL:[NSURL URLWithString:@"https://thunder.highfidelity.com/builds/api/tags/latest?format=json"]]; [request setHTTPMethod:@"GET"]; [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"]; - - NSURLSession* session = [NSURLSession sharedSession]; + // We're using an ephermeral session here to ensure the tags api response is never cached. + NSURLSession * session = [NSURLSession sessionWithConfiguration:NSURLSessionConfiguration.ephemeralSessionConfiguration]; NSURLSessionDataTask* dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { + + NSLog(@"Latest Build Request error: %@", error); + NSLog(@"Latest Build Request Data: %@", data); + NSHTTPURLResponse *ne = (NSHTTPURLResponse *)response; + NSLog(@"Latest Build Request Response: %ld", [ne statusCode]); Launcher* sharedLauncher = [Launcher sharedLauncher]; - NSLog(@"credentials request finished"); NSMutableData* webData = [NSMutableData data]; [webData appendData:data]; NSString* jsonString = [[NSString alloc] initWithBytes: [webData mutableBytes] length:[data length] encoding:NSUTF8StringEncoding]; NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding]; - id json = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:nil]; + NSLog(@"Latest Build Request -> json string: %@", jsonString); + NSError *jsonError = nil; + id json = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&jsonError]; + + if (jsonError) { + NSLog(@"Latest Build request: Failed to convert Json to data"); + } NSFileManager* fileManager = [NSFileManager defaultManager]; - NSArray *values = [json valueForKey:@"results"]; + NSArray *values = [json valueForKey:@"results"]; NSDictionary *value = [values objectAtIndex:0]; @@ -35,12 +64,17 @@ BOOL appDirectoryExist = [fileManager fileExistsAtPath:[[sharedLauncher getAppPath] stringByAppendingString:@"interface.app"]]; dispatch_async(dispatch_get_main_queue(), ^{ - Settings* settings = [Settings sharedSettings]; - NSInteger currentVersion = [settings latestBuildVersion]; + + NSInteger currentVersion = [self getCurrentVersion]; + NSLog(@"Latest Build Request -> does build directory exist: %@", appDirectoryExist ? @"TRUE" : @"FALSE"); + NSLog(@"Latest Build Request -> current version: %ld", currentVersion); + NSLog(@"Latest Build Request -> latest version: %ld", buildNumber.integerValue); + NSLog(@"Latest Build Request -> mac url: %@", macInstallerUrl); BOOL latestVersionAvailable = (currentVersion != buildNumber.integerValue); [[Settings sharedSettings] buildVersion:buildNumber.integerValue]; BOOL shouldDownloadInterface = (latestVersionAvailable || !appDirectoryExist); + NSLog(@"Latest Build Request -> SHOULD DOWNLOAD: %@", shouldDownloadInterface ? @"TRUE" : @"FALSE"); [sharedLauncher shouldDownloadLatestBuild:shouldDownloadInterface :macInstallerUrl]; }); }]; @@ -90,11 +124,10 @@ NSDictionary* macInstallerObject = [installers objectForKey:@"mac"]; NSString* macInstallerUrl = [macInstallerObject valueForKey:@"zip_url"]; - BOOL appDirectoryExist = [fileManager fileExistsAtPath:[[sharedLauncher getAppPath] stringByAppendingString:@"interface.app"]]; - - Settings* settings = [Settings sharedSettings]; - NSInteger currentVersion = [settings latestBuildVersion]; - BOOL latestVersionAvailable = (currentVersion != buildNumber.integerValue); + NSString* interfaceAppPath = [[sharedLauncher getAppPath] stringByAppendingString:@"interface.app"]; + BOOL appDirectoryExist = [fileManager fileExistsAtPath:interfaceAppPath]; + + BOOL latestVersionAvailable = ([self getCurrentVersion] != buildNumber.integerValue); [[Settings sharedSettings] buildVersion:buildNumber.integerValue]; BOOL shouldDownloadInterface = (latestVersionAvailable || !appDirectoryExist); diff --git a/launchers/darwin/src/main.mm b/launchers/darwin/src/main.mm index 7feab64d86..b6555aad87 100644 --- a/launchers/darwin/src/main.mm +++ b/launchers/darwin/src/main.mm @@ -5,13 +5,13 @@ void redirectLogToDocuments() { NSString* filePath = [[NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingString:@"/Launcher/"]; - + if (![[NSFileManager defaultManager] fileExistsAtPath:filePath]) { NSError * error = nil; [[NSFileManager defaultManager] createDirectoryAtPath:filePath withIntermediateDirectories:TRUE attributes:nil error:&error]; } NSString *pathForLog = [filePath stringByAppendingPathComponent:@"log.txt"]; - + freopen([pathForLog cStringUsingEncoding:NSASCIIStringEncoding],"a+",stderr); } @@ -23,12 +23,12 @@ int main(int argc, const char* argv[]) { NSLog(@"launcher is already running"); return 0; } - + [NSApplication sharedApplication]; Launcher* sharedLauncher = [Launcher sharedLauncher]; [Settings sharedSettings]; [NSApp setDelegate: sharedLauncher]; - + // Referenced from https://stackoverflow.com/questions/9155015/handle-cmd-q-in-cocoa-application-and-menu-item-quit-application-programmatic id menubar = [[NSMenu new] autorelease]; id appMenuItem = [[NSMenuItem new] autorelease]; @@ -40,7 +40,7 @@ int main(int argc, const char* argv[]) { id quitMenuItem = [[[NSMenuItem alloc] initWithTitle:quitTitle action:@selector(terminate:) keyEquivalent:@"q"] autorelease]; [appMenu addItem:quitMenuItem]; [appMenuItem setSubmenu:appMenu]; - + [[NSApplication sharedApplication] activateIgnoringOtherApps:TRUE]; return NSApplicationMain(argc, argv); } diff --git a/launchers/win32/Launcher.rc b/launchers/win32/Launcher.rc index 49f3ca53c5..4d9e0ffcd6 100644 --- a/launchers/win32/Launcher.rc +++ b/launchers/win32/Launcher.rc @@ -103,7 +103,7 @@ BEGIN CTEXT "",IDC_MESSAGE2_LABEL,35,172,239,15,NOT WS_VISIBLE CTEXT "",IDC_ACTION2_LABEL,15,147,278,25,NOT WS_VISIBLE RTEXT "",IDC_TERMS,15,172,180,15,NOT WS_VISIBLE - LTEXT "",IDC_TERMS2,197,172,80,15,NOT WS_VISIBLE + CONTROL "",IDC_TERMS_LINK,"Button", BS_OWNERDRAW | BS_FLAT | NOT WS_VISIBLE | WS_TABSTOP,197,172,80,15 CTEXT "",IDC_TROUBLE,65,203,174,15,NOT WS_VISIBLE CONTROL "NEXT",IDC_BUTTON_NEXT,"Button",BS_OWNERDRAW | BS_FLAT | NOT WS_VISIBLE | WS_TABSTOP,107,158,94,16 CONTROL "Having Trouble?",IDC_TROUBLE_LINK,"Button",BS_OWNERDRAW | BS_FLAT | NOT WS_VISIBLE | WS_TABSTOP,126,203,56,11 diff --git a/launchers/win32/LauncherDlg.cpp b/launchers/win32/LauncherDlg.cpp index d3cae39013..a4fe9e494b 100644 --- a/launchers/win32/LauncherDlg.cpp +++ b/launchers/win32/LauncherDlg.cpp @@ -37,6 +37,7 @@ static CString GRAPHIK_REGULAR = _T("Graphik-Regular"); static CString GRAPHIK_SEMIBOLD = _T("Graphik-Semibold"); static CString TROUBLE_URL = _T("https://www.highfidelity.com/hq-support"); +static CString TERMS_URL = _T("https://www.highfidelity.com/termsofservice"); CLauncherDlg::CLauncherDlg(CWnd* pParent) @@ -54,6 +55,7 @@ void CLauncherDlg::DoDataExchange(CDataExchange* pDX) { DDX_Control(pDX, IDC_BUTTON_NEXT, m_btnNext); DDX_Control(pDX, IDC_TROUBLE_LINK, m_trouble_link); + DDX_Control(pDX, IDC_TERMS_LINK, m_terms_link); DDX_Control(pDX, IDC_ORGNAME, m_orgname); DDX_Control(pDX, IDC_USERNAME, m_username); DDX_Control(pDX, IDC_PASSWORD, m_password); @@ -69,6 +71,7 @@ BEGIN_MESSAGE_MAP(CLauncherDlg, CDialog) ON_EN_SETFOCUS(IDC_PASSWORD, &CLauncherDlg::OnPassEditChangeFocus) ON_BN_CLICKED(IDC_BUTTON_NEXT, &CLauncherDlg::OnNextClicked) ON_BN_CLICKED(IDC_TROUBLE_LINK, &CLauncherDlg::OnTroubleClicked) + ON_BN_CLICKED(IDC_TERMS_LINK, &CLauncherDlg::OnTermsClicked) ON_WM_CTLCOLOR() ON_WM_DRAWITEM() ON_WM_SETCURSOR() @@ -103,7 +106,6 @@ BOOL CLauncherDlg::OnInitDialog() { m_password_banner = (CStatic *)GetDlgItem(IDC_PASSWORD_BANNER); m_terms = (CStatic *)GetDlgItem(IDC_TERMS); - m_terms2 = (CStatic *)GetDlgItem(IDC_TERMS2); m_trouble = (CStatic *)GetDlgItem(IDC_TROUBLE); m_voxel = (CStatic *)GetDlgItem(IDC_VOXEL); @@ -213,6 +215,9 @@ BOOL CLauncherDlg::getHQInfo(const CString& orgname) { CString lowerOrgName = orgname; lowerOrgName.MakeLower(); LauncherUtils::hMac256(lowerOrgName, LAUNCHER_HMAC_SECRET, hash); + CString msg; + msg.Format(_T("Calculated hash: \"%s\" => \"%s\""), lowerOrgName, hash); + theApp._manager.addToLog(msg); return theApp._manager.readOrganizationJSON(hash) == LauncherUtils::ResponseError::NoError; } @@ -220,6 +225,10 @@ afx_msg void CLauncherDlg::OnTroubleClicked() { LauncherUtils::executeOnForeground(TROUBLE_URL, _T("")); } +afx_msg void CLauncherDlg::OnTermsClicked() { + LauncherUtils::executeOnForeground(TERMS_URL, _T("")); +} + afx_msg void CLauncherDlg::OnNextClicked() { if (_drawStep != DrawStep::DrawChoose) { CString token; @@ -272,7 +281,7 @@ void CLauncherDlg::drawLogo(CHwndRenderTarget* pRenderTarget) { CD2DBitmap m_pBitmamLogo(pRenderTarget, IDB_PNG2, _T("PNG")); auto size = pRenderTarget->GetSize(); int logoWidth = 231; - int logoHeight = 181; + int logoHeight = 173; float logoPosX = 0.5f * (size.width - logoWidth); float logoPosY = 0.95f * (size.height - logoHeight); CD2DRectF logoRec(logoPosX, logoPosY, logoPosX + logoWidth, logoPosY + logoHeight); @@ -359,9 +368,9 @@ void CLauncherDlg::prepareChoose() { m_action_label->SetWindowTextW(_T("Choose a display name")); m_message_label->SetWindowTextW(_T("This is the name that your teammates will see.")); m_terms->ShowWindow(SW_SHOW); - m_terms2->ShowWindow(SW_SHOW); + m_terms_link.ShowWindow(SW_SHOW); m_terms->SetWindowTextW(_T("By signing in, you agree to the High Fidelity")); - m_terms2->SetWindowTextW(_T("Terms of Service")); + m_terms_link.SetWindowTextW(_T("Terms of Service")); CRect rec; m_btnNext.GetWindowRect(&rec); ScreenToClient(&rec); @@ -377,7 +386,7 @@ void CLauncherDlg::prepareProcess(DrawStep step) { m_trouble->ShowWindow(SW_HIDE); m_trouble_link.ShowWindow(SW_HIDE); m_terms->ShowWindow(SW_HIDE); - m_terms2->ShowWindow(SW_HIDE); + m_terms_link.ShowWindow(SW_HIDE); m_orgname_banner->ShowWindow(SW_HIDE); m_username_banner->ShowWindow(SW_HIDE); m_password_banner->ShowWindow(SW_HIDE); @@ -462,7 +471,7 @@ BOOL CLauncherDlg::getTextFormat(int resID, TextFormat& formatOut) { case IDC_TERMS: formatOut.size = TERMS_FONT_SIZE; break; - case IDC_TERMS2: + case IDC_TERMS_LINK: formatOut.size = TERMS_FONT_SIZE; formatOut.isBold = true; break; @@ -539,6 +548,14 @@ void CLauncherDlg::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct) dc.SelectObject(buttonFont); } dc.DrawText(_T("Having Trouble"), CRect(rect.left, rect.top, rect.right, rect.bottom), DT_CENTER | DT_VCENTER | DT_SINGLELINE); + } else if (nIDCtl == IDC_TERMS_LINK) { + dc.FillSolidRect(rect, COLOR_BLACK); + dc.SetTextColor(COLOR_LIGHT_GREY); + CFont buttonFont; + if (LauncherUtils::getFont(GRAPHIK_SEMIBOLD, TERMS_FONT_SIZE, true, buttonFont)) { + dc.SelectObject(buttonFont); + } + dc.DrawText(_T("Terms of Service"), CRect(rect.left, rect.top, rect.right, rect.bottom), DT_LEFT | DT_TOP | DT_SINGLELINE); } } diff --git a/launchers/win32/LauncherDlg.h b/launchers/win32/LauncherDlg.h index beaaedcdb6..6357c2a5b0 100644 --- a/launchers/win32/LauncherDlg.h +++ b/launchers/win32/LauncherDlg.h @@ -72,13 +72,13 @@ protected: HICON m_hIcon; CButton m_btnNext; CButton m_trouble_link; + CButton m_terms_link; CStatic* m_message_label; CStatic* m_action_label; CStatic* m_message2_label; CStatic* m_action2_label; CStatic* m_terms; - CStatic* m_terms2; CStatic* m_trouble; CStatic* m_voxel; @@ -111,6 +111,7 @@ protected: afx_msg HCURSOR OnQueryDragIcon(); afx_msg void OnNextClicked(); afx_msg void OnTroubleClicked(); + afx_msg void OnTermsClicked(); afx_msg void OnOrgEditChangeFocus(); afx_msg void OnUserEditChangeFocus(); afx_msg void OnPassEditChangeFocus(); diff --git a/launchers/win32/LauncherManager.cpp b/launchers/win32/LauncherManager.cpp index fc79287457..47c84f1124 100644 --- a/launchers/win32/LauncherManager.cpp +++ b/launchers/win32/LauncherManager.cpp @@ -427,6 +427,11 @@ BOOL LauncherManager::extractApplication() { LauncherUtils::cStringToStd(installPath), [&](int type, int size) { onZipExtracted((ZipType)type, size); }); + if (success) { + addToLog(_T("Created thread for unzipping application.")); + } else { + addToLog(_T("Failed to create thread for unzipping application.")); + } return success; } @@ -449,6 +454,11 @@ BOOL LauncherManager::installContent() { LauncherUtils::cStringToStd(contentPath), [&](int type, int size) { onZipExtracted((ZipType)type, size); }); + if (success) { + addToLog(_T("Created thread for unzipping content.")); + } else { + addToLog(_T("Failed to create thread for unzipping content.")); + } return success; } diff --git a/launchers/win32/LauncherUtils.cpp b/launchers/win32/LauncherUtils.cpp index cfb8b765c5..3ffbd37c58 100644 --- a/launchers/win32/LauncherUtils.cpp +++ b/launchers/win32/LauncherUtils.cpp @@ -16,6 +16,7 @@ #pragma comment(lib, "winhttp") +#include "LauncherApp.h" #include "LauncherUtils.h" CString LauncherUtils::urlEncodeString(const CString& url) { @@ -183,6 +184,17 @@ LauncherUtils::ResponseError LauncherUtils::makeHTTPCall(const CString& callerNa BOOL haveContentLength = WinHttpQueryHeaders(hrequest, WINHTTP_QUERY_CONTENT_LENGTH, NULL, &szContentLength, &bufferBytes, &dwHeaderIndex); + DWORD statusCode; + DWORD statusCodeSize = sizeof(statusCode); + WinHttpQueryHeaders(hrequest, + WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, + NULL, + &statusCode, &statusCodeSize, WINHTTP_NO_HEADER_INDEX); + + CString msg; + msg.Format(_T("Status code response (%s%s): %lu"), mainUrl, dirUrl, statusCode); + theApp._manager.addToLog(msg); + DWORD dwContentLength; if (haveContentLength) { dwContentLength = _wtoi(szContentLength); @@ -225,19 +237,37 @@ BOOL LauncherUtils::getFont(const CString& fontName, int fontSize, bool isBold, } uint64_t LauncherUtils::extractZip(const std::string& zipFile, const std::string& path, std::vector& files) { + { + CString msg; + msg.Format(_T("Reading zip file %s, extracting to %s"), CString(zipFile.c_str()), CString(path.c_str())); + theApp._manager.addToLog(msg); + } + mz_zip_archive zip_archive; memset(&zip_archive, 0, sizeof(zip_archive)); auto status = mz_zip_reader_init_file(&zip_archive, zipFile.c_str(), 0); - if (!status) return 0; + if (!status) { + auto zip_error = mz_zip_get_last_error(&zip_archive); + auto zip_error_msg = mz_zip_get_error_string(zip_error); + CString msg; + msg.Format(_T("Failed to initialize miniz: %d %s"), zip_error, CString(zip_error_msg)); + theApp._manager.addToLog(msg); + return 0; + } + int fileCount = (int)mz_zip_reader_get_num_files(&zip_archive); if (fileCount == 0) { + theApp._manager.addToLog(_T("Zip archive has a file count of 0")); + mz_zip_reader_end(&zip_archive); return 0; } mz_zip_archive_file_stat file_stat; if (!mz_zip_reader_file_stat(&zip_archive, 0, &file_stat)) { + theApp._manager.addToLog(_T("Zip archive cannot be stat'd")); + mz_zip_reader_end(&zip_archive); return 0; } @@ -263,6 +293,12 @@ uint64_t LauncherUtils::extractZip(const std::string& zipFile, const std::string } } + { + CString msg; + msg.Format(_T("Done unzipping archive, total size: %llu"), totalSize); + theApp._manager.addToLog(msg); + } + // Close the archive, freeing any resources it was using mz_zip_reader_end(&zip_archive); return totalSize; diff --git a/launchers/win32/resource.h b/launchers/win32/resource.h index 51e6e7bd71..f5a1e3ef07 100644 --- a/launchers/win32/resource.h +++ b/launchers/win32/resource.h @@ -23,7 +23,7 @@ #define IDC_USERNAME_BANNER 1019 #define IDC_PASSWORD_BANNER 1020 #define IDC_TERMS 1021 -#define IDC_TERMS2 1022 +#define IDC_TERMS_LINK 1022 #define IDC_TROUBLE 1023 #define IDC_VOXEL 1024 #define IDC_TROUBLE_LINK 1027 diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index a58b8745d7..0f0c67b846 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -2025,6 +2025,9 @@ void Rig::updateFromControllerParameters(const ControllerParameters& params, flo if (params.isTalking) { if (_talkIdleInterpTime < 1.0f) { _talkIdleInterpTime += dt / TOTAL_EASE_IN_TIME; + if (_talkIdleInterpTime > 1.0f) { + _talkIdleInterpTime = 1.0f; + } float easeOutInValue = _talkIdleInterpTime < 0.5f ? 4.0f * powf(_talkIdleInterpTime, 3.0f) : 4.0f * powf((_talkIdleInterpTime - 1.0f), 3.0f) + 1.0f; _animVars.set("idleOverlayAlpha", easeOutInValue); } else { @@ -2033,6 +2036,9 @@ void Rig::updateFromControllerParameters(const ControllerParameters& params, flo } else { if (_talkIdleInterpTime < 1.0f) { _talkIdleInterpTime += dt / TOTAL_EASE_OUT_TIME; + if (_talkIdleInterpTime > 1.0f) { + _talkIdleInterpTime = 1.0f; + } float easeOutInValue = _talkIdleInterpTime < 0.5f ? 4.0f * powf(_talkIdleInterpTime, 3.0f) : 4.0f * powf((_talkIdleInterpTime - 1.0f), 3.0f) + 1.0f; float talkAlpha = 1.0f - easeOutInValue; _animVars.set("idleOverlayAlpha", talkAlpha); @@ -2287,6 +2293,24 @@ void Rig::buildAbsoluteRigPoses(const AnimPoseVec& relativePoses, AnimPoseVec& a } } +int Rig::getOverrideJointCount() const { + int count = 0; + for (size_t i = 0; i < _internalPoseSet._overrideFlags.size(); i++) { + if (_internalPoseSet._overrideFlags[i]) { + count++; + } + } + return count; +} + +bool Rig::getFlowActive() const { + return _internalFlow.getActive(); +} + +bool Rig::getNetworkGraphActive() const { + return _sendNetworkNode; +} + glm::mat4 Rig::getJointTransform(int jointIndex) const { static const glm::mat4 IDENTITY; if (isIndexValid(jointIndex)) { diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index 02fa965f64..9baf4644f2 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -246,6 +246,10 @@ public: float getUnscaledEyeHeight() const; void buildAbsoluteRigPoses(const AnimPoseVec& relativePoses, AnimPoseVec& absolutePosesOut) const; + int getOverrideJointCount() const; + bool getFlowActive() const; + bool getNetworkGraphActive() const; + signals: void onLoadComplete(); diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 41396811f0..04ab0f7973 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -375,6 +375,9 @@ AudioClient::AudioClient() : } AudioClient::~AudioClient() { + + stop(); + if (_codec && _encoder) { _codec->releaseEncoder(_encoder); _encoder = nullptr; diff --git a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp index 9828a8beda..b8912d95b6 100644 --- a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp @@ -109,6 +109,20 @@ bool Basic2DWindowOpenGLDisplayPlugin::internalActivate() { return Parent::internalActivate(); } +gpu::PipelinePointer Basic2DWindowOpenGLDisplayPlugin::getRenderTexturePipeline() { +#if defined(Q_OS_ANDROID) + return _linearToSRGBPipeline; +#else + +#ifndef USE_GLES + return _SRGBToLinearPipeline; +#else + return _drawTexturePipeline; +#endif + +#endif +} + void Basic2DWindowOpenGLDisplayPlugin::compositeExtra() { #if defined(Q_OS_ANDROID) auto& virtualPadManager = VirtualPad::Manager::instance(); diff --git a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h index cc304c19c2..68301ff8b4 100644 --- a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h @@ -37,6 +37,8 @@ public: virtual void pluginUpdate() override {}; + virtual gpu::PipelinePointer getRenderTexturePipeline() override; + protected: mutable bool _isThrottled = false; diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp index 6c6c2aee80..75cdf5ebef 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp @@ -48,17 +48,21 @@ #include "CompositorHelper.h" #include "Logging.h" #include "RefreshRateController.h" + +using namespace shader::gpu::program; + extern QThread* RENDER_THREAD; class PresentThread : public QThread, public Dependency { using Mutex = std::mutex; using Condition = std::condition_variable; using Lock = std::unique_lock; + public: PresentThread() { connect(qApp, &QCoreApplication::aboutToQuit, [this] { - shutdown(); + shutdown(); }); setObjectName("Present"); @@ -78,12 +82,11 @@ public: Lock lock(_mutex); _shutdown = true; - _condition.wait(lock, [&] { return !_shutdown; }); + _condition.wait(lock, [&] { return !_shutdown; }); qCDebug(displayPlugins) << "Present thread shutdown"; } } - void setNewDisplayPlugin(OpenGLDisplayPlugin* plugin) { Lock lock(_mutex); if (isRunning()) { @@ -100,7 +103,6 @@ public: _context->moveToThread(this); } - virtual void run() override { PROFILE_SET_THREAD_NAME("Present Thread"); @@ -127,7 +129,6 @@ public: _condition.notify_one(); } - { // Main thread does it's thing while we wait on the lock to release Lock lock(_mutex); @@ -197,7 +198,7 @@ public: #if defined(Q_OS_MAC) _context->doneCurrent(); #endif - + _refreshRateController->sleepThreadIfNeeded(this, currentPlugin->isHmd()); } @@ -231,7 +232,6 @@ public: _condition.notify_one(); } - private: void makeCurrent(); void doneCurrent(); @@ -241,7 +241,6 @@ private: // Used to allow the main thread to perform context operations Condition _condition; - QThread* _targetOperationThread { nullptr }; bool _pendingOtherThreadOperation { false }; bool _finishedOtherThreadOperation { false }; @@ -298,7 +297,6 @@ bool OpenGLDisplayPlugin::activate() { return false; } - // This should not return until the new context has been customized // and the old context (if any) has been uncustomized presentThread->setNewDisplayPlugin(this); @@ -330,7 +328,7 @@ void OpenGLDisplayPlugin::deactivate() { _container->showDisplayPluginsTools(false); if (!_container->currentDisplayActions().isEmpty()) { - foreach(auto itemInfo, _container->currentDisplayActions()) { + foreach (auto itemInfo, _container->currentDisplayActions()) { _container->removeMenuItem(itemInfo.first, itemInfo.second); } _container->currentDisplayActions().clear(); @@ -364,7 +362,6 @@ void OpenGLDisplayPlugin::customizeContext() { image = image.convertToFormat(QImage::Format_ARGB32); } if ((image.width() > 0) && (image.height() > 0)) { - cursorData.texture = gpu::Texture::createStrict( gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA), image.width(), image.height(), @@ -380,57 +377,45 @@ void OpenGLDisplayPlugin::customizeContext() { } } - if (!_presentPipeline) { + if (!_linearToSRGBPipeline) { gpu::StatePointer blendState = gpu::StatePointer(new gpu::State()); blendState->setDepthTest(gpu::State::DepthTest(false)); blendState->setBlendFunction(true, - gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, - gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); + gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, + gpu::State::INV_SRC_ALPHA, + gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, + gpu::State::ONE); gpu::StatePointer scissorState = gpu::StatePointer(new gpu::State()); scissorState->setDepthTest(gpu::State::DepthTest(false)); scissorState->setScissorEnable(true); - { -#ifdef Q_OS_ANDROID - gpu::ShaderPointer program = gpu::Shader::createProgram(shader::gpu::program::DrawTextureGammaLinearToSRGB); -#else - gpu::ShaderPointer program = gpu::Shader::createProgram(shader::gpu::program::DrawTexture); -#endif - _simplePipeline = gpu::Pipeline::create(program, scissorState); - } - { -#ifdef Q_OS_ANDROID - gpu::ShaderPointer program = gpu::Shader::createProgram(shader::gpu::program::DrawTextureGammaLinearToSRGB); -#else - gpu::ShaderPointer program = gpu::Shader::createProgram(shader::gpu::program::DrawTextureGammaSRGBToLinear); -#endif - _presentPipeline = gpu::Pipeline::create(program, scissorState); - } + _drawTexturePipeline = gpu::Pipeline::create(gpu::Shader::createProgram(DrawTexture), scissorState); - { - gpu::ShaderPointer program = gpu::Shader::createProgram(shader::gpu::program::DrawTexture); - _hudPipeline = gpu::Pipeline::create(program, blendState); - } - { - gpu::ShaderPointer program = gpu::Shader::createProgram(shader::gpu::program::DrawTextureMirroredX); - _mirrorHUDPipeline = gpu::Pipeline::create(program, blendState); - } + _linearToSRGBPipeline = gpu::Pipeline::create(gpu::Shader::createProgram(DrawTextureGammaLinearToSRGB), scissorState); - { - gpu::ShaderPointer program = gpu::Shader::createProgram(shader::gpu::program::DrawTransformedTexture); - _cursorPipeline = gpu::Pipeline::create(program, blendState); - } + _SRGBToLinearPipeline = gpu::Pipeline::create(gpu::Shader::createProgram(DrawTextureGammaSRGBToLinear), scissorState); + + _hudPipeline = gpu::Pipeline::create(gpu::Shader::createProgram(DrawTexture), blendState); + + _mirrorHUDPipeline = gpu::Pipeline::create(gpu::Shader::createProgram(DrawTextureMirroredX), blendState); + + _cursorPipeline = gpu::Pipeline::create(gpu::Shader::createProgram(DrawTransformedTexture), blendState); } + updateCompositeFramebuffer(); } void OpenGLDisplayPlugin::uncustomizeContext() { - _presentPipeline.reset(); + + _drawTexturePipeline.reset(); + _linearToSRGBPipeline.reset(); + _SRGBToLinearPipeline.reset(); _cursorPipeline.reset(); _hudPipeline.reset(); _mirrorHUDPipeline.reset(); _compositeFramebuffer.reset(); + withPresentThreadLock([&] { _currentFrame.reset(); _lastFrame = nullptr; @@ -441,7 +426,6 @@ void OpenGLDisplayPlugin::uncustomizeContext() { }); } - // Pressing Alt (and Meta) key alone activates the menubar because its style inherits the // SHMenuBarAltKeyNavigation from QWindowsStyle. This makes it impossible for a scripts to // receive keyPress events for the Alt (and Meta) key in a reliable manner. @@ -522,11 +506,18 @@ void OpenGLDisplayPlugin::captureFrame(const std::string& filename) const { }); } -void OpenGLDisplayPlugin::renderFromTexture(gpu::Batch& batch, const gpu::TexturePointer& texture, const glm::ivec4& viewport, const glm::ivec4& scissor) { +void OpenGLDisplayPlugin::renderFromTexture(gpu::Batch& batch, + const gpu::TexturePointer& texture, + const glm::ivec4& viewport, + const glm::ivec4& scissor) { renderFromTexture(batch, texture, viewport, scissor, nullptr); } -void OpenGLDisplayPlugin::renderFromTexture(gpu::Batch& batch, const gpu::TexturePointer& texture, const glm::ivec4& viewport, const glm::ivec4& scissor, const gpu::FramebufferPointer& copyFbo /*=gpu::FramebufferPointer()*/) { +void OpenGLDisplayPlugin::renderFromTexture(gpu::Batch& batch, + const gpu::TexturePointer& texture, + const glm::ivec4& viewport, + const glm::ivec4& scissor, + const gpu::FramebufferPointer& copyFbo /*=gpu::FramebufferPointer()*/) { auto fbo = gpu::FramebufferPointer(); batch.enableStereo(false); batch.resetViewTransform(); @@ -535,16 +526,14 @@ void OpenGLDisplayPlugin::renderFromTexture(gpu::Batch& batch, const gpu::Textur batch.setStateScissorRect(scissor); batch.setViewportTransform(viewport); batch.setResourceTexture(0, texture); -#ifndef USE_GLES - batch.setPipeline(_presentPipeline); -#else - batch.setPipeline(_simplePipeline); -#endif + + batch.setPipeline(getRenderTexturePipeline()); + batch.draw(gpu::TRIANGLE_STRIP, 4); if (copyFbo) { gpu::Vec4i copyFboRect(0, 0, copyFbo->getWidth(), copyFbo->getHeight()); gpu::Vec4i sourceRect(scissor.x, scissor.y, scissor.x + scissor.z, scissor.y + scissor.w); - float aspectRatio = (float)scissor.w / (float) scissor.z; // height/width + float aspectRatio = (float)scissor.w / (float)scissor.z; // height/width // scale width first int xOffset = 0; int yOffset = 0; @@ -603,9 +592,10 @@ std::function OpenGL hudEyeViewports[eye] = eyeViewport(eye); }); return [=](gpu::Batch& batch, const gpu::TexturePointer& hudTexture, bool mirror) { - if (hudPipeline && hudTexture) { + auto pipeline = mirror ? hudMirrorPipeline : hudPipeline; + if (pipeline && hudTexture) { batch.enableStereo(false); - batch.setPipeline(mirror ? hudMirrorPipeline : hudPipeline); + batch.setPipeline(pipeline); batch.setResourceTexture(0, hudTexture); if (hudStereo) { for_each_eye([&](Eye eye) { @@ -652,7 +642,7 @@ void OpenGLDisplayPlugin::compositeScene() { batch.setStateScissorRect(ivec4(uvec2(), _compositeFramebuffer->getSize())); batch.resetViewTransform(); batch.setProjectionTransform(mat4()); - batch.setPipeline(_simplePipeline); + batch.setPipeline(getCompositeScenePipeline()); batch.setResourceTexture(0, _currentFrame->framebuffer->getRenderBuffer(0)); batch.draw(gpu::TRIANGLE_STRIP, 4); }); @@ -666,17 +656,6 @@ void OpenGLDisplayPlugin::compositeLayers() { compositeScene(); } -#ifdef HIFI_ENABLE_NSIGHT_DEBUG - if (false) // do not draw the HUD if running nsight debug -#endif - { - PROFILE_RANGE_EX(render_detail, "handleHUDBatch", 0xff0077ff, (uint64_t)presentCount()) - auto hudOperator = getHUDOperator(); - withPresentThreadLock([&] { - _hudOperator = hudOperator; - }); - } - { PROFILE_RANGE_EX(render_detail, "compositeExtra", 0xff0077ff, (uint64_t)presentCount()) compositeExtra(); @@ -697,7 +676,7 @@ void OpenGLDisplayPlugin::internalPresent() { render([&](gpu::Batch& batch) { // Note: _displayTexture must currently be the same size as the display. uvec2 dims = _displayTexture ? uvec2(_displayTexture->getDimensions()) : getSurfacePixels(); - auto viewport = ivec4(uvec2(0), dims); + auto viewport = ivec4(uvec2(0), dims); renderFromTexture(batch, _displayTexture ? _displayTexture : _compositeFramebuffer->getRenderBuffer(0), viewport, viewport); }); swapBuffers(); @@ -780,7 +759,7 @@ float OpenGLDisplayPlugin::presentRate() const { return _presentRate.rate(); } -std::function OpenGLDisplayPlugin::getRefreshRateOperator() { +std::function OpenGLDisplayPlugin::getRefreshRateOperator() { return [](int targetRefreshRate) { auto refreshRateController = DependencyManager::get()->getRefreshRateController(); refreshRateController->setRefreshRateLimitPeriod(targetRefreshRate); @@ -796,7 +775,6 @@ float OpenGLDisplayPlugin::renderRate() const { return _renderRate.rate(); } - void OpenGLDisplayPlugin::swapBuffers() { static auto context = _container->getPrimaryWidget()->context(); context->swapBuffers(); @@ -928,7 +906,6 @@ void OpenGLDisplayPlugin::updateCompositeFramebuffer() { auto renderSize = glm::uvec2(getRecommendedRenderSize()); if (!_compositeFramebuffer || _compositeFramebuffer->getSize() != renderSize) { _compositeFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("OpenGLDisplayPlugin::composite", gpu::Element::COLOR_RGBA_32, renderSize.x, renderSize.y)); - // _compositeFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("OpenGLDisplayPlugin::composite", gpu::Element::COLOR_SRGBA_32, renderSize.x, renderSize.y)); } } @@ -959,14 +936,13 @@ void OpenGLDisplayPlugin::copyTextureToQuickFramebuffer(NetworkTexturePointer ne glClearColor(0.0f, 0.0f, 0.0f, 0.0f); glClear(GL_COLOR_BUFFER_BIT); - // maintain aspect ratio, filling the width first if possible. If that makes the height too // much, fill height instead. TODO: only do this when texture changes GLint newX = 0; GLint newY = 0; float aspectRatio = (float)texHeight / (float)texWidth; GLint newWidth = target->width(); - GLint newHeight = std::round(aspectRatio * (float) target->width()); + GLint newHeight = std::round(aspectRatio * (float)target->width()); if (newHeight > target->height()) { newHeight = target->height(); newWidth = std::round((float)target->height() / aspectRatio); @@ -975,7 +951,7 @@ void OpenGLDisplayPlugin::copyTextureToQuickFramebuffer(NetworkTexturePointer ne newY = (target->height() - newHeight) / 2; } - glBlitNamedFramebuffer(fbo[0], fbo[1], 0, 0, texWidth, texHeight, newX, newY, newX + newWidth, newY + newHeight, GL_DEPTH_BUFFER_BIT|GL_COLOR_BUFFER_BIT, GL_NEAREST); + glBlitNamedFramebuffer(fbo[0], fbo[1], 0, 0, texWidth, texHeight, newX, newY, newX + newWidth, newY + newHeight, GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT, GL_NEAREST); // don't delete the textures! glDeleteFramebuffers(2, fbo); @@ -984,3 +960,11 @@ void OpenGLDisplayPlugin::copyTextureToQuickFramebuffer(NetworkTexturePointer ne #endif } +gpu::PipelinePointer OpenGLDisplayPlugin::getRenderTexturePipeline() { + return _drawTexturePipeline; +} + +gpu::PipelinePointer OpenGLDisplayPlugin::getCompositeScenePipeline() { + return _drawTexturePipeline; +} + diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h index 562c5af5cf..777c74822a 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h @@ -23,11 +23,9 @@ #include -namespace gpu { - namespace gl { - class GLBackend; - } -} +namespace gpu { namespace gl { +class GLBackend; +}} // namespace gpu::gl class RefreshRateController; @@ -35,10 +33,12 @@ class OpenGLDisplayPlugin : public DisplayPlugin { Q_OBJECT Q_PROPERTY(float hudAlpha MEMBER _hudAlpha) using Parent = DisplayPlugin; + protected: using Mutex = std::mutex; using Lock = std::unique_lock; using Condition = std::condition_variable; + public: ~OpenGLDisplayPlugin(); // These must be final to ensure proper ordering of operations @@ -55,13 +55,9 @@ public: void captureFrame(const std::string& outputName) const override; void submitFrame(const gpu::FramePointer& newFrame) override; - glm::uvec2 getRecommendedRenderSize() const override { - return getSurfacePixels(); - } + glm::uvec2 getRecommendedRenderSize() const override { return getSurfacePixels(); } - glm::uvec2 getRecommendedUiSize() const override { - return getSurfaceSize(); - } + glm::uvec2 getRecommendedUiSize() const override { return getSurfaceSize(); } virtual bool setDisplayTexture(const QString& name) override; virtual bool onDisplayTextureReset() { return false; }; @@ -84,7 +80,10 @@ public: // Three threads, one for rendering, one for texture transfers, one reserved for the GL driver int getRequiredThreadCount() const override { return 3; } - void copyTextureToQuickFramebuffer(NetworkTexturePointer source, QOpenGLFramebufferObject* target, GLsync* fenceSync) override; + virtual std::function getHUDOperator() override; + void copyTextureToQuickFramebuffer(NetworkTexturePointer source, + QOpenGLFramebufferObject* target, + GLsync* fenceSync) override; protected: friend class PresentThread; @@ -102,9 +101,8 @@ protected: virtual QThread::Priority getPresentPriority() { return QThread::HighPriority; } virtual void compositeLayers(); virtual void compositeScene(); - virtual std::function getHUDOperator(); virtual void compositePointer(); - virtual void compositeExtra() {}; + virtual void compositeExtra(){}; // These functions must only be called on the presentation thread virtual void customizeContext(); @@ -121,8 +119,15 @@ protected: // Plugin specific functionality to send the composed scene to the output window or device virtual void internalPresent(); - void renderFromTexture(gpu::Batch& batch, const gpu::TexturePointer& texture, const glm::ivec4& viewport, const glm::ivec4& scissor, const gpu::FramebufferPointer& fbo); - void renderFromTexture(gpu::Batch& batch, const gpu::TexturePointer& texture, const glm::ivec4& viewport, const glm::ivec4& scissor); + void renderFromTexture(gpu::Batch& batch, + const gpu::TexturePointer& texture, + const glm::ivec4& viewport, + const glm::ivec4& scissor, + const gpu::FramebufferPointer& fbo); + void renderFromTexture(gpu::Batch& batch, + const gpu::TexturePointer& texture, + const glm::ivec4& viewport, + const glm::ivec4& scissor); virtual void updateFrameData(); virtual glm::mat4 getViewCorrection() { return glm::mat4(); } @@ -134,7 +139,7 @@ protected: void render(std::function f); - bool _vsyncEnabled { true }; + bool _vsyncEnabled{ true }; QThread* _presentThread{ nullptr }; std::queue _newFrameQueue; RateCounter<200> _droppedFrameRate; @@ -143,17 +148,21 @@ protected: RateCounter<200> _renderRate; gpu::FramePointer _currentFrame; - gpu::Frame* _lastFrame { nullptr }; + gpu::Frame* _lastFrame{ nullptr }; mat4 _prevRenderView; gpu::FramebufferPointer _compositeFramebuffer; gpu::PipelinePointer _hudPipeline; gpu::PipelinePointer _mirrorHUDPipeline; gpu::ShaderPointer _mirrorHUDPS; - gpu::PipelinePointer _simplePipeline; - gpu::PipelinePointer _presentPipeline; + gpu::PipelinePointer _drawTexturePipeline; + gpu::PipelinePointer _linearToSRGBPipeline; + gpu::PipelinePointer _SRGBToLinearPipeline; gpu::PipelinePointer _cursorPipeline; gpu::TexturePointer _displayTexture{}; - float _compositeHUDAlpha { 1.0f }; + float _compositeHUDAlpha{ 1.0f }; + + virtual gpu::PipelinePointer getRenderTexturePipeline(); + virtual gpu::PipelinePointer getCompositeScenePipeline(); struct CursorData { QImage image; @@ -163,19 +172,19 @@ protected: }; std::map _cursorsData; - bool _lockCurrentTexture { false }; + bool _lockCurrentTexture{ false }; void assertNotPresentThread() const; void assertIsPresentThread() const; - template + template void withPresentThreadLock(F f) const { assertIsPresentThread(); Lock lock(_presentMutex); f(); } - template + template void withNonPresentThreadLock(F f) const { assertNotPresentThread(); Lock lock(_presentMutex); @@ -192,4 +201,3 @@ protected: QImage getScreenshot(float aspectRatio); QImage getSecondaryCameraScreenshot(); }; - diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp index 874454b391..0c7d83cdd8 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp @@ -1,4 +1,4 @@ -// +// // Created by Bradley Austin Davis on 2016/02/15 // Copyright 2016 High Fidelity, Inc. // @@ -174,6 +174,10 @@ float HmdDisplayPlugin::getLeftCenterPixel() const { return leftCenterPixel; } +gpu::PipelinePointer HmdDisplayPlugin::getRenderTexturePipeline() { + return _SRGBToLinearPipeline; +} + void HmdDisplayPlugin::internalPresent() { PROFILE_RANGE_EX(render, __FUNCTION__, 0xff00ff00, (uint64_t)presentCount()) @@ -402,25 +406,18 @@ void HmdDisplayPlugin::HUDRenderer::build() { format->setAttribute(gpu::Stream::POSITION, gpu::Stream::POSITION, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ), 0); format->setAttribute(gpu::Stream::TEXCOORD, gpu::Stream::TEXCOORD, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV)); uniformsBuffer = std::make_shared(sizeof(Uniforms), nullptr); - updatePipeline(); + + auto program = gpu::Shader::createProgram(shader::render_utils::program::hmd_ui); + gpu::StatePointer state = gpu::StatePointer(new gpu::State()); + state->setDepthTest(gpu::State::DepthTest(true, true, gpu::LESS_EQUAL)); + state->setBlendFunction(true, + gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, + gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); + + pipeline = gpu::Pipeline::create(program, state); } -void HmdDisplayPlugin::HUDRenderer::updatePipeline() { - if (!pipeline) { - auto program = gpu::Shader::createProgram(shader::render_utils::program::hmd_ui); - gpu::StatePointer state = gpu::StatePointer(new gpu::State()); - state->setDepthTest(gpu::State::DepthTest(true, true, gpu::LESS_EQUAL)); - state->setBlendFunction(true, - gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, - gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); - - pipeline = gpu::Pipeline::create(program, state); - } -} - -std::function HmdDisplayPlugin::HUDRenderer::render(HmdDisplayPlugin& plugin) { - updatePipeline(); - +std::function HmdDisplayPlugin::HUDRenderer::render() { auto hudPipeline = pipeline; auto hudFormat = format; auto hudVertices = vertices; @@ -479,7 +476,7 @@ void HmdDisplayPlugin::compositePointer() { } std::function HmdDisplayPlugin::getHUDOperator() { - return _hudRenderer.render(*this); + return _hudRenderer.render(); } HmdDisplayPlugin::~HmdDisplayPlugin() { diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h index e952b1e8db..1f34e1b52a 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h @@ -48,8 +48,11 @@ public: void pluginUpdate() override {}; + std::function getHUDOperator() override; virtual StencilMaskMode getStencilMaskMode() const override { return StencilMaskMode::PAINT; } + virtual gpu::PipelinePointer getRenderTexturePipeline() override; + signals: void hmdMountedChanged(); void hmdVisibleChanged(bool visible); @@ -62,7 +65,6 @@ protected: bool internalActivate() override; void internalDeactivate() override; - std::function getHUDOperator() override; void compositePointer() override; void internalPresent() override; void customizeContext() override; @@ -105,7 +107,7 @@ private: gpu::BufferPointer vertices; gpu::BufferPointer indices; uint32_t indexCount { 0 }; - gpu::PipelinePointer pipeline; + gpu::PipelinePointer pipeline { nullptr }; gpu::BufferPointer uniformsBuffer; @@ -123,7 +125,6 @@ private: static const int VERTEX_STRIDE { sizeof(Vertex) }; void build(); - void updatePipeline(); - std::function render(HmdDisplayPlugin& plugin); + std::function render(); } _hudRenderer; }; diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 1ecbcb0c8b..5cb7b89de5 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -207,7 +207,7 @@ void EntityTreeRenderer::stopDomainAndNonOwnedEntities() { if (entityItem && !entityItem->getScript().isEmpty()) { if (!(entityItem->isLocalEntity() || (entityItem->isAvatarEntity() && entityItem->getOwningAvatarID() == getTree()->getMyAvatarSessionUUID()))) { - if (entityItem->contains(_avatarPosition)) { + if (_currentEntitiesInside.contains(entityID)) { _entitiesScriptEngine->callEntityScriptMethod(entityID, "leaveEntity"); } _entitiesScriptEngine->unloadEntityScript(entityID, true); @@ -222,6 +222,7 @@ void EntityTreeRenderer::clearDomainAndNonOwnedEntities() { auto sessionUUID = getTree()->getMyAvatarSessionUUID(); std::unordered_map savedEntities; + std::unordered_set savedRenderables; // remove all entities from the scene auto scene = _viewState->getMain3DScene(); if (scene) { @@ -232,11 +233,12 @@ void EntityTreeRenderer::clearDomainAndNonOwnedEntities() { fadeOutRenderable(renderer); } else { savedEntities[entry.first] = entry.second; + savedRenderables.insert(entry.second); } } } - _renderablesToUpdate = savedEntities; + _renderablesToUpdate = savedRenderables; _entitiesInScene = savedEntities; if (_layeredZones.clearDomainAndNonOwnedZones(sessionUUID)) { @@ -256,18 +258,28 @@ void EntityTreeRenderer::clear() { } // reset the engine - if (_wantScripts && !_shuttingDown) { - resetEntitiesScriptEngine(); - } - // remove all entities from the scene auto scene = _viewState->getMain3DScene(); - if (scene) { - for (const auto& entry : _entitiesInScene) { - const auto& renderer = entry.second; - fadeOutRenderable(renderer); + if (_shuttingDown) { + if (scene) { + render::Transaction transaction; + for (const auto& entry : _entitiesInScene) { + const auto& renderer = entry.second; + renderer->removeFromScene(scene, transaction); + } + scene->enqueueTransaction(transaction); } } else { - qCWarning(entitiesrenderer) << "EntitityTreeRenderer::clear(), Unexpected null scene, possibly during application shutdown"; + if (_wantScripts) { + resetEntitiesScriptEngine(); + } + if (scene) { + for (const auto& entry : _entitiesInScene) { + const auto& renderer = entry.second; + fadeOutRenderable(renderer); + } + } else { + qCWarning(entitiesrenderer) << "EntitityTreeRenderer::clear(), Unexpected null scene"; + } } _entitiesInScene.clear(); _renderablesToUpdate.clear(); @@ -379,13 +391,7 @@ void EntityTreeRenderer::updateChangedEntities(const render::ScenePointer& scene PerformanceTimer pt("change"); std::unordered_set changedEntities; _changedEntitiesGuard.withWriteLock([&] { -#if 0 - // FIXME Weird build failure in latest VC update that fails to compile when using std::swap changedEntities.swap(_changedEntities); -#else - changedEntities.insert(_changedEntities.begin(), _changedEntities.end()); - _changedEntities.clear(); -#endif }); { @@ -394,7 +400,7 @@ void EntityTreeRenderer::updateChangedEntities(const render::ScenePointer& scene auto renderable = renderableForEntityId(entityId); if (renderable) { // only add valid renderables _renderablesToUpdate - _renderablesToUpdate.insert({ entityId, renderable }); + _renderablesToUpdate.insert(renderable); } } } @@ -404,8 +410,7 @@ void EntityTreeRenderer::updateChangedEntities(const render::ScenePointer& scene // we expect to update all renderables within available time budget PROFILE_RANGE_EX(simulation_physics, "UpdateRenderables", 0xffff00ff, (uint64_t)_renderablesToUpdate.size()); uint64_t updateStart = usecTimestampNow(); - for (const auto& entry : _renderablesToUpdate) { - const auto& renderable = entry.second; + for (const auto& renderable : _renderablesToUpdate) { assert(renderable); // only valid renderables are added to _renderablesToUpdate renderable->updateInScene(scene, transaction); } @@ -414,8 +419,8 @@ void EntityTreeRenderer::updateChangedEntities(const render::ScenePointer& scene // compute average per-renderable update cost float cost = (float)(usecTimestampNow() - updateStart) / (float)(numRenderables); - const float blend = 0.1f; - _avgRenderableUpdateCost = (1.0f - blend) * _avgRenderableUpdateCost + blend * cost; + const float BLEND = 0.1f; + _avgRenderableUpdateCost = (1.0f - BLEND) * _avgRenderableUpdateCost + BLEND * cost; } else { // we expect the cost to updating all renderables to exceed available time budget // so we first sort by priority and update in order until out of time @@ -440,43 +445,40 @@ void EntityTreeRenderer::updateChangedEntities(const render::ScenePointer& scene PrioritySortUtil::PriorityQueue sortedRenderables(views); sortedRenderables.reserve(_renderablesToUpdate.size()); { - PROFILE_RANGE_EX(simulation_physics, "SortRenderables", 0xffff00ff, (uint64_t)_renderablesToUpdate.size()); - std::unordered_map::iterator itr = _renderablesToUpdate.begin(); - while (itr != _renderablesToUpdate.end()) { - assert(itr->second); // only valid renderables are added to _renderablesToUpdate - sortedRenderables.push(SortableRenderer(itr->second)); - ++itr; + PROFILE_RANGE_EX(simulation_physics, "BuildSortedRenderables", 0xffff00ff, (uint64_t)_renderablesToUpdate.size()); + for (const auto& renderable : _renderablesToUpdate) { + assert(renderable); // only valid renderables are added to _renderablesToUpdate + sortedRenderables.push(SortableRenderer(renderable)); } } { - PROFILE_RANGE_EX(simulation_physics, "UpdateRenderables", 0xffff00ff, sortedRenderables.size()); + PROFILE_RANGE_EX(simulation_physics, "SortAndUpdateRenderables", 0xffff00ff, sortedRenderables.size()); // compute remaining time budget + const auto& sortedRenderablesVector = sortedRenderables.getSortedVector(); uint64_t updateStart = usecTimestampNow(); - uint64_t timeBudget = MIN_SORTED_UPDATE_RENDERABLES_TIME_BUDGET; uint64_t sortCost = updateStart - sortStart; + uint64_t timeBudget = MIN_SORTED_UPDATE_RENDERABLES_TIME_BUDGET; if (sortCost < MAX_UPDATE_RENDERABLES_TIME_BUDGET - MIN_SORTED_UPDATE_RENDERABLES_TIME_BUDGET) { timeBudget = MAX_UPDATE_RENDERABLES_TIME_BUDGET - sortCost; } uint64_t expiry = updateStart + timeBudget; // process the sorted renderables - size_t numSorted = sortedRenderables.size(); - const auto& sortedRenderablesVector = sortedRenderables.getSortedVector(); for (const auto& sortedRenderable : sortedRenderablesVector) { if (usecTimestampNow() > expiry) { break; } const auto& renderable = sortedRenderable.getRenderer(); renderable->updateInScene(scene, transaction); - _renderablesToUpdate.erase(renderable->getEntity()->getID()); + _renderablesToUpdate.erase(renderable); } // compute average per-renderable update cost - size_t numUpdated = numSorted - sortedRenderables.size() + 1; // add one to avoid divide by zero + size_t numUpdated = sortedRenderables.size() - _renderablesToUpdate.size() + 1; // add one to avoid divide by zero float cost = (float)(usecTimestampNow() - updateStart) / (float)(numUpdated); - const float blend = 0.1f; - _avgRenderableUpdateCost = (1.0f - blend) * _avgRenderableUpdateCost + blend * cost; + const float BLEND = 0.1f; + _avgRenderableUpdateCost = (1.0f - BLEND) * _avgRenderableUpdateCost + BLEND * cost; } } } @@ -980,7 +982,6 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event) { void EntityTreeRenderer::deletingEntity(const EntityItemID& entityID) { // If it's in a pending queue, remove it - _renderablesToUpdate.erase(entityID); _entitiesToAdd.erase(entityID); auto itr = _entitiesInScene.find(entityID); @@ -990,7 +991,7 @@ void EntityTreeRenderer::deletingEntity(const EntityItemID& entityID) { } if (_tree && !_shuttingDown && _entitiesScriptEngine && !itr->second->getEntity()->getScript().isEmpty()) { - if (itr->second->getEntity()->contains(_avatarPosition)) { + if (_currentEntitiesInside.contains(entityID)) { _entitiesScriptEngine->callEntityScriptMethod(entityID, "leaveEntity"); } _entitiesScriptEngine->unloadEntityScript(entityID, true); @@ -1003,6 +1004,7 @@ void EntityTreeRenderer::deletingEntity(const EntityItemID& entityID) { } auto renderable = itr->second; + _renderablesToUpdate.erase(renderable); _entitiesInScene.erase(itr); if (!renderable) { @@ -1037,7 +1039,7 @@ void EntityTreeRenderer::checkAndCallPreload(const EntityItemID& entityID, bool QString scriptUrl = entity->getScript(); if ((shouldLoad && unloadFirst) || scriptUrl.isEmpty()) { if (_entitiesScriptEngine) { - if (entity->contains(_avatarPosition)) { + if (_currentEntitiesInside.contains(entityID)) { _entitiesScriptEngine->callEntityScriptMethod(entityID, "leaveEntity"); } _entitiesScriptEngine->unloadEntityScript(entityID); @@ -1056,10 +1058,14 @@ void EntityTreeRenderer::fadeOutRenderable(const EntityRendererPointer& renderab render::Transaction transaction; auto scene = _viewState->getMain3DScene(); - transaction.setTransitionFinishedOperator(renderable->getRenderItemID(), [scene, renderable]() { - render::Transaction transaction; - renderable->removeFromScene(scene, transaction); - scene->enqueueTransaction(transaction); + EntityRendererWeakPointer weakRenderable = renderable; + transaction.setTransitionFinishedOperator(renderable->getRenderItemID(), [scene, weakRenderable]() { + auto renderable = weakRenderable.lock(); + if (renderable) { + render::Transaction transaction; + renderable->removeFromScene(scene, transaction); + scene->enqueueTransaction(transaction); + } }); scene->enqueueTransaction(transaction); diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.h b/libraries/entities-renderer/src/EntityTreeRenderer.h index f794d947ed..bea04f106b 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.h +++ b/libraries/entities-renderer/src/EntityTreeRenderer.h @@ -246,7 +246,7 @@ private: ReadWriteLockable _changedEntitiesGuard; std::unordered_set _changedEntities; - std::unordered_map _renderablesToUpdate; + std::unordered_set _renderablesToUpdate; std::unordered_map _entitiesInScene; std::unordered_map _entitiesToAdd; diff --git a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp index 7faebfdf56..ec1c890583 100644 --- a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp @@ -70,7 +70,7 @@ bool ShapeEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPoin return true; } - if (_color != entity->getColor()) { + if (_color != toGlm(entity->getColor())) { return true; } if (_alpha != entity->getAlpha()) { @@ -130,12 +130,12 @@ void ShapeEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPoint } }); - glm::u8vec3 color = entity->getColor(); + glm::vec3 color = toGlm(entity->getColor()); float alpha = entity->getAlpha(); if (_color != color || _alpha != alpha) { _color = color; _alpha = alpha; - _material->setAlbedo(toGlm(_color)); + _material->setAlbedo(color); _material->setOpacity(_alpha); auto materials = _materials.find("0"); diff --git a/libraries/entities-renderer/src/RenderableShapeEntityItem.h b/libraries/entities-renderer/src/RenderableShapeEntityItem.h index c9fd4801d5..6e4b05f716 100644 --- a/libraries/entities-renderer/src/RenderableShapeEntityItem.h +++ b/libraries/entities-renderer/src/RenderableShapeEntityItem.h @@ -43,7 +43,7 @@ private: PulsePropertyGroup _pulseProperties; std::shared_ptr _material { std::make_shared() }; - glm::u8vec3 _color; + glm::vec3 _color { NAN }; float _alpha; glm::vec3 _position; diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index 3b615ba467..bb3e99157e 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -7,6 +7,7 @@ // #include "RenderableWebEntityItem.h" +#include #include #include @@ -46,7 +47,7 @@ static uint64_t MAX_NO_RENDER_INTERVAL = 30 * USECS_PER_SECOND; static uint8_t YOUTUBE_MAX_FPS = 30; // Don't allow more than 20 concurrent web views -static uint32_t _currentWebCount { 0 }; +static std::atomic _currentWebCount(0); static const uint32_t MAX_CONCURRENT_WEB_VIEWS = 20; static QTouchDevice _touchDevice; @@ -356,16 +357,15 @@ void WebEntityRenderer::buildWebSurface(const EntityItemPointer& entity, const Q void WebEntityRenderer::destroyWebSurface() { QSharedPointer webSurface; - ContentType contentType = ContentType::NoContent; withWriteLock([&] { webSurface.swap(_webSurface); - _contentType = contentType; - }); + _contentType = ContentType::NoContent; - if (webSurface) { - --_currentWebCount; - WebEntityRenderer::releaseWebSurface(webSurface, _cachedWebSurface, _connections); - } + if (webSurface) { + --_currentWebCount; + WebEntityRenderer::releaseWebSurface(webSurface, _cachedWebSurface, _connections); + } + }); } glm::vec2 WebEntityRenderer::getWindowSize(const TypedEntityPointer& entity) const { diff --git a/libraries/entities/src/AmbientLightPropertyGroup.h b/libraries/entities/src/AmbientLightPropertyGroup.h index 591ea6a6fa..d1e0ea0e55 100644 --- a/libraries/entities/src/AmbientLightPropertyGroup.h +++ b/libraries/entities/src/AmbientLightPropertyGroup.h @@ -28,7 +28,7 @@ class EntityTreeElementExtraEncodeData; class ReadBitstreamToTreeParams; /**jsdoc - * Ambient light is defined by the following properties. + * Ambient light is defined by the following properties: * @typedef {object} Entities.AmbientLight * @property {number} ambientIntensity=0.5 - The intensity of the light. * @property {string} ambientURL="" - A cube map image that defines the color of the light coming from each direction. If diff --git a/libraries/entities/src/AnimationPropertyGroup.cpp b/libraries/entities/src/AnimationPropertyGroup.cpp index 7e97787ff2..03cd3a0742 100644 --- a/libraries/entities/src/AnimationPropertyGroup.cpp +++ b/libraries/entities/src/AnimationPropertyGroup.cpp @@ -48,16 +48,18 @@ bool operator!=(const AnimationPropertyGroup& a, const AnimationPropertyGroup& b /**jsdoc - * The AnimationProperties are used to configure an animation. + * An animation is configured by the following properties: * @typedef {object} Entities.AnimationProperties * @property {string} url="" - The URL of the FBX file that has the animation. + * @property {boolean} allowTranslation=true - true to enable translations contained in the animation to be + * played, false to disable translations. * @property {number} fps=30 - The speed in frames/s that the animation is played at. * @property {number} firstFrame=0 - The first frame to play in the animation. * @property {number} lastFrame=100000 - The last frame to play in the animation. * @property {number} currentFrame=0 - The current frame being played in the animation. * @property {boolean} running=false - If true then the animation should play. - * @property {boolean} loop=true - If true then the animation should be continuously repeated in a loop. - * @property {boolean} hold=false - If true then the rotations and translations of the last frame played should be + * @property {boolean} loop=true - If true then the animation is continuously repeated in a loop. + * @property {boolean} hold=false - If true then the rotations and translations of the last frame played are * maintained when the animation stops playing. */ void AnimationPropertyGroup::copyToScriptValue(const EntityPropertyFlags& desiredProperties, QScriptValue& properties, QScriptEngine* engine, bool skipDefaults, EntityItemProperties& defaultEntityProperties) const { diff --git a/libraries/entities/src/BloomPropertyGroup.h b/libraries/entities/src/BloomPropertyGroup.h index a1f9b6d748..12743c9298 100644 --- a/libraries/entities/src/BloomPropertyGroup.h +++ b/libraries/entities/src/BloomPropertyGroup.h @@ -31,9 +31,8 @@ static const float INITIAL_BLOOM_THRESHOLD { 0.7f }; static const float INITIAL_BLOOM_SIZE { 0.9f }; /**jsdoc - * Bloom is defined by the following properties. + * Bloom is defined by the following properties: * @typedef {object} Entities.Bloom - * * @property {number} bloomIntensity=0.25 - The intensity of the bloom effect. * @property {number} bloomThreshold=0.7 - The threshold for the bloom effect. * @property {number} bloomSize=0.9 - The size of the bloom effect. diff --git a/libraries/entities/src/EntityDynamicInterface.cpp b/libraries/entities/src/EntityDynamicInterface.cpp index 1115559342..4586b665ee 100644 --- a/libraries/entities/src/EntityDynamicInterface.cpp +++ b/libraries/entities/src/EntityDynamicInterface.cpp @@ -94,47 +94,48 @@ variables. These argument variables are used by the code which is run when bull #include "EntityItem.h" /**jsdoc -*

An entity action may be one of the following types:

-* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -*
ValueTypeDescriptionArguments
"far-grab"Avatar actionMoves and rotates an entity to a target position and orientation, optionally relative to another entity. Collisions -* between the entity and the user's avatar are disabled during the far-grab.{@link Entities.ActionArguments-FarGrab}
"hold"Avatar actionPositions and rotates an entity relative to an avatar's hand. Collisions between the entity and the user's avatar -* are disabled during the hold.{@link Entities.ActionArguments-Hold}
"offset"Object actionMoves an entity so that it is a set distance away from a target point.{@link Entities.ActionArguments-Offset}
"tractor"Object actionMoves and rotates an entity to a target position and orientation, optionally relative to another entity.{@link Entities.ActionArguments-Tractor}
"travel-oriented"Object actionOrients an entity to align with its direction of travel.{@link Entities.ActionArguments-TravelOriented}
"hinge"Object constraintLets an entity pivot about an axis or connects two entities with a hinge joint.{@link Entities.ActionArguments-Hinge}
"slider"Object constraintLets an entity slide and rotate along an axis, or connects two entities that slide and rotate along a shared -* axis.{@link Entities.ActionArguments-Slider|ActionArguments-Slider}
"cone-twist"Object constraintConnects two entities with a joint that can move through a cone and can twist.{@link Entities.ActionArguments-ConeTwist}
"ball-socket"Object constraintConnects two entities with a ball and socket joint.{@link Entities.ActionArguments-BallSocket}
"spring"Synonym for "tractor". Legacy value.
-* @typedef {string} Entities.ActionType -*/ + *

An entity action may be one of the following types:

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
ValueTypeDescriptionArguments
"far-grab"Avatar actionMoves and rotates an entity to a target position and orientation, optionally relative to another entity. Collisions + * between the entity and the user's avatar are disabled during the far-grab.{@link Entities.ActionArguments-FarGrab}
"hold"Avatar actionPositions and rotates an entity relative to an avatar's hand. Collisions between the entity and the user's avatar + * are disabled during the hold.{@link Entities.ActionArguments-Hold}
"offset"Object actionMoves an entity so that it is a set distance away from a target point.{@link Entities.ActionArguments-Offset}
"tractor"Object actionMoves and rotates an entity to a target position and orientation, optionally relative to another entity.{@link Entities.ActionArguments-Tractor}
"travel-oriented"Object actionOrients an entity to align with its direction of travel.{@link Entities.ActionArguments-TravelOriented}
"hinge"Object constraintLets an entity pivot about an axis or connects two entities with a hinge joint.{@link Entities.ActionArguments-Hinge}
"slider"Object constraintLets an entity slide and rotate along an axis, or connects two entities that slide and rotate along a shared + * axis.{@link Entities.ActionArguments-Slider|ActionArguments-Slider}
"cone-twist"Object constraintConnects two entities with a joint that can move through a cone and can twist.{@link Entities.ActionArguments-ConeTwist}
"ball-socket"Object constraintConnects two entities with a ball and socket joint.{@link Entities.ActionArguments-BallSocket}
"spring"Synonym for "tractor". + * Deprecated.
+ * @typedef {string} Entities.ActionType + */ // Note: The "none" action type is not listed because it's an internal "uninitialized" value and not useful for scripts. EntityDynamicType EntityDynamicInterface::dynamicTypeFromString(QString dynamicTypeString) { QString normalizedDynamicTypeString = dynamicTypeString.toLower().remove('-').remove('_'); diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 805eabc877..5b3bbb88ee 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -608,27 +608,27 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { } /**jsdoc - * Different entity types have different properties: some common to all entities (listed below) and some specific to each - * {@link Entities.EntityType|EntityType} (linked to below). The properties are accessed as an object of property names and - * values. + * Different entity types have different properties: some common to all entities (listed in the table) and some specific to + * each {@link Entities.EntityType|EntityType} (linked to below). The properties are accessed as an object of property names + * and values. * * @typedef {object} Entities.EntityProperties * @property {Uuid} id - The ID of the entity. Read-only. * @property {string} name="" - A name for the entity. Need not be unique. - * @property {Entities.EntityType} type - The entity type. You cannot change the type of an entity after it's created. (Though - * its value may switch among "Box", "Shape", and "Sphere" depending on changes to - * the shape property set for entities of these types.) Read-only. - * @property {EntityHostType} entityHostType="domain" - How this entity will behave, including if and how it is sent to other people. - * The value can only be set at entity creation by using the entityHostType parameter in - * {@link Entities.addEntity}. Read-only. - * @property {boolean} avatarEntity=false - If true then the entity is an avatar entity; An avatar entity follows you to each domain you visit, - * rendering at the same world coordinates unless it's parented to your avatar. Value cannot be changed after the entity is created.
- * The value can only be set at entity creation by using the entityHostType parameter in - * {@link Entities.addEntity}. clientOnly is an alias. Read-only. - * @property {boolean} localEntity=false - If true then the entity is a local entity; Local entities only render for you and are not sent over the wire. - * Value cannot be changed after the entity is created.
- * The value can only be set at entity creation by using the entityHostType parameter in - * {@link Entities.addEntity}. Read-only. + * @property {Entities.EntityType} type - The entity's type. You cannot change the type of an entity after it's created. + * However, its value may switch among "Box", "Shape", and "Sphere" depending on + * changes to the shape property set for entities of these types. Read-only. + * + * @property {Entities.EntityHostType} entityHostType="domain" - How the entity is hosted and sent to others for display. + * The value can only be set at entity creation by one of the {@link Entities.addEntity} methods. Read-only. + * @property {boolean} avatarEntity=false - true if the entity is an {@link Entities.EntityHostType|avatar entity}, + * false if it isn't. The value is per the entityHostType property value, set at entity creation + * by one of the {@link Entities.addEntity} methods. Read-only. + * @property {boolean} clientOnly=false - A synonym for avatarEntity. Read-only. + * @property {boolean} localEntity=false - true if the entity is a {@link Entities.EntityHostType|local entity}, + * false if it isn't. The value is per the entityHostType property value, set at entity creation + * by one of the {@link Entities.addEntity} methods. Read-only. + * * @property {Uuid} owningAvatarID=Uuid.NULL - The session ID of the owning avatar if avatarEntity is * true, otherwise {@link Uuid(0)|Uuid.NULL}. Read-only. * @@ -648,19 +648,21 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * entity's properties other than locked cannot be changed, and the entity cannot be deleted. * @property {boolean} visible=true - Whether or not the entity is rendered. If true then the entity is rendered. * @property {boolean} canCastShadow=true - Whether or not the entity can cast a shadow. Currently applicable only to - * {@link Entities.EntityType|Model} and {@link Entities.EntityType|Shape} entities. Shadows are cast if inside a - * {@link Entities.EntityType|Zone} entity with castShadows enabled in its - * {@link Entities.EntityProperties-Zone|keyLight} property. - * @property {boolean} isVisibleInSecondaryCamera=true - Whether or not the entity is rendered in the secondary camera. If true then the entity is rendered. - * @property {RenderLayer} renderLayer="world" - In which layer this entity renders. - * @property {PrimitiveMode} primitiveMode="solid" - How this entity's geometry is rendered. - * @property {boolean} ignorePickIntersection=false - If true, picks ignore the entity. + * {@link Entities.EntityProperties-Model|Model} and {@link Entities.EntityProperties-Shape|Shape} entities. Shadows are + * cast if inside a {@link Entities.EntityProperties-Zone|Zone} entity with castShadows enabled in its + * keyLight property. + * @property {boolean} isVisibleInSecondaryCamera=true - true if the entity is rendered in the secondary camera, + * false if it isn't. + * @property {Entities.RenderLayer} renderLayer="world" - Which layer the entity renders in. + * @property {Entities.PrimitiveMode} primitiveMode="solid" - How the entity's geometry is rendered. + * @property {boolean} ignorePickIntersection=false - true if {@link Picks} and {@link RayPick} ignore the entity, + * false if they don't. * - * @property {Vec3} position=0,0,0 - The position of the entity. - * @property {Quat} rotation=0,0,0,1 - The orientation of the entity with respect to world coordinates. + * @property {Vec3} position=0,0,0 - The position of the entity in world coordinates. + * @property {Quat} rotation=0,0,0,1 - The orientation of the entity in world coordinates. * @property {Vec3} registrationPoint=0.5,0.5,0.5 - The point in the entity that is set to the entity's position and is rotated - * about, {@link Vec3(0)|Vec3.ZERO} – {@link Vec3(0)|Vec3.ONE}. A value of {@link Vec3(0)|Vec3.ZERO} is the entity's - * minimum x, y, z corner; a value of {@link Vec3(0)|Vec3.ONE} is the entity's maximum x, y, z corner. + * about, range {@link Vec3(0)|Vec3.ZERO} – {@link Vec3(0)|Vec3.ONE}. A value of {@link Vec3(0)|Vec3.ZERO} is the + * entity's minimum x, y, z corner; a value of {@link Vec3(0)|Vec3.ONE} is the entity's maximum x, y, z corner. * * @property {Vec3} naturalPosition=0,0,0 - The center of the entity's unscaled mesh model if it has one, otherwise * {@link Vec3(0)|Vec3.ZERO}. Read-only. @@ -668,81 +670,83 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * {@link Vec3(0)|Vec3.ONE}. Read-only. * * @property {Vec3} velocity=0,0,0 - The linear velocity of the entity in m/s with respect to world coordinates. - * @property {number} damping=0.39347 - How much to slow down the linear velocity of an entity over time, 0.0 - * – 1.0. A higher damping value slows down the entity more quickly. The default value is for an - * exponential decay timescale of 2.0s, where it takes 2.0s for the movement to slow to 1/e = 0.368 of its - * initial value. + * @property {number} damping=0.39347 - How much the linear velocity of an entity slows down over time, range + * 0.01.0. A higher damping value slows down the entity more quickly. The default value + * is for an exponential decay timescale of 2.0s, where it takes 2.0s for the movement to slow to 1/e = 0.368 + * of its initial value. * @property {Vec3} angularVelocity=0,0,0 - The angular velocity of the entity in rad/s with respect to its axes, about its * registration point. - * @property {number} angularDamping=0.39347 - How much to slow down the angular velocity of an entity over time, + * @property {number} angularDamping=0.39347 - How much the angular velocity of an entity slows down over time, range * 0.01.0. A higher damping value slows down the entity more quickly. The default value * is for an exponential decay timescale of 2.0s, where it takes 2.0s for the movement to slow to 1/e = 0.368 * of its initial value. * * @property {Vec3} gravity=0,0,0 - The acceleration due to gravity in m/s2 that the entity should move with, in - * world coordinates. Set to { x: 0, y: -9.8, z: 0 } to simulate Earth's gravity. Gravity is applied to an - * entity's motion only if its dynamic property is true. If changing an entity's - * gravity from {@link Vec3(0)|Vec3.ZERO}, you need to give it a small velocity in order to kick - * off physics simulation. - * The gravity value is applied in addition to the acceleration value. + * world coordinates. Use a value of { x: 0, y: -9.8, z: 0 } to simulate Earth's gravity. Gravity is applied + * to an entity's motion only if its dynamic property is true. The gravity value is + * applied in addition to the acceleration value. + *

If changing an entity's gravity from {@link Vec3(0)|Vec3.ZERO}, you need to give it a small + * velocity in order to kick off physics simulation.

* @property {Vec3} acceleration=0,0,0 - A general acceleration in m/s2 that the entity should move with, in world * coordinates. The acceleration is applied to an entity's motion only if its dynamic property is - * true. If changing an entity's acceleration from {@link Vec3(0)|Vec3.ZERO}, you need to give it - * a small velocity in order to kick off physics simulation. - * The acceleration value is applied in addition to the gravity value. - * @property {number} restitution=0.5 - The "bounciness" of an entity when it collides, 0.0 – + * true. The acceleration value is applied in addition to the gravity value. + *

If changing an entity's acceleration from {@link Vec3(0)|Vec3.ZERO}, you need to give it a small + * velocity in order to kick off physics simulation.

+ * @property {number} restitution=0.5 - The "bounciness" of an entity when it collides, range 0.0 – * 0.99. The higher the value, the more bouncy. - * @property {number} friction=0.5 - How much to slow down an entity when it's moving against another, 0.0 – - * 10.0. The higher the value, the more quickly it slows down. Examples: 0.1 for ice, + * @property {number} friction=0.5 - How much an entity slows down when it's moving against another, range 0.0 + * – 10.0. The higher the value, the more quickly it slows down. Examples: 0.1 for ice, * 0.9 for sandpaper. - * @property {number} density=1000 - The density of the entity in kg/m3, 100 for balsa wood – - * 10000 for silver. The density is used in conjunction with the entity's bounding box volume to work out its - * mass in the application of physics. + * @property {number} density=1000 - The density of the entity in kg/m3, range 100 for balsa wood + * – 10000 for silver. The density is used in conjunction with the entity's bounding box volume to work + * out its mass in the application of physics. * * @property {boolean} collisionless=false - Whether or not the entity should collide with items per its - * collisionMask property. If true then the entity does not collide. A synonym is ignoreForCollisions. + * collisionMask property. If true then the entity does not collide. + * @property {boolean} ignoreForCollisions - Synonym for collisionless. * @property {CollisionMask} collisionMask=31 - What types of items the entity should collide with. * @property {string} collidesWith="static,dynamic,kinematic,myAvatar,otherAvatar," - Synonym for collisionMask, * in text format. - * @property {string} collisionSoundURL="" - The sound to play when the entity experiences a collision. Valid file formats are - * as per the {@link SoundCache} object. + * @property {string} collisionSoundURL="" - The sound that's played when the entity experiences a collision. Valid file + * formats are per {@link SoundObject}. * @property {boolean} dynamic=false - Whether or not the entity should be affected by collisions. If true then - * the entity's movement is affected by collisions. A synonym is collisionsWillMove. + * the entity's movement is affected by collisions. + * @property {boolean} collisionsWillMove - A synonym for dynamic. * - * @property {string} href="" - A "hifi://" metaverse address that a user is taken to when they click on the entity. + * @property {string} href="" - A "hifi://" metaverse address that a user is teleported to when they click on the entity. * @property {string} description="" - A description of the href property value. * - * @property {string} userData="" - Used to store extra data about the entity in JSON format. WARNING: Other apps such as the - * Create app can also use this property, so make sure you handle data stored by other apps — edit only your bit and - * leave the rest of the data intact. You can use JSON.parse() to parse the string into a JavaScript object - * which you can manipulate the properties of, and use JSON.stringify() to convert the object into a string to - * put in the property. + * @property {string} userData="" - Used to store extra data about the entity in JSON format. + *

Warning: Other apps may also use this property, so make sure you handle data stored by other apps: + * edit only your bit and leave the rest of the data intact. You can use JSON.parse() to parse the string into + * a JavaScript object which you can manipulate the properties of, and use JSON.stringify() to convert the + * object into a string to put back in the property.

* - * @property {string} privateUserData="" - Like userData, but only accessible by Entity Server Scripts, AC scripts, and users - * who are given "Can Get and Set Private User Data" permissions from the ACL matrix on the Domain Settings page. + * @property {string} privateUserData="" - Like userData, but only accessible by server entity scripts, assignment + * client scripts, and users who have "Can Get and Set Private User Data" permissions in the domain. * * @property {string} script="" - The URL of the client entity script, if any, that is attached to the entity. - * @property {number} scriptTimestamp=0 - Intended to be used to indicate when the client entity script was loaded. Should be + * @property {number} scriptTimestamp=0 - Used to indicate when the client entity script was loaded. Should be * an integer number of milliseconds since midnight GMT on January 1, 1970 (e.g., as supplied by Date.now(). * If you update the property's value, the script is re-downloaded and reloaded. This is how the "reload" * button beside the "script URL" field in properties tab of the Create app works. * @property {string} serverScripts="" - The URL of the server entity script, if any, that is attached to the entity. * - * @property {Uuid} parentID=Uuid.NULL - The ID of the entity or avatar that this entity is parented to. {@link Uuid(0)|Uuid.NULL} - * if the entity is not parented. - * @property {number} parentJointIndex=65535 - The joint of the entity or avatar that this entity is parented to. Use + * @property {Uuid} parentID=Uuid.NULL - The ID of the entity or avatar that the entity is parented to. + * {@link Uuid(0)|Uuid.NULL} if the entity is not parented. + * @property {number} parentJointIndex=65535 - The joint of the entity or avatar that the entity is parented to. Use * 65535 or -1 to parent to the entity or avatar's position and orientation rather than a joint. * @property {Vec3} localPosition=0,0,0 - The position of the entity relative to its parent if the entity is parented, - * otherwise the same value as position. If the entity is parented to an avatar and is an avatarEntity + * otherwise the same value as position. If the entity is parented to an avatar and is an avatar entity * so that it scales with the avatar, this value remains the original local position value while the avatar scale changes. * @property {Quat} localRotation=0,0,0,1 - The rotation of the entity relative to its parent if the entity is parented, * otherwise the same value as rotation. * @property {Vec3} localVelocity=0,0,0 - The velocity of the entity relative to its parent if the entity is parented, * otherwise the same value as velocity. * @property {Vec3} localAngularVelocity=0,0,0 - The angular velocity of the entity relative to its parent if the entity is - * parented, otherwise the same value as position. + * parented, otherwise the same value as angularVelocity. * @property {Vec3} localDimensions - The dimensions of the entity. If the entity is parented to an avatar and is an - * avatarEntity so that it scales with the avatar, this value remains the original dimensions value while the + * avatar entity so that it scales with the avatar, this value remains the original dimensions value while the * avatar scale changes. * * @property {Entities.BoundingBox} boundingBox - The axis-aligned bounding box that tightly encloses the entity. @@ -753,57 +757,59 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * unnecessary entity server updates. Scripts should not change this property's value. * * @property {string} actionData="" - Base-64 encoded compressed dump of the actions associated with the entity. This property - * is typically not used in scripts directly; rather, functions that manipulate an entity's actions update it. - * The size of this property increases with the number of actions. Because this property value has to fit within a High - * Fidelity datagram packet there is a limit to the number of actions that an entity can have, and edits which would result - * in overflow are rejected. Read-only. + * is typically not used in scripts directly; rather, functions that manipulate an entity's actions update it, e.g., + * {@link Entities.addAction}. The size of this property increases with the number of actions. Because this property value + * has to fit within a High Fidelity datagram packet there is a limit to the number of actions that an entity can have, and + * edits which would result in overflow are rejected. Read-only. * @property {Entities.RenderInfo} renderInfo - Information on the cost of rendering the entity. Currently information is only * provided for Model entities. Read-only. * - * @property {boolean} cloneable=false - If true then the entity can be cloned via {@link Entities.cloneEntity}. + * @property {boolean} cloneable=false - If true then the domain or avatar entity can be cloned via + * {@link Entities.cloneEntity}. * @property {number} cloneLifetime=300 - The entity lifetime for clones created from this entity. * @property {number} cloneLimit=0 - The total number of clones of this entity that can exist in the domain at any given time. * @property {boolean} cloneDynamic=false - If true then clones created from this entity will have their * dynamic property set to true. * @property {boolean} cloneAvatarEntity=false - If true then clones created from this entity will be created as - * avatar entities: their avatarEntity property will be set to true. + * avatar entities. * @property {Uuid} cloneOriginID - The ID of the entity that this entity was cloned from. * - * @property {Entities.Grab} grab - The grab-related properties. + * @property {Entities.Grab} grab - The entity's grab-related properties. * * @property {string} itemName="" - Certifiable name of the Marketplace item. * @property {string} itemDescription="" - Certifiable description of the Marketplace item. * @property {string} itemCategories="" - Certifiable category of the Marketplace item. - * @property {string} itemArtist="" - Certifiable artist that created the Marketplace item. + * @property {string} itemArtist="" - Certifiable artist that created the Marketplace item. * @property {string} itemLicense="" - Certifiable license URL for the Marketplace item. * @property {number} limitedRun=4294967295 - Certifiable maximum integer number of editions (copies) of the Marketplace item * allowed to be sold. * @property {number} editionNumber=0 - Certifiable integer edition (copy) number or the Marketplace item. Each copy sold in * the Marketplace is numbered sequentially, starting at 1. * @property {number} entityInstanceNumber=0 - Certifiable integer instance number for identical entities in a Marketplace - * item. A Marketplace item may have identical parts. If so, then each is numbered sequentially with an instance number. + * item. A Marketplace item may have multiple, identical parts. If so, then each is numbered sequentially with an instance + * number. * @property {string} marketplaceID="" - Certifiable UUID for the Marketplace item, as used in the URL of the item's download * and its Marketplace Web page. * @property {string} certificateID="" - Hash of the entity's static certificate JSON, signed by the artist's private key. * @property {number} staticCertificateVersion=0 - The version of the method used to generate the certificateID. * - * @see The different entity types have additional properties as follows: + * @comment The different entity types have additional properties as follows: * @see {@link Entities.EntityProperties-Box|EntityProperties-Box} - * @see {@link Entities.EntityProperties-Sphere|EntityProperties-Sphere} - * @see {@link Entities.EntityProperties-Shape|EntityProperties-Shape} - * @see {@link Entities.EntityProperties-Model|EntityProperties-Model} - * @see {@link Entities.EntityProperties-Text|EntityProperties-Text} + * @see {@link Entities.EntityProperties-Gizmo|EntityProperties-Gizmo} + * @see {@link Entities.EntityProperties-Grid|EntityProperties-Grid} * @see {@link Entities.EntityProperties-Image|EntityProperties-Image} - * @see {@link Entities.EntityProperties-Web|EntityProperties-Web} - * @see {@link Entities.EntityProperties-ParticleEffect|EntityProperties-ParticleEffect} + * @see {@link Entities.EntityProperties-Light|EntityProperties-Light} * @see {@link Entities.EntityProperties-Line|EntityProperties-Line} + * @see {@link Entities.EntityProperties-Material|EntityProperties-Material} + * @see {@link Entities.EntityProperties-Model|EntityProperties-Model} + * @see {@link Entities.EntityProperties-ParticleEffect|EntityProperties-ParticleEffect} * @see {@link Entities.EntityProperties-PolyLine|EntityProperties-PolyLine} * @see {@link Entities.EntityProperties-PolyVox|EntityProperties-PolyVox} - * @see {@link Entities.EntityProperties-Grid|EntityProperties-Grid} - * @see {@link Entities.EntityProperties-Gizmo|EntityProperties-Gizmo} - * @see {@link Entities.EntityProperties-Light|EntityProperties-Light} + * @see {@link Entities.EntityProperties-Shape|EntityProperties-Shape} + * @see {@link Entities.EntityProperties-Sphere|EntityProperties-Sphere} + * @see {@link Entities.EntityProperties-Text|EntityProperties-Text} + * @see {@link Entities.EntityProperties-Web|EntityProperties-Web} * @see {@link Entities.EntityProperties-Zone|EntityProperties-Zone} - * @see {@link Entities.EntityProperties-Material|EntityProperties-Material} */ /**jsdoc @@ -812,20 +818,23 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * when the entity is created. If its shape property value is subsequently changed then the entity's * type will be reported as "Sphere" if the shape is set to "Sphere", * otherwise it will be reported as "Shape". + * * @typedef {object} Entities.EntityProperties-Box + * @see {@link Entities.EntityProperties-Shape|EntityProperties-Shape} */ /**jsdoc - * The "Light" {@link Entities.EntityType|EntityType} adds local lighting effects. - * It has properties in addition to the common {@link Entities.EntityProperties|EntityProperties}. + * The "Light" {@link Entities.EntityType|EntityType} adds local lighting effects. It has properties in addition + * to the common {@link Entities.EntityProperties|EntityProperties}. + * * @typedef {object} Entities.EntityProperties-Light - * @property {Vec3} dimensions=0.1,0.1,0.1 - The dimensions of the entity. Entity surface outside these dimensions are not lit + * @property {Vec3} dimensions=0.1,0.1,0.1 - The dimensions of the entity. Surfaces outside these dimensions are not lit * by the light. * @property {Color} color=255,255,255 - The color of the light emitted. * @property {number} intensity=1 - The brightness of the light. * @property {number} falloffRadius=0.1 - The distance from the light's center at which intensity is reduced by 25%. * @property {boolean} isSpotlight=false - If true then the light is directional, emitting along the entity's - * local negative z-axis; otherwise the light is a point light which emanates in all directions. + * local negative z-axis; otherwise, the light is a point light which emanates in all directions. * @property {number} exponent=0 - Affects the softness of the spotlight beam: the higher the value the softer the beam. * @property {number} cutoff=1.57 - Affects the size of the spotlight beam: the higher the value the larger the beam. * @example Create a spotlight pointing at the ground. @@ -845,8 +854,9 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { /**jsdoc * The "Line" {@link Entities.EntityType|EntityType} draws thin, straight lines between a sequence of two or more - * points. Deprecated: Use PolyLines instead. - * It has properties in addition to the common {@link Entities.EntityProperties|EntityProperties}. + * points. It has properties in addition to the common {@link Entities.EntityProperties|EntityProperties}. + *

Deprecated: Use PolyLines instead.

+ * * @typedef {object} Entities.EntityProperties-Line * @property {Vec3} dimensions=0.1,0.1,0.1 - The dimensions of the entity. Must be sufficient to contain all the * linePoints. @@ -871,41 +881,46 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { */ /**jsdoc - * The "Material" {@link Entities.EntityType|EntityType} modifies the existing materials on entities and avatars. - * It has properties in addition to the common {@link Entities.EntityProperties|EntityProperties}.
- * To apply a material to an entity, set the material entity's parentID property to the entity ID. + * The "Material" {@link Entities.EntityType|EntityType} modifies existing materials on entities and avatars. It + * has properties in addition to the common {@link Entities.EntityProperties|EntityProperties}. + *

To apply a material to an entity, set the material entity's parentID property to the entity ID. * To apply a material to an avatar, set the material entity's parentID property to the avatar's session UUID. * To apply a material to your avatar such that it persists across domains and log-ins, create the material as an avatar entity - * by setting the entityHostType parameter in {@link Entities.addEntity} to "avatar". - * Material entities render as non-scalable spheres if they don't have their parent set. + * by setting the entityHostType parameter in {@link Entities.addEntity} to "avatar" and set the + * entity's parentID property to MyAvatar.SELF_ID. + * Material entities render as non-scalable spheres if they don't have their parent set.

+ * * @typedef {object} Entities.EntityProperties-Material - * @property {string} materialURL="" - URL to a {@link MaterialResource}. If you append #name to the URL, the - * material with that name in the {@link MaterialResource} will be applied to the entity.
- * Alternatively, set the property value to "materialData" to use the materialData property - * for the {@link MaterialResource} values. - * @property {number} priority=0 - The priority for applying the material to its parent. Only the highest priority material is + * @property {Vec3} dimensions=0.1,0.1,0.1 - Used when materialMappingMode == "projected". + * @property {string} materialURL="" - URL to a {@link Entities.MaterialResource|MaterialResource}. If you append + * "#name" to the URL, the material with that name in the {@link Entities.MaterialResource|MaterialResource} + * will be applied to the entity. Alternatively, set the property value to "materialData" to use the + * materialData property for the {@link Entities.MaterialResource|MaterialResource} values. + * @property {string} materialData="" - Used to store {@link Entities.MaterialResource|MaterialResource} data as a JSON string. + * You can use JSON.parse() to parse the string into a JavaScript object which you can manipulate the + * properties of, and use JSON.stringify() to convert the object into a string to put in the property. + * @property {number} priority=0 - The priority for applying the material to its parent. Only the highest priority material is * applied, with materials of the same priority randomly assigned. Materials that come with the model have a priority of * 0. * @property {string} parentMaterialName="0" - Selects the mesh part or parts within the parent to which to apply the material. * If in the format "mat::string", all mesh parts with material name "string" are replaced. - * Otherwise the property value is parsed as an unsigned integer, specifying the mesh part index to modify. If "all", - * all mesh parts will be replaced. If an array (starts with "[" and ends with "]"), the string will be - * split at each "," and each element will be parsed as either a number or a string if it starts with - * "mat::". In other words, "[0,1,mat::string,mat::string2]" will replace mesh parts 0 and 1, and any - * mesh parts with material "string" or "string2". Do not put spaces around the commas. Invalid values - * are parsed to 0. + * If "all" then all mesh parts are replaced. + * Otherwise the property value is parsed as an unsigned integer, specifying the mesh part index to modify. + *

If the string represents an array (starts with "[" and ends with "]"), the string is split + * at each "," and each element parsed as either a number or a string if it starts with "mat::". + * For example, "[0,1,mat::string,mat::string2]" will replace mesh parts 0 and 1, and any mesh parts with + * material "string" or "string2". Do not put spaces around the commas. Invalid values are parsed + * to 0.

* @property {string} materialMappingMode="uv" - How the material is mapped to the entity. Either "uv" or - * "projected". In "uv" mode, the material will be evaluated within the UV space of the mesh it is applied to. In - * "projected" mode, the 3D transform of the Material Entity will be used to evaluate the texture coordinates for the material. + * "projected". In "uv" mode, the material is evaluated within the UV space of the mesh it is + * applied to. In "projected" mode, the 3D transform (position, rotation, and dimensions) of the Material + * entity is used to evaluate the texture coordinates for the material. * @property {Vec2} materialMappingPos=0,0 - Offset position in UV-space of the top left of the material, range * { x: 0, y: 0 }{ x: 1, y: 1 }. * @property {Vec2} materialMappingScale=1,1 - How much to scale the material within the parent's UV-space. * @property {number} materialMappingRot=0 - How much to rotate the material within the parent's UV-space, in degrees. - * @property {string} materialData="" - Used to store {@link MaterialResource} data as a JSON string. You can use - * JSON.parse() to parse the string into a JavaScript object which you can manipulate the properties of, and - * use JSON.stringify() to convert the object into a string to put in the property. - * @property {boolean} materialRepeat=true - If true, the material will repeat. If false, fragments outside of texCoord 0 - 1 will be discarded. - * Works in both "uv" and "projected" modes. + * @property {boolean} materialRepeat=true - If true, the material repeats. If false, fragments + * outside of texCoord 0 – 1 will be discarded. Works in both "uv" and
"projected"
modes. * @example Color a sphere using a Material entity. * var entityID = Entities.addEntity({ * type: "Sphere", @@ -931,15 +946,20 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { */ /**jsdoc - * The "Model" {@link Entities.EntityType|EntityType} displays an FBX or OBJ model. - * It has properties in addition to the common {@link Entities.EntityProperties|EntityProperties}. + * The "Model" {@link Entities.EntityType|EntityType} displays a glTF, FBX, or OBJ model. When adding an entity, + * if no dimensions value is specified then the model is automatically sized to its + * {@link Entities.EntityProperties|naturalDimensions}. It has properties in addition to the common + * {@link Entities.EntityProperties|EntityProperties}. + * * @typedef {object} Entities.EntityProperties-Model * @property {Vec3} dimensions=0.1,0.1,0.1 - The dimensions of the entity. When adding an entity, if no dimensions * value is specified then the model is automatically sized to its * {@link Entities.EntityProperties|naturalDimensions}. - * @property {Vec3} modelScale - The scale factor applied to the model's dimensions. Deprecated. - * @property {Color} color=255,255,255 - Currently not used. - * @property {string} modelURL="" - The URL of the FBX of OBJ model. Baked FBX models' URLs end in ".baked.fbx".
+ * @property {string} modelURL="" - The URL of the glTF, FBX, or OBJ model. glTF models may be in JSON or binary format + * (".gltf" or ".glb" URLs respectively). Baked FBX models' URLs end in ".baked.fbx". Model files may also be compressed in GZ + * format, in which case the URL ends in ".gz". + * @property {Vec3} modelScale - The scale factor applied to the model's dimensions. + *

Deprecated: This property is deprecated and will be removed.

* @property {string} textures="" - A JSON string of texture name, URL pairs used when rendering the model in place of the * model's original textures. Use a texture name from the originalTextures property to override that texture. * Only the texture names and URLs to be overridden need be specified; original textures are used where there are no @@ -948,6 +968,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * @property {string} originalTextures="{}" - A JSON string of texture name, URL pairs used in the model. The property value is * filled in after the entity has finished rezzing (i.e., textures have loaded). You can use JSON.parse() to * parse the JSON string into a JavaScript object of name, URL pairs. Read-only. + * @property {Color} color=255,255,255 - Currently not used. * * @property {ShapeType} shapeType="none" - The shape of the collision hull used if collisions are enabled. * @property {string} compoundShapeURL="" - The model file to use for the compound shape if shapeType is @@ -955,22 +976,22 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * * @property {Entities.AnimationProperties} animation - An animation to play on the model. * - * @property {Quat[]} jointRotations=[] - Joint rotations applied to the model; [] if none are applied or the + * @property {Quat[]} jointRotations=[]] - Joint rotations applied to the model; [] if none are applied or the * model hasn't loaded. The array indexes are per {@link Entities.getJointIndex|getJointIndex}. Rotations are relative to - * each joint's parent.
- * Joint rotations can be set by {@link Entities.setLocalJointRotation|setLocalJointRotation} and similar functions, or by - * setting the value of this property. If you set a joint rotation using this property you also need to set the - * corresponding jointRotationsSet value to true. - * @property {boolean[]} jointRotationsSet=[] - true values for joints that have had rotations applied, + * each joint's parent. + *

Joint rotations can be set by {@link Entities.setLocalJointRotation|setLocalJointRotation} and similar functions, or + * by setting the value of this property. If you set a joint rotation using this property you also need to set the + * corresponding jointRotationsSet value to true.

+ * @property {boolean[]} jointRotationsSet=[]] - true values for joints that have had rotations applied, * false otherwise; [] if none are applied or the model hasn't loaded. The array indexes are per * {@link Entities.getJointIndex|getJointIndex}. - * @property {Vec3[]} jointTranslations=[] - Joint translations applied to the model; [] if none are applied or + * @property {Vec3[]} jointTranslations=[]] - Joint translations applied to the model; [] if none are applied or * the model hasn't loaded. The array indexes are per {@link Entities.getJointIndex|getJointIndex}. Rotations are relative - * to each joint's parent.
- * Joint translations can be set by {@link Entities.setLocalJointTranslation|setLocalJointTranslation} and similar + * to each joint's parent. + *

Joint translations can be set by {@link Entities.setLocalJointTranslation|setLocalJointTranslation} and similar * functions, or by setting the value of this property. If you set a joint translation using this property you also need to - * set the corresponding jointTranslationsSet value to true. - * @property {boolean[]} jointTranslationsSet=[] - true values for joints that have had translations applied, + * set the corresponding jointTranslationsSet value to true.

+ * @property {boolean[]} jointTranslationsSet=[]] - true values for joints that have had translations applied, * false otherwise; [] if none are applied or the model hasn't loaded. The array indexes are per * {@link Entities.getJointIndex|getJointIndex}. * @property {boolean} relayParentJoints=false - If true and the entity is parented to an avatar, then the @@ -990,98 +1011,103 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { */ /**jsdoc - * The "ParticleEffect" {@link Entities.EntityType|EntityType} displays a particle system that can be used to + * The "ParticleEffect" {@link Entities.EntityType|EntityType} displays a particle system that can be used to * simulate things such as fire, smoke, snow, magic spells, etc. The particles emanate from an ellipsoid or part thereof. * It has properties in addition to the common {@link Entities.EntityProperties|EntityProperties}. + * * @typedef {object} Entities.EntityProperties-ParticleEffect - - * @property {boolean} isEmitting=true - If true then particles are emitted. + * @property {boolean} isEmitting=true - true if particles are being emitted, false if they aren't. * @property {number} maxParticles=1000 - The maximum number of particles to render at one time. Older particles are deleted if * necessary when new ones are created. * @property {number} lifespan=3s - How long, in seconds, each particle lives. * @property {number} emitRate=15 - The number of particles per second to emit. * @property {number} emitSpeed=5 - The speed, in m/s, that each particle is emitted at. - * @property {number} speedSpread=1 - The spread in speeds at which particles are emitted at. If emitSpeed == 5 - * and speedSpread == 1, particles will be emitted with speeds in the range 4m/s – 6m/s. + * @property {number} speedSpread=1 - The spread in speeds at which particles are emitted at. For example, if + * emitSpeed == 5 and speedSpread == 1, particles will be emitted with speeds in the range + * 46m/s. * @property {Vec3} emitAcceleration=0,-9.8,0 - The acceleration that is applied to each particle during its lifetime. The * default is Earth's gravity value. - * @property {Vec3} accelerationSpread=0,0,0 - The spread in accelerations that each particle is given. If + * @property {Vec3} accelerationSpread=0,0,0 - The spread in accelerations that each particle is given. For example, if * emitAccelerations == {x: 0, y: -9.8, z: 0} and accelerationSpread == * {x: 0, y: 1, z: 0}, each particle will have an acceleration in the range {x: 0, y: -10.8, z: 0} * – {x: 0, y: -8.8, z: 0}. * @property {Vec3} dimensions - The dimensions of the particle effect, i.e., a bounding box containing all the particles - * during their lifetimes, assuming that emitterShouldTrail is false. Read-only. + * during their lifetimes, assuming that emitterShouldTrail == false. Read-only. * @property {boolean} emitterShouldTrail=false - If true then particles are "left behind" as the emitter moves, - * otherwise they stay with the entity's dimensions. + * otherwise they stay within the entity's dimensions. * * @property {Quat} emitOrientation=-0.707,0,0,0.707 - The orientation of particle emission relative to the entity's axes. By * default, particles emit along the entity's local z-axis, and azimuthStart and azimuthFinish * are relative to the entity's local x-axis. The default value is a rotation of -90 degrees about the local x-axis, i.e., * the particles emit vertically. - * @property {Vec3} emitDimensions=0,0,0 - The dimensions of the shape from which particles are emitted. The shape is specified with - * shapeType. + * + * @property {ShapeType} shapeType="ellipsoid" - The shape from which particles are emitted. + * @property {string} compoundShapeURL="" - The model file to use for the compound shape if shapeType == + * "compound". + * @property {Vec3} emitDimensions=0,0,0 - The dimensions of the shape from which particles are emitted. * @property {number} emitRadiusStart=1 - The starting radius within the shape at which particles start being emitted; * range 0.01.0 for the center to the surface, respectively. * Particles are emitted from the portion of the shape that lies between emitRadiusStart and the * shape's surface. * @property {number} polarStart=0 - The angle in radians from the entity's local z-axis at which particles start being emitted * within the shape; range 0Math.PI. Particles are emitted from the portion of the - * shape that lies between polarStart and polarFinish. Only used if shapeType is - * ellipsoid or sphere. + * shape that lies between polarStart and polarFinish. Only used if shapeType is + * "ellipsoid" or "sphere". * @property {number} polarFinish=0 - The angle in radians from the entity's local z-axis at which particles stop being emitted * within the shape; range 0Math.PI. Particles are emitted from the portion of the - * shape that lies between polarStart and polarFinish. Only used if shapeType is - * ellipsoid or sphere. + * shape that lies between polarStart and polarFinish. Only used if shapeType is + * "ellipsoid" or "sphere". * @property {number} azimuthStart=-Math.PI - The angle in radians from the entity's local x-axis about the entity's local * z-axis at which particles start being emitted; range -Math.PIMath.PI. Particles are - * emitted from the portion of the shape that lies between azimuthStart and azimuthFinish. - * Only used if shapeType is ellipsoid, sphere, or circle. + * emitted from the portion of the shape that lies between azimuthStart and azimuthFinish. + * Only used if shapeType is "ellipsoid", "sphere", or "circle". * @property {number} azimuthFinish=Math.PI - The angle in radians from the entity's local x-axis about the entity's local * z-axis at which particles stop being emitted; range -Math.PIMath.PI. Particles are - * emitted from the portion of the shape that lies between azimuthStart and azimuthFinish. - * Only used if shapeType is ellipsoid, sphere, or circle.. + * emitted from the portion of the shape that lies between azimuthStart and azimuthFinish. + * Only used if shapeType is "ellipsoid", "sphere", or "circle". * * @property {string} textures="" - The URL of a JPG or PNG image file to display for each particle. If you want transparency, * use PNG format. * @property {number} particleRadius=0.025 - The radius of each particle at the middle of its life. - * @property {number} radiusStart=NaN - The radius of each particle at the start of its life. If NaN, the + * @property {number} radiusStart=null - The radius of each particle at the start of its life. If null, the * particleRadius value is used. - * @property {number} radiusFinish=NaN - The radius of each particle at the end of its life. If NaN, the + * @property {number} radiusFinish=null - The radius of each particle at the end of its life. If null, the * particleRadius value is used. - * @property {number} radiusSpread=0 - The spread in radius that each particle is given. If particleRadius == 0.5 - * and radiusSpread == 0.25, each particle will have a radius in the range 0.25 – - * 0.75. + * @property {number} radiusSpread=0 - The spread in radius that each particle is given. For example, if + * particleRadius == 0.5 and radiusSpread == 0.25, each particle will have a radius in the range + * 0.250.75. * @property {Color} color=255,255,255 - The color of each particle at the middle of its life. - * @property {ColorFloat} colorStart={} - The color of each particle at the start of its life. If any of the component values are - * undefined, the color value is used. - * @property {ColorFloat} colorFinish={} - The color of each particle at the end of its life. If any of the component values are - * undefined, the color value is used. - * @property {Color} colorSpread=0,0,0 - The spread in color that each particle is given. If + * @property {ColorFloat} colorStart=null,null,null - The color of each particle at the start of its life. If any of the + * component values are undefined, the color value is used. + * @property {ColorFloat} colorFinish=null,null,null - The color of each particle at the end of its life. If any of the + * component values are undefined, the color value is used. + * @property {Color} colorSpread=0,0,0 - The spread in color that each particle is given. For example, if * color == {red: 100, green: 100, blue: 100} and colorSpread == * {red: 10, green: 25, blue: 50}, each particle will have a color in the range * {red: 90, green: 75, blue: 50}{red: 110, green: 125, blue: 150}. - * @property {number} alpha=1 - The alpha of each particle at the middle of its life. - * @property {number} alphaStart=NaN - The alpha of each particle at the start of its life. If NaN, the + * @property {number} alpha=1 - The opacity of each particle at the middle of its life. + * @property {number} alphaStart=null - The opacity of each particle at the start of its life. If null, the * alpha value is used. - * @property {number} alphaFinish=NaN - The alpha of each particle at the end of its life. If NaN, the + * @property {number} alphaFinish=null - The opacity of each particle at the end of its life. If null, the * alpha value is used. - * @property {number} alphaSpread=0 - The spread in alpha that each particle is given. If alpha == 0.5 - * and alphaSpread == 0.25, each particle will have an alpha in the range 0.25 – - * 0.75. - * @property {number} particleSpin=0 - The spin of each particle at the middle of its life. In the range -2*PI2*PI. - * @property {number} spinStart=NaN - The spin of each particle at the start of its life. In the range -2*PI2*PI. - * If NaN, the particleSpin value is used. - * @property {number} spinFinish=NaN - The spin of each particle at the end of its life. In the range -2*PI2*PI. - * If NaN, the particleSpin value is used. - * @property {number} spinSpread=0 - The spread in spin that each particle is given. In the range 02*PI. If particleSpin == PI - * and spinSpread == PI/2, each particle will have a spin in the range PI/23*PI/2. - * @property {boolean} rotateWithEntity=false - Whether or not the particles' spin will rotate with the entity. If false, when particleSpin == 0, the particles will point - * up in the world. If true, they will point towards the entity's up vector, based on its orientation. - * @property {Entities.Pulse} pulse - The pulse-related properties. Deprecated. - * - * @property {ShapeType} shapeType="ellipsoid" - The shape of the collision hull used if collisions are enabled. - * @property {string} compoundShapeURL="" - The model file to use for the compound shape if shapeType is - * "compound". + * @property {number} alphaSpread=0 - The spread in alpha that each particle is given. For example, if + * alpha == 0.5 and alphaSpread == 0.25, each particle will have an alpha in the range + * 0.250.75. + * @property {Entities.Pulse} pulse - Color and alpha pulse. + *

Deprecated: This property is deprecated and will be removed.

+ * @property {number} particleSpin=0 - The rotation of each particle at the middle of its life, range -2 * Math.PI + * – 2 * Math.PI radians. + * @property {number} spinStart=null - The rotation of each particle at the start of its life, range -2 * Math.PI + * – 2 * Math.PI radians. If null, the particleSpin value is used. + * @property {number} spinFinish=null - The rotation of each particle at the end of its life, range -2 * Math.PI + * – 2 * Math.PI radians. If null, the particleSpin value is used. + * @property {number} spinSpread=0 - The spread in spin that each particle is given, range 0 – + * 2 * Math.PI radians. For example, if particleSpin == Math.PI and + * spinSpread == Math.PI / 2, each particle will have a rotation in the range Math.PI / 2 – + * 3 * Math.PI / 2. + * @property {boolean} rotateWithEntity=false - true to make the particles' rotations relative to the entity's + * instantaneous rotation, false to make them relative to world coordinates. If true with + * particleSpin == 0, the particles keep oriented per the entity's orientation. * * @example Create a ball of green smoke. * particles = Entities.addEntity({ @@ -1102,27 +1128,30 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { */ /**jsdoc - * The "PolyLine" {@link Entities.EntityType|EntityType} draws textured, straight lines between a sequence of - * points. - * It has properties in addition to the common {@link Entities.EntityProperties|EntityProperties}. + * The "PolyLine" {@link Entities.EntityType|EntityType} draws textured, straight lines between a sequence of + * points. It has properties in addition to the common {@link Entities.EntityProperties|EntityProperties}. + * * @typedef {object} Entities.EntityProperties-PolyLine - * @property {Vec3} dimensions=1,1,1 - The dimensions of the entity, i.e., the size of the bounding box that contains the - * lines drawn. - * @property {Vec3[]} linePoints=[] - The sequence of points to draw lines between. The values are relative to the entity's - * position. A maximum of 70 points can be specified. Must be specified in order for the entity to render. - * @property {Vec3[]} normals=[] - The normal vectors for the line's surface at the linePoints. The values are + * @property {Vec3} dimensions=0.1,0.1,0.1 - The dimensions of the entity, i.e., the size of the bounding box that contains the + * lines drawn. Read-only. + * @property {Vec3[]} linePoints=[]] - The sequence of points to draw lines between. The values are relative to the entity's + * position. A maximum of 70 points can be specified. + * @property {Vec3[]} normals=[]] - The normal vectors for the line's surface at the linePoints. The values are * relative to the entity's orientation. Must be specified in order for the entity to render. - * @property {number[]} strokeWidths=[] - The widths, in m, of the line at the linePoints. Must be specified in + * @property {number[]} strokeWidths=[]] - The widths, in m, of the line at the linePoints. Must be specified in * order for the entity to render. - * @property {Vec3[]} strokeColors=[] - The base colors of each point, which are multiplied with the color of the texture, going from 0-1. - * If strokeColors.length < the number of points, color is used for the remaining points. - * @property {Color} color=255,255,255 - Used as the color for each point if strokeColors is too short. + * @property {Vec3[]} strokeColors=[]] - The base colors of each point, with values in the range 0.0,0.0,0.0 + * — 1.0,1.0,1.0. These colors are multiplied with the color of the texture. If there are more line + * points than stroke colors, the color property value is used for the remaining points. + *

Warning: The ordinate values are in the range 0.01.0.

+ * @property {Color} color=255,255,255 - Used as the color for each point if strokeColors doesn't have a value for + * the point. * @property {string} textures="" - The URL of a JPG or PNG texture to use for the lines. If you want transparency, use PNG * format. * @property {boolean} isUVModeStretch=true - If true, the texture is stretched to fill the whole line, otherwise * the texture repeats along the line. - * @property {bool} glow=false - If true, the alpha of the strokes will drop off farther from the center. - * @property {bool} faceCamera=false - If true, each line segment will rotate to face the camera. + * @property {boolean} glow=false - If true, the opacity of the strokes drops off away from the line center. + * @property {boolean} faceCamera=false - If true, each line segment rotates to face the camera. * @example Draw a textured "V". * var entity = Entities.addEntity({ * type: "PolyLine", @@ -1147,12 +1176,13 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { */ /**jsdoc - * The "PolyVox" {@link Entities.EntityType|EntityType} displays a set of textured voxels. + * The "PolyVox" {@link Entities.EntityType|EntityType} displays a set of textured voxels. * It has properties in addition to the common {@link Entities.EntityProperties|EntityProperties}. * If you have two or more neighboring PolyVox entities of the same size abutting each other, you can display them as joined by - * configuring their voxelSurfaceStyle and neighbor ID properties.
- * PolyVox entities uses a library from Volumes of Fun. Their - * library documentation may be useful to read. + * configuring their voxelSurfaceStyle and various neighbor ID properties. + *

PolyVox entities uses a library from Volumes of Fun. Their + * library documentation may be useful to read.

+ * * @typedef {object} Entities.EntityProperties-PolyVox * @property {Vec3} dimensions=0.1,0.1,0.1 - The dimensions of the entity. * @property {Vec3} voxelVolumeSize=32,32,32 - Integer number of voxels along each axis of the entity, in the range @@ -1167,24 +1197,24 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * rejected. * @property {Entities.PolyVoxSurfaceStyle} voxelSurfaceStyle=2 - The style of rendering the voxels' surface and how * neighboring PolyVox entities are joined. - * @property {string} xTextureURL="" - URL of the texture to map to surfaces perpendicular to the entity's local x-axis. JPG or - * PNG format. If no texture is specified the surfaces display white. - * @property {string} yTextureURL="" - URL of the texture to map to surfaces perpendicular to the entity's local y-axis. JPG or - * PNG format. If no texture is specified the surfaces display white. - * @property {string} zTextureURL="" - URL of the texture to map to surfaces perpendicular to the entity's local z-axis. JPG or - * PNG format. If no texture is specified the surfaces display white. - * @property {Uuid} xNNeighborID=Uuid.NULL - ID of the neighboring PolyVox entity in the entity's -ve local x-axis direction, - * if you want them joined. Set to {@link Uuid(0)|Uuid.NULL} if there is none or you don't want to join them. - * @property {Uuid} yNNeighborID=Uuid.NULL - ID of the neighboring PolyVox entity in the entity's -ve local y-axis direction, - * if you want them joined. Set to {@link Uuid(0)|Uuid.NULL} if there is none or you don't want to join them. - * @property {Uuid} zNNeighborID=Uuid.NULL - ID of the neighboring PolyVox entity in the entity's -ve local z-axis direction, - * if you want them joined. Set to {@link Uuid(0)|Uuid.NULL} if there is none or you don't want to join them. - * @property {Uuid} xPNeighborID=Uuid.NULL - ID of the neighboring PolyVox entity in the entity's +ve local x-axis direction, - * if you want them joined. Set to {@link Uuid(0)|Uuid.NULL} if there is none or you don't want to join them. - * @property {Uuid} yPNeighborID=Uuid.NULL - ID of the neighboring PolyVox entity in the entity's +ve local y-axis direction, - * if you want them joined. Set to {@link Uuid(0)|Uuid.NULL} if there is none or you don't want to join them. - * @property {Uuid} zPNeighborID=Uuid.NULL - ID of the neighboring PolyVox entity in the entity's +ve local z-axis direction, - * if you want them joined. Set to {@link Uuid(0)|Uuid.NULL} if there is none or you don't want to join them. + * @property {string} xTextureURL="" - The URL of the texture to map to surfaces perpendicular to the entity's local x-axis. + * JPG or PNG format. If no texture is specified the surfaces display white. + * @property {string} yTextureURL="" - The URL of the texture to map to surfaces perpendicular to the entity's local y-axis. + * JPG or PNG format. If no texture is specified the surfaces display white. + * @property {string} zTextureURL="" - The URL of the texture to map to surfaces perpendicular to the entity's local z-axis. + * JPG or PNG format. If no texture is specified the surfaces display white. + * @property {Uuid} xNNeighborID=Uuid.NULL - The ID of the neighboring PolyVox entity in the entity's -ve local x-axis + * direction, if you want them joined. Set to {@link Uuid(0)|Uuid.NULL} if there is none or you don't want to join them. + * @property {Uuid} yNNeighborID=Uuid.NULL - The ID of the neighboring PolyVox entity in the entity's -ve local y-axis + * direction, if you want them joined. Set to {@link Uuid(0)|Uuid.NULL} if there is none or you don't want to join them. + * @property {Uuid} zNNeighborID=Uuid.NULL - The ID of the neighboring PolyVox entity in the entity's -ve local z-axis + * direction, if you want them joined. Set to {@link Uuid(0)|Uuid.NULL} if there is none or you don't want to join them. + * @property {Uuid} xPNeighborID=Uuid.NULL - The ID of the neighboring PolyVox entity in the entity's +ve local x-axis + * direction, if you want them joined. Set to {@link Uuid(0)|Uuid.NULL} if there is none or you don't want to join them. + * @property {Uuid} yPNeighborID=Uuid.NULL - The ID of the neighboring PolyVox entity in the entity's +ve local y-axis + * direction, if you want them joined. Set to {@link Uuid(0)|Uuid.NULL} if there is none or you don't want to join them. + * @property {Uuid} zPNeighborID=Uuid.NULL - The ID of the neighboring PolyVox entity in the entity's +ve local z-axis + * direction, if you want them joined. Set to {@link Uuid(0)|Uuid.NULL} if there is none or you don't want to join them. * @example Create a textured PolyVox sphere. * var position = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0.5, z: -8 })); * var texture = "http://public.highfidelity.com/cozza13/tuscany/Concrete2.jpg"; @@ -1203,12 +1233,14 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { /**jsdoc * The "Shape" {@link Entities.EntityType|EntityType} displays an entity of a specified shape. * It has properties in addition to the common {@link Entities.EntityProperties|EntityProperties}. + * * @typedef {object} Entities.EntityProperties-Shape * @property {Entities.Shape} shape="Sphere" - The shape of the entity. * @property {Vec3} dimensions=0.1,0.1,0.1 - The dimensions of the entity. * @property {Color} color=255,255,255 - The color of the entity. - * @property {number} alpha=1 - The alpha of the shape. - * @property {Entities.Pulse} pulse - The pulse-related properties. Deprecated. + * @property {number} alpha=1 - The opacity of the entity, range 0.01.0. + * @property {Entities.Pulse} pulse - Color and alpha pulse. + *

Deprecated: This property is deprecated and will be removed.

* @example Create a cylinder. * var shape = Entities.addEntity({ * type: "Shape", @@ -1225,30 +1257,39 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * when the entity is created. If its shape property value is subsequently changed then the entity's * type will be reported as "Box" if the shape is set to "Cube", * otherwise it will be reported as "Shape". + * * @typedef {object} Entities.EntityProperties-Sphere + * @see {@link Entities.EntityProperties-Shape|EntityProperties-Shape} */ /**jsdoc * The "Text" {@link Entities.EntityType|EntityType} displays a 2D rectangle of text in the domain. * It has properties in addition to the common {@link Entities.EntityProperties|EntityProperties}. + * * @typedef {object} Entities.EntityProperties-Text * @property {Vec3} dimensions=0.1,0.1,0.01 - The dimensions of the entity. * @property {string} text="" - The text to display on the face of the entity. Text wraps if necessary to fit. New lines can be * created using \n. Overflowing lines are not displayed. * @property {number} lineHeight=0.1 - The height of each line of text (thus determining the font size). * @property {Color} textColor=255,255,255 - The color of the text. - * @property {number} textAlpha=1.0 - The text alpha. + * @property {number} textAlpha=1.0 - The opacity of the text. * @property {Color} backgroundColor=0,0,0 - The color of the background rectangle. - * @property {number} backgroundAlpha=1.0 - The background alpha. - * @property {BillboardMode} billboardMode="none" - If "none", the entity is not billboarded. If "yaw", the entity will be - * oriented to follow your camera around the y-axis. If "full" the entity will be oriented to face your camera. The following deprecated - * behavior is also supported: you can also set "faceCamera" to true to set billboardMode to "yaw", and you can set - * "isFacingAvatar" to true to set billboardMode to "full". Setting either to false sets the mode to "none" + * @property {number} backgroundAlpha=1.0 - The opacity of the background. + * @property {Entities.Pulse} pulse - Color and alpha pulse. + *

Deprecated: This property is deprecated and will be removed.

* @property {number} leftMargin=0.0 - The left margin, in meters. * @property {number} rightMargin=0.0 - The right margin, in meters. * @property {number} topMargin=0.0 - The top margin, in meters. * @property {number} bottomMargin=0.0 - The bottom margin, in meters. - * @property {Entities.Pulse} pulse - The pulse-related properties. Deprecated. + * @property {BillboardMode} billboardMode="none" - Whether the entity is billboarded to face the camera. + * @property {boolean} faceCamera - true if billboardMode is "yaw", otherwise + * false. Setting this property to false sets the billboardMode to + * "none". + *

Deprecated: This property is deprecated and will be removed.

+ * @property {boolean} isFacingAvatar - true if billboardMode is "full", otherwise + * false. Setting this property to false sets the billboardMode to + * "none". + *

Deprecated: This property is deprecated and will be removed.

* @example Create a text entity. * var text = Entities.addEntity({ * type: "Text", @@ -1262,28 +1303,36 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { */ /**jsdoc - * The "Web" {@link Entities.EntityType|EntityType} displays a browsable Web page. Each user views their own copy - * of the Web page: if one user navigates to another page on the entity, other users do not see the change; if a video is being - * played, users don't see it in sync. - * The entity has properties in addition to the common {@link Entities.EntityProperties|EntityProperties}. + * The "Web" {@link Entities.EntityType|EntityType} displays a browsable web page. Each user views their own copy + * of the web page: if one user navigates to another page on the entity, other users do not see the change; if a video is being + * played, users don't see it in sync. It has properties in addition to the common + * {@link Entities.EntityProperties|EntityProperties}. + * * @typedef {object} Entities.EntityProperties-Web * @property {Vec3} dimensions=0.1,0.1,0.01 - The dimensions of the entity. - * @property {Color} color=255,255,255 - The color of the web surface. - * @property {number} alpha=1 - The alpha of the web surface. - * @property {BillboardMode} billboardMode="none" - If "none", the entity is not billboarded. If "yaw", the entity will be - * oriented to follow your camera around the y-axis. If "full" the entity will be oriented to face your camera. The following deprecated - * behavior is also supported: you can also set "faceCamera" to true to set billboardMode to "yaw", and you can set - * "isFacingAvatar" to true to set billboardMode to "full". Setting either to false sets the mode to "none" - * @property {string} sourceUrl="" - The URL of the Web page to display. This value does not change as you or others navigate + * @property {string} sourceUrl="" - The URL of the web page to display. This value does not change as you or others navigate * on the Web entity. + * @property {Color} color=255,255,255 - The color of the web surface. + * @property {number} alpha=1 - The opacity of the web surface. + * @property {Entities.Pulse} pulse - Color and alpha pulse. + *

Deprecated: This property is deprecated and will be removed.

+ * @property {BillboardMode} billboardMode="none" - Whether the entity is billboarded to face the camera. + * @property {boolean} faceCamera - true if billboardMode is "yaw", otherwise + * false. Setting this property to false sets the billboardMode to + * "none". + *

Deprecated: This property is deprecated and will be removed.

+ * @property {boolean} isFacingAvatar - true if billboardMode is "full", otherwise + * false. Setting this property to false sets the billboardMode to + * "none". + *

Deprecated: This property is deprecated and will be removed.

* @property {number} dpi=30 - The resolution to display the page at, in dots per inch. If you convert this to dots per meter * (multiply by 1 / 0.0254 = 39.3701) then multiply dimensions.x and dimensions.y by that value * you get the resolution in pixels. - * @property {string} scriptURL="" - The URL of a JavaScript file to inject into the Web page. - * @property {number} maxFPS=10 - The maximum update rate for the Web content, in frames/second. + * @property {string} scriptURL="" - The URL of a JavaScript file to inject into the web page. + * @property {number} maxFPS=10 - The maximum update rate for the web content, in frames/second. * @property {WebInputMode} inputMode="touch" - The user input mode to use. - * @property {Entities.Pulse} pulse - The pulse-related properties. Deprecated. - * @property {boolean} showKeyboardFocusHighlight - Whether or not to show the keyboard focus highlight when this entity has focus. + * @property {boolean} showKeyboardFocusHighlight=true - true to highlight the entity when it has keyboard focus, + * false to not display the highlight. * @example Create a Web entity displaying at 1920 x 1080 resolution. * var METERS_TO_INCHES = 39.3701; * var entity = Entities.addEntity({ @@ -1303,74 +1352,54 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { /**jsdoc * The "Zone" {@link Entities.EntityType|EntityType} is a volume of lighting effects and avatar permissions. - * Avatar interaction events such as {@link Entities.enterEntity} are also often used with a Zone entity. - * It has properties in addition to the common {@link Entities.EntityProperties|EntityProperties}. - * @typedef {object} Entities.EntityProperties-Zone - * @property {Vec3} dimensions=0.1,0.1,0.1 - The size of the volume in which the zone's lighting effects and avatar permissions - * have effect. + * Avatar interaction events such as {@link Entities.enterEntity} are also often used with a Zone entity. It has properties in + * addition to the common {@link Entities.EntityProperties|EntityProperties}. * - * @property {ShapeType} shapeType="box" - The shape of the volume in which the zone's lighting effects and avatar + * @typedef {object} Entities.EntityProperties-Zone + * @property {Vec3} dimensions=0.1,0.1,0.1 - The dimensions of the volume in which the zone's lighting effects and avatar + * permissions have effect. + * + * @property {ShapeType} shapeType="box" - The shape of the volume in which the zone's lighting effects and avatar * permissions have effect. Reverts to the default value if set to "none", or set to "compound" * and compoundShapeURL is "". * @property {string} compoundShapeURL="" - The model file to use for the compound shape if shapeType is * "compound". * - * @property {string} keyLightMode="inherit" - Configures the key light in the zone. Possible values:
- * "inherit": The key light from any enclosing zone continues into this zone.
- * "disabled": The key light from any enclosing zone and the key light of this zone are disabled in this - * zone.
- * "enabled": The key light properties of this zone are enabled, overriding the key light of from any - * enclosing zone. + * @property {Entities.ComponentMode} keyLightMode="inherit" - Configures the key light in the zone. * @property {Entities.KeyLight} keyLight - The key light properties of the zone. * - * @property {string} ambientLightMode="inherit" - Configures the ambient light in the zone. Possible values:
- * "inherit": The ambient light from any enclosing zone continues into this zone.
- * "disabled": The ambient light from any enclosing zone and the ambient light of this zone are disabled in - * this zone.
- * "enabled": The ambient light properties of this zone are enabled, overriding the ambient light from any - * enclosing zone. + * @property {Entities.ComponentMode} ambientLightMode="inherit" - Configures the ambient light in the zone. * @property {Entities.AmbientLight} ambientLight - The ambient light properties of the zone. * - * @property {string} skyboxMode="inherit" - Configures the skybox displayed in the zone. Possible values:
- * "inherit": The skybox from any enclosing zone is dislayed in this zone.
- * "disabled": The skybox from any enclosing zone and the skybox of this zone are disabled in this zone.
- * "enabled": The skybox properties of this zone are enabled, overriding the skybox from any enclosing zone. + * @property {Entities.ComponentMode} skyboxMode="inherit" - Configures the skybox displayed in the zone. * @property {Entities.Skybox} skybox - The skybox properties of the zone. * - * @property {string} hazeMode="inherit" - Configures the haze in the zone. Possible values:
- * "inherit": The haze from any enclosing zone continues into this zone.
- * "disabled": The haze from any enclosing zone and the haze of this zone are disabled in this zone.
- * "enabled": The haze properties of this zone are enabled, overriding the haze from any enclosing zone. + * @property {Entities.ComponentMode} hazeMode="inherit" - Configures the haze in the zone. * @property {Entities.Haze} haze - The haze properties of the zone. * - * @property {string} bloomMode="inherit" - Configures the bloom in the zone. Possible values:
- * "inherit": The bloom from any enclosing zone continues into this zone.
- * "disabled": The bloom from any enclosing zone and the bloom of this zone are disabled in this zone.
- * "enabled": The bloom properties of this zone are enabled, overriding the bloom from any enclosing zone. + * @property {Entities.ComponentMode} bloomMode="inherit" - Configures the bloom in the zone. * @property {Entities.Bloom} bloom - The bloom properties of the zone. * - * @property {boolean} flyingAllowed=true - If true then visitors can fly in the zone; otherwise they cannot. - * Only works on domain entities. + * @property {boolean} flyingAllowed=true - If true then visitors can fly in the zone; otherwise, they cannot. + * Only works for domain entities. * @property {boolean} ghostingAllowed=true - If true then visitors with avatar collisions turned off will not - * collide with content in the zone; otherwise visitors will always collide with content in the zone. Only works on domain entities. + * collide with content in the zone; otherwise, visitors will always collide with content in the zone. Only works for domain + * entities. * @property {string} filterURL="" - The URL of a JavaScript file that filters changes to properties of entities within the * zone. It is periodically executed for each entity in the zone. It can, for example, be used to not allow changes to - * certain properties.
- * - * @property {string} avatarPriority="inherit" - Configures the update priority of contained avatars to other clients.
- * "inherit": Priority from enclosing zones is unchanged.
- * "crowd": Priority in this zone is the normal priority.
- * "hero": Avatars in this zone will have an increased update priority + * certain properties: *
- *
  * function filter(properties) {
- *     // Test and edit properties object values,
+ *     // Check and edit properties object values,
  *     // e.g., properties.modelURL, as required.
  *     return properties;
  * }
  * 
* + * @property {Entities.AvatarPriorityMode} avatarPriority="inherit" - Configures the priority of updates from avatars in the + * zone to other clients. + * * @example Create a zone that casts a red key light along the x-axis. * var zone = Entities.addEntity({ * type: "Zone", @@ -1388,20 +1417,30 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { /**jsdoc * The "Image" {@link Entities.EntityType|EntityType} displays an image on a 2D rectangle in the domain. * It has properties in addition to the common {@link Entities.EntityProperties|EntityProperties}. + * * @typedef {object} Entities.EntityProperties-Image + * @property {Vec3} dimensions=0.1,0.1,0.01 - The dimensions of the entity. * @property {string} imageURL="" - The URL of the image to use. - * @property {boolean} emissive=false - Whether or not the image should be emissive (unlit). - * @property {boolean} keepAspectRatio=true - Whether or not the image should maintain its aspect ratio. - * @property {BillboardMode} billboardMode="none" - If "none", the entity is not billboarded. If "yaw", the entity will be - * oriented to follow your camera around the y-axis. If "full" the entity will be oriented to face your camera. The following deprecated - * behavior is also supported: you can also set "faceCamera" to true to set billboardMode to "yaw", and you can set - * "isFacingAvatar" to true to set billboardMode to "full". Setting either to false sets the mode to "none" - * @property {Rect} subImage={ x: 0, y: 0, width: -1, height: -1 } - The portion of the image to display. If width or height are -1, defaults to - * the full image in that dimension. + * @property {boolean} emissive=false - true fi the the image should be emissive (unlit), false if it + * shouldn't. + * @property {boolean} keepAspectRatio=true - true if the image should maintain its aspect ratio, + * false if it shouldn't. + * @property {Rect} subImage=0,0,0,0 - The portion of the image to display. If width or height are 0, it defaults + * to the full image in that dimension. * @property {Color} color=255,255,255 - The color of the image. - * @property {number} alpha=1 - The alpha of the image. - * @property {Entities.Pulse} pulse - The pulse-related properties. Deprecated. - * @example Create a image entity. + * @property {number} alpha=1 - The opacity of the image. + * @property {Entities.Pulse} pulse - Color and alpha pulse. + *

Deprecated: This property is deprecated and will be removed.

+ * @property {BillboardMode} billboardMode="none" - Whether the entity is billboarded to face the camera. + * @property {boolean} faceCamera - true if billboardMode is "yaw", otherwise + * false. Setting this property to false sets the billboardMode to + * "none". + *

Deprecated: This property is deprecated and will be removed.

+ * @property {boolean} isFacingAvatar - true if billboardMode is "full", otherwise + * false. Setting this property to false sets the billboardMode to + * "none". + *

Deprecated: This property is deprecated and will be removed.

+ * @example Create an image entity. * var image = Entities.addEntity({ * type: "Image", * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -5 })), @@ -1415,16 +1454,19 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { /**jsdoc * The "Grid" {@link Entities.EntityType|EntityType} displays a grid on a 2D plane. * It has properties in addition to the common {@link Entities.EntityProperties|EntityProperties}. + * * @typedef {object} Entities.EntityProperties-Grid + * @property {Vec3} dimensions - 0.1,0.1,0.01 - The dimensions of the entity. * @property {Color} color=255,255,255 - The color of the grid. - * @property {number} alpha=1 - The alpha of the grid. + * @property {number} alpha=1 - The opacity of the grid. + * @property {Entities.Pulse} pulse - Color and alpha pulse. + *

Deprecated: This property is deprecated and will be removed.

* @property {boolean} followCamera=true - If true, the grid is always visible even as the camera moves to another * position. * @property {number} majorGridEvery=5 - Integer number of minorGridEvery intervals at which to draw a thick grid * line. Minimum value = 1. * @property {number} minorGridEvery=1 - Real number of meters at which to draw thin grid lines. Minimum value = * 0.001. - * @property {Entities.Pulse} pulse - The pulse-related properties. Deprecated. * @example Create a grid entity. * var grid = Entities.addEntity({ * type: "Grid", @@ -1440,8 +1482,10 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { /**jsdoc * The "Gizmo" {@link Entities.EntityType|EntityType} displays an entity that could be used as UI. * It has properties in addition to the common {@link Entities.EntityProperties|EntityProperties}. + * * @typedef {object} Entities.EntityProperties-Gizmo - * @property {GizmoType} gizmoType="ring" - The gizmo type of the entity. + * @property {Vec3} dimensions=0.1,0.001,0.1 - The dimensions of the entity. + * @property {Entities.GizmoType} gizmoType="ring" - The gizmo type of the entity. * @property {Entities.RingGizmo} ring - The ring gizmo properties. */ @@ -1850,7 +1894,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool * @typedef {object} Entities.RenderInfo * @property {number} verticesCount - The number of vertices in the entity. * @property {number} texturesCount - The number of textures in the entity. - * @property {number} textureSize - The total size of the textures in the entity, in bytes. + * @property {number} texturesSize - The total size of the textures in the entity, in bytes. * @property {boolean} hasTransparent - Is true if any of the textures has transparency. * @property {number} drawCalls - The number of draw calls required to render the entity. */ @@ -2832,6 +2876,13 @@ bool EntityItemProperties::getPropertyInfo(const QString& propertyName, EntityPr return false; } +/**jsdoc + * Information about an entity property. + * @typedef {object} Entities.EntityPropertyInfo + * @property {number} propertyEnum - The internal number of the property. + * @property {string} minimum - The minimum numerical value the property may have, if available, otherwise "". + * @property {string} maximum - The maximum numerical value the property may have, if available, otherwise "". + */ QScriptValue EntityPropertyInfoToScriptValue(QScriptEngine* engine, const EntityPropertyInfo& propertyInfo) { QScriptValue obj = engine->newObject(); obj.setProperty("propertyEnum", propertyInfo.propertyEnum); diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h index c3b0371029..0666bb98df 100644 --- a/libraries/entities/src/EntityScriptingInterface.h +++ b/libraries/entities/src/EntityScriptingInterface.h @@ -57,7 +57,7 @@ private: }; /**jsdoc - * The result of a {@link PickRay} search using {@link Entities.findRayIntersection|findRayIntersection}. + * The result of a {@link Entities.findRayIntersection|findRayIntersection} search using a {@link PickRay}. * @typedef {object} Entities.RayToEntityIntersectionResult * @property {boolean} intersects - true if the {@link PickRay} intersected an entity, otherwise * false. @@ -101,9 +101,43 @@ public: }; /**jsdoc - * The Entities API provides facilities to create and interact with entities. Entities are 2D and 3D objects that are visible - * to everyone and typically are persisted to the domain. For Interface scripts, the entities available are those that - * Interface has displayed and so knows about. + * The Entities API provides facilities to create and interact with entities. Entities are 2D or 3D objects + * displayed in-world. Depending on their {@link Entities.EntityHostType|EntityHostType}, they may persist in the domain as + * "domain" entities, travel to different domains with a user as "avatar" entities, or be visible only to an individual user as + * "local" entities (a.k.a. "overlays"). + * + *

Note: For Interface scripts, the entities available to scripts are those that Interface has displayed and so knows + * about.

+ * + *

Entity Methods

+ * + *

Some of the API's signals correspond to entity methods that are called, if present, in the entity being interacted with. + * The client or server entity script must expose them as a property. However, unlike {@link Entities.callEntityMethod}, server + * entity scripts do not need to list them in an remotelyCallable property. The entity methods are called with + * parameters per their corresponding signal.

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Method NameCorresponding Signal
clickDownOnEntity{@link Entities.clickDownOnEntity}
clickReleaseOnEntity{@link Entities.clickReleaseOnEntity}
collisionWithEntity{@link Entities.collisionWithEntity}
enterEntity{@link Entities.enterEntity}
holdingClickOnEntity{@link Entities.holdingClickOnEntity}
hoverEnterEntity{@link Entities.hoverEnterEntity}
hoverLeaveEntity{@link Entities.hoverLeaveEntity}
hoverOverEntity{@link Entities.hoverOverEntity}
leaveEntity{@link Entities.leaveEntity}
mouseDoublePressOnEntity{@link Entities.mouseDoublePressOnEntity}
mouseMoveOnEntity{@link Entities.mouseMoveOnEntity}
mouseMoveEventDeprecated: This is a synonym for + * mouseMoveOnEntity.
mousePressOnEntity{@link Entities.mousePressOnEntity}
mouseReleaseOnEntity{@link Entities.mouseReleaseOnEntity}
+ *

See {@link Entities.clickDownOnEntity} for an example.

* * @namespace Entities * @@ -113,8 +147,8 @@ public: * @hifi-server-entity * @hifi-assignment-client * - * @property {Uuid} keyboardFocusEntity - Get or set the {@link Entities.EntityType|Web} entity that has keyboard focus. - * If no entity has keyboard focus, get returns null; set to null or {@link Uuid(0)|Uuid.NULL} to + * @property {Uuid} keyboardFocusEntity - The {@link Entities.EntityProperties-Web|Web} entity that has keyboard focus. If no + * Web entity has keyboard focus, get returns null; set to null or {@link Uuid(0)|Uuid.NULL} to * clear keyboard focus. */ /// handles scripting of Entity commands from JS passed to assigned clients @@ -150,17 +184,20 @@ public: const QVector& entityIdsToInclude, const QVector& entityIdsToDiscard); /**jsdoc - * Get the properties of multiple entities. - * @function Entities.getMultipleEntityProperties - * @param {Uuid[]} entityIDs - The IDs of the entities to get the properties of. - * @param {string[]|string} [desiredProperties=[]] - Either string with property name or array of the names of the properties - * to get. If the array is empty, all properties are returned. - * @returns {Entities.EntityProperties[]} The properties of the entity if the entity can be found, otherwise an empty object. - * @example Retrieve the names of the nearby entities - * var SEARCH_RADIUS = 50; // meters - * var entityIds = Entities.findEntities(MyAvatar.position, SEARCH_RADIUS); - * var propertySets = Entities.getMultipleEntityProperties(entityIds, "name"); - * print("Nearby entity names: " + JSON.stringify(propertySets)); + * Gets the properties of multiple entities. + * @function Entities.getMultipleEntityProperties + * @param {Uuid[]} entityIDs - The IDs of the entities to get the properties of. + * @param {string[]|string} [desiredProperties=[]] - The name or names of the properties to get. For properties that are + * objects (e.g., the "keyLight" property), use the property and subproperty names in dot notation (e.g., + * "keyLight.color"). + * @returns {Entities.EntityProperties[]} The specified properties of each entity for each entity that can be found. If + * none of the entities can be found then an empty array. If no properties are specified, then all properties are + * returned. + * @example Retrieve the names of the nearby entities + * var SEARCH_RADIUS = 50; // meters + * var entityIDs = Entities.findEntities(MyAvatar.position, SEARCH_RADIUS); + * var propertySets = Entities.getMultipleEntityProperties(entityIDs, "name"); + * print("Nearby entity names: " + JSON.stringify(propertySets)); */ static QScriptValue getMultipleEntityProperties(QScriptContext* context, QScriptEngine* engine); QScriptValue getMultipleEntityPropertiesInternal(QScriptEngine* engine, QVector entityIDs, const QScriptValue& extendedDesiredProperties); @@ -170,13 +207,12 @@ public: public slots: /**jsdoc - * Check whether or not you can change the locked property of entities. Locked entities have their - * locked property set to true and cannot be edited or deleted. Whether or not you can change - * entities' locked properties is configured in the domain server's permissions. + * Checks whether or not the script can change the locked property of entities. Locked entities have their + * locked property set to true and cannot be edited or deleted. * @function Entities.canAdjustLocks - * @returns {boolean} true if the client can change the locked property of entities, - * otherwise false. - * @example Set an entity's locked property to true if you can. + * @returns {boolean} true if the domain server will allow the script to change the locked + * property of entities, otherwise false. + * @example Lock an entity if you can. * if (Entities.canAdjustLocks()) { * Entities.editEntity(entityID, { locked: true }); * } else { @@ -186,42 +222,43 @@ public slots: Q_INVOKABLE bool canAdjustLocks(); /**jsdoc - * Check whether or not you can rez (create) new entities in the domain. + * Checks whether or not the script can rez (create) new entities in the domain. * @function Entities.canRez - * @returns {boolean} true if the domain server will allow the script to rez (create) new entities, - * otherwise false. + * @returns {boolean} true if the domain server will allow the script to rez (create) new entities, otherwise + * false. */ Q_INVOKABLE bool canRez(); /**jsdoc - * Check whether or not you can rez (create) new temporary entities in the domain. Temporary entities are entities with a - * finite lifetime property value set. + * Checks whether or not the script can rez (create) new temporary entities in the domain. Temporary entities are entities + * with a finite lifetime property value set. * @function Entities.canRezTmp - * @returns {boolean} true if the domain server will allow the script to rez (create) new temporary - * entities, otherwise false. + * @returns {boolean} true if the domain server will allow the script to rez (create) new temporary entities, + * otherwise false. */ Q_INVOKABLE bool canRezTmp(); /**jsdoc - * Check whether or not you can rez (create) new certified entities in the domain. Certified entities are entities that have - * PoP certificates. + * Checks whether or not the script can rez (create) new certified entities in the domain. Certified entities are entities + * that have PoP certificates. * @function Entities.canRezCertified - * @returns {boolean} true if the domain server will allow the script to rez (create) new certified - * entities, otherwise false. + * @returns {boolean} true if the domain server will allow the script to rez (create) new certified entities, + * otherwise false. */ Q_INVOKABLE bool canRezCertified(); /**jsdoc - * Check whether or not you can rez (create) new temporary certified entities in the domain. Temporary entities are entities - * with a finite lifetime property value set. Certified entities are entities that have PoP certificates. + * Checks whether or not the script can rez (create) new temporary certified entities in the domain. Temporary entities are + * entities with a finite lifetime property value set. Certified entities are entities that have PoP + * certificates. * @function Entities.canRezTmpCertified - * @returns {boolean} true if the domain server will allow the script to rez (create) new temporary - * certified entities, otherwise false. + * @returns {boolean} true if the domain server will allow the script to rez (create) new temporary certified + * entities, otherwise false. */ Q_INVOKABLE bool canRezTmpCertified(); /**jsdoc - * Check whether or not you can make changes to the asset server's assets. + * Checks whether or not the script can make changes to the asset server's assets. * @function Entities.canWriteAssets * @returns {boolean} true if the domain server will allow the script to make changes to the asset server's * assets, otherwise false. @@ -229,7 +266,7 @@ public slots: Q_INVOKABLE bool canWriteAssets(); /**jsdoc - * Check whether or not you can replace the domain's content set. + * Checks whether or not the script can replace the domain's content set. * @function Entities.canReplaceContent * @returns {boolean} true if the domain server will allow the script to replace the domain's content set, * otherwise false. @@ -237,43 +274,48 @@ public slots: Q_INVOKABLE bool canReplaceContent(); /**jsdoc - * Check whether or not you can get and set private user data. + * Checks whether or not the script can get and set the privateUserData property of entities. * @function Entities.canGetAndSetPrivateUserData - * @returns {boolean} true if the domain server will allow the user to get and set private user data, - * otherwise false. + * @returns {boolean} true if the domain server will allow the script to get and set the + * privateUserData property of entities, otherwise false. */ Q_INVOKABLE bool canGetAndSetPrivateUserData(); /**jsdoc - *

How an entity is sent over the wire.

+ *

How an entity is hosted and sent to others for display.

* * * * * - * - * - * + * + * + * * *
ValueDescription
domainDomain entities are sent over the entity server to everyone else
avatarAvatar entities are sent over the avatar entity and are associated with one avatar
localLocal entities are not sent over the wire and will only render for you, locally
"domain"Domain entities are stored on the domain, are visible to everyone, and are + * sent to everyone by the entity server.
"avatar"Avatar entities are stored on an Interface client, are visible to everyone, + * and are sent to everyone by the avatar mixer. They follow the client to each domain visited, displaying at the + * same domain coordinates unless parented to the client's avatar.
"local"Local entities are ephemeral — they aren't stored anywhere — and + * are visible only to the client. They follow the client to each domain visited, displaying at the same domain + * coordinates unless parented to the client's avatar. Additionally, local entities are always + * collisionless.
- * @typedef {string} EntityHostType + * @typedef {string} Entities.EntityHostType */ /**jsdoc - * Add a new entity with specified properties. + * Adds a new domain, avatar, or local entity. * @function Entities.addEntity * @param {Entities.EntityProperties} properties - The properties of the entity to create. - * @param {EntityHostType} [entityHostType="domain"] - If "avatar" the entity is created as an avatar entity. An avatar entity - * follows you to each domain you visit, rendering at the same world coordinates unless it's parented to your avatar. - * If "local", the entity is created as a local entity, which will only render for you and isn't sent over the wire. - * Otherwise it is created as a normal entity and sent over the entity server. + * @param {Entities.EntityHostType} [entityHostType="domain"] - The type of entity to create. + * @returns {Uuid} The ID of the entity if successfully created, otherwise {@link Uuid(0)|Uuid.NULL}. - * @example Create a box entity in front of your avatar. + * @example Create a box domain entity in front of your avatar. * var entityID = Entities.addEntity({ * type: "Box", * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -5 })), * rotation: MyAvatar.orientation, - * dimensions: { x: 0.5, y: 0.5, z: 0.5 } + * dimensions: { x: 0.5, y: 0.5, z: 0.5 }, + * lifetime: 300 // Delete after 5 minutes. * }); * print("Entity created: " + entityID); */ @@ -290,10 +332,13 @@ public slots: } /**jsdoc - * Add a new entity with specified properties. + * Adds a new avatar entity ({@link Entities.EntityProperties|entityHostType} property is + * "avatar") or domain entity ({@link Entities.EntityProperties|entityHostType} property is + * "domain"). * @function Entities.addEntity * @param {Entities.EntityProperties} properties - The properties of the entity to create. - * @param {boolean} [avatarEntity=false] - Whether to create an avatar entity or a domain entity + * @param {boolean} [avatarEntity=false] - true to create an avatar entity, false to create a + * domain entity. * @returns {Uuid} The ID of the entity if successfully created, otherwise {@link Uuid(0)|Uuid.NULL}. */ Q_INVOKABLE QUuid addEntity(const EntityItemProperties& properties, bool avatarEntity = false) { @@ -303,13 +348,18 @@ public slots: /// temporary method until addEntity can be used from QJSEngine /// Deliberately not adding jsdoc, only used internally. + // FIXME: Deprecate and remove from the API. Q_INVOKABLE QUuid addModelEntity(const QString& name, const QString& modelUrl, const QString& textures, const QString& shapeType, bool dynamic, bool collisionless, bool grabbable, const glm::vec3& position, const glm::vec3& gravity); /**jsdoc - * Create a clone of an entity. A clone can be created by a client that doesn't have rez permissions in the current domain. - * The entity must have its cloneable property set to true. The clone has a modified name, other - * properties set per its clone related-properties, and its clone-related properties are set to defaults. + * Creates a clone of an entity. The clone has a modified name property, other properties set per the original + * entity's clone-related {@link Entities.EntityProperties|properties} (e.g., cloneLifetime), and + * clone-related properties set to defaults. + *

Domain entities must have their cloneable property value be true in order to be cloned. A + * domain entity can be cloned by a client that doesn't have rez permissions in the domain.

+ *

Avatar entities must have their cloneable and cloneAvatarEntity property values be + * true in order to be cloned.

* @function Entities.cloneEntity * @param {Uuid} entityID - The ID of the entity to clone. * @returns {Uuid} The ID of the new entity if successfully cloned, otherwise {@link Uuid(0)|Uuid.NULL}. @@ -317,18 +367,21 @@ public slots: Q_INVOKABLE QUuid cloneEntity(const QUuid& entityID); /**jsdoc - * Get the properties of an entity. + * Gets an entity's property values. * @function Entities.getEntityProperties * @param {Uuid} entityID - The ID of the entity to get the properties of. - * @param {string[]} [desiredProperties=[]] - Array of the names of the properties to get. If the array is empty, - * all properties are returned. - * @returns {Entities.EntityProperties} The properties of the entity if the entity can be found, otherwise an empty object. + * @param {string|string[]} [desiredProperties=[]] - The name or names of the properties to get. For properties that are + * objects (e.g., the "keyLight" property), use the property and subproperty names in dot notation (e.g., + * "keyLight.color"). + * @returns {Entities.EntityProperties} The specified properties of the entity if the entity can be found, otherwise an + * empty object. If no properties are specified, then all properties are returned. * @example Report the color of a new box entity. * var entityID = Entities.addEntity({ * type: "Box", * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -5 })), * rotation: MyAvatar.orientation, - * dimensions: { x: 0.5, y: 0.5, z: 0.5 } + * dimensions: { x: 0.5, y: 0.5, z: 0.5 }, + * lifetime: 300 // Delete after 5 minutes. * }); * var properties = Entities.getEntityProperties(entityID, ["color"]); * print("Entity color: " + JSON.stringify(properties.color)); @@ -337,31 +390,34 @@ public slots: Q_INVOKABLE EntityItemProperties getEntityProperties(const QUuid& entityID, EntityPropertyFlags desiredProperties); /**jsdoc - * Update an entity with specified properties. + * Edits an entity, changing one or more of its property values. * @function Entities.editEntity * @param {Uuid} entityID - The ID of the entity to edit. - * @param {Entities.EntityProperties} properties - The properties to update the entity with. - * @returns {Uuid} The ID of the entity if the edit was successful, otherwise null. + * @param {Entities.EntityProperties} properties - The new property values. + * @returns {Uuid} The ID of the entity if the edit was successful, otherwise null or {@link Uuid|Uuid.NULL}. * @example Change the color of an entity. * var entityID = Entities.addEntity({ * type: "Box", * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -5 })), * rotation: MyAvatar.orientation, - * dimensions: { x: 0.5, y: 0.5, z: 0.5 } + * dimensions: { x: 0.5, y: 0.5, z: 0.5 }, + * lifetime: 300 // Delete after 5 minutes. * }); * var properties = Entities.getEntityProperties(entityID, ["color"]); * print("Entity color: " + JSON.stringify(properties.color)); * - * Entities.editEntity(entityID, { - * color: { red: 255, green: 0, blue: 0 } - * }); - * properties = Entities.getEntityProperties(entityID, ["color"]); - * print("Entity color: " + JSON.stringify(properties.color)); + * Script.setTimeout(function () { // Wait for the entity to be created before editing. + * Entities.editEntity(entityID, { + * color: { red: 255, green: 0, blue: 0 } + * }); + * properties = Entities.getEntityProperties(entityID, ["color"]); + * print("Entity color: " + JSON.stringify(properties.color)); + * }, 50); */ Q_INVOKABLE QUuid editEntity(const QUuid& entityID, const EntityItemProperties& properties); /**jsdoc - * Delete an entity. + * Deletes an entity. * @function Entities.deleteEntity * @param {Uuid} entityID - The ID of the entity to delete. * @example Delete an entity a few seconds after creating it. @@ -379,23 +435,79 @@ public slots: Q_INVOKABLE void deleteEntity(const QUuid& entityID); /**jsdoc - * Get an entities type as a string. - * @function Entities.deleteEntity - * @param {Uuid} id - The id of the entity to get the type of. + * Gets an entity's type. + * @function Entities.getEntityType + * @param {Uuid} id - The ID of the entity to get the type of. + * @returns {Entities.EntityType} The type of the entity. */ Q_INVOKABLE QString getEntityType(const QUuid& entityID); /**jsdoc - * Get the entity script object. In particular, this is useful for accessing the event bridge for a Web - * entity. + * Gets an entity's script object. In particular, this is useful for accessing a {@link Entities.EntityProperties-Web|Web} + * entity's HTML EventBridge script object to exchange messages with the web page script. + *

Alternatively, you can use {@link Entities.emitScriptEvent} and {@link Entities.webEventReceived} to exchange + * messages with a Web entity over its event bridge.

* @function Entities.getEntityObject - * @param {Uuid} id - The ID of the entity to get the script object of. + * @param {Uuid} id - The ID of the entity to get the script object for. * @returns {object} The script object for the entity if found. + * @example Exchange messages with a Web entity. + * // HTML file, name: "webEntity.html". + * + * + * + * HELLO + * + * + *

HELLO

+ * + * + * + * + * // Script file. + * var webEntity = Entities.addEntity({ + * type: "Web", + * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0.5, z: -3 })), + * rotation: MyAvatar.orientation, + * sourceUrl: Script.resolvePath("webEntity.html"), + * alpha: 1.0, + * lifetime: 300 // 5 min + * }); + * + * var webEntityObject; + * + * function onWebEventReceived(message) { + * // Message received. + * print("Message received: " + message); + * + * // Send a message back. + * webEntityObject.emitScriptEvent(message + " back"); + * } + * + * Script.setTimeout(function () { + * webEntityObject = Entities.getEntityObject(webEntity); + * webEntityObject.webEventReceived.connect(onWebEventReceived); + * }, 500); + * + * Script.scriptEnding.connect(function () { + * Entities.deleteEntity(webEntity); + * }); */ Q_INVOKABLE QObject* getEntityObject(const QUuid& id); /**jsdoc - * Check whether an entities's assets have been loaded. For example, for an Model entity the result indicates + * Checks whether an entities's assets have been loaded. For example, for an Model entity the result indicates * whether its textures have been loaded. * @function Entities.isLoaded * @param {Uuid} id - The ID of the entity to check. @@ -404,15 +516,15 @@ public slots: Q_INVOKABLE bool isLoaded(const QUuid& id); /**jsdoc - * Check if there is an object of a given ID. + * Checks if there is an entity with a specified ID. * @function Entities.isAddedEntity * @param {Uuid} id - The ID to check. - * @returns {boolean} true if an object with the given ID exists, false otherwise. + * @returns {boolean} true if an entity with the specified ID exists, false if it doesn't. */ Q_INVOKABLE bool isAddedEntity(const QUuid& id); /**jsdoc - * Calculates the size of the given text in the specified object if it is a text entity. + * Calculates the size of some text in a text entity. * @function Entities.textSize * @param {Uuid} id - The ID of the entity to use for calculation. * @param {string} text - The string to calculate the size of. @@ -422,47 +534,151 @@ public slots: Q_INVOKABLE QSizeF textSize(const QUuid& id, const QString& text); /**jsdoc - * Call a method in a client entity script from a client script or client entity script, or call a method in a server - * entity script from a server entity script. The entity script method must be exposed as a property in the target client + * Calls a method in a client entity script from an Interface, avatar, or client entity script, or calls a method in a + * server entity script from a server entity script. The entity script method must be exposed as a property in the target * entity script. Additionally, if calling a server entity script, the server entity script must include the method's name * in an exposed property called remotelyCallable that is an array of method names that can be called. * @function Entities.callEntityMethod * @param {Uuid} entityID - The ID of the entity to call the method in. - * @param {string} method - The name of the method to call. - * @param {string[]} [parameters=[]] - The parameters to call the specified method with. + * @param {string} method - The name of the method to call. The method is called with the entity ID as the first parameter + * and the parameters value as the second parameter. + * @param {string[]} [parameters=[]] - The additional parameters to call the specified method with. + * @example Call a method in a client entity script from an Interface script. + * // Client entity script. + * var entityScript = (function () { + * this.entityMethod = function (id, params) { + * print("Method at entity : " + id + " ; " + params[0] + ", " + params[1]); + * }; + * }); + * + * // Entity that hosts the client entity script. + * var entityID = Entities.addEntity({ + * type: "Box", + * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -5 })), + * dimensions: { x: 0.5, y: 0.5, z: 0.5 }, + * script: "(" + entityScript + ")", // Could host the script on a Web server instead. + * lifetime: 300 // Delete after 5 minutes. + * }); + * + * // Interface script call to the client entity script. + * Script.setTimeout(function () { + * Entities.callEntityMethod(entityID, "entityMethod", ["hello", 12]); + * }, 1000); // Wait for the entity to be created. */ Q_INVOKABLE void callEntityMethod(const QUuid& entityID, const QString& method, const QStringList& params = QStringList()); /**jsdoc - * Call a method in a server entity script from a client script or client entity script. The entity script method must be - * exposed as a property in the target server entity script. Additionally, the target server entity script must include the - * method's name in an exposed property called remotelyCallable that is an array of method names that can be - * called. + * Calls a method in a server entity script from an Interface, avatar, or client entity script. The server entity script + * method must be exposed as a property in the target server entity script. Additionally, the server entity script must + * include the method's name in an exposed property called remotelyCallable that is an array of method names + * that can be called. * @function Entities.callEntityServerMethod * @param {Uuid} entityID - The ID of the entity to call the method in. - * @param {string} method - The name of the method to call. - * @param {string[]} [parameters=[]] - The parameters to call the specified method with. + * @param {string} method - The name of the method to call. The method is called with the entity ID as the first parameter + * and the parameters value as the second parameter. + * @param {string[]} [parameters=[]] - The additional parameters to call the specified method with. + * @example Call a method in a server entity script from an Interface script. + * // Server entity script. + * var entityScript = (function () { + * this.entityMethod = function (id, params) { + * print("Method at entity : " + id + " ; " + params[0] + ", " + params[1]); // In server log. + * }; + * this.remotelyCallable = [ + * "entityMethod" + * ]; + * }); + * + * // Entity that hosts the server entity script. + * var entityID = Entities.addEntity({ + * type: "Box", + * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -5 })), + * dimensions: { x: 0.5, y: 0.5, z: 0.5 }, + * serverScripts: "(" + entityScript + ")", // Could host the script on a Web server instead. + * lifetime: 300 // Delete after 5 minutes. + * }); + * + * // Interface script call to the server entity script. + * Script.setTimeout(function () { + * Entities.callEntityServerMethod(entityID, "entityMethod", ["hello", 12]); + * }, 1000); // Wait for the entity to be created. */ Q_INVOKABLE void callEntityServerMethod(const QUuid& entityID, const QString& method, const QStringList& params = QStringList()); /**jsdoc - * Call a method in a specific user's client entity script from a server entity script. The entity script method must be - * exposed as a property in the target client entity script. + * Calls a method in a specific user's client entity script from a server entity script. The entity script method must be + * exposed as a property in the target client entity script. Additionally, the client entity script must + * include the method's name in an exposed property called remotelyCallable that is an array of method names + * that can be called. * @function Entities.callEntityClientMethod * @param {Uuid} clientSessionID - The session ID of the user to call the method in. * @param {Uuid} entityID - The ID of the entity to call the method in. - * @param {string} method - The name of the method to call. - * @param {string[]} [parameters=[]] - The parameters to call the specified method with. + * @param {string} method - The name of the method to call. The method is called with the entity ID as the first parameter + * and the parameters value as the second parameter. + * @param {string[]} [parameters=[]] - The additional parameters to call the specified method with. + * @example Call a method in a client entity script from a server entity script. + * // Client entity script. + * var clientEntityScript = (function () { + * this.entityMethod = function (id, params) { + * print("Method at client entity : " + id + " ; " + params[0] + ", " + params[1]); + * }; + * this.remotelyCallable = [ + * "entityMethod" + * ]; + * }); + * + * // Server entity script. + * var serverEntityScript = (function () { + * var clientSessionID, + * clientEntityID; + * + * function callClientMethod() { + * // Server entity script call to client entity script. + * Entities.callEntityClientMethod(clientSessionID, clientEntityID, "entityMethod", ["hello", 12]); + * } + * + * // Obtain client entity details then call client entity method. + * this.entityMethod = function (id, params) { + * clientSessionID = params[0]; + * clientEntityID = params[1]; + * callClientMethod(); + * }; + * this.remotelyCallable = [ + * "entityMethod" + * ]; + * }); + * + * // Entity that hosts the client entity script. + * var clientEntityID = Entities.addEntity({ + * type: "Box", + * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: -1, y: 0, z: -5 })), + * dimensions: { x: 0.5, y: 0.5, z: 0.5 }, + * script: "(" + clientEntityScript + ")", // Could host the script on a Web server instead. + * lifetime: 300 // Delete after 5 minutes. + * }); + * + * // Entity that hosts the server entity script. + * var serverEntityID = Entities.addEntity({ + * type: "Box", + * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 1, y: 0, z: -5 })), + * dimensions: { x: 0.5, y: 0.5, z: 0.5 }, + * serverScripts: "(" + serverEntityScript + ")", // Could host the script on a Web server instead. + * lifetime: 300 // Delete after 5 minutes. + * }); + * + * // Interface script call to the server entity script. + * Script.setTimeout(function () { + * Entities.callEntityServerMethod(serverEntityID, "entityMethod", [MyAvatar.sessionUUID, clientEntityID]); + * }, 1000); // Wait for the entities to be created. */ Q_INVOKABLE void callEntityClientMethod(const QUuid& clientSessionID, const QUuid& entityID, const QString& method, const QStringList& params = QStringList()); /**jsdoc - * Find the non-local entity with a position closest to a specified point and within a specified radius. + * Finds the domain or avatar entity with a position closest to a specified point and within a specified radius. * @function Entities.findClosestEntity * @param {Vec3} center - The point about which to search. * @param {number} radius - The radius within which to search. - * @returns {Uuid} The ID of the entity that is closest to the center and within the radius if + * @returns {Uuid} The ID of the entity that is closest to the center and within the radius, if * there is one, otherwise null. * @example Find the closest entity within 10m of your avatar. * var entityID = Entities.findClosestEntity(MyAvatar.position, 10); @@ -472,12 +688,12 @@ public slots: Q_INVOKABLE QUuid findClosestEntity(const glm::vec3& center, float radius) const; /**jsdoc - * Find all non-local entities that intersect a sphere defined by a center point and radius. + * Finds all domain and avatar entities that intersect a sphere. * @function Entities.findEntities * @param {Vec3} center - The point about which to search. * @param {number} radius - The radius within which to search. - * @returns {Uuid[]} An array of entity IDs that were found that intersect the search sphere. The array is empty if no - * entities could be found. + * @returns {Uuid[]} An array of entity IDs that intersect the search sphere. The array is empty if no entities could be + * found. * @example Report how many entities are within 10m of your avatar. * var entityIDs = Entities.findEntities(MyAvatar.position, 10); * print("Number of entities within 10m: " + entityIDs.length); @@ -486,8 +702,7 @@ public slots: Q_INVOKABLE QVector findEntities(const glm::vec3& center, float radius) const; /**jsdoc - * Find all non-local entities whose axis-aligned boxes intersect a search axis-aligned box defined by its minimum coordinates corner - * and dimensions. + * Finds all domain and avatar entities whose axis-aligned boxes intersect a search axis-aligned box. * @function Entities.findEntitiesInBox * @param {Vec3} corner - The corner of the search AA box with minimum co-ordinate values. * @param {Vec3} dimensions - The dimensions of the search AA box. @@ -498,12 +713,12 @@ public slots: Q_INVOKABLE QVector findEntitiesInBox(const glm::vec3& corner, const glm::vec3& dimensions) const; /**jsdoc - * Find all non-local entities whose axis-aligned boxes intersect a search frustum. + * Finds all domain and avatar entities whose axis-aligned boxes intersect a search frustum. * @function Entities.findEntitiesInFrustum * @param {ViewFrustum} frustum - The frustum to search in. The position, orientation, * projection, and centerRadius properties must be specified. - * @returns {Uuid[]} An array of entity IDs axis-aligned boxes intersect the frustum. The array is empty if no entities - * could be found. + * @returns {Uuid[]} An array of entity IDs whose axis-aligned boxes intersect the search frustum. The array is empty if no + * entities could be found. * @example Report the number of entities in view. * var entityIDs = Entities.findEntitiesInFrustum(Camera.frustum); * print("Number of entities in view: " + entityIDs.length); @@ -512,7 +727,7 @@ public slots: Q_INVOKABLE QVector findEntitiesInFrustum(QVariantMap frustum) const; /**jsdoc - * Find all non-local entities of a particular type that intersect a sphere defined by a center point and radius. + * Finds all domain and avatar entities of a particular type that intersect a sphere. * @function Entities.findEntitiesByType * @param {Entities.EntityType} entityType - The type of entity to search for. * @param {Vec3} center - The point about which to search. @@ -527,33 +742,35 @@ public slots: Q_INVOKABLE QVector findEntitiesByType(const QString entityType, const glm::vec3& center, float radius) const; /**jsdoc - * Find all non-local entities with a particular name that intersect a sphere defined by a center point and radius. - * @function Entities.findEntitiesByName - * @param {string} entityName - The name of the entity to search for. - * @param {Vec3} center - The point about which to search. - * @param {number} radius - The radius within which to search. - * @param {boolean} [caseSensitive=false] - If true then the search is case-sensitive. - * @returns {Uuid[]} An array of entity IDs that have the specified name and intersect the search sphere. The array is empty - * if no entities could be found. - * @example Report the number of entities with the name, "Light-Target". - * var entityIDs = Entities.findEntitiesByName("Light-Target", MyAvatar.position, 10, false); - * print("Number of entities with the name Light-Target: " + entityIDs.length); - */ + * Finds all domain and avatar entities with a particular name that intersect a sphere. + * @function Entities.findEntitiesByName + * @param {string} entityName - The name of the entity to search for. + * @param {Vec3} center - The point about which to search. + * @param {number} radius - The radius within which to search. + * @param {boolean} [caseSensitive=false] - If true then the search is case-sensitive. + * @returns {Uuid[]} An array of entity IDs that have the specified name and intersect the search sphere. The array is + * empty if no entities could be found. + * @example Report the number of entities with the name, "Light-Target". + * var entityIDs = Entities.findEntitiesByName("Light-Target", MyAvatar.position, 10, false); + * print("Number of entities with the name Light-Target: " + entityIDs.length); + */ Q_INVOKABLE QVector findEntitiesByName(const QString entityName, const glm::vec3& center, float radius, bool caseSensitiveSearch = false) const; /**jsdoc - * Find the first non-local entity intersected by a {@link PickRay}. Light and Zone entities are not - * intersected unless they've been configured as pickable using {@link Entities.setLightsArePickable|setLightsArePickable} - * and {@link Entities.setZonesArePickable|setZonesArePickable}, respectively.
+ * Finds the first avatar or domain entity intersected by a {@link PickRay}. Light and Zone + * entities are not intersected unless they've been configured as pickable using + * {@link Entities.setLightsArePickable|setLightsArePickable} and {@link Entities.setZonesArePickable|setZonesArePickable}, + * respectively. * @function Entities.findRayIntersection - * @param {PickRay} pickRay - The PickRay to use for finding entities. - * @param {boolean} [precisionPicking=false] - If true and the intersected entity is a Model - * entity, the result's extraInfo property includes more information than it otherwise would. + * @param {PickRay} pickRay - The pick ray to use for finding entities. + * @param {boolean} [precisionPicking=false] - true to pick against precise meshes, false to pick + * against coarse meshes. If true and the intersected entity is a Model entity, the result's + * extraInfo property includes more information than it otherwise would. * @param {Uuid[]} [entitiesToInclude=[]] - If not empty then the search is restricted to these entities. * @param {Uuid[]} [entitiesToDiscard=[]] - Entities to ignore during the search. * @param {boolean} [visibleOnly=false] - If true then only entities that are - * {@link Entities.EntityProperties|visible} are searched. + * {@link Entities.EntityProperties|visible} are searched. * @param {boolean} [collideableOnly=false] - If true then only entities that are not * {@link Entities.EntityProperties|collisionless} are searched. * @returns {Entities.RayToEntityIntersectionResult} The result of the search for the first intersected entity. @@ -587,14 +804,14 @@ public slots: Q_INVOKABLE bool reloadServerScripts(const QUuid& entityID); /**jsdoc - * Gets the status of server entity script attached to an entity + * Gets the status of a server entity script attached to an entity. * @function Entities.getServerScriptStatus - * @param {Uuid} entityID - The ID of the entity to get the server entity script status for. + * @param {Uuid} entityID - The ID of the entity to get the server entity script status of. * @param {Entities~getServerScriptStatusCallback} callback - The function to call upon completion. * @returns {boolean} true always. */ /**jsdoc - * Called when {@link Entities.getServerScriptStatus} is complete. + * Called when a {@link Entities.getServerScriptStatus} call is complete. * @callback Entities~getServerScriptStatusCallback * @param {boolean} success - true if the server entity script status could be obtained, otherwise * false. @@ -606,102 +823,104 @@ public slots: Q_INVOKABLE bool getServerScriptStatus(const QUuid& entityID, QScriptValue callback); /**jsdoc - * Get metadata for certain entity properties such as script and serverScripts. - * @function Entities.queryPropertyMetadata - * @param {Uuid} entityID - The ID of the entity to get the metadata for. - * @param {string} property - The property name to get the metadata for. - * @param {Entities~queryPropertyMetadataCallback} callback - The function to call upon completion. - * @returns {boolean} true if the request for metadata was successfully sent to the server, otherwise - * false. - * @throws Throws an error if property is not handled yet or callback is not a function. - */ + * Gets metadata for certain entity properties such as script and serverScripts. + * @function Entities.queryPropertyMetadata + * @param {Uuid} entityID - The ID of the entity to get the metadata for. + * @param {string} property - The property name to get the metadata for. + * @param {Entities~queryPropertyMetadataCallback} callback - The function to call upon completion. + * @returns {boolean} true if the request for metadata was successfully sent to the server, otherwise + * false. + * @throws Throws an error if property is not handled yet or callback is not a function. + */ /**jsdoc - * Get metadata for certain entity properties such as script and serverScripts. - * @function Entities.queryPropertyMetadata - * @param {Uuid} entityID - The ID of the entity to get the metadata for. - * @param {string} property - The property name to get the metadata for. - * @param {object} scope - The "this" context that the callback will be executed within. - * @param {Entities~queryPropertyMetadataCallback} callback - The function to call upon completion. - * @returns {boolean} true if the request for metadata was successfully sent to the server, otherwise - * false. - * @throws Throws an error if property is not handled yet or callback is not a function. - */ + * Gets metadata for certain entity properties such as script and serverScripts. + * @function Entities.queryPropertyMetadata + * @param {Uuid} entityID - The ID of the entity to get the metadata for. + * @param {string} property - The property name to get the metadata for. + * @param {object} scope - The "this" context that the callback will be executed within. + * @param {Entities~queryPropertyMetadataCallback} callback - The function to call upon completion. + * @returns {boolean} true if the request for metadata was successfully sent to the server, otherwise + * false. + * @throws Throws an error if property is not handled yet or callback is not a function. + */ /**jsdoc - * Called when {@link Entities.queryPropertyMetadata} is complete. - * @callback Entities~queryPropertyMetadataCallback - * @param {string} error - undefined if there was no error, otherwise an error message. - * @param {object} result - The metadata for the requested entity property if there was no error, otherwise - * undefined. - */ + * Called when a {@link Entities.queryPropertyMetadata} call is complete. + * @callback Entities~queryPropertyMetadataCallback + * @param {string} error - undefined if there was no error, otherwise an error message. + * @param {object} result - The metadata for the requested entity property if there was no error, otherwise + * undefined. + */ Q_INVOKABLE bool queryPropertyMetadata(const QUuid& entityID, QScriptValue property, QScriptValue scopeOrCallback, QScriptValue methodOrName = QScriptValue()); /**jsdoc - * Set whether or not ray picks intersect the bounding box of {@link Entities.EntityType|Light} entities. By default, Light - * entities are not intersected. The setting lasts for the Interface session. Ray picks are done using - * {@link Entities.findRayIntersection|findRayIntersection}, or the {@link Picks} API. + * Sets whether or not ray picks intersect the bounding box of {@link Entities.EntityProperties-Light|Light} entities. By + * default, Light entities are not intersected. The setting lasts for the Interface session. Ray picks are done using + * {@link Entities.findRayIntersection|findRayIntersection}, or the {@link Picks} API. * @function Entities.setLightsArePickable - * @param {boolean} value - Set true to make ray picks intersect the bounding box of - * {@link Entities.EntityType|Light} entities, otherwise false. + * @param {boolean} value - true to make ray picks intersect the bounding box of + * {@link Entities.EntityProperties-Light|Light} entities, otherwise false. */ // FIXME move to a renderable entity interface Q_INVOKABLE void setLightsArePickable(bool value); /**jsdoc - * Get whether or not ray picks intersect the bounding box of {@link Entities.EntityType|Light} entities. Ray picks are - * done using {@link Entities.findRayIntersection|findRayIntersection}, or the {@link Picks} API. + * Gets whether or not ray picks intersect the bounding box of {@link Entities.EntityProperties-Light|Light} entities. Ray + * picks are done using {@link Entities.findRayIntersection|findRayIntersection}, or the {@link Picks} API. * @function Entities.getLightsArePickable - * @returns {boolean} true if ray picks intersect the bounding box of {@link Entities.EntityType|Light} - * entities, otherwise false. + * @returns {boolean} true if ray picks intersect the bounding box of + * {@link Entities.EntityProperties-Light|Light} entities, otherwise false. */ // FIXME move to a renderable entity interface Q_INVOKABLE bool getLightsArePickable() const; /**jsdoc - * Set whether or not ray picks intersect the bounding box of {@link Entities.EntityType|Zone} entities. By default, Light - * entities are not intersected. The setting lasts for the Interface session. Ray picks are done using - * {@link Entities.findRayIntersection|findRayIntersection}, or the {@link Picks} API. + * Sets whether or not ray picks intersect the bounding box of {@link Entities.EntityProperties-Zone|Zone} entities. By + * default, Zone entities are not intersected. The setting lasts for the Interface session. Ray picks are done using + * {@link Entities.findRayIntersection|findRayIntersection}, or the {@link Picks} API. * @function Entities.setZonesArePickable - * @param {boolean} value - Set true to make ray picks intersect the bounding box of - * {@link Entities.EntityType|Zone} entities, otherwise false. + * @param {boolean} value - true to make ray picks intersect the bounding box of + * {@link Entities.EntityProperties-Zone|Zone} entities, otherwise false. */ // FIXME move to a renderable entity interface Q_INVOKABLE void setZonesArePickable(bool value); /**jsdoc - * Get whether or not ray picks intersect the bounding box of {@link Entities.EntityType|Zone} entities. Ray picks are - * done using {@link Entities.findRayIntersection|findRayIntersection}, or the {@link Picks} API. + * Gets whether or not ray picks intersect the bounding box of {@link Entities.EntityProperties-Zone|Zone} entities. Ray + * picks are done using {@link Entities.findRayIntersection|findRayIntersection}, or the {@link Picks} API. * @function Entities.getZonesArePickable - * @returns {boolean} true if ray picks intersect the bounding box of {@link Entities.EntityType|Zone} - * entities, otherwise false. + * @returns {boolean} true if ray picks intersect the bounding box of + * {@link Entities.EntityProperties-Zone|Zone} entities, otherwise false. */ // FIXME move to a renderable entity interface Q_INVOKABLE bool getZonesArePickable() const; /**jsdoc - * Set whether or not {@link Entities.EntityType|Zone} entities' boundaries should be drawn. Currently not used. + * Sets whether or not {@link Entities.EntityProperties-Zone|Zone} entities' boundaries should be drawn. Currently not + * used. * @function Entities.setDrawZoneBoundaries - * @param {boolean} value - Set to true if {@link Entities.EntityType|Zone} entities' boundaries should be + * @param {boolean} value - true if {@link Entities.EntityProperties-Zone|Zone} entities' boundaries should be * drawn, otherwise false. */ // FIXME move to a renderable entity interface Q_INVOKABLE void setDrawZoneBoundaries(bool value); /**jsdoc - * Get whether or not {@link Entities.EntityType|Zone} entities' boundaries should be drawn. Currently not used. - * @function Entities.getDrawZoneBoundaries - * @returns {boolean} true if {@link Entities.EntityType|Zone} entities' boundaries should be drawn, - * otherwise false. - */ + * Gets whether or not {@link Entities.EntityProperties-Zone|Zone} entities' boundaries should be drawn. Currently + * not used. + * @function Entities.getDrawZoneBoundaries + * @returns {boolean} true if {@link Entities.EntityProperties-Zone|Zone} entities' boundaries should be + * drawn, otherwise false. + */ // FIXME move to a renderable entity interface Q_INVOKABLE bool getDrawZoneBoundaries() const; /**jsdoc - * Set the values of all voxels in a spherical portion of a {@link Entities.EntityType|PolyVox} entity. + * Sets the values of all voxels in a spherical portion of a {@link Entities.EntityProperties-PolyVox|PolyVox} entity. * @function Entities.setVoxelSphere - * @param {Uuid} entityID - The ID of the {@link Entities.EntityType|PolyVox} entity. + * @param {Uuid} entityID - The ID of the {@link Entities.EntityProperties-PolyVox|PolyVox} entity. * @param {Vec3} center - The center of the sphere of voxels to set, in world coordinates. * @param {number} radius - The radius of the sphere of voxels to set, in world coordinates. * @param {number} value - If value % 256 == 0 then each voxel is cleared, otherwise each voxel is set. @@ -720,9 +939,9 @@ public slots: Q_INVOKABLE bool setVoxelSphere(const QUuid& entityID, const glm::vec3& center, float radius, int value); /**jsdoc - * Set the values of all voxels in a capsule-shaped portion of a {@link Entities.EntityType|PolyVox} entity. + * Sets the values of all voxels in a capsule-shaped portion of a {@link Entities.EntityProperties-PolyVox|PolyVox} entity. * @function Entities.setVoxelCapsule - * @param {Uuid} entityID - The ID of the {@link Entities.EntityType|PolyVox} entity. + * @param {Uuid} entityID - The ID of the {@link Entities.EntityProperties-PolyVox|PolyVox} entity. * @param {Vec3} start - The center of the sphere of voxels to set, in world coordinates. * @param {Vec3} end - The center of the sphere of voxels to set, in world coordinates. * @param {number} radius - The radius of the capsule cylinder and spherical ends, in world coordinates. @@ -744,14 +963,14 @@ public slots: Q_INVOKABLE bool setVoxelCapsule(const QUuid& entityID, const glm::vec3& start, const glm::vec3& end, float radius, int value); /**jsdoc - * Set the value of a particular voxels in a {@link Entities.EntityType|PolyVox} entity. + * Sets the value of a particular voxel in a {@link Entities.EntityProperties-PolyVox|PolyVox} entity. * @function Entities.setVoxel - * @param {Uuid} entityID - The ID of the {@link Entities.EntityType|PolyVox} entity. + * @param {Uuid} entityID - The ID of the {@link Entities.EntityProperties-PolyVox|PolyVox} entity. * @param {Vec3} position - The position relative to the minimum axes values corner of the entity. The * position coordinates are rounded to the nearest integer to get the voxel coordinate. The minimum axes * corner voxel is { x: 0, y: 0, z: 0 }. * @param {number} value - If value % 256 == 0 then voxel is cleared, otherwise the voxel is set. - * @example Create a cube PolyVox entity and clear the minimum axes corner voxel. + * @example Create a cube PolyVox entity and clear the minimum axes' corner voxel. * var entity = Entities.addEntity({ * type: "PolyVox", * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0.5, z: -8 })), @@ -766,9 +985,9 @@ public slots: Q_INVOKABLE bool setVoxel(const QUuid& entityID, const glm::vec3& position, int value); /**jsdoc - * Set the values of all voxels in a {@link Entities.EntityType|PolyVox} entity. + * Sets the values of all voxels in a {@link Entities.EntityProperties-PolyVox|PolyVox} entity. * @function Entities.setAllVoxels - * @param {Uuid} entityID - The ID of the {@link Entities.EntityType|PolyVox} entity. + * @param {Uuid} entityID - The ID of the {@link Entities.EntityProperties-PolyVox|PolyVox} entity. * @param {number} value - If value % 256 == 0 then each voxel is cleared, otherwise each voxel is set. * @example Create a PolyVox cube. * var entity = Entities.addEntity({ @@ -784,9 +1003,9 @@ public slots: Q_INVOKABLE bool setAllVoxels(const QUuid& entityID, int value); /**jsdoc - * Set the values of all voxels in a cubic portion of a {@link Entities.EntityType|PolyVox} entity. + * Sets the values of all voxels in a cubic portion of a {@link Entities.EntityProperties-PolyVox|PolyVox} entity. * @function Entities.setVoxelsInCuboid - * @param {Uuid} entityID - The ID of the {@link Entities.EntityType|PolyVox} entity. + * @param {Uuid} entityID - The ID of the {@link Entities.EntityProperties-PolyVox|PolyVox} entity. * @param {Vec3} lowPosition - The position of the minimum axes value corner of the cube of voxels to set, in voxel * coordinates. * @param {Vec3} cuboidSize - The size of the cube of voxels to set, in voxel coordinates. @@ -809,14 +1028,14 @@ public slots: Q_INVOKABLE bool setVoxelsInCuboid(const QUuid& entityID, const glm::vec3& lowPosition, const glm::vec3& cuboidSize, int value); /**jsdoc - * Convert voxel coordinates in a {@link Entities.EntityType|PolyVox} entity to world coordinates. Voxel coordinates are - * relative to the minimum axes values corner of the entity with a scale of Vec3.ONE being the dimensions of - * each voxel. + * Converts voxel coordinates in a {@link Entities.EntityProperties-PolyVox|PolyVox} entity to world coordinates. Voxel + * coordinates are relative to the minimum axes values corner of the entity with a scale of Vec3.ONE being the + * dimensions of each voxel. * @function Entities.voxelCoordsToWorldCoords - * @param {Uuid} entityID - The ID of the {@link Entities.EntityType|PolyVox} entity. + * @param {Uuid} entityID - The ID of the {@link Entities.EntityProperties-PolyVox|PolyVox} entity. * @param {Vec3} voxelCoords - The voxel coordinates. May be fractional and outside the entity's bounding box. * @returns {Vec3} The world coordinates of the voxelCoords if the entityID is a - * {@link Entities.EntityType|PolyVox} entity, otherwise {@link Vec3(0)|Vec3.ZERO}. + * {@link Entities.EntityProperties-PolyVox|PolyVox} entity, otherwise {@link Vec3(0)|Vec3.ZERO}. * @example Create a PolyVox cube with the 0,0,0 voxel replaced by a sphere. * // Cube PolyVox with 0,0,0 voxel missing. * var polyVox = Entities.addEntity({ @@ -844,26 +1063,29 @@ public slots: Q_INVOKABLE glm::vec3 voxelCoordsToWorldCoords(const QUuid& entityID, glm::vec3 voxelCoords); /**jsdoc - * Convert world coordinates to voxel coordinates in a {@link Entities.EntityType|PolyVox} entity. Voxel coordinates are - * relative to the minimum axes values corner of the entity, with a scale of Vec3.ONE being the dimensions of - * each voxel. + * Converts world coordinates to voxel coordinates in a {@link Entities.EntityProperties-PolyVox|PolyVox} entity. Voxel + * coordinates are relative to the minimum axes values corner of the entity, with a scale of Vec3.ONE being + * the dimensions of each voxel. * @function Entities.worldCoordsToVoxelCoords - * @param {Uuid} entityID - The ID of the {@link Entities.EntityType|PolyVox} entity. + * @param {Uuid} entityID - The ID of the {@link Entities.EntityProperties-PolyVox|PolyVox} entity. * @param {Vec3} worldCoords - The world coordinates. May be outside the entity's bounding box. * @returns {Vec3} The voxel coordinates of the worldCoords if the entityID is a - * {@link Entities.EntityType|PolyVox} entity, otherwise {@link Vec3(0)|Vec3.ZERO}. The value may be fractional. + * {@link Entities.EntityProperties-PolyVox|PolyVox} entity, otherwise {@link Vec3(0)|Vec3.ZERO}. The value may be + * fractional and outside the entity's bounding box. */ // FIXME move to a renderable entity interface Q_INVOKABLE glm::vec3 worldCoordsToVoxelCoords(const QUuid& entityID, glm::vec3 worldCoords); /**jsdoc - * Convert voxel coordinates in a {@link Entities.EntityType|PolyVox} entity to local coordinates relative to the minimum - * axes value corner of the entity, with the scale being the same as world coordinates. + * Converts voxel coordinates in a {@link Entities.EntityProperties-PolyVox|PolyVox} entity to local coordinates. Local + * coordinates are relative to the minimum axes value corner of the entity, with the scale being the same as world + * coordinates. Voxel coordinates are relative to the minimum axes values corner of the entity, with a scale of + * Vec3.ONE being the dimensions of each voxel. * @function Entities.voxelCoordsToLocalCoords - * @param {Uuid} entityID - The ID of the {@link Entities.EntityType|PolyVox} entity. + * @param {Uuid} entityID - The ID of the {@link Entities.EntityProperties-PolyVox|PolyVox} entity. * @param {Vec3} voxelCoords - The voxel coordinates. May be fractional and outside the entity's bounding box. * @returns {Vec3} The local coordinates of the voxelCoords if the entityID is a - * {@link Entities.EntityType|PolyVox} entity, otherwise {@link Vec3(0)|Vec3.ZERO}. + * {@link Entities.EntityProperties-PolyVox|PolyVox} entity, otherwise {@link Vec3(0)|Vec3.ZERO}. * @example Get the world dimensions of a voxel in a PolyVox entity. * var polyVox = Entities.addEntity({ * type: "PolyVox", @@ -879,25 +1101,30 @@ public slots: Q_INVOKABLE glm::vec3 voxelCoordsToLocalCoords(const QUuid& entityID, glm::vec3 voxelCoords); /**jsdoc - * Convert local coordinates to voxel coordinates in a {@link Entities.EntityType|PolyVox} entity. Local coordinates are - * relative to the minimum axes value corner of the entity, with the scale being the same as world coordinates. + * Converts local coordinates to voxel coordinates in a {@link Entities.EntityProperties-PolyVox|PolyVox} entity. Local + * coordinates are relative to the minimum axes value corner of the entity, with the scale being the same as world + * coordinates. Voxel coordinates are relative to the minimum axes values corner of the entity, with a scale of + * Vec3.ONE being the dimensions of each voxel. * @function Entities.localCoordsToVoxelCoords - * @param {Uuid} entityID - The ID of the {@link Entities.EntityType|PolyVox} entity. + * @param {Uuid} entityID - The ID of the {@link Entities.EntityProperties-PolyVox|PolyVox} entity. * @param {Vec3} localCoords - The local coordinates. May be outside the entity's bounding box. * @returns {Vec3} The voxel coordinates of the worldCoords if the entityID is a - * {@link Entities.EntityType|PolyVox} entity, otherwise {@link Vec3(0)|Vec3.ZERO}. The value may be fractional. + * {@link Entities.EntityProperties-PolyVox|PolyVox} entity, otherwise {@link Vec3(0)|Vec3.ZERO}. The value may be + * fractional and outside the entity's bounding box. */ // FIXME move to a renderable entity interface Q_INVOKABLE glm::vec3 localCoordsToVoxelCoords(const QUuid& entityID, glm::vec3 localCoords); /**jsdoc - * Set the linePoints property of a {@link Entities.EntityType|Line} entity. + * Sets all the points in a {@link Entities.EntityProperties-Line|Line} entity. * @function Entities.setAllPoints - * @param {Uuid} entityID - The ID of the {@link Entities.EntityType|Line} entity. - * @param {Vec3[]} points - The array of points to set the entity's linePoints property to. - * @returns {boolean} true if the entity's property was updated, otherwise false. The property - * may fail to be updated if the entity does not exist, the entity is not a {@link Entities.EntityType|Line} entity, + * @param {Uuid} entityID - The ID of the {@link Entities.EntityProperties-Line|Line} entity. + * @param {Vec3[]} points - The points that the entity should draw lines between. + * @returns {boolean} true if the entity was updated, otherwise false. The property may fail to + * be updated if the entity does not exist, the entity is not a {@link Entities.EntityProperties-Line|Line} entity, * one of the points is outside the entity's dimensions, or the number of points is greater than the maximum allowed. + * @deprecated This function is deprecated and will be removed. Use {@link Entities.EntityProperties-PolyLine|PolyLine} + * entities instead. * @example Change the shape of a Line entity. * // Draw a horizontal line between two points. * var entity = Entities.addEntity({ @@ -925,13 +1152,15 @@ public slots: Q_INVOKABLE bool setAllPoints(const QUuid& entityID, const QVector& points); /**jsdoc - * Append a point to a {@link Entities.EntityType|Line} entity. + * Appends a point to a {@link Entities.EntityProperties-Line|Line} entity. * @function Entities.appendPoint - * @param {Uuid} entityID - The ID of the {@link Entities.EntityType|Line} entity. + * @param {Uuid} entityID - The ID of the {@link Entities.EntityProperties-Line|Line} entity. * @param {Vec3} point - The point to add to the line. The coordinates are relative to the entity's position. * @returns {boolean} true if the point was added to the line, otherwise false. The point may - * fail to be added if the entity does not exist, the entity is not a {@link Entities.EntityType|Line} entity, the - * point is outside the entity's dimensions, or the maximum number of points has been reached. + * fail to be added if the entity does not exist, the entity is not a {@link Entities.EntityProperties-Line|Line} + * entity, the point is outside the entity's dimensions, or the maximum number of points has been reached. + * @deprecated This function is deprecated and will be removed. Use {@link Entities.EntityProperties-PolyLine|PolyLine} + * entities instead. * @example Append a point to a Line entity. * // Draw a line between two points. * var entity = Entities.addEntity({ @@ -948,26 +1177,29 @@ public slots: * }); * * // Add a third point to create a "V". - * Entities.appendPoint(entity, { x: 1, y: 1, z: 0 }); + * Script.setTimeout(function () { + * Entities.appendPoint(entity, { x: 1, y: 1, z: 0 }); + * }, 50); // Wait for the entity to be created. */ Q_INVOKABLE bool appendPoint(const QUuid& entityID, const glm::vec3& point); /**jsdoc - * Dumps debug information about all entities in Interface's local in-memory tree of entities it knows about to the program log. + * Dumps debug information about all entities in Interface's local in-memory tree of entities it knows about to the program + * log. * @function Entities.dumpTree */ Q_INVOKABLE void dumpTree() const; /**jsdoc - * Add an action to an entity. An action is registered with the physics engine and is applied every physics simulation + * Adds an action to an entity. An action is registered with the physics engine and is applied every physics simulation * step. Any entity may have more than one action associated with it, but only as many as will fit in an entity's - * actionData property. + * {@link Entities.EntityProperties|actionData} property. * @function Entities.addAction * @param {Entities.ActionType} actionType - The type of action. * @param {Uuid} entityID - The ID of the entity to add the action to. - * @param {Entities.ActionArguments} arguments - Configure the action. - * @returns {Uuid} The ID of the action added if successfully added, otherwise null. + * @param {Entities.ActionArguments} arguments - Configures the action. + * @returns {Uuid} The ID of the action if successfully added, otherwise null. * @example Constrain a cube to move along a vertical line. * var entityID = Entities.addEntity({ * type: "Box", @@ -988,7 +1220,7 @@ public slots: Q_INVOKABLE QUuid addAction(const QString& actionTypeString, const QUuid& entityID, const QVariantMap& arguments); /**jsdoc - * Update an entity action. + * Updates an entity action. * @function Entities.updateAction * @param {Uuid} entityID - The ID of the entity with the action to update. * @param {Uuid} actionID - The ID of the action to update. @@ -998,67 +1230,67 @@ public slots: Q_INVOKABLE bool updateAction(const QUuid& entityID, const QUuid& actionID, const QVariantMap& arguments); /**jsdoc - * Delete an action from an entity. + * Deletes an action from an entity. * @function Entities.deleteAction * @param {Uuid} entityID - The ID of entity to delete the action from. * @param {Uuid} actionID - The ID of the action to delete. - * @returns {boolean} true if the update was successful, otherwise false. + * @returns {boolean} true if the delete was successful, otherwise false. */ Q_INVOKABLE bool deleteAction(const QUuid& entityID, const QUuid& actionID); /**jsdoc - * Get the IDs of the actions that are associated with an entity. + * Gets the IDs of the actions that are associated with an entity. * @function Entities.getActionIDs * @param {Uuid} entityID - The entity to get the action IDs for. - * @returns {Uuid[]} An array of action IDs if any are found, otherwise an empty array. + * @returns {Uuid[]} The action IDs if any are found, otherwise an empty array. */ Q_INVOKABLE QVector getActionIDs(const QUuid& entityID); /**jsdoc - * Get the arguments of an action. + * Gets the arguments of an action. * @function Entities.getActionArguments * @param {Uuid} entityID - The ID of the entity with the action. * @param {Uuid} actionID - The ID of the action to get the arguments of. - * @returns {Entities.ActionArguments} The arguments of the requested action if found, otherwise an empty object. + * @returns {Entities.ActionArguments} The arguments of the action if found, otherwise an empty object. */ Q_INVOKABLE QVariantMap getActionArguments(const QUuid& entityID, const QUuid& actionID); /**jsdoc - * Get the translation of a joint in a {@link Entities.EntityType|Model} entity relative to the entity's position and - * orientation. + * Gets the translation of a joint in a {@link Entities.EntityProperties-Model|Model} entity relative to the entity's + * position and orientation. * @function Entities.getAbsoluteJointTranslationInObjectFrame * @param {Uuid} entityID - The ID of the entity. * @param {number} jointIndex - The integer index of the joint. * @returns {Vec3} The translation of the joint relative to the entity's position and orientation if the entity is a - * {@link Entities.EntityType|Model} entity, the entity is loaded, and the joint index is valid; otherwise + * {@link Entities.EntityProperties-Model|Model} entity, the entity is loaded, and the joint index is valid; otherwise * {@link Vec3(0)|Vec3.ZERO}. */ // FIXME move to a renderable entity interface Q_INVOKABLE glm::vec3 getAbsoluteJointTranslationInObjectFrame(const QUuid& entityID, int jointIndex); /**jsdoc - * Get the index of the parent joint. + * Gets the index of the parent joint of a joint in a {@link Entities.EntityProperties-Model|Model} entity. * @function Entities.getJointParent * @param {Uuid} entityID - The ID of the entity. * @param {number} index - The integer index of the joint. - * @returns {number} The index of the parent joint. + * @returns {number} The index of the parent joint if found, otherwise -1. */ Q_INVOKABLE int getJointParent(const QUuid& entityID, int index); /**jsdoc - * Get the translation of a joint in a {@link Entities.EntityType|Model} entity relative to the entity's position and - * orientation. + * Gets the translation of a joint in a {@link Entities.EntityProperties-Model|Model} entity relative to the entity's + * position and orientation. * @function Entities.getAbsoluteJointRotationInObjectFrame * @param {Uuid} entityID - The ID of the entity. * @param {number} jointIndex - The integer index of the joint. * @returns {Quat} The rotation of the joint relative to the entity's orientation if the entity is a - * {@link Entities.EntityType|Model} entity, the entity is loaded, and the joint index is valid; otherwise + * {@link Entities.EntityProperties-Model|Model} entity, the entity is loaded, and the joint index is valid; otherwise * {@link Quat(0)|Quat.IDENTITY}. * @example Compare the local and absolute rotations of an avatar model's left hand joint. * entityID = Entities.addEntity({ * type: "Model", - * modelURL: "https://hifi-content.s3.amazonaws.com/milad/production/Examples/Models/Avatars/blue_suited.fbx", + * modelURL: "https://github.com/highfidelity/hifi-api-docs/blob/master/docs/blue_suited.fbx?raw=true", * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -5 })), * rotation: MyAvatar.orientation, * lifetime: 300 // Delete after 5 minutes. @@ -1077,32 +1309,33 @@ public slots: Q_INVOKABLE glm::quat getAbsoluteJointRotationInObjectFrame(const QUuid& entityID, int jointIndex); /**jsdoc - * Set the translation of a joint in a {@link Entities.EntityType|Model} entity relative to the entity's position and - * orientation. + * Sets the translation of a joint in a {@link Entities.EntityProperties-Model|Model} entity relative to the entity's + * position and orientation. * @function Entities.setAbsoluteJointTranslationInObjectFrame * @param {Uuid} entityID - The ID of the entity. * @param {number} jointIndex - The integer index of the joint. * @param {Vec3} translation - The translation to set the joint to relative to the entity's position and orientation. - * @returns {boolean} trueif the entity is a {@link Entities.EntityType|Model} entity, the entity is loaded, - * the joint index is valid, and the translation is different to the joint's current translation; otherwise + * @returns {boolean} trueif the entity is a {@link Entities.EntityProperties-Model|Model} entity, the entity + * is loaded, the joint index is valid, and the translation is different to the joint's current translation; otherwise * false. */ // FIXME move to a renderable entity interface Q_INVOKABLE bool setAbsoluteJointTranslationInObjectFrame(const QUuid& entityID, int jointIndex, glm::vec3 translation); /**jsdoc - * Set the rotation of a joint in a {@link Entities.EntityType|Model} entity relative to the entity's position and - * orientation. + * Sets the rotation of a joint in a {@link Entities.EntityProperties-Model|Model} entity relative to the entity's position + * and orientation. * @function Entities.setAbsoluteJointRotationInObjectFrame * @param {Uuid} entityID - The ID of the entity. * @param {number} jointIndex - The integer index of the joint. * @param {Quat} rotation - The rotation to set the joint to relative to the entity's orientation. - * @returns {boolean} true if the entity is a {@link Entities.EntityType|Model} entity, the entity is loaded, - * the joint index is valid, and the rotation is different to the joint's current rotation; otherwise false. + * @returns {boolean} true if the entity is a {@link Entities.EntityProperties-Model|Model} entity, the entity + * is loaded, the joint index is valid, and the rotation is different to the joint's current rotation; otherwise + * false. * @example Raise an avatar model's left palm. * entityID = Entities.addEntity({ * type: "Model", - * modelURL: "https://hifi-content.s3.amazonaws.com/milad/production/Examples/Models/Avatars/blue_suited.fbx", + * modelURL: "https://github.com/highfidelity/hifi-api-docs/blob/master/docs/blue_suited.fbx?raw=true", * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -5 })), * rotation: MyAvatar.orientation, * lifetime: 300 // Delete after 5 minutes. @@ -1122,27 +1355,27 @@ public slots: /**jsdoc - * Get the local translation of a joint in a {@link Entities.EntityType|Model} entity. + * Gets the local translation of a joint in a {@link Entities.EntityProperties-Model|Model} entity. * @function Entities.getLocalJointTranslation * @param {Uuid} entityID - The ID of the entity. * @param {number} jointIndex - The integer index of the joint. - * @returns {Vec3} The local translation of the joint if the entity is a {@link Entities.EntityType|Model} entity, the - * entity is loaded, and the joint index is valid; otherwise {@link Vec3(0)|Vec3.ZERO}. + * @returns {Vec3} The local translation of the joint if the entity is a {@link Entities.EntityProperties-Model|Model} + * entity, the entity is loaded, and the joint index is valid; otherwise {@link Vec3(0)|Vec3.ZERO}. */ // FIXME move to a renderable entity interface Q_INVOKABLE glm::vec3 getLocalJointTranslation(const QUuid& entityID, int jointIndex); /**jsdoc - * Get the local rotation of a joint in a {@link Entities.EntityType|Model} entity. + * Gets the local rotation of a joint in a {@link Entities.EntityProperties-Model|Model} entity. * @function Entities.getLocalJointRotation * @param {Uuid} entityID - The ID of the entity. * @param {number} jointIndex - The integer index of the joint. - * @returns {Quat} The local rotation of the joint if the entity is a {@link Entities.EntityType|Model} entity, the entity - * is loaded, and the joint index is valid; otherwise {@link Quat(0)|Quat.IDENTITY}. + * @returns {Quat} The local rotation of the joint if the entity is a {@link Entities.EntityProperties-Model|Model} entity, + * the entity is loaded, and the joint index is valid; otherwise {@link Quat(0)|Quat.IDENTITY}. * @example Report the local rotation of an avatar model's head joint. * entityID = Entities.addEntity({ * type: "Model", - * modelURL: "https://hifi-content.s3.amazonaws.com/milad/production/Examples/Models/Avatars/blue_suited.fbx", + * modelURL: "https://github.com/highfidelity/hifi-api-docs/blob/master/docs/blue_suited.fbx?raw=true", * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -5 })), * rotation: MyAvatar.orientation, * lifetime: 300 // Delete after 5 minutes. @@ -1159,30 +1392,31 @@ public slots: Q_INVOKABLE glm::quat getLocalJointRotation(const QUuid& entityID, int jointIndex); /**jsdoc - * Set the local translation of a joint in a {@link Entities.EntityType|Model} entity. + * Sets the local translation of a joint in a {@link Entities.EntityProperties-Model|Model} entity. * @function Entities.setLocalJointTranslation * @param {Uuid} entityID - The ID of the entity. * @param {number} jointIndex - The integer index of the joint. * @param {Vec3} translation - The local translation to set the joint to. - * @returns {boolean} trueif the entity is a {@link Entities.EntityType|Model} entity, the entity is loaded, - * the joint index is valid, and the translation is different to the joint's current translation; otherwise + * @returns {boolean} trueif the entity is a {@link Entities.EntityProperties-Model|Model} entity, the entity + * is loaded, the joint index is valid, and the translation is different to the joint's current translation; otherwise * false. */ // FIXME move to a renderable entity interface Q_INVOKABLE bool setLocalJointTranslation(const QUuid& entityID, int jointIndex, glm::vec3 translation); /**jsdoc - * Set the local rotation of a joint in a {@link Entities.EntityType|Model} entity. + * Sets the local rotation of a joint in a {@link Entities.EntityProperties-Model|Model} entity. * @function Entities.setLocalJointRotation * @param {Uuid} entityID - The ID of the entity. * @param {number} jointIndex - The integer index of the joint. * @param {Quat} rotation - The local rotation to set the joint to. - * @returns {boolean} true if the entity is a {@link Entities.EntityType|Model} entity, the entity is loaded, - * the joint index is valid, and the rotation is different to the joint's current rotation; otherwise false. + * @returns {boolean} true if the entity is a {@link Entities.EntityProperties-Model|Model} entity, the entity + * is loaded, the joint index is valid, and the rotation is different to the joint's current rotation; otherwise + * false. * @example Make an avatar model turn its head left. * entityID = Entities.addEntity({ * type: "Model", - * modelURL: "https://hifi-content.s3.amazonaws.com/milad/production/Examples/Models/Avatars/blue_suited.fbx", + * modelURL: "https://github.com/highfidelity/hifi-api-docs/blob/master/docs/blue_suited.fbx?raw=true", * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -5 })), * rotation: MyAvatar.orientation, * lifetime: 300 // Delete after 5 minutes. @@ -1201,29 +1435,29 @@ public slots: /**jsdoc - * Set the local translations of joints in a {@link Entities.EntityType|Model} entity. + * Sets the local translations of joints in a {@link Entities.EntityProperties-Model|Model} entity. * @function Entities.setLocalJointTranslations * @param {Uuid} entityID - The ID of the entity. * @param {Vec3[]} translations - The local translations to set the joints to. - * @returns {boolean} trueif the entity is a {@link Entities.EntityType|Model} entity, the entity is loaded, - * the model has joints, and at least one of the translations is different to the model's current translations; - * otherwise false. + * @returns {boolean} trueif the entity is a {@link Entities.EntityProperties-Model|Model} entity, the entity + * is loaded, the model has joints, and at least one of the translations is different to the model's current + * translations; otherwise false. */ // FIXME move to a renderable entity interface Q_INVOKABLE bool setLocalJointTranslations(const QUuid& entityID, const QVector& translations); /**jsdoc - * Set the local rotations of joints in a {@link Entities.EntityType|Model} entity. + * Sets the local rotations of joints in a {@link Entities.EntityProperties-Model|Model} entity. * @function Entities.setLocalJointRotations * @param {Uuid} entityID - The ID of the entity. * @param {Quat[]} rotations - The local rotations to set the joints to. - * @returns {boolean} true if the entity is a {@link Entities.EntityType|Model} entity, the entity is loaded, - * the model has joints, and at least one of the rotations is different to the model's current rotations; otherwise - * false. + * @returns {boolean} true if the entity is a {@link Entities.EntityProperties-Model|Model} entity, the entity + * is loaded, the model has joints, and at least one of the rotations is different to the model's current rotations; + * otherwise false. * @example Raise both palms of an avatar model. * entityID = Entities.addEntity({ * type: "Model", - * modelURL: "https://hifi-content.s3.amazonaws.com/milad/production/Examples/Models/Avatars/blue_suited.fbx", + * modelURL: "https://github.com/highfidelity/hifi-api-docs/blob/master/docs/blue_suited.fbx?raw=true", * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -5 })), * rotation: MyAvatar.orientation, * lifetime: 300 // Delete after 5 minutes. @@ -1255,16 +1489,16 @@ public slots: Q_INVOKABLE bool setLocalJointRotations(const QUuid& entityID, const QVector& rotations); /**jsdoc - * Set the local rotations and translations of joints in a {@link Entities.EntityType|Model} entity. This is the same as - * calling both {@link Entities.setLocalJointRotations|setLocalJointRotations} and + * Sets the local rotations and translations of joints in a {@link Entities.EntityProperties-Model|Model} entity. This is + * the same as calling both {@link Entities.setLocalJointRotations|setLocalJointRotations} and * {@link Entities.setLocalJointTranslations|setLocalJointTranslations} at the same time. * @function Entities.setLocalJointsData * @param {Uuid} entityID - The ID of the entity. * @param {Quat[]} rotations - The local rotations to set the joints to. * @param {Vec3[]} translations - The local translations to set the joints to. - * @returns {boolean} true if the entity is a {@link Entities.EntityType|Model} entity, the entity is loaded, - * the model has joints, and at least one of the rotations or translations is different to the model's current values; - * otherwise false. + * @returns {boolean} true if the entity is a {@link Entities.EntityProperties-Model|Model} entity, the entity + * is loaded, the model has joints, and at least one of the rotations or translations is different to the model's + * current values; otherwise false. */ // FIXME move to a renderable entity interface Q_INVOKABLE bool setLocalJointsData(const QUuid& entityID, @@ -1273,17 +1507,17 @@ public slots: /**jsdoc - * Get the index of a named joint in a {@link Entities.EntityType|Model} entity. + * Gets the index of a named joint in a {@link Entities.EntityProperties-Model|Model} entity. * @function Entities.getJointIndex * @param {Uuid} entityID - The ID of the entity. * @param {string} name - The name of the joint. - * @returns {number} The integer index of the joint if the entity is a {@link Entities.EntityType|Model} entity, the entity - * is loaded, and the joint is present; otherwise -1. The joint indexes are in order per - * {@link Entities.getJointNames|getJointNames}. + * @returns {number} The integer index of the joint if the entity is a {@link Entities.EntityProperties-Model|Model} + * entity, the entity is loaded, and the joint is present; otherwise -1. The joint indexes are in order + * per {@link Entities.getJointNames|getJointNames}. * @example Report the index of a model's head joint. * entityID = Entities.addEntity({ * type: "Model", - * modelURL: "https://hifi-content.s3.amazonaws.com/milad/production/Examples/Models/Avatars/blue_suited.fbx", + * modelURL: "https://github.com/highfidelity/hifi-api-docs/blob/master/docs/blue_suited.fbx?raw=true", * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -5 })), * rotation: MyAvatar.orientation, * lifetime: 300 // Delete after 5 minutes. @@ -1299,15 +1533,16 @@ public slots: Q_INVOKABLE int getJointIndex(const QUuid& entityID, const QString& name); /**jsdoc - * Get the names of all the joints in a {@link Entities.EntityType|Model} entity. + * Gets the names of all the joints in a {@link Entities.EntityProperties-Model|Model} entity. * @function Entities.getJointNames - * @param {Uuid} entityID - The ID of the {@link Entities.EntityType|Model} entity. - * @returns {string[]} The names of all the joints in the entity if it is a {@link Entities.EntityType|Model} entity and - * is loaded, otherwise an empty array. The joint names are in order per {@link Entities.getJointIndex|getJointIndex}. + * @param {Uuid} entityID - The ID of the {@link Entities.EntityProperties-Model|Model} entity. + * @returns {string[]} The names of all the joints in the entity if it is a {@link Entities.EntityProperties-Model|Model} + * entity and is loaded, otherwise an empty array. The joint names are in order per + * {@link Entities.getJointIndex|getJointIndex}. * @example Report a model's joint names. * entityID = Entities.addEntity({ * type: "Model", - * modelURL: "https://hifi-content.s3.amazonaws.com/milad/production/Examples/Models/Avatars/blue_suited.fbx", + * modelURL: "https://github.com/highfidelity/hifi-api-docs/blob/master/docs/blue_suited.fbx?raw=true", * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -5 })), * rotation: MyAvatar.orientation, * lifetime: 300 // Delete after 5 minutes. @@ -1324,7 +1559,8 @@ public slots: /**jsdoc - * Get the IDs of entities and avatars that are directly parented to an entity or avatar model. Recurse on the IDs returned by the function to get all descendants of an entity or avatar. + * Gets the IDs of entities and avatars that are directly parented to an entity or avatar model. To get all descendants, + * you can recurse on the IDs returned. * @function Entities.getChildrenIDs * @param {Uuid} parentID - The ID of the entity or avatar to get the children IDs of. * @returns {Uuid[]} An array of entity and avatar IDs that are parented directly to the parentID @@ -1354,7 +1590,8 @@ public slots: Q_INVOKABLE QVector getChildrenIDs(const QUuid& parentID); /**jsdoc - * Get the IDs of entities and avatars that are directly parented to an entity or avatar model's joint. + * Gets the IDs of entities and avatars that are directly parented to an entity or avatar model's joint. To get all + * descendants, you can use {@link Entities.getChildrenIDs|getChildrenIDs} to recurse on the IDs returned. * @function Entities.getChildrenIDsOfJoint * @param {Uuid} parentID - The ID of the entity or avatar to get the children IDs of. * @param {number} jointIndex - Integer number of the model joint to get the children IDs of. @@ -1378,18 +1615,20 @@ public slots: * var root = createEntity("Root", position, Uuid.NULL); * var child = createEntity("Child", Vec3.sum(position, { x: 0, y: -1, z: 0 }), root); * - * Entities.editEntity(root, { - * parentID: MyAvatar.sessionUUID, - * parentJointIndex: MyAvatar.getJointIndex("RightHand") - * }); - * - * var children = Entities.getChildrenIDsOfJoint(MyAvatar.sessionUUID, MyAvatar.getJointIndex("RightHand")); - * print("Children of hand: " + JSON.stringify(children)); // Only the root entity. + * Script.setTimeout(function () { // Wait for the entity to be created before editing. + * Entities.editEntity(root, { + * parentID: MyAvatar.sessionUUID, + * parentJointIndex: MyAvatar.getJointIndex("RightHand") + * }); + * + * var children = Entities.getChildrenIDsOfJoint(MyAvatar.sessionUUID, MyAvatar.getJointIndex("RightHand")); + * print("Children of hand: " + JSON.stringify(children)); // Only the root entity. + * }, 50); */ Q_INVOKABLE QVector getChildrenIDsOfJoint(const QUuid& parentID, int jointIndex); /**jsdoc - * Check whether an entity has an entity as an ancestor (parent, parent's parent, etc.). + * Checks whether an entity has an entity as an ancestor (parent, parent's parent, etc.). * @function Entities.isChildOfParent * @param {Uuid} childID - The ID of the child entity to test for being a child, grandchild, etc. * @param {Uuid} parentID - The ID of the parent entity to test for being a parent, grandparent, etc. @@ -1418,12 +1657,11 @@ public slots: Q_INVOKABLE bool isChildOfParent(const QUuid& childID, const QUuid& parentID); /**jsdoc - * Get the type — entity or avatar — of an in-world item. + * Gets the type — entity or avatar — of an in-world item. * @function Entities.getNestableType * @param {Uuid} id - The ID of the item to get the type of. - * @returns {string} The type of the item: "entity" if the item is an entity, "avatar" - * if the item is an avatar; otherwise "unknown" if the item cannot be found. - * @example Print some nestable types. + * @returns {Entities.NestableType} The type of the item. + * @example Report some nestable types. * var entity = Entities.addEntity({ * type: "Sphere", * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 1, z: -2 })), @@ -1436,22 +1674,22 @@ public slots: Q_INVOKABLE QString getNestableType(const QUuid& id); /**jsdoc - * Get the ID of the {@link Entities.EntityType|Web} entity that has keyboard focus. + * Gets the ID of the {@link Entities.EntityProperties-Web|Web} entity that has keyboard focus. * @function Entities.getKeyboardFocusEntity - * @returns {Uuid} The ID of the {@link Entities.EntityType|Web} entity that has focus, if any, otherwise null. + * @returns {Uuid} The ID of the {@link Entities.EntityProperties-Web|Web} entity that has focus, if any, otherwise null. */ Q_INVOKABLE QUuid getKeyboardFocusEntity() const; /**jsdoc - * Set the {@link Entities.EntityType|Web} entity that has keyboard focus. + * Sets the {@link Entities.EntityProperties-Web|Web} entity that has keyboard focus. * @function Entities.setKeyboardFocusEntity - * @param {Uuid} id - The ID of the {@link Entities.EntityType|Web} entity to set keyboard focus to. Use + * @param {Uuid} id - The ID of the {@link Entities.EntityProperties-Web|Web} entity to set keyboard focus to. Use * null or {@link Uuid(0)|Uuid.NULL} to unset keyboard focus from an entity. */ Q_INVOKABLE void setKeyboardFocusEntity(const QUuid& id); /**jsdoc - * Emit a {@link Entities.mousePressOnEntity|mousePressOnEntity} event. + * Emits a {@link Entities.mousePressOnEntity|mousePressOnEntity} event. * @function Entities.sendMousePressOnEntity * @param {Uuid} entityID - The ID of the entity to emit the event for. * @param {PointerEvent} event - The event details. @@ -1459,7 +1697,7 @@ public slots: Q_INVOKABLE void sendMousePressOnEntity(const EntityItemID& id, const PointerEvent& event); /**jsdoc - * Emit a {@link Entities.mouseMoveOnEntity|mouseMoveOnEntity} event. + * Emits a {@link Entities.mouseMoveOnEntity|mouseMoveOnEntity} event. * @function Entities.sendMouseMoveOnEntity * @param {Uuid} entityID - The ID of the entity to emit the event for. * @param {PointerEvent} event - The event details. @@ -1467,7 +1705,7 @@ public slots: Q_INVOKABLE void sendMouseMoveOnEntity(const EntityItemID& id, const PointerEvent& event); /**jsdoc - * Emit a {@link Entities.mouseReleaseOnEntity|mouseReleaseOnEntity} event. + * Emits a {@link Entities.mouseReleaseOnEntity|mouseReleaseOnEntity} event. * @function Entities.sendMouseReleaseOnEntity * @param {Uuid} entityID - The ID of the entity to emit the event for. * @param {PointerEvent} event - The event details. @@ -1475,7 +1713,7 @@ public slots: Q_INVOKABLE void sendMouseReleaseOnEntity(const EntityItemID& id, const PointerEvent& event); /**jsdoc - * Emit a {@link Entities.clickDownOnEntity|clickDownOnEntity} event. + * Emits a {@link Entities.clickDownOnEntity|clickDownOnEntity} event. * @function Entities.sendClickDownOnEntity * @param {Uuid} entityID - The ID of the entity to emit the event for. * @param {PointerEvent} event - The event details. @@ -1483,7 +1721,7 @@ public slots: Q_INVOKABLE void sendClickDownOnEntity(const EntityItemID& id, const PointerEvent& event); /**jsdoc - * Emit a {@link Entities.holdingClickOnEntity|holdingClickOnEntity} event. + * Emits a {@link Entities.holdingClickOnEntity|holdingClickOnEntity} event. * @function Entities.sendHoldingClickOnEntity * @param {Uuid} entityID - The ID of the entity to emit the event for. * @param {PointerEvent} event - The event details. @@ -1491,7 +1729,7 @@ public slots: Q_INVOKABLE void sendHoldingClickOnEntity(const EntityItemID& id, const PointerEvent& event); /**jsdoc - * Emit a {@link Entities.clickReleaseOnEntity|clickReleaseOnEntity} event. + * Emits a {@link Entities.clickReleaseOnEntity|clickReleaseOnEntity} event. * @function Entities.sendClickReleaseOnEntity * @param {Uuid} entityID - The ID of the entity to emit the event for. * @param {PointerEvent} event - The event details. @@ -1499,7 +1737,7 @@ public slots: Q_INVOKABLE void sendClickReleaseOnEntity(const EntityItemID& id, const PointerEvent& event); /**jsdoc - * Emit a {@link Entities.hoverEnterEntity|hoverEnterEntity} event. + * Emits a {@link Entities.hoverEnterEntity|hoverEnterEntity} event. * @function Entities.sendHoverEnterEntity * @param {Uuid} entityID - The ID of the entity to emit the event for. * @param {PointerEvent} event - The event details. @@ -1507,7 +1745,7 @@ public slots: Q_INVOKABLE void sendHoverEnterEntity(const EntityItemID& id, const PointerEvent& event); /**jsdoc - * Emit a {@link Entities.hoverOverEntity|hoverOverEntity} event. + * Emits a {@link Entities.hoverOverEntity|hoverOverEntity} event. * @function Entities.sendHoverOverEntity * @param {Uuid} entityID - The ID of the entity to emit the event for. * @param {PointerEvent} event - The event details. @@ -1515,7 +1753,7 @@ public slots: Q_INVOKABLE void sendHoverOverEntity(const EntityItemID& id, const PointerEvent& event); /**jsdoc - * Emit a {@link Entities.hoverLeaveEntity|hoverLeaveEntity} event. + * Emits a {@link Entities.hoverLeaveEntity|hoverLeaveEntity} event. * @function Entities.sendHoverLeaveEntity * @param {Uuid} entityID - The ID of the entity to emit the event for. * @param {PointerEvent} event - The event details. @@ -1523,8 +1761,8 @@ public slots: Q_INVOKABLE void sendHoverLeaveEntity(const EntityItemID& id, const PointerEvent& event); /**jsdoc - * Check whether an entity wants hand controller pointer events. For example, a {@link Entities.EntityType|Web} entity does - * but a {@link Entities.EntityType|Shape} entity doesn't. + * Checks whether an entity wants hand controller pointer events. For example, a {@link Entities.EntityProperties-Web|Web} + * entity does but a {@link Entities.EntityProperties-Shape|Shape} entity doesn't. * @function Entities.wantsHandControllerPointerEvents * @param {Uuid} entityID - The ID of the entity. * @returns {boolean} true if the entity can be found and it wants hand controller pointer events, otherwise @@ -1533,35 +1771,89 @@ public slots: Q_INVOKABLE bool wantsHandControllerPointerEvents(const QUuid& id); /**jsdoc - * Send a script event over a {@link Entities.EntityType|Web} entity's EventBridge to the Web page's scripts. + * Sends a message to a {@link Entities.EntityProperties-Web|Web} entity's HTML page. To receive the message, the web + * page's script must connect to the EventBridge that is automatically provided to the script: + *
EventBridge.scriptEventReceived.connect(function(message) {
+     *     ...
+     * });
+ *

Use {@link Entities.webEventReceived} to receive messages from the Web entity's HTML page.

+ *

Alternatively, you can use {@link Entities.getEntityObject} to exchange messages over a Web entity's HTML event + * bridge.

* @function Entities.emitScriptEvent - * @param {Uuid} entityID - The ID of the {@link Entities.EntityType|Web} entity. + * @param {Uuid} entityID - The ID of the Web entity to send the message to. * @param {string} message - The message to send. + * @example Exchange messages with a Web entity. + * // HTML file, name: "webEntity.html". + * + * + * + * HELLO + * + * + *

HELLO

+ * + * + * + * + * // Script file. + * var webEntity = Entities.addEntity({ + * type: "Web", + * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0.5, z: -3 })), + * rotation: MyAvatar.orientation, + * sourceUrl: Script.resolvePath("webEntity.html"), + * alpha: 1.0, + * lifetime: 300 // 5 min + * }); + * + * function onWebEventReceived(entityID, message) { + * if (entityID === webEntity) { + * // Message received. + * print("Message received: " + message); + * + * // Send a message back. + * Entities.emitScriptEvent(webEntity, message + " back"); + * } + * } + * + * Entities.webEventReceived.connect(onWebEventReceived); */ Q_INVOKABLE void emitScriptEvent(const EntityItemID& entityID, const QVariant& message); /**jsdoc - * Check whether an axis-aligned box and a capsule intersect. + * Checks whether an axis-aligned box and a capsule intersect. * @function Entities.AABoxIntersectsCapsule * @param {Vec3} brn - The bottom right near (minimum axes values) corner of the AA box. * @param {Vec3} dimensions - The dimensions of the AA box. * @param {Vec3} start - One end of the capsule. * @param {Vec3} end - The other end of the capsule. - * @param {number} radius - The radiues of the capsule. + * @param {number} radius - The radius of the capsule. * @returns {boolean} true if the AA box and capsule intersect, otherwise false. */ Q_INVOKABLE bool AABoxIntersectsCapsule(const glm::vec3& low, const glm::vec3& dimensions, const glm::vec3& start, const glm::vec3& end, float radius); /**jsdoc - * Get the meshes in a {@link Entities.EntityType|Model} or {@link Entities.EntityType|PolyVox} entity. + * Gets the meshes in a {@link Entities.EntityProperties-Model|Model} or {@link Entities.EntityProperties-PolyVox|PolyVox} + * entity. * @function Entities.getMeshes * @param {Uuid} entityID - The ID of the Model or PolyVox entity to get the meshes of. * @param {Entities~getMeshesCallback} callback - The function to call upon completion. * @deprecated This function is deprecated and will be removed. Use the {@link Graphics} API instead. */ /**jsdoc - * Called when {@link Entities.getMeshes} is complete. + * Called when a {@link Entities.getMeshes} call is complete. * @callback Entities~getMeshesCallback * @param {MeshProxy[]} meshes - If success< is true, a {@link MeshProxy} per mesh in the * Model or PolyVox entity; otherwise undefined. @@ -1573,7 +1865,7 @@ public slots: Q_INVOKABLE void getMeshes(const QUuid& entityID, QScriptValue callback); /**jsdoc - * Get the object to world transform, excluding scale, of an entity. + * Gets the object to world transform, excluding scale, of an entity. * @function Entities.getEntityTransform * @param {Uuid} entityID - The ID of the entity. * @returns {Mat4} The entity's object to world transform excluding scale (i.e., translation and rotation, with scale of 1) @@ -1601,11 +1893,12 @@ public slots: Q_INVOKABLE glm::mat4 getEntityTransform(const QUuid& entityID); /**jsdoc - * Get the object to parent transform, excluding scale, of an entity. + * Gets the object to parent transform, excluding scale, of an entity. * @function Entities.getEntityLocalTransform * @param {Uuid} entityID - The ID of the entity. * @returns {Mat4} The entity's object to parent transform excluding scale (i.e., translation and rotation, with scale of - * 1) if the entity can be found, otherwise a transform with zero translation and rotation and a scale of 1. + * 1) if the entity can be found, otherwise a transform with zero translation and rotation and a scale of 1. If the + * entity doesn't have a parent, its world transform is returned. * @example Position and rotation in an entity's local transform. * function createEntity(position, rotation, parent) { * var entity = Entities.addEntity({ @@ -1636,141 +1929,193 @@ public slots: /**jsdoc + * Converts a position in world coordinates to a position in an avatar, entity, or joint's local coordinates. * @function Entities.worldToLocalPosition - * @param {Vec3} worldPosition - * @param {Uuid} parentID - * @param {number} parentJointIndex - * @param {boolean} scalesWithparent - * @returns {Vec3} + * @param {Vec3} worldPosition - The world position to convert. + * @param {Uuid} parentID - The avatar or entity that the local coordinates are based on. + * @param {number} [parentJointIndex=-1] - The joint in the avatar or entity that the local coordinates are based on. If + * -1 then no joint is used and the local coordinates are based solely on the avatar or entity. + * @param {boolean} [scalesWithParent= false] - true to scale the local position per the parent's scale, + * false for the local position to be at world scale. + * @returns {Vec3} The position converted to local coordinates if successful, otherwise {@link Vec3(0)|Vec3.ZERO}. + * @example Report the local coordinates of an entity parented to another. + * var parentPosition = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -5 })); + * var childPosition = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 1, z: -5 })); + * + * var parentEntity = Entities.addEntity({ + * type: "Box", + * position: parentPosition, + * rotation: MyAvatar.orientation, + * dimensions: { x: 0.5, y: 0.5, z: 0.5 }, + * lifetime: 300 // Delete after 5 minutes. + * }); + * var childEntity = Entities.addEntity({ + * type: "Sphere", + * position: childPosition, + * dimensions: { x: 0.5, y: 0.5, z: 0.5 }, + * parentID: parentEntity, + * lifetime: 300 // Delete after 5 minutes. + * }); + * + * var localPosition = Entities.worldToLocalPosition(childPosition, parentEntity); + * print("Local position: " + JSON.stringify(localPosition)); // 0, 1, 0. + * localPosition = Entities.getEntityProperties(childEntity, "localPosition").localPosition; + * print("Local position: " + JSON.stringify(localPosition)); // The same. */ Q_INVOKABLE glm::vec3 worldToLocalPosition(glm::vec3 worldPosition, const QUuid& parentID, int parentJointIndex = -1, bool scalesWithParent = false); /**jsdoc + * Converts a rotation or orientation in world coordinates to rotation in an avatar, entity, or joint's local coordinates. * @function Entities.worldToLocalRotation - * @param {Quat} worldRotation - * @param {Uuid} parentID - * @param {number} parentJointIndex - * @param {boolean} scalesWithparent - * @returns {Quat} + * @param {Quat} worldRotation - The world rotation to convert. + * @param {Uuid} parentID - The avatar or entity that the local coordinates are based on. + * @param {number} [parentJointIndex=-1] - The joint in the avatar or entity that the local coordinates are based on. If + * -1 then no joint is used and the local coordinates are based solely on the avatar or entity. + * @param {boolean} [scalesWithParent=false] - Not used in the calculation. + * @returns {Quat} The rotation converted to local coordinates if successful, otherwise {@link Quat(0)|Quat.IDENTITY}. */ Q_INVOKABLE glm::quat worldToLocalRotation(glm::quat worldRotation, const QUuid& parentID, int parentJointIndex = -1, bool scalesWithParent = false); /**jsdoc + * Converts a velocity in world coordinates to a velocity in an avatar, entity, or joint's local coordinates. * @function Entities.worldToLocalVelocity - * @param {Vec3} worldVelocity - * @param {Uuid} parentID - * @param {number} parentJointIndex - * @param {boolean} scalesWithparent - * @returns {Vec3} + * @param {Vec3} worldVelocity - The world velocity to convert. + * @param {Uuid} parentID - The avatar or entity that the local coordinates are based on. + * @param {number} [parentJointIndex=-1] - The joint in the avatar or entity that the local coordinates are based on. If + * -1 then no joint is used and the local coordinates are based solely on the avatar or entity. + * @param {boolean} [scalesWithParent=false] - true to scale the local velocity per the parent's scale, + * false for the local velocity to be at world scale. + * @returns {Vec3} The velocity converted to local coordinates if successful, otherwise {@link Vec3(0)|Vec3.ZERO}. */ Q_INVOKABLE glm::vec3 worldToLocalVelocity(glm::vec3 worldVelocity, const QUuid& parentID, int parentJointIndex = -1, bool scalesWithParent = false); /**jsdoc + * Converts a Euler angular velocity in world coordinates to an angular velocity in an avatar, entity, or joint's local + * coordinates. * @function Entities.worldToLocalAngularVelocity - * @param {Vec3} worldAngularVelocity - * @param {Uuid} parentID - * @param {number} parentJointIndex - * @param {boolean} scalesWithparent - * @returns {Vec3} + * @param {Vec3} worldAngularVelocity - The world Euler angular velocity to convert. (Can be in any unit, e.g., deg/s or + * rad/s.) + * @param {Uuid} parentID - The avatar or entity that the local coordinates are based on. + * @param {number} [parentJointIndex=-1] - The joint in the avatar or entity that the local coordinates are based on. If + * -1 then no joint is used and the local coordinates are based solely on the avatar or entity. + * @param {boolean} [scalesWithParent=false] - Not used in the calculation. + * @returns {Vec3} The angular velocity converted to local coordinates if successful, otherwise {@link Vec3(0)|Vec3.ZERO}. */ Q_INVOKABLE glm::vec3 worldToLocalAngularVelocity(glm::vec3 worldAngularVelocity, const QUuid& parentID, int parentJointIndex = -1, bool scalesWithParent = false); /**jsdoc + * Converts dimensions in world coordinates to dimensions in an avatar or entity's local coordinates. * @function Entities.worldToLocalDimensions - * @param {Vec3} worldDimensions - * @param {Uuid} parentID - * @param {number} parentJointIndex - * @param {boolean} scalesWithparent - * @returns {Vec3} + * @param {Vec3} worldDimensions - The world dimensions to convert. + * @param {Uuid} parentID - The avatar or entity that the local coordinates are based on. + * @param {number} [parentJointIndex=-1] - Not used in the calculation. + * @param {boolean} [scalesWithParent=false] - true to scale the local dimensions per the parent's scale, + * false for the local dimensions to be at world scale. + * @returns {Vec3} The dimensions converted to local coordinates if successful, otherwise {@link Vec3(0)|Vec3.ZERO}. */ Q_INVOKABLE glm::vec3 worldToLocalDimensions(glm::vec3 worldDimensions, const QUuid& parentID, int parentJointIndex = -1, bool scalesWithParent = false); /**jsdoc + * Converts a position in an avatar, entity, or joint's local coordinate to a position in world coordinates. * @function Entities.localToWorldPosition - * @param {Vec3} localPosition - * @param {Uuid} parentID - * @param {number} parentJointIndex - * @param {boolean} scalesWithparent - * @returns {Vec3} + * @param {Vec3} localPosition - The local position to convert. + * @param {Uuid} parentID - The avatar or entity that the local coordinates are based on. + * @param {number} [parentJointIndex=-1] - The joint in the avatar or entity that the local coordinates are based on. If + * -1 then no joint is used and the local coordinates are based solely on the avatar or entity. + * @param {boolean} [scalesWithparent=false] - true if the local dimensions are scaled per the parent's scale, + * false if the local dimensions are at world scale. + * @returns {Vec3} The position converted to world coordinates if successful, otherwise {@link Vec3(0)|Vec3.ZERO}. */ Q_INVOKABLE glm::vec3 localToWorldPosition(glm::vec3 localPosition, const QUuid& parentID, int parentJointIndex = -1, bool scalesWithParent = false); /**jsdoc + * Converts a rotation or orientation in an avatar, entity, or joint's local coordinate to a rotation in world coordinates. * @function Entities.localToWorldRotation - * @param {Quat} localRotation - * @param {Uuid} parentID - * @param {number} parentJointIndex - * @param {boolean} scalesWithparent - * @returns {Quat} + * @param {Quat} localRotation - The local rotation to convert. + * @param {Uuid} parentID - The avatar or entity that the local coordinates are based on. + * @param {number} [parentJointIndex=-1] - The joint in the avatar or entity that the local coordinates are based on. If + * -1 then no joint is used and the local coordinates are based solely on the avatar or entity. + * @param {boolean} [scalesWithParent= false] - Not used in the calculation. + * @returns {Quat} The rotation converted to local coordinates if successful, otherwise {@link Quat(0)|Quat.IDENTITY}. */ Q_INVOKABLE glm::quat localToWorldRotation(glm::quat localRotation, const QUuid& parentID, int parentJointIndex = -1, bool scalesWithParent = false); /**jsdoc + * Converts a velocity in an avatar, entity, or joint's local coordinate to a velocity in world coordinates. * @function Entities.localToWorldVelocity - * @param {Vec3} localVelocity - * @param {Uuid} parentID - * @param {number} parentJointIndex - * @param {boolean} scalesWithparent - * @returns {Vec3} + * @param {Vec3} localVelocity - The local velocity to convert. + * @param {Uuid} parentID - The avatar or entity that the local coordinates are based on. + * @param {number} [parentJointIndex=-1] - The joint in the avatar or entity that the local coordinates are based on. If + * -1 then no joint is used and the local coordinates are based solely on the avatar or entity. + * @param {boolean} [scalesWithParent= false] - true if the local velocity is scaled per the parent's scale, + * false if the local velocity is at world scale. + * @returns {Vec3} The velocity converted to world coordinates it successful, otherwise {@link Vec3(0)|Vec3.ZERO}. */ Q_INVOKABLE glm::vec3 localToWorldVelocity(glm::vec3 localVelocity, const QUuid& parentID, int parentJointIndex = -1, bool scalesWithParent = false); /**jsdoc + * Converts a Euler angular velocity in an avatar, entity, or joint's local coordinate to an angular velocity in world + * coordinates. * @function Entities.localToWorldAngularVelocity - * @param {Vec3} localAngularVelocity - * @param {Uuid} parentID - * @param {number} parentJointIndex - * @param {boolean} scalesWithparent - * @returns {Vec3} + * @param {Vec3} localAngularVelocity - The local Euler angular velocity to convert. (Can be in any unit, e.g., deg/s or + * rad/s.) + * @param {Uuid} parentID - The avatar or entity that the local coordinates are based on. + * @param {number} [parentJointIndex=-1] - The joint in the avatar or entity that the local coordinates are based on. If + * -1 then no joint is used and the local coordinates are based solely on the avatar or entity. + * @param {boolean} [scalesWithParent= false] - Not used in the calculation. + * @returns {Vec3} The angular velocity converted to world coordinates if successful, otherwise {@link Vec3(0)|Vec3.ZERO}. */ Q_INVOKABLE glm::vec3 localToWorldAngularVelocity(glm::vec3 localAngularVelocity, const QUuid& parentID, int parentJointIndex = -1, bool scalesWithParent = false); /**jsdoc + * Converts dimensions in an avatar or entity's local coordinates to dimensions in world coordinates. * @function Entities.localToWorldDimensions - * @param {Vec3} localDimensions - * @param {Uuid} parentID - * @param {number} parentJointIndex - * @param {boolean} scalesWithparent - * @returns {Vec3} + * @param {Vec3} localDimensions - The local dimensions to convert. + * @param {Uuid} parentID - The avatar or entity that the local coordinates are based on. + * @param {number} [parentJointIndex=-1] - Not used in the calculation. + * @param {boolean} [scalesWithParent= false] - true if the local dimensions are scaled per the parent's + * scale, false if the local dimensions are at world scale. + * @returns {Vec3} The dimensions converted to world coordinates if successful, otherwise {@link Vec3(0)|Vec3.ZERO}. */ Q_INVOKABLE glm::vec3 localToWorldDimensions(glm::vec3 localDimensions, const QUuid& parentID, int parentJointIndex = -1, bool scalesWithParent = false); /**jsdoc - * Get the static certificate for an entity. The static certificate contains static properties of the item which cannot - * be altered. - * @function Entities.getStaticCertificateJSON - * @param {Uuid} entityID - The ID of the entity to get the static certificate for. - * @returns {string} The entity's static certificate as a JSON string if the entity can be found, otherwise an empty string. - */ + * Gets the static certificate for an entity. The static certificate contains static properties of the item which cannot + * be altered. + * @function Entities.getStaticCertificateJSON + * @param {Uuid} entityID - The ID of the entity to get the static certificate for. + * @returns {string} The entity's static certificate as a JSON string if the entity can be found, otherwise "". + */ Q_INVOKABLE QString getStaticCertificateJSON(const QUuid& entityID); /**jsdoc - * Verify the entity's proof of provenance, i.e., that the entity's certificateID property was produced by + * Verifies the entity's proof of provenance, i.e., that the entity's certificateID property was produced by * High Fidelity signing the entity's static certificate JSON. * @function Entities.verifyStaticCertificateProperties * @param {Uuid} entityID - The ID of the entity to verify. - * @returns {boolean} true if the entity can be found an its certificateID property is present - * and its value matches the entity's static certificate JSON; otherwise false. + * @returns {boolean} true if the entity can be found, its certificateID property is present, and + * its value matches the entity's static certificate JSON; otherwise false. */ Q_INVOKABLE bool verifyStaticCertificateProperties(const QUuid& entityID); /**jsdoc - * Get information about entity properties including a minimum to maximum range for numerical properties - * as well as property enum value. + * Gets information about an entity property, including a minimum to maximum range for some numerical properties. * @function Entities.getPropertyInfo * @param {string} propertyName - The name of the property to get the information for. - * @returns {Entities.EntityPropertyInfo} The information data including propertyEnum, minimum, and maximum - * if the property can be found, otherwise an empty object. + * @returns {Entities.EntityPropertyInfo} The information about the property if it can be found, otherwise an empty object. + * @example Report property info. for some properties. + * print("alpha: " + JSON.stringify(Entities.getPropertyInfo("alpha"))); + * print("script: " + JSON.stringify(Entities.getPropertyInfo("script"))); */ Q_INVOKABLE const EntityPropertyInfo getPropertyInfo(const QString& propertyName) const; signals: /**jsdoc - * Triggered on the client that is the physics simulation owner during the collision of two entities. Note: Isn't triggered + * Triggered on the client that is the physics simulation owner during the collision of two entities. Note: Isn't triggered * for a collision with an avatar. - *

See also, {@link Script.addEventHandler}.

+ *

See also, {@link Entities|Entity Methods} and {@link Script.addEventHandler}.

* @function Entities.collisionWithEntity * @param {Uuid} idA - The ID of one entity in the collision. For an entity script, this is the ID of the entity containing * the script. @@ -1813,7 +2158,7 @@ signals: * Triggered when your ability to change the locked property of entities changes. * @function Entities.canAdjustLocksChanged * @param {boolean} canAdjustLocks - true if the script can change the locked property of an - * entity, otherwise false. + * entity, false if it can't. * @returns {Signal} * @example Report when your ability to change locks changes. * function onCanAdjustLocksChanged(canAdjustLocks) { @@ -1826,7 +2171,7 @@ signals: /**jsdoc * Triggered when your ability to rez (create) entities changes. * @function Entities.canRezChanged - * @param {boolean} canRez - true if the script can rez (create) entities, otherwise false. + * @param {boolean} canRez - true if the script can rez (create) entities, false if it can't. * @returns {Signal} */ void canRezChanged(bool canRez); @@ -1835,8 +2180,8 @@ signals: * Triggered when your ability to rez (create) temporary entities changes. Temporary entities are entities with a finite * lifetime property value set. * @function Entities.canRezTmpChanged - * @param {boolean} canRezTmp - true if the script can rez (create) temporary entities, otherwise - * false. + * @param {boolean} canRezTmp - true if the script can rez (create) temporary entities, false if + * it can't. * @returns {Signal} */ void canRezTmpChanged(bool canRezTmp); @@ -1845,8 +2190,8 @@ signals: * Triggered when your ability to rez (create) certified entities changes. Certified entities are entities that have PoP * certificates. * @function Entities.canRezCertifiedChanged - * @param {boolean} canRezCertified - true if the script can rez (create) certified entities, otherwise - * false. + * @param {boolean} canRezCertified - true if the script can rez (create) certified entities, + * false if it can't. * @returns {Signal} */ void canRezCertifiedChanged(bool canRezCertified); @@ -1856,7 +2201,7 @@ signals: * finite lifetime property value set. Certified entities are entities that have PoP certificates. * @function Entities.canRezTmpCertifiedChanged * @param {boolean} canRezTmpCertified - true if the script can rez (create) temporary certified entities, - * otherwise false. + * false if it can't. * @returns {Signal} */ void canRezTmpCertifiedChanged(bool canRezTmpCertified); @@ -1865,7 +2210,7 @@ signals: * Triggered when your ability to make changes to the asset server's assets changes. * @function Entities.canWriteAssetsChanged * @param {boolean} canWriteAssets - true if the script can change the ? property of an entity, - * otherwise false. + * false if it can't. * @returns {Signal} */ void canWriteAssetsChanged(bool canWriteAssets); @@ -1873,8 +2218,8 @@ signals: /**jsdoc * Triggered when your ability to get and set private user data changes. * @function Entities.canGetAndSetPrivateUserDataChanged - * @param {boolean} canGetAndSetPrivateUserData - true if you can change the privateUserData property of an entity, - * otherwise false. + * @param {boolean} canGetAndSetPrivateUserData - true if the script change the privateUserData + * property of an entity, false if it can't. * @returns {Signal} */ void canGetAndSetPrivateUserDataChanged(bool canGetAndSetPrivateUserData); @@ -1883,9 +2228,9 @@ signals: /**jsdoc * Triggered when a mouse button is clicked while the mouse cursor is on an entity, or a controller trigger is fully * pressed while its laser is on an entity. - *

See also, {@link Script.addEventHandler}.

+ *

See also, {@link Entities|Entity Methods} and {@link Script.addEventHandler}.

* @function Entities.mousePressOnEntity - * @param {Uuid} entityID - The ID of the entity that was pressed. + * @param {Uuid} entityID - The ID of the entity that was pressed on. * @param {PointerEvent} event - Details of the event. * @returns {Signal} * @example Report when an entity is clicked with the mouse or laser. @@ -1900,7 +2245,7 @@ signals: /**jsdoc * Triggered when a mouse button is double-clicked while the mouse cursor is on an entity. * @function Entities.mouseDoublePressOnEntity - * @param {Uuid} entityID - The ID of the entity that was double-pressed. + * @param {Uuid} entityID - The ID of the entity that was double-pressed on. * @param {PointerEvent} event - Details of the event. * @returns {Signal} */ @@ -1908,7 +2253,7 @@ signals: /**jsdoc * Repeatedly triggered while the mouse cursor or controller laser moves on an entity. - *

See also, {@link Script.addEventHandler}.

+ *

See also, {@link Entities|Entity Methods} and {@link Script.addEventHandler}.

* @function Entities.mouseMoveOnEntity * @param {Uuid} entityID - The ID of the entity that was moved on. * @param {PointerEvent} event - Details of the event. @@ -1919,7 +2264,7 @@ signals: /**jsdoc * Triggered when a mouse button is released after clicking on an entity or the controller trigger is partly or fully * released after pressing on an entity, even if the mouse pointer or controller laser has moved off the entity. - *

See also, {@link Script.addEventHandler}.

+ *

See also, {@link Entities|Entity Methods} and {@link Script.addEventHandler}.

* @function Entities.mouseReleaseOnEntity * @param {Uuid} entityID - The ID of the entity that was originally pressed. * @param {PointerEvent} event - Details of the event. @@ -1945,19 +2290,42 @@ signals: /**jsdoc - * Triggered when a mouse button is clicked while the mouse cursor is on an entity. Note: Not triggered by controller. - *

See also, {@link Script.addEventHandler}.

+ * Triggered when a mouse button is clicked while the mouse cursor is on an entity. Note: Not triggered by controllers. + *

See also, {@link Entities|Entity Methods} and {@link Script.addEventHandler}.

* @function Entities.clickDownOnEntity - * @param {Uuid} entityID - The ID of the entity that was clicked. + * @param {Uuid} entityID - The ID of the entity that was clicked on. * @param {PointerEvent} event - Details of the event. * @returns {Signal} + * @example Compare clickDownOnEntity signal and entity script method. + * var entityScript = (function () { + * // Method is called for only this entity. + * this.clickDownOnEntity = function (entityID, event) { + * print("Entity : Clicked sphere ; " + event.type); + * }; + * }); + * + * var sphereID = Entities.addEntity({ + * type: "Sphere", + * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -3 })), + * script: "(" + entityScript + ")", // Could host the script on a Web server instead. + * lifetime: 300 // Delete after 5 minutes. + * }); + * + * Entities.clickDownOnEntity.connect(function (entityID, event) { + * // Signal is triggered for all entities. + * if (entityID === sphereID) { + * print("Interface : Clicked sphere ; " + event.type); + * } else { + * print("Interface : Clicked another entity ; " + event.type); + * } + * }); */ void clickDownOnEntity(const EntityItemID& entityItemID, const PointerEvent& event); /**jsdoc * Repeatedly triggered while a mouse button continues to be held after clicking an entity, even if the mouse cursor has - * moved off the entity. Note: Not triggered by controller. - *

See also, {@link Script.addEventHandler}.

+ * moved off the entity. Note: Not triggered by controllers. + *

See also, {@link Entities|Entity Methods} and {@link Script.addEventHandler}.

* @function Entities.holdingClickOnEntity * @param {Uuid} entityID - The ID of the entity that was originally clicked. * @param {PointerEvent} event - Details of the event. @@ -1967,8 +2335,8 @@ signals: /**jsdoc * Triggered when a mouse button is released after clicking on an entity, even if the mouse cursor has moved off the - * entity. Note: Not triggered by controller. - *

See also, {@link Script.addEventHandler}.

+ * entity. Note: Not triggered by controllers. + *

See also, {@link Entities|Entity Methods} and {@link Script.addEventHandler}.

* @function Entities.clickReleaseOnEntity * @param {Uuid} entityID - The ID of the entity that was originally clicked. * @param {PointerEvent} event - Details of the event. @@ -1978,7 +2346,7 @@ signals: /**jsdoc * Triggered when the mouse cursor or controller laser starts hovering on an entity. - *

See also, {@link Script.addEventHandler}.

+ *

See also, {@link Entities|Entity Methods} and {@link Script.addEventHandler}.

* @function Entities.hoverEnterEntity * @param {Uuid} entityID - The ID of the entity that is being hovered. * @param {PointerEvent} event - Details of the event. @@ -1988,7 +2356,7 @@ signals: /**jsdoc * Repeatedly triggered when the mouse cursor or controller laser moves while hovering over an entity. - *

See also, {@link Script.addEventHandler}.

+ *

See also, {@link Entities|Entity Methods} and {@link Script.addEventHandler}.

* @function Entities.hoverOverEntity * @param {Uuid} entityID - The ID of the entity that is being hovered. * @param {PointerEvent} event - Details of the event. @@ -1998,7 +2366,7 @@ signals: /**jsdoc * Triggered when the mouse cursor or controller laser stops hovering over an entity. - *

See also, {@link Script.addEventHandler}.

+ *

See also, {@link Entities|Entity Methods} and {@link Script.addEventHandler}.

* @function Entities.hoverLeaveEntity * @param {Uuid} entityID - The ID of the entity that was being hovered. * @param {PointerEvent} event - Details of the event. @@ -2008,42 +2376,17 @@ signals: /**jsdoc - * Triggered when an avatar enters an entity. - *

See also, {@link Script.addEventHandler}.

+ * Triggered when an avatar enters an entity, but only if the entity has an entity method exposed for this event. + *

See also, {@link Entities|Entity Methods} and {@link Script.addEventHandler}.

* @function Entities.enterEntity * @param {Uuid} entityID - The ID of the entity that the avatar entered. * @returns {Signal} - * @example Change the color of an entity when an avatar enters or leaves. - * var entityScript = (function () { - * this.enterEntity = function (entityID) { - * print("Enter entity"); - * Entities.editEntity(entityID, { - * color: { red: 255, green: 64, blue: 64 }, - * }); - * }; - * this.leaveEntity = function (entityID) { - * print("Leave entity"); - * Entities.editEntity(entityID, { - * color: { red: 128, green: 128, blue: 128 }, - * }); - * }; - * }); - * - * var entityID = Entities.addEntity({ - * type: "Sphere", - * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -5 })), - * dimensions: { x: 3, y: 3, z: 3 }, - * color: { red: 128, green: 128, blue: 128 }, - * collisionless: true, // So that avatar can walk through entity. - * script: "(" + entityScript + ")", // Could host the script on a Web server instead. - * lifetime: 300 // Delete after 5 minutes. - * }); - */ + */ void enterEntity(const EntityItemID& entityItemID); /**jsdoc - * Triggered when an avatar leaves an entity. - *

See also, {@link Script.addEventHandler}.

+ * Triggered when an avatar leaves an entity, but only if the entity has an entity method exposed for this event. + *

See also, {@link Entities|Entity Methods} and {@link Script.addEventHandler}.

* @function Entities.leaveEntity * @param {Uuid} entityID - The ID of the entity that the avatar left. * @returns {Signal} @@ -2064,9 +2407,9 @@ signals: void deletingEntity(const EntityItemID& entityID); /**jsdoc - * Triggered when an entity is added to Interface's local in-memory tree of entities it knows about. This may occur when + * Triggered when an entity is added to Interface's local in-memory tree of entities it knows about. This may occur when * entities are loaded upon visiting a domain, when the user rotates their view so that more entities become visible, and - * when any type of entity is added (e.g., by {@Entities.addEntity|addEntity}). + * when any type of entity is created (e.g., by {@link Entities.addEntity|addEntity}). * @function Entities.addingEntity * @param {Uuid} entityID - The ID of the entity added. * @returns {Signal} @@ -2078,33 +2421,33 @@ signals: void addingEntity(const EntityItemID& entityID); /**jsdoc - * Triggered when an 'wearable' entity is deleted. - * @function Entities.deletingWearable - * @param {Uuid} entityID - The ID of the 'wearable' entity deleted. - * @returns {Signal} - * @example Report when an 'wearable' entity is deleted. - * Entities.deletingWearable.connect(function (entityID) { - * print("Deleted wearable: " + entityID); - * }); - */ + * Triggered when a "wearable" entity is deleted, for example when removing a "wearable" from your avatar. + * @function Entities.deletingWearable + * @param {Uuid} entityID - The ID of the "wearable" entity deleted. + * @returns {Signal} + * @example Report when a "wearable" entity is deleted. + * Entities.deletingWearable.connect(function (entityID) { + * print("Deleted wearable: " + entityID); + * }); + */ void deletingWearable(const EntityItemID& entityID); /**jsdoc - * Triggered when an 'wearable' entity is added to Interface's local in-memory tree of entities it knows about. This may occur when - * 'wearable' entities are added to avatar - * @function Entities.addingWearable - * @param {Uuid} entityID - The ID of the 'wearable' entity added. - * @returns {Signal} - * @example Report when an 'wearable' entity is added. - * Entities.addingWearable.connect(function (entityID) { - * print("Added wearable: " + entityID); - * }); - */ + * Triggered when a "wearable" entity is added to Interface's local in-memory tree of entities it knows about, for example + * when adding a "wearable" to your avatar. + * @function Entities.addingWearable + * @param {Uuid} entityID - The ID of the "wearable" entity added. + * @returns {Signal} + * @example Report when a "wearable" entity is added. + * Entities.addingWearable.connect(function (entityID) { + * print("Added wearable: " + entityID); + * }); + */ void addingWearable(const EntityItemID& entityID); /**jsdoc - * Triggered when you disconnect from a domain, at which time Interface's local in-memory tree of entities it knows about - * is cleared. + * Triggered when you disconnect from a domain, at which time Interface's local in-memory tree of entities that it knows + * about is cleared. * @function Entities.clearingEntities * @returns {Signal} * @example Report when Interfaces's entity tree is cleared. @@ -2115,10 +2458,14 @@ signals: void clearingEntities(); /**jsdoc - * Triggered in when a script in a {@link Entities.EntityType|Web} entity's Web page script sends an event over the - * script's EventBridge. + * Triggered when a script in a {@link Entities.EntityProperties-Web|Web} entity's HTML sends an event over the entity's + * HTML event bridge. The HTML web page can send a message by calling: + *
EventBridge.emitWebEvent(message);
+ *

Use {@link Entities.emitScriptEvent} to send messages to the Web entity's HTML page.

+ *

Alternatively, you can use {@link Entities.getEntityObject} to exchange messages over a Web entity's HTML event + * bridge.

* @function Entities.webEventReceived - * @param {Uuid} entityID - The ID of the entity that event was received from. + * @param {Uuid} entityID - The ID of the Web entity that the message was received from. * @param {string} message - The message received. * @returns {Signal} */ diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index dbc347631d..dca3ac595a 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -2121,9 +2121,10 @@ void EntityTree::fixupNeedsParentFixups() { _needsParentFixup.clear(); } + std::unordered_set seenEntityIds; QMutableVectorIterator iter(entitiesToFixup); while (iter.hasNext()) { - EntityItemWeakPointer entityWP = iter.next(); + const auto& entityWP = iter.next(); EntityItemPointer entity = entityWP.lock(); if (!entity) { // entity was deleted before we found its parent @@ -2131,6 +2132,17 @@ void EntityTree::fixupNeedsParentFixups() { continue; } + const auto id = entity->getID(); + // BUGZ-771 some entities seem to never be removed by the below logic and further seem to accumulate dupes within the _needsParentFixup list + // This block ensures that duplicates are removed from entitiesToFixup before it's re-appended to _needsParentFixup + if (0 != seenEntityIds.count(id)) { + // Entity was duplicated inside entitiesToFixup + iter.remove(); + continue; + } + + seenEntityIds.insert(id); + entity->requiresRecalcBoxes(); bool queryAACubeSuccess { false }; bool maxAACubeSuccess { false }; diff --git a/libraries/entities/src/EntityTypes.h b/libraries/entities/src/EntityTypes.h index 91b71513dc..8fdc752cac 100644 --- a/libraries/entities/src/EntityTypes.h +++ b/libraries/entities/src/EntityTypes.h @@ -42,7 +42,10 @@ public: * ValueDescriptionProperties * * - * "Box"A rectangular prism. This is a synonym of "Shape" for the case + * "Shape"A basic entity such as a cube. + * See also, the "Box" and "Sphere" entity types. + * {@link Entities.EntityProperties-Shape|EntityProperties-Shape} + * "Box"A rectangular prism. This is a synonym of "Shape" for the case * where the entity's shape property value is "Cube".
* If an entity is created with its type * set to "Box" it will always be created with a shape property value of @@ -56,16 +59,13 @@ public: * "Sphere". If an entity of type Box or Shape has its shape set * to "Sphere" then its type will be reported as "Sphere". * {@link Entities.EntityProperties-Sphere|EntityProperties-Sphere} - * "Shape"A basic entity such as a cube. - * See also, the "Box" and "Sphere" entity types. - * {@link Entities.EntityProperties-Shape|EntityProperties-Shape} - * "Model"A mesh model from an FBX or OBJ file. + * "Model"A mesh model from a glTf, FBX, or OBJ file. * {@link Entities.EntityProperties-Model|EntityProperties-Model} * "Text"A pane of text oriented in space. * {@link Entities.EntityProperties-Text|EntityProperties-Text} * "Image"An image oriented in space. * {@link Entities.EntityProperties-Image|EntityProperties-Image} - * "Web"A browsable Web page. + * "Web"A browsable web page. * {@link Entities.EntityProperties-Web|EntityProperties-Web} * "ParticleEffect"A particle system that can be used to simulate things such as fire, * smoke, snow, magic spells, etc. @@ -78,7 +78,7 @@ public: * {@link Entities.EntityProperties-PolyVox|EntityProperties-PolyVox} * "Grid"A grid of lines in a plane. * {@link Entities.EntityProperties-Grid|EntityProperties-Grid} - * "Gizmo"An entity with various UI-related properties. + * "Gizmo"A gizmo intended for UI. * {@link Entities.EntityProperties-Gizmo|EntityProperties-Gizmo} * "Light"A local lighting effect. * {@link Entities.EntityProperties-Light|EntityProperties-Light} diff --git a/libraries/entities/src/GrabPropertyGroup.h b/libraries/entities/src/GrabPropertyGroup.h index d76ac46a81..75804378aa 100644 --- a/libraries/entities/src/GrabPropertyGroup.h +++ b/libraries/entities/src/GrabPropertyGroup.h @@ -41,35 +41,33 @@ static const glm::vec3 INITIAL_EQUIPPABLE_INDICATOR_OFFSET { glm::vec3(0.0f) }; /**jsdoc - * Grab is defined by the following properties. + * Grabbing behavior is defined by the following properties: + * * @typedef {object} Entities.Grab - * - * @property {boolean} grabbable=true - If true the entity can be grabbed. - * @property {boolean} grabKinematic=true - If true the entity is updated in a kinematic manner. - * If false it will be grabbed using a tractor action. A kinematic grab will make the item appear more - * tightly held, but will cause it to behave poorly when interacting with dynamic entities. - * @property {boolean} grabFollowsController=true - If true the entity will follow the motions of the - * hand-controller even if the avatar's hand can't get to the implied position. This should be true - * for tools, pens, etc and false for things meant to decorate the hand. - * - * @property {boolean} triggerable=false - If true the entity will receive calls to trigger + * @property {boolean} grabbable=true - If true then the entity can be grabbed. + * @property {boolean} grabKinematic=true - If true then the entity will be updated in a kinematic manner when + * grabbed; if false it will be grabbed using a tractor action. A kinematic grab will make the item appear + * more tightly held but will cause it to behave poorly when interacting with dynamic entities. + * @property {boolean} grabFollowsController=true - If true then the entity will follow the motions of the hand + * controller even if the avatar's hand can't get to the implied position. This should be set true for tools, + * pens, etc. and false for things meant to decorate the hand. + * @property {boolean} triggerable=false - If true then the entity will receive calls to trigger * {@link Controller|Controller entity methods}. - * - * @property {boolean} equippable=true - If true the entity can be equipped. + * @property {boolean} grabDelegateToParent=true - If true and the entity is grabbed, the grab will be transferred + * to its parent entity if there is one; if false, a child entity can be grabbed and moved relative to its + * parent. + * @property {boolean} equippable=true - If true then the entity can be equipped. * @property {Vec3} equippableLeftPosition=0,0,0 - Positional offset from the left hand, when equipped. * @property {Quat} equippableLeftRotation=0,0,0,1 - Rotational offset from the left hand, when equipped. * @property {Vec3} equippableRightPosition=0,0,0 - Positional offset from the right hand, when equipped. * @property {Quat} equippableRightRotation=0,0,0,1 - Rotational offset from the right hand, when equipped. - * * @property {string} equippableIndicatorURL="" - If non-empty, this model will be used to indicate that an * entity is equippable, rather than the default. - * @property {Vec3} equippableIndicatorScale=1,1,1 - If equippableIndicatorURL is non-empty, this controls the + * @property {Vec3} equippableIndicatorScale=1,1,1 - If equippableIndicatorURL is non-empty, this controls the scale of the displayed indicator. - * @property {Vec3} equippableIndicatorOffset=0,0,0 - If equippableIndicatorURL is non-empty, this controls the + * @property {Vec3} equippableIndicatorOffset=0,0,0 - If equippableIndicatorURL is non-empty, this controls the relative offset of the displayed object from the equippable entity. */ - - class GrabPropertyGroup : public PropertyGroup { public: // EntityItemProperty related helpers diff --git a/libraries/entities/src/HazePropertyGroup.h b/libraries/entities/src/HazePropertyGroup.h index 595dbeaf51..116bfd9687 100644 --- a/libraries/entities/src/HazePropertyGroup.h +++ b/libraries/entities/src/HazePropertyGroup.h @@ -43,7 +43,7 @@ static const float INITIAL_KEY_LIGHT_ALTITUDE{ 200.0f }; // FIXME: Document hazeAttenuationKeyLight, hazeKeyLightRange, and hazeKeyLightAltitude once they're working and are provided // in the Create app's UI. /**jsdoc - * Haze is defined by the following properties. + * Haze is defined by the following properties: * @typedef {object} Entities.Haze * * @property {number} hazeRange=1000 - The horizontal distance at which visibility is reduced to 95%; i.e., 95% of each pixel's @@ -56,7 +56,7 @@ static const float INITIAL_KEY_LIGHT_ALTITUDE{ 200.0f }; * haze color are blended 50/50. * * @property {boolean} hazeAltitudeEffect=false - If true then haze decreases with altitude as defined by the - * entity's local coordinate system; hazeBaseRef and
hazeCeiling
are used. + * entity's local coordinate system; hazeBaseRef and hazeCeiling are used. * @property {number} hazeBaseRef=0 - The y-axis value in the entity's local coordinate system at which the haze density starts * reducing with altitude. * @property {number} hazeCeiling=200 - The y-axis value in the entity's local coordinate system at which the haze density has @@ -65,9 +65,11 @@ static const float INITIAL_KEY_LIGHT_ALTITUDE{ 200.0f }; * @property {number} hazeBackgroundBlend=0 - The proportion of the skybox image to show through the haze: 0.0 * displays no skybox image; 1.0 displays no haze. * - * @property {boolean} hazeAttenuateKeyLight=false - Currently not supported. - * @property {number} hazeKeyLightRange=1000 - Currently not supported. - * @property {number} hazeKeyLightAltitude=200 - Currently not supported. + * @property {boolean} hazeAttenuateKeyLight=false - If true then the haze attenuates the key light; + * hazeKeyLightRange and hazeKeyLightAltitude are used. + * @property {number} hazeKeyLightRange=1000 - The distance at which the haze attenuates the key light by 95%. + * @property {number} hazeKeyLightAltitude=200 - The altitude at which the haze starts attenuating the key light (i.e., the + * altitude at which the distance starts being calculated). */ class HazePropertyGroup : public PropertyGroup { public: diff --git a/libraries/entities/src/KeyLightPropertyGroup.h b/libraries/entities/src/KeyLightPropertyGroup.h index d7fa75a32e..31e0cba1c6 100644 --- a/libraries/entities/src/KeyLightPropertyGroup.h +++ b/libraries/entities/src/KeyLightPropertyGroup.h @@ -28,13 +28,13 @@ class EntityTreeElementExtraEncodeData; class ReadBitstreamToTreeParams; /**jsdoc - * A key light is defined by the following properties. + * A key light is defined by the following properties: * @typedef {object} Entities.KeyLight * @property {Color} color=255,255,255 - The color of the light. * @property {number} intensity=1 - The intensity of the light. * @property {Vec3} direction=0,-1,0 - The direction the light is shining. * @property {boolean} castShadows=false - If true then shadows are cast. Shadows are cast by avatars, plus - * {@link Entities.EntityType|Model} and {@link Entities.EntityType|Shape} entities that have their + * {@link Entities.EntityProperties-Model|Model} and {@link Entities.EntityProperties-Shape|Shape} entities that have their * {@link Entities.EntityProperties|canCastShadow} property set to true. */ class KeyLightPropertyGroup : public PropertyGroup { diff --git a/libraries/entities/src/PolyVoxEntityItem.h b/libraries/entities/src/PolyVoxEntityItem.h index a6076dfda7..9d02cbcdad 100644 --- a/libraries/entities/src/PolyVoxEntityItem.h +++ b/libraries/entities/src/PolyVoxEntityItem.h @@ -63,7 +63,7 @@ class PolyVoxEntityItem : public EntityItem { virtual int getOnCount() const { return 0; } /**jsdoc - *

A PolyVoxSurfaceStyle may be one of the following:

+ *

The surface of a {@link Entities.EntityProperties-PolyVox|PolyVox} entity may be one of the following styles:

* * * diff --git a/libraries/entities/src/PulsePropertyGroup.h b/libraries/entities/src/PulsePropertyGroup.h index f54db39149..634ab654a7 100644 --- a/libraries/entities/src/PulsePropertyGroup.h +++ b/libraries/entities/src/PulsePropertyGroup.h @@ -26,19 +26,17 @@ class OctreePacketData; class ReadBitstreamToTreeParams; /**jsdoc - * Pulse is defined by the following properties. + * A color and alpha pulse that an entity may have. * @typedef {object} Entities.Pulse - * * @property {number} min=0 - The minimum value of the pulse multiplier. * @property {number} max=1 - The maximum value of the pulse multiplier. * @property {number} period=1 - The duration of the color and alpha pulse, in seconds. A pulse multiplier value goes from * min to max, then max to min in one period. - * @property {PulseMode} colorMode="none" - If "in", the color is pulsed in phase with the pulse period; if "out" + * @property {Entities.PulseMode} colorMode="none" - If "in", the color is pulsed in phase with the pulse period; if "out" * the color is pulsed out of phase with the pulse period. - * @property {PulseMode} alphaMode="none" - If "in", the alpha is pulsed in phase with the pulse period; if "out" + * @property {Entities.PulseMode} alphaMode="none" - If "in", the alpha is pulsed in phase with the pulse period; if "out" * the alpha is pulsed out of phase with the pulse period. */ - class PulsePropertyGroup : public PropertyGroup { public: // EntityItemProperty related helpers diff --git a/libraries/entities/src/RingGizmoPropertyGroup.h b/libraries/entities/src/RingGizmoPropertyGroup.h index 42202eb509..1f23152fc7 100644 --- a/libraries/entities/src/RingGizmoPropertyGroup.h +++ b/libraries/entities/src/RingGizmoPropertyGroup.h @@ -25,31 +25,32 @@ class ReadBitstreamToTreeParams; using u8vec3Color = glm::u8vec3; /**jsdoc - * A RingGizmo is defined by the following properties. + * A {@link Entities.EntityProperties-Gizmo|ring Gizmo} entity is defined by the following properties: * @typedef {object} Entities.RingGizmo * - * @property {number} startAngle=0 - The angle at which the ring will start, in degrees. - * @property {number} endAngle=360 - The angle at which the ring will end, in degrees. - * @property {number} innerRadius=0 - The inner radius of the ring as a fraction of the total radius. 0-1. + * @property {number} startAngle=0 - The angle at which the ring starts, in degrees. + * @property {number} endAngle=360 - The angle at which the ring ends, in degrees. + * @property {number} innerRadius=0 - The inner radius of the ring as a fraction of the total radius, range 0.0 + * — 1.0. - * @property {Color} innerStartColor - The color at the inner start point of the ring. - * @property {Color} innerEndColor - The color at the inner end point of the ring. - * @property {Color} outerStartColor - The color at the outer start point of the ring. - * @property {Color} outerEndColor - The color at the outer end point of the ring. - * @property {number} innerStartAlpha=1 - The alpha at the inner start point of the ring. - * @property {number} innerEndAlpha=1 - The alpha at the inner end point of the ring. - * @property {number} outerStartAlpha=1 - The alpha at the outer start point of the ring. - * @property {number} outerEndAlpha=1 - The alpha at the outer end point of the ring. + * @property {Color} innerStartColor=255,255,255 - The color at the inner start point of the ring. + * @property {Color} innerEndColor=255,255,255 - The color at the inner end point of the ring. + * @property {Color} outerStartColor=255,255,255 - The color at the outer start point of the ring. + * @property {Color} outerEndColor=255,255,255 - The color at the outer end point of the ring. + * @property {number} innerStartAlpha=1 - The opacity at the inner start point of the ring. + * @property {number} innerEndAlpha=1 - The opacity at the inner end point of the ring. + * @property {number} outerStartAlpha=1 - The opacity at the outer start point of the ring. + * @property {number} outerEndAlpha=1 - The opacity at the outer end point of the ring. - * @property {boolean} hasTickMarks=false - Whether or not to render tick marks. - * @property {number} majorTickMarksAngle - The angle between major tick marks, in degrees. - * @property {number} minorTickMarksAngle - The angle between minor tick marks, in degrees. - * @property {number} majorTickMarksLength - The length of the major tick marks, as a fraction of the radius. A positive value draws tick marks - * outwards from the inner radius; a negative value draws tick marks inwards from the outer radius. - * @property {number} minorTickMarksLength - The length of the minor tick marks, as a fraction of the radius. A positive value draws tick marks - * outwards from the inner radius; a negative value draws tick marks inwards from the outer radius. - * @property {Color} majorTickMarksColor - The color of the major tick marks. - * @property {Color} minorTickMarksColor - The color of the minor tick marks. + * @property {boolean} hasTickMarks=false - true to render tick marks, otherwise false. + * @property {number} majorTickMarksAngle=0 - The angle between major tick marks, in degrees. + * @property {number} minorTickMarksAngle=0 - The angle between minor tick marks, in degrees. + * @property {number} majorTickMarksLength=0 - The length of the major tick marks as a fraction of the radius. A positive value + * draws tick marks outwards from the inner radius; a negative value draws tick marks inwards from the outer radius. + * @property {number} minorTickMarksLength=0 - The length of the minor tick marks, as a fraction of the radius. A positive + * value draws tick marks outwards from the inner radius; a negative value draws tick marks inwards from the outer radius. + * @property {Color} majorTickMarksColor=255,255,255 - The color of the major tick marks. + * @property {Color} minorTickMarksColor=255,255,255 - The color of the minor tick marks. */ class RingGizmoPropertyGroup : public PropertyGroup { diff --git a/libraries/entities/src/ShapeEntityItem.cpp b/libraries/entities/src/ShapeEntityItem.cpp index 869ae2985f..513daa0b09 100644 --- a/libraries/entities/src/ShapeEntityItem.cpp +++ b/libraries/entities/src/ShapeEntityItem.cpp @@ -22,8 +22,8 @@ namespace entity { /**jsdoc - *

A Shape, Box, or Sphere {@link Entities.EntityType|EntityType} may display as - * one of the following geometrical shapes:

+ *

A "Shape", "Box", or "Sphere" {@link Entities.EntityType|EntityType} may + * display as one of the following geometrical shapes:

*
ValueTypeDescription
* * diff --git a/libraries/entities/src/SkyboxPropertyGroup.h b/libraries/entities/src/SkyboxPropertyGroup.h index c3f9b421f4..0c128aa730 100644 --- a/libraries/entities/src/SkyboxPropertyGroup.h +++ b/libraries/entities/src/SkyboxPropertyGroup.h @@ -30,7 +30,7 @@ class EntityTreeElementExtraEncodeData; class ReadBitstreamToTreeParams; /**jsdoc - * A skybox is defined by the following properties. + * A skybox is defined by the following properties: * @typedef {object} Entities.Skybox * @property {Color} color=0,0,0 - Sets the color of the sky if url is "", otherwise modifies the * color of the cube map image. diff --git a/libraries/gl/src/gl/Context.cpp b/libraries/gl/src/gl/Context.cpp index a0d52ee223..d5d06d1195 100644 --- a/libraries/gl/src/gl/Context.cpp +++ b/libraries/gl/src/gl/Context.cpp @@ -27,6 +27,10 @@ #include "GLHelpers.h" #include "QOpenGLContextWrapper.h" +#if defined(GL_CUSTOM_CONTEXT) +#include +#endif + using namespace gl; #if defined(GL_CUSTOM_CONTEXT) @@ -42,7 +46,10 @@ std::atomic Context::_totalSwapchainMemoryUsage { 0 }; size_t Context::getSwapchainMemoryUsage() { return _totalSwapchainMemoryUsage.load(); } size_t Context::evalSurfaceMemoryUsage(uint32_t width, uint32_t height, uint32_t pixelSize) { - return width * height * pixelSize; + size_t result = width; + result *= height; + result *= pixelSize; + return result; } void Context::updateSwapchainMemoryUsage(size_t prevSize, size_t newSize) { @@ -126,7 +133,7 @@ void Context::clear() { #if defined(GL_CUSTOM_CONTEXT) static void setupPixelFormatSimple(HDC hdc) { - // FIXME build the PFD based on the + // FIXME build the PFD based on the static const PIXELFORMATDESCRIPTOR pfd = // pfd Tells Windows How We Want Things To Be { sizeof(PIXELFORMATDESCRIPTOR), // Size Of This Pixel Format Descriptor @@ -176,6 +183,7 @@ static void setupPixelFormatSimple(HDC hdc) { #define WGL_CONTEXT_PROFILE_MASK_ARB 0x9126 // Context create flag bits +#define WGL_CONTEXT_ROBUST_ACCESS_BIT_ARB 0x00000004 #define WGL_CONTEXT_DEBUG_BIT_ARB 0x00000001 #define WGL_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001 #define WGL_CONTEXT_ES2_PROFILE_BIT_EXT 0x00000004 @@ -196,17 +204,17 @@ GLAPI PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribsARB; Q_GUI_EXPORT QOpenGLContext *qt_gl_global_share_context(); #if defined(GL_CUSTOM_CONTEXT) -bool Context::makeCurrent() { - BOOL result = wglMakeCurrent(_hdc, _hglrc); - assert(result); - updateSwapchainMemoryCounter(); - return result; -} - void Context::swapBuffers() { - SwapBuffers(_hdc); -} - void Context::doneCurrent() { - wglMakeCurrent(0, 0); +bool Context::makeCurrent() { + BOOL result = wglMakeCurrent(_hdc, _hglrc); + assert(result); + updateSwapchainMemoryCounter(); + return result; +} +void Context::swapBuffers() { + SwapBuffers(_hdc); +} +void Context::doneCurrent() { + wglMakeCurrent(0, 0); } #endif @@ -305,11 +313,18 @@ void Context::create(QOpenGLContext* shareContext) { #else contextAttribs.push_back(WGL_CONTEXT_CORE_PROFILE_BIT_ARB); #endif - contextAttribs.push_back(WGL_CONTEXT_FLAGS_ARB); - if (enableDebugLogger()) { - contextAttribs.push_back(WGL_CONTEXT_DEBUG_BIT_ARB); - } else { - contextAttribs.push_back(0); + { + int contextFlags = 0; + if (enableDebugLogger()) { + contextFlags |= WGL_CONTEXT_DEBUG_BIT_ARB; + } +#ifdef USE_KHR_ROBUSTNESS + contextFlags |= WGL_CONTEXT_ROBUST_ACCESS_BIT_ARB; +#endif + if (contextFlags != 0) { + contextAttribs.push_back(WGL_CONTEXT_FLAGS_ARB); + contextAttribs.push_back(contextFlags); + } } contextAttribs.push_back(0); HGLRC shareHglrc = nullptr; @@ -323,8 +338,8 @@ void Context::create(QOpenGLContext* shareContext) { if (_hglrc != 0) { createWrapperContext(); } - } - + } + if (_hglrc == 0) { // fallback, if the context creation failed, or USE_CUSTOM_CONTEXT is false qtCreate(shareContext); diff --git a/libraries/gl/src/gl/Context.h b/libraries/gl/src/gl/Context.h index 5254d58d38..7beb59e33f 100644 --- a/libraries/gl/src/gl/Context.h +++ b/libraries/gl/src/gl/Context.h @@ -23,7 +23,7 @@ class QOpenGLContext; class QThread; class QOpenGLDebugMessage; -#if defined(Q_OS_WIN) && defined(USE_GLES) +#if defined(Q_OS_WIN) && (defined(USE_GLES) || defined(USE_KHR_ROBUSTNESS)) //#if defined(Q_OS_WIN) #define GL_CUSTOM_CONTEXT #endif diff --git a/libraries/material-networking/src/material-networking/MaterialCache.cpp b/libraries/material-networking/src/material-networking/MaterialCache.cpp index 5e86ea506a..4f6d149fbe 100644 --- a/libraries/material-networking/src/material-networking/MaterialCache.cpp +++ b/libraries/material-networking/src/material-networking/MaterialCache.cpp @@ -70,10 +70,10 @@ bool NetworkMaterialResource::parseJSONColor(const QJsonValue& array, glm::vec3& } /**jsdoc - * A material or set of materials such as may be used by a {@link Entities.EntityType|Material} entity. - * @typedef {object} MaterialResource + * A material or set of materials used by a {@link Entities.EntityType|Material entity}. + * @typedef {object} Entities.MaterialResource * @property {number} materialVersion=1 - The version of the material. Currently not used. - * @property {Material|Material[]} materials - The details of the material or materials. + * @property {Entities.Material|Entities.Material[]} materials - The details of the material or materials. */ NetworkMaterialResource::ParsedMaterials NetworkMaterialResource::parseJSONMaterials(const QJsonDocument& materialJSON, const QUrl& baseUrl) { ParsedMaterials toReturn; @@ -109,60 +109,72 @@ NetworkMaterialResource::ParsedMaterials NetworkMaterialResource::parseJSONMater } /**jsdoc - * A material such as may be used by a {@link Entities.EntityType|Material} entity. - * @typedef {object} Material + * A material used in a {@link Entities.MaterialResource|MaterialResource}. + * @typedef {object} Entities.Material * @property {string} model="hifi_pbr" - Different material models support different properties and rendering modes. - * Supported models are: "hifi_pbr" + * Supported models are: "hifi_pbr". * @property {string} name="" - A name for the material. Supported by all material models. - * @property {Color|RGBS|string} emissive - The emissive color, i.e., the color that the material emits. A {@link Color} value - * is treated as sRGB. A {@link RGBS} value can be either RGB or sRGB. Set to "fallthrough" to fallthrough to - * the material below. "hifi_pbr" model only. - * @property {number|string} opacity=1.0 - The opacity, 0.01.0. Set to "fallthrough" to fallthrough to - * the material below. "hifi_pbr" model only. - * @property {boolean|string} unlit=false - If true, the material is not lit. Set to "fallthrough" to fallthrough to - * the material below. "hifi_pbr" model only. - * @property {Color|RGBS|string} albedo - The albedo color. A {@link Color} value is treated as sRGB. A {@link RGBS} value can - * be either RGB or sRGB. Set to "fallthrough" to fallthrough to the material below. Set to "fallthrough" to fallthrough to - * the material below. "hifi_pbr" model only. - * @property {number|string} roughness - The roughness, 0.01.0. Set to "fallthrough" to fallthrough to - * the material below. "hifi_pbr" model only. - * @property {number|string} metallic - The metallicness, 0.01.0. Set to "fallthrough" to fallthrough to - * the material below. "hifi_pbr" model only. - * @property {number|string} scattering - The scattering, 0.01.0. Set to "fallthrough" to fallthrough to - * the material below. "hifi_pbr" model only. - * @property {string} emissiveMap - URL of emissive texture image. Set to "fallthrough" to fallthrough to - * the material below. "hifi_pbr" model only. - * @property {string} albedoMap - URL of albedo texture image. Set to "fallthrough" to fallthrough to - * the material below. "hifi_pbr" model only. - * @property {string} opacityMap - URL of opacity texture image. Set value the same as the albedoMap value for - * transparency. "hifi_pbr" model only. - * @property {string} roughnessMap - URL of roughness texture image. Can use this or glossMap, but not both. Set to "fallthrough" - * to fallthrough to the material below. "hifi_pbr" model only. - * @property {string} glossMap - URL of gloss texture image. Can use this or roughnessMap, but not both. Set to "fallthrough" - * to fallthrough to the material below. "hifi_pbr" model only. - * @property {string} metallicMap - URL of metallic texture image. Can use this or specularMap, but not both. Set to "fallthrough" - * to fallthrough to the material below. "hifi_pbr" model only. - * @property {string} specularMap - URL of specular texture image. Can use this or metallicMap, but not both. Set to "fallthrough" - * to fallthrough to the material below. "hifi_pbr" model only. - * @property {string} normalMap - URL of normal texture image. Can use this or bumpMap, but not both. Set to "fallthrough" - * to fallthrough to the material below. "hifi_pbr" model only. - * @property {string} bumpMap - URL of bump texture image. Can use this or normalMap, but not both. Set to "fallthrough" - * to fallthrough to the material below. "hifi_pbr" model only. - * @property {string} occlusionMap - URL of occlusion texture image. Set to "fallthrough" to fallthrough to the material below. "hifi_pbr" model only. - * @property {string} scatteringMap - URL of scattering texture image. Only used if normalMap or - * bumpMap is specified. Set to "fallthrough" to fallthrough to the material below. "hifi_pbr" model only. - * @property {string} lightMap - URL of light map texture image. Currently not used.. Set to "fallthrough" - * to fallthrough to the material below. "hifi_pbr" model only. - * @property {string} texCoordTransform0 - The transform to use for all of the maps besides occlusionMap and lightMap. Currently unused. Set to - * "fallthrough" to fallthrough to the material below. "hifi_pbr" model only. - * @property {string} texCoordTransform1 - The transform to use for occlusionMap and lightMap. Currently unused. Set to "fallthrough" - * to fallthrough to the material below. "hifi_pbr" model only. - * @property {string} lightmapParams - Parameters for controlling how lightMap is used. Currently unused. Set to "fallthrough" - * to fallthrough to the material below. "hifi_pbr" model only. - * @property {string} materialParams - Parameters for controlling the material projection and repition. Currently unused. Set to "fallthrough" - * to fallthrough to the material below. "hifi_pbr" model only. - * @property {bool} defaultFallthrough=false - If true, all properties will fallthrough to the material below unless they are set. If - * false, they will respect the individual properties' fallthrough state. "hifi_pbr" model only. + * @property {ColorFloat|RGBS|string} emissive - The emissive color, i.e., the color that the material emits. A + * {@link ColorFloat} value is treated as sRGB and must have component values in the range 0.0 — + * 1.0. A {@link RGBS} value can be either RGB or sRGB. + * Set to "fallthrough" to fall through to the material below. "hifi_pbr" model only. + * @property {number|string} opacity=1.0 - The opacity, range 0.01.0. + * Set to "fallthrough" to fall through to the material below. "hifi_pbr" model only. + * @property {boolean|string} unlit=false - If true, the material is not lit, otherwise it is. + * Set to "fallthrough" to fall through to the material below. "hifi_pbr" model only. + * @property {ColorFloat|RGBS|string} albedo - The albedo color. A {@link ColorFloat} value is treated as sRGB and must have + * component values in the range 0.01.0. A {@link RGBS} value can be either RGB or sRGB. + * Set to "fallthrough" to fall through to the material below. "hifi_pbr" model only. + * @property {number|string} roughness - The roughness, range 0.01.0. + * Set to "fallthrough" to fall through to the material below. "hifi_pbr" model only. + * @property {number|string} metallic - The metallicness, range 0.01.0. + * Set to "fallthrough" to fall through to the material below. "hifi_pbr" model only. + * @property {number|string} scattering - The scattering, range 0.01.0. + * Set to "fallthrough" to fall through to the material below. "hifi_pbr" model only. + * @property {string} emissiveMap - The URL of the emissive texture image. + * Set to "fallthrough" to fall through to the material below. "hifi_pbr" model only. + * @property {string} albedoMap - The URL of the albedo texture image. + * Set to "fallthrough" to fall through to the material below. "hifi_pbr" model only. + * @property {string} opacityMap - The URL of the opacity texture image. Set the value the same as the albedoMap + * value for transparency. + * "hifi_pbr" model only. + * @property {string} roughnessMap - The URL of the roughness texture image. You can use this or glossMap, but not + * both. + * Set to "fallthrough" to fall through to the material below. "hifi_pbr" model only. + * @property {string} glossMap - The URL of the gloss texture image. You can use this or roughnessMap, but not + * both. + * Set to "fallthrough" to fall through to the material below. "hifi_pbr" model only. + * @property {string} metallicMap - The URL of the metallic texture image. You can use this or specularMap, but + * not both. + * Set to "fallthrough" to fall through to the material below. "hifi_pbr" model only. + * @property {string} specularMap - The URL of the specular texture image. You can use this or metallicMap, but + * not both. + * Set to "fallthrough" to fall through to the material below. "hifi_pbr" model only. + * @property {string} normalMap - The URL of the normal texture image. You can use this or bumpMap, but not both. + * Set to "fallthrough" to fall through to the material below. "hifi_pbr" model only. + * @property {string} bumpMap - The URL of the bump texture image. You can use this or normalMap, but not both. + * Set to "fallthrough" to fall through to the material below. "hifi_pbr" model only. + * @property {string} occlusionMap - The URL of the occlusion texture image. + * Set to "fallthrough" to fall through to the material below. "hifi_pbr" model only. + * @property {string} scatteringMap - The URL of the scattering texture image. Only used if normalMap or + * bumpMap is specified. + * Set to "fallthrough" to fall through to the material below. "hifi_pbr" model only. + * @property {string} lightMap - The URL of the light map texture image. + * Set to "fallthrough" to fall through to the material below. "hifi_pbr" model only. + * @property {Mat4|string} texCoordTransform0 - The transform to use for all of the maps apart from occlusionMap + * and lightMap. + * Set to "fallthrough" to fall through to the material below. "hifi_pbr" model only. + * @property {Mat4|string} texCoordTransform1 - The transform to use for occlusionMap and lightMap. + * Set to "fallthrough" to fall through to the material below. "hifi_pbr" model only. + * @property {string} lightmapParams - Parameters for controlling how lightMap is used. + * Set to "fallthrough" to fall through to the material below. "hifi_pbr" model only. + *

Currently not used.

+ * @property {string} materialParams - Parameters for controlling the material projection and repetition. + * Set to "fallthrough" to fall through to the material below. "hifi_pbr" model only. + *

Currently not used.

+ * @property {boolean} defaultFallthrough=false - If true, all properties fall through to the material below + * unless they are set. If false, they respect their individual fall-through setting. "hifi_pbr" + * model only. */ // Note: See MaterialEntityItem.h for default values used in practice. std::pair> NetworkMaterialResource::parseJSONMaterial(const QJsonObject& materialJSON, const QUrl& baseUrl) { @@ -399,7 +411,7 @@ std::pair> NetworkMaterialResource material->setPropertyDoesFallthrough(graphics::Material::ExtraFlagBit::LIGHTMAP_PARAMS); } } - // TODO: implement lightmapParams + // TODO: implement lightmapParams and update JSDoc } else if (key == "materialParams") { auto value = materialJSON.value(key); if (value.isString()) { @@ -408,7 +420,7 @@ std::pair> NetworkMaterialResource material->setPropertyDoesFallthrough(graphics::Material::ExtraFlagBit::MATERIAL_PARAMS); } } - // TODO: implement materialParams + // TODO: implement materialParams and update JSDoc } else if (key == "defaultFallthrough") { auto value = materialJSON.value(key); if (value.isBool()) { diff --git a/libraries/networking/CMakeLists.txt b/libraries/networking/CMakeLists.txt index c3592c5da2..9f63f2cb00 100644 --- a/libraries/networking/CMakeLists.txt +++ b/libraries/networking/CMakeLists.txt @@ -1,6 +1,6 @@ set(TARGET_NAME networking) setup_hifi_library(Network) -link_hifi_libraries(shared) +link_hifi_libraries(shared platform) target_openssl() target_tbb() diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp index 3a7d3e0a67..e2e9d33eb6 100644 --- a/libraries/networking/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -956,7 +956,7 @@ void AccountManager::saveLoginStatus(bool isLoggedIn) { QProcess launcher; launcher.setProgram(launcherPath); launcher.startDetached(); - qApp->quit(); + QMetaObject::invokeMethod(qApp, "quit", Qt::QueuedConnection); } } } \ No newline at end of file diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index 8fefe5820c..ac0f9e0b07 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -450,13 +450,16 @@ qint64 LimitedNodeList::sendPacket(std::unique_ptr packet, const HifiS auto size = sendUnreliablePacket(*packet, sockAddr, hmacAuth); if (size < 0) { auto now = usecTimestampNow(); - eachNode([now](const SharedNodePointer & node) { - qCDebug(networking) << "Stats for " << node->getPublicSocket() << "\n" - << " Last Heard Microstamp: " << node->getLastHeardMicrostamp() << " (" << (now - node->getLastHeardMicrostamp()) << "usec ago)\n" - << " Outbound Kbps: " << node->getOutboundKbps() << "\n" - << " Inbound Kbps: " << node->getInboundKbps() << "\n" - << " Ping: " << node->getPingMs(); - }); + if (now - _sendErrorStatsTime > ERROR_STATS_PERIOD_US) { + _sendErrorStatsTime = now; + eachNode([now](const SharedNodePointer& node) { + qCDebug(networking) << "Stats for " << node->getPublicSocket() << "\n" + << " Last Heard Microstamp: " << node->getLastHeardMicrostamp() << " (" << (now - node->getLastHeardMicrostamp()) << "usec ago)\n" + << " Outbound Kbps: " << node->getOutboundKbps() << "\n" + << " Inbound Kbps: " << node->getInboundKbps() << "\n" + << " Ping: " << node->getPingMs(); + }); + } } return size; } @@ -996,7 +999,7 @@ void LimitedNodeList::sendSTUNRequest() { const int NUM_INITIAL_STUN_REQUESTS_BEFORE_FAIL = 10; if (!_hasCompletedInitialSTUN) { - qCDebug(networking) << "Sending intial stun request to" << STUN_SERVER_HOSTNAME; + qCDebug(networking) << "Sending initial stun request to" << STUN_SERVER_HOSTNAME; if (_numInitialSTUNRequests > NUM_INITIAL_STUN_REQUESTS_BEFORE_FAIL) { // we're still trying to do our initial STUN we're over the fail threshold @@ -1185,7 +1188,7 @@ void LimitedNodeList::stopInitialSTUNUpdate(bool success) { // We now setup a timer here to fire every so often to check that our IP address has not changed. // Or, if we failed - if will check if we can eventually get a public socket - const int STUN_IP_ADDRESS_CHECK_INTERVAL_MSECS = 30 * 1000; + const int STUN_IP_ADDRESS_CHECK_INTERVAL_MSECS = 10 * 1000; QTimer* stunOccasionalTimer = new QTimer { this }; connect(stunOccasionalTimer, &QTimer::timeout, this, &LimitedNodeList::sendSTUNRequest); @@ -1243,15 +1246,22 @@ void LimitedNodeList::errorTestingLocalSocket() { } void LimitedNodeList::setLocalSocket(const HifiSockAddr& sockAddr) { - if (sockAddr != _localSockAddr) { + if (sockAddr.getAddress() != _localSockAddr.getAddress()) { if (_localSockAddr.isNull()) { qCInfo(networking) << "Local socket is" << sockAddr; + _localSockAddr = sockAddr; } else { qCInfo(networking) << "Local socket has changed from" << _localSockAddr << "to" << sockAddr; + _localSockAddr = sockAddr; + if (_hasTCPCheckedLocalSocket) { // Force a port change for NAT: + reset(); + _nodeSocket.rebind(0); + _localSockAddr.setPort(_nodeSocket.localPort()); + qCInfo(networking) << "Local port changed to" << _localSockAddr.getPort(); + } } - _localSockAddr = sockAddr; emit localSockAddrChanged(_localSockAddr); } } diff --git a/libraries/networking/src/LimitedNodeList.h b/libraries/networking/src/LimitedNodeList.h index f9f6bf3b3e..5f24401b10 100644 --- a/libraries/networking/src/LimitedNodeList.h +++ b/libraries/networking/src/LimitedNodeList.h @@ -497,6 +497,9 @@ private: float _outboundKbps { 0.0f }; bool _dropOutgoingNodeTraffic { false }; + + quint64 _sendErrorStatsTime { (quint64)0 }; + static const quint64 ERROR_STATS_PERIOD_US { 1 * USECS_PER_SECOND }; }; #endif // hifi_LimitedNodeList_h diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 3d367bc761..9dd7716823 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -26,6 +26,8 @@ #include #include #include +#include +#include #include "AccountManager.h" #include "AddressManager.h" @@ -42,6 +44,7 @@ using namespace std::chrono; const int KEEPALIVE_PING_INTERVAL_MS = 1000; +const int MAX_SYSTEM_INFO_SIZE = 1000; NodeList::NodeList(char newOwnerType, int socketListenPort, int dtlsListenPort) : LimitedNodeList(socketListenPort, dtlsListenPort), @@ -418,6 +421,21 @@ void NodeList::sendDomainServerCheckIn() { auto accountManager = DependencyManager::get(); packetStream << FingerprintUtils::getMachineFingerprint(); + auto desc = platform::getAll(); + + QByteArray systemInfo(desc.dump().c_str()); + QByteArray compressedSystemInfo = qCompress(systemInfo); + + if (compressedSystemInfo.size() > MAX_SYSTEM_INFO_SIZE) { + // Highly unlikely, as not even unreasonable machines will + // overflow the max size, but prevent MTU overflow anyway. + // We could do something sophisticated like clearing specific + // values if they're too big, but we'll save that for later. + compressedSystemInfo.clear(); + } + + packetStream << compressedSystemInfo; + packetStream << _connectReason; if (_nodeDisconnectTimestamp < _nodeConnectTimestamp) { diff --git a/libraries/networking/src/ThreadedAssignment.cpp b/libraries/networking/src/ThreadedAssignment.cpp index 2b415073f2..b8a8f65080 100644 --- a/libraries/networking/src/ThreadedAssignment.cpp +++ b/libraries/networking/src/ThreadedAssignment.cpp @@ -20,6 +20,7 @@ #include #include +#include #include "NetworkLogging.h" ThreadedAssignment::ThreadedAssignment(ReceivedMessage& message) : @@ -38,6 +39,16 @@ ThreadedAssignment::ThreadedAssignment(ReceivedMessage& message) : // if the NL tells us we got a DS response, clear our member variable of queued check-ins auto nodeList = DependencyManager::get(); connect(nodeList.data(), &NodeList::receivedDomainServerList, this, &ThreadedAssignment::clearQueuedCheckIns); + + platform::create(); + if (!platform::enumeratePlatform()) { + qCDebug(networking) << "Failed to enumerate platform."; + } +} + +ThreadedAssignment::~ThreadedAssignment() { + stop(); + platform::destroy(); } void ThreadedAssignment::setFinished(bool isFinished) { diff --git a/libraries/networking/src/ThreadedAssignment.h b/libraries/networking/src/ThreadedAssignment.h index e76533b2a1..12096cf23f 100644 --- a/libraries/networking/src/ThreadedAssignment.h +++ b/libraries/networking/src/ThreadedAssignment.h @@ -22,7 +22,7 @@ class ThreadedAssignment : public Assignment { Q_OBJECT public: ThreadedAssignment(ReceivedMessage& message); - ~ThreadedAssignment() { stop(); } + ~ThreadedAssignment(); virtual void aboutToFinish() { }; void addPacketStatsAndSendStatsPacket(QJsonObject statsObject); diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index 30066b68e8..ed68fe89dc 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -72,7 +72,7 @@ PacketVersion versionForPacketType(PacketType packetType) { return static_cast(DomainConnectionDeniedVersion::IncludesExtraInfo); case PacketType::DomainConnectRequest: - return static_cast(DomainConnectRequestVersion::HasReason); + return static_cast(DomainConnectRequestVersion::HasCompressedSystemInfo); case PacketType::DomainServerAddedNode: return static_cast(DomainServerAddedNodeVersion::PermissionsGrid); diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 93a5d4e2b4..6230b8b11e 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -346,7 +346,9 @@ enum class DomainConnectRequestVersion : PacketVersion { HasMachineFingerprint, AlwaysHasMachineFingerprint, HasTimestamp, - HasReason + HasReason, + HasSystemInfo, + HasCompressedSystemInfo }; enum class DomainConnectionDeniedVersion : PacketVersion { diff --git a/libraries/networking/src/udt/Socket.cpp b/libraries/networking/src/udt/Socket.cpp index 3a7a056c77..c56f276560 100644 --- a/libraries/networking/src/udt/Socket.cpp +++ b/libraries/networking/src/udt/Socket.cpp @@ -33,6 +33,7 @@ using namespace udt; Socket::Socket(QObject* parent, bool shouldChangeSocketOptions) : QObject(parent), + _udpSocket(parent), _readyReadBackupTimer(new QTimer(this)), _shouldChangeSocketOptions(shouldChangeSocketOptions) { @@ -50,6 +51,7 @@ Socket::Socket(QObject* parent, bool shouldChangeSocketOptions) : } void Socket::bind(const QHostAddress& address, quint16 port) { + _udpSocket.bind(address, port); if (_shouldChangeSocketOptions) { @@ -75,7 +77,7 @@ void Socket::rebind() { } void Socket::rebind(quint16 localPort) { - _udpSocket.close(); + _udpSocket.abort(); bind(QHostAddress::AnyIPv4, localPort); } diff --git a/libraries/octree/src/OctreeEditPacketSender.cpp b/libraries/octree/src/OctreeEditPacketSender.cpp index d8d3041270..7b84205691 100644 --- a/libraries/octree/src/OctreeEditPacketSender.cpp +++ b/libraries/octree/src/OctreeEditPacketSender.cpp @@ -46,6 +46,7 @@ bool OctreeEditPacketSender::serversExist() const { void OctreeEditPacketSender::queuePacketToNode(const QUuid& nodeUUID, std::unique_ptr packet) { bool wantDebug = false; + QMutexLocker lock(&_packetsQueueLock); DependencyManager::get()->eachNode([&](const SharedNodePointer& node){ // only send to the NodeTypes that are getMyNodeType() if (node->getType() == getMyNodeType() @@ -324,6 +325,8 @@ bool OctreeEditPacketSender::process() { void OctreeEditPacketSender::processNackPacket(ReceivedMessage& message, SharedNodePointer sendingNode) { // parse sending node from packet, retrieve packet history for that node + QMutexLocker lock(&_packetsQueueLock); + // if packet history doesn't exist for the sender node (somehow), bail if (_sentPacketHistories.count(sendingNode->getUUID()) == 0) { return; @@ -345,7 +348,7 @@ void OctreeEditPacketSender::processNackPacket(ReceivedMessage& message, SharedN } void OctreeEditPacketSender::nodeKilled(SharedNodePointer node) { - // TODO: add locks + QMutexLocker lock(&_packetsQueueLock); QUuid nodeUUID = node->getUUID(); _pendingEditPackets.erase(nodeUUID); _outgoingSequenceNumbers.erase(nodeUUID); diff --git a/libraries/octree/src/OctreeEditPacketSender.h b/libraries/octree/src/OctreeEditPacketSender.h index 1dad5b74c7..318f6b81bb 100644 --- a/libraries/octree/src/OctreeEditPacketSender.h +++ b/libraries/octree/src/OctreeEditPacketSender.h @@ -86,19 +86,20 @@ protected: void processPreServerExistsPackets(); // These are packets which are destined from know servers but haven't been released because they're still too small + // protected by _packetsQueueLock std::unordered_map _pendingEditPackets; // These are packets that are waiting to be processed because we don't yet know if there are servers int _maxPendingMessages; bool _releaseQueuedMessagesPending; QMutex _pendingPacketsLock; - QMutex _packetsQueueLock; // don't let different threads release the queue while another thread is writing to it + QMutex _packetsQueueLock{ QMutex::Recursive }; // don't let different threads release the queue while another thread is writing to it std::list _preServerEdits; // these will get packed into other larger packets std::list> _preServerSingleMessagePackets; // these will go out as is QMutex _releaseQueuedPacketMutex; - // TODO: add locks for this and _pendingEditPackets + // protected by _packetsQueueLock std::unordered_map _sentPacketHistories; std::unordered_map _outgoingSequenceNumbers; }; diff --git a/libraries/octree/src/OctreeScriptingInterface.h b/libraries/octree/src/OctreeScriptingInterface.h index e501dd166a..c7b0552511 100644 --- a/libraries/octree/src/OctreeScriptingInterface.h +++ b/libraries/octree/src/OctreeScriptingInterface.h @@ -37,7 +37,7 @@ private slots: public slots: /**jsdoc - * Set the maximum number of entity packets that the client can send per second. + * Sets the maximum number of entity packets that the client can send per second. * @function Entities.setPacketsPerSecond * @param {number} packetsPerSecond - Integer maximum number of entity packets that the client can send per second. */ @@ -45,7 +45,7 @@ public slots: void setPacketsPerSecond(int packetsPerSecond) { return _packetSender->setPacketsPerSecond(packetsPerSecond); } /**jsdoc - * Get the maximum number of entity packets that the client can send per second. + * Gets the maximum number of entity packets that the client can send per second. * @function Entities.getPacketsPerSecond * @returns {number} Integer maximum number of entity packets that the client can send per second. */ @@ -53,7 +53,7 @@ public slots: int getPacketsPerSecond() const { return _packetSender->getPacketsPerSecond(); } /**jsdoc - * Check whether servers exist for the client to send entity packets to, i.e., whether you are connected to a domain and + * Checks whether servers exist for the client to send entity packets to, i.e., whether you are connected to a domain and * its entity server is working. * @function Entities.serversExist * @returns {boolean} true if servers exist for the client to send entity packets to, otherwise @@ -63,7 +63,7 @@ public slots: bool serversExist() const { return _packetSender->serversExist(); } /**jsdoc - * Check whether the client has entity packets waiting to be sent. + * Checks whether the client has entity packets waiting to be sent. * @function Entities.hasPacketsToSend * @returns {boolean} true if the client has entity packets waiting to be sent, otherwise false. */ @@ -71,7 +71,7 @@ public slots: bool hasPacketsToSend() const { return _packetSender->hasPacketsToSend(); } /**jsdoc - * Get the number of entity packets the client has waiting to be sent. + * Gets the number of entity packets the client has waiting to be sent. * @function Entities.packetsToSendCount * @returns {number} Integer number of entity packets the client has waiting to be sent. */ @@ -79,7 +79,7 @@ public slots: int packetsToSendCount() const { return (int)_packetSender->packetsToSendCount(); } /**jsdoc - * Get the entity packets per second send rate of the client over its lifetime. + * Gets the entity packets per second send rate of the client over its lifetime. * @function Entities.getLifetimePPS * @returns {number} Entity packets per second send rate of the client over its lifetime. */ @@ -87,7 +87,7 @@ public slots: float getLifetimePPS() const { return _packetSender->getLifetimePPS(); } /**jsdoc - * Get the entity bytes per second send rate of the client over its lifetime. + * Gets the entity bytes per second send rate of the client over its lifetime. * @function Entities.getLifetimeBPS * @returns {number} Entity bytes per second send rate of the client over its lifetime. */ @@ -95,7 +95,7 @@ public slots: float getLifetimeBPS() const { return _packetSender->getLifetimeBPS(); } /**jsdoc - * Get the entity packets per second queued rate of the client over its lifetime. + * Gets the entity packets per second queued rate of the client over its lifetime. * @function Entities.getLifetimePPSQueued * @returns {number} Entity packets per second queued rate of the client over its lifetime. */ @@ -103,7 +103,7 @@ public slots: float getLifetimePPSQueued() const { return _packetSender->getLifetimePPSQueued(); } /**jsdoc - * Get the entity bytes per second queued rate of the client over its lifetime. + * Gets the entity bytes per second queued rate of the client over its lifetime. * @function Entities.getLifetimeBPSQueued * @returns {number} Entity bytes per second queued rate of the client over its lifetime. */ @@ -111,7 +111,7 @@ public slots: float getLifetimeBPSQueued() const { return _packetSender->getLifetimeBPSQueued(); } /**jsdoc - * Get the lifetime of the client from the first entity packet sent until now, in microseconds. + * Gets the lifetime of the client from the first entity packet sent until now, in microseconds. * @function Entities.getLifetimeInUsecs * @returns {number} Lifetime of the client from the first entity packet sent until now, in microseconds. */ @@ -119,7 +119,7 @@ public slots: long long unsigned int getLifetimeInUsecs() const { return _packetSender->getLifetimeInUsecs(); } /**jsdoc - * Get the lifetime of the client from the first entity packet sent until now, in seconds. + * Gets the lifetime of the client from the first entity packet sent until now, in seconds. * @function Entities.getLifetimeInSeconds * @returns {number} Lifetime of the client from the first entity packet sent until now, in seconds. */ @@ -127,7 +127,7 @@ public slots: float getLifetimeInSeconds() const { return _packetSender->getLifetimeInSeconds(); } /**jsdoc - * Get the total number of entity packets sent by the client over its lifetime. + * Gets the total number of entity packets sent by the client over its lifetime. * @function Entities.getLifetimePacketsSent * @returns {number} The total number of entity packets sent by the client over its lifetime. */ @@ -135,7 +135,7 @@ public slots: long long unsigned int getLifetimePacketsSent() const { return _packetSender->getLifetimePacketsSent(); } /**jsdoc - * Get the total bytes of entity packets sent by the client over its lifetime. + * Gets the total bytes of entity packets sent by the client over its lifetime. * @function Entities.getLifetimeBytesSent * @returns {number} The total bytes of entity packets sent by the client over its lifetime. */ @@ -143,7 +143,7 @@ public slots: long long unsigned int getLifetimeBytesSent() const { return _packetSender->getLifetimeBytesSent(); } /**jsdoc - * Get the total number of entity packets queued by the client over its lifetime. + * Gets the total number of entity packets queued by the client over its lifetime. * @function Entities.getLifetimePacketsQueued * @returns {number} The total number of entity packets queued by the client over its lifetime. */ @@ -151,7 +151,7 @@ public slots: long long unsigned int getLifetimePacketsQueued() const { return _packetSender->getLifetimePacketsQueued(); } /**jsdoc - * Get the total bytes of entity packets queued by the client over its lifetime. + * Gets the total bytes of entity packets queued by the client over its lifetime. * @function Entities.getLifetimeBytesQueued * @returns {number} The total bytes of entity packets queued by the client over its lifetime. */ diff --git a/libraries/physics/src/ObjectActionOffset.cpp b/libraries/physics/src/ObjectActionOffset.cpp index 24f8a1ba8f..a99c875106 100644 --- a/libraries/physics/src/ObjectActionOffset.cpp +++ b/libraries/physics/src/ObjectActionOffset.cpp @@ -145,7 +145,7 @@ bool ObjectActionOffset::updateArguments(QVariantMap arguments) { /**jsdoc * The "offset" {@link Entities.ActionType|ActionType} moves an entity so that it is a set distance away from a * target point. - * It has arguments in addition to the common {@link Entities.ActionArguments|ActionArguments}. + * It has arguments in addition to the common {@link Entities.ActionArguments|ActionArguments}: * * @typedef {object} Entities.ActionArguments-Offset * @property {Vec3} pointToOffsetFrom=0,0,0 - The target point to offset the entity from. diff --git a/libraries/physics/src/ObjectActionTractor.cpp b/libraries/physics/src/ObjectActionTractor.cpp index b53c2e137a..4925faaf2a 100644 --- a/libraries/physics/src/ObjectActionTractor.cpp +++ b/libraries/physics/src/ObjectActionTractor.cpp @@ -341,15 +341,15 @@ bool ObjectActionTractor::updateArguments(QVariantMap arguments) { /**jsdoc * The "tractor" {@link Entities.ActionType|ActionType} moves and rotates an entity to a target position and * orientation, optionally relative to another entity. - * It has arguments in addition to the common {@link Entities.ActionArguments|ActionArguments}. + * It has arguments in addition to the common {@link Entities.ActionArguments|ActionArguments}: * * @typedef {object} Entities.ActionArguments-Tractor + * @property {Uuid} otherID=null - If an entity ID, the targetPosition and targetRotation are + * relative to the entity's position and rotation. + * @property {Uuid} otherJointIndex=null - If a joint index in the otherID entity, the targetPosition + * and targetRotation are relative to the entity joint's position and rotation. * @property {Vec3} targetPosition=0,0,0 - The target position. * @property {Quat} targetRotation=0,0,0,1 - The target rotation. - * @property {Uuid} otherID=null - If an entity ID, the targetPosition and targetRotation are - * relative to this entity's position and rotation. - * @property {Uuid} otherJointIndex=null - If an entity JointIndex, the targetPosition and - * targetRotation are relative to this entity's joint's position and rotation. * @property {number} linearTimeScale=3.4e+38 - Controls how long it takes for the entity's position to catch up with the * target position. The value is the time for the action to catch up to 1/e = 0.368 of the target value, where the action * is applied using an exponential decay. diff --git a/libraries/physics/src/ObjectActionTravelOriented.cpp b/libraries/physics/src/ObjectActionTravelOriented.cpp index b27ec40ae4..025c74b96a 100644 --- a/libraries/physics/src/ObjectActionTravelOriented.cpp +++ b/libraries/physics/src/ObjectActionTravelOriented.cpp @@ -152,7 +152,7 @@ bool ObjectActionTravelOriented::updateArguments(QVariantMap arguments) { /**jsdoc * The "travel-oriented" {@link Entities.ActionType|ActionType} orients an entity to align with its direction of * travel. - * It has arguments in addition to the common {@link Entities.ActionArguments|ActionArguments}. + * It has arguments in addition to the common {@link Entities.ActionArguments|ActionArguments}: * * @typedef {object} Entities.ActionArguments-TravelOriented * @property {Vec3} forward=0,0,0 - The axis of the entity to align with the entity's direction of travel. diff --git a/libraries/physics/src/ObjectConstraintBallSocket.cpp b/libraries/physics/src/ObjectConstraintBallSocket.cpp index ddb0c75ac9..5b925c9e88 100644 --- a/libraries/physics/src/ObjectConstraintBallSocket.cpp +++ b/libraries/physics/src/ObjectConstraintBallSocket.cpp @@ -183,11 +183,11 @@ bool ObjectConstraintBallSocket::updateArguments(QVariantMap arguments) { /**jsdoc * The "ball-socket" {@link Entities.ActionType|ActionType} connects two entities with a ball and socket joint. - * It has arguments in addition to the common {@link Entities.ActionArguments|ActionArguments}. + * It has arguments in addition to the common {@link Entities.ActionArguments|ActionArguments}: * * @typedef {object} Entities.ActionArguments-BallSocket - * @property {Vec3} pivot=0,0,0 - The local offset of the joint relative to the entity's position. * @property {Uuid} otherEntityID=null - The ID of the other entity that is connected to the joint. + * @property {Vec3} pivot=0,0,0 - The local offset of the joint relative to the entity's position. * @property {Vec3} otherPivot=0,0,0 - The local offset of the joint relative to the other entity's position. */ QVariantMap ObjectConstraintBallSocket::getArguments() { diff --git a/libraries/physics/src/ObjectConstraintConeTwist.cpp b/libraries/physics/src/ObjectConstraintConeTwist.cpp index 9551532182..6763f82773 100644 --- a/libraries/physics/src/ObjectConstraintConeTwist.cpp +++ b/libraries/physics/src/ObjectConstraintConeTwist.cpp @@ -264,17 +264,18 @@ bool ObjectConstraintConeTwist::updateArguments(QVariantMap arguments) { /**jsdoc * The "cone-twist" {@link Entities.ActionType|ActionType} connects two entities with a joint that can move * through a cone and can twist. - * It has arguments in addition to the common {@link Entities.ActionArguments|ActionArguments}. + * It has arguments in addition to the common {@link Entities.ActionArguments|ActionArguments}: * * @typedef {object} Entities.ActionArguments-ConeTwist + * @property {Uuid} otherEntityID=null - The ID of the other entity that is connected to the joint. * @property {Vec3} pivot=0,0,0 - The local offset of the joint relative to the entity's position. * @property {Vec3} axis=1,0,0 - The axis of the entity that moves through the cone. Must be a non-zero vector. - * @property {Uuid} otherEntityID=null - The ID of the other entity that is connected to the joint. * @property {Vec3} otherPivot=0,0,0 - The local offset of the joint relative to the other entity's position. * @property {Vec3} otherAxis=1,0,0 - The axis of the other entity that moves through the cone. Must be a non-zero vector. - * @property {number} swingSpan1=6.238 - The angle through which the joint can move in one axis of the cone, in radians. - * @property {number} swingSpan2=6.238 - The angle through which the joint can move in the other axis of the cone, in radians. - * @property {number} twistSpan=6.238 - The angle through with the joint can twist, in radians. + * @property {number} swingSpan1=2*Math.PI - The angle through which the joint can move in one axis of the cone, in radians. + * @property {number} swingSpan2=2*Math.PI - The angle through which the joint can move in the other axis of the cone, in + * radians. + * @property {number} twistSpan=2*Math.PI - The angle through with the joint can twist, in radians. */ QVariantMap ObjectConstraintConeTwist::getArguments() { QVariantMap arguments = ObjectDynamic::getArguments(); diff --git a/libraries/physics/src/ObjectConstraintHinge.cpp b/libraries/physics/src/ObjectConstraintHinge.cpp index 67573918cb..3059d82bf3 100644 --- a/libraries/physics/src/ObjectConstraintHinge.cpp +++ b/libraries/physics/src/ObjectConstraintHinge.cpp @@ -248,18 +248,19 @@ bool ObjectConstraintHinge::updateArguments(QVariantMap arguments) { /**jsdoc * The "hinge" {@link Entities.ActionType|ActionType} lets an entity pivot about an axis or connects two entities * with a hinge joint. - * It has arguments in addition to the common {@link Entities.ActionArguments|ActionArguments}. + * It has arguments in addition to the common {@link Entities.ActionArguments|ActionArguments}: * * @typedef {object} Entities.ActionArguments-Hinge + * @property {Uuid} otherEntityID=null - The ID of the other entity that is connected to the joint, if any. If none is + * specified then the first entity simply pivots about its specified axis. * @property {Vec3} pivot=0,0,0 - The local offset of the joint relative to the entity's position. * @property {Vec3} axis=1,0,0 - The axis of the entity that it pivots about. Must be a non-zero vector. - * @property {Uuid} otherEntityID=null - The ID of the other entity that is connected to the joint, if any. If none is - * specified then the first entity simply pivots about its specified axis. * @property {Vec3} otherPivot=0,0,0 - The local offset of the joint relative to the other entity's position. * @property {Vec3} otherAxis=1,0,0 - The axis of the other entity that it pivots about. Must be a non-zero vector. - * @property {number} low=-6.283 - The most negative angle that the hinge can take, in radians. - * @property {number} high=6.283 - The most positive angle that the hinge can take, in radians. - * @property {number} angle=0 - The current angle of the hinge. Read-only. + * @property {number} low=-2*Math.PI - The most negative angle that the hinge can take, in radians. + * @property {number} high=2*Math.PI - The most positive angle that the hinge can take, in radians. + * @property {number} angle=0 - The current angle of the hinge, in radians, range -Math.PI – + * Math.PI. Read-only. */ QVariantMap ObjectConstraintHinge::getArguments() { QVariantMap arguments = ObjectDynamic::getArguments(); diff --git a/libraries/physics/src/ObjectConstraintSlider.cpp b/libraries/physics/src/ObjectConstraintSlider.cpp index 7865e871de..11374298cf 100644 --- a/libraries/physics/src/ObjectConstraintSlider.cpp +++ b/libraries/physics/src/ObjectConstraintSlider.cpp @@ -264,27 +264,27 @@ bool ObjectConstraintSlider::updateArguments(QVariantMap arguments) { /**jsdoc * The "slider" {@link Entities.ActionType|ActionType} lets an entity slide and rotate along an axis, or connects * two entities that slide and rotate along a shared axis. - * It has arguments in addition to the common {@link Entities.ActionArguments|ActionArguments}. + * It has arguments in addition to the common {@link Entities.ActionArguments|ActionArguments}: * * @typedef {object} Entities.ActionArguments-Slider + * @property {Uuid} otherEntityID=null - The ID of the other entity that is connected to the joint, if any. If none is + * specified then the first entity simply slides and rotates about its specified axis. * @property {Vec3} point=0,0,0 - The local position of a point in the entity that slides along the axis. * @property {Vec3} axis=1,0,0 - The axis of the entity that slides along the joint. Must be a non-zero vector. - * @property {Uuid} otherEntityID=null - The ID of the other entity that is connected to the joint, if any. If non is - * specified then the first entity simply slides and rotates about its specified axis. * @property {Vec3} otherPoint=0,0,0 - The local position of a point in the other entity that slides along the axis. - * @property {Vec3} axis=1,0,0 - The axis of the other entity that slides along the joint. Must be a non-zero vector. + * @property {Vec3} otherAxis=1,0,0 - The axis of the other entity that slides along the joint. Must be a non-zero vector. * @property {number} linearLow=1.17e-38 - The most negative linear offset from the entity's initial point that the entity can * have along the slider. * @property {number} linearHigh=3.40e+38 - The most positive linear offset from the entity's initial point that the entity can * have along the slider. - * @property {number} angularLow=-6.283 - The most negative angle that the entity can rotate about the axis if the action + * @property {number} angularLow=-2*Math.PI - The most negative angle that the entity can rotate about the axis if the action * involves only one entity, otherwise the most negative angle the rotation can be between the two entities. In radians. - * @property {number} angularHigh=6.283 - The most positive angle that the entity can rotate about the axis if the action + * @property {number} angularHigh=Math.PI - The most positive angle that the entity can rotate about the axis if the action * involves only one entity, otherwise the most positive angle the rotation can be between the two entities. In radians. * @property {number} linearPosition=0 - The current linear offset the entity is from its initial point if the action involves * only one entity, otherwise the linear offset between the two entities' action points. Read-only. * @property {number} angularPosition=0 - The current angular offset of the entity from its initial rotation if the action - * involves only one entity, otherwise the angular offset between the two entities. Read-only. + * involves only one entity, otherwise the angular offset between the two entities. In radians. Read-only. */ QVariantMap ObjectConstraintSlider::getArguments() { QVariantMap arguments = ObjectDynamic::getArguments(); diff --git a/libraries/physics/src/ObjectDynamic.cpp b/libraries/physics/src/ObjectDynamic.cpp index 28323a8b92..f02b7b1c4c 100644 --- a/libraries/physics/src/ObjectDynamic.cpp +++ b/libraries/physics/src/ObjectDynamic.cpp @@ -94,36 +94,36 @@ bool ObjectDynamic::updateArguments(QVariantMap arguments) { } /**jsdoc -* Different entity action types have different arguments: some common to all actions (listed below) and some specific to each -* {@link Entities.ActionType|ActionType} (linked to below). The arguments are accessed as an object of property names and -* values. -* -* @typedef {object} Entities.ActionArguments -* @property {Entities.ActionType} type - The type of action. -* @property {string} tag="" - A string that a script can use for its own purposes. -* @property {number} ttl=0 - How long the action should exist, in seconds, before it is automatically deleted. A value of -* 0 means that the action should not be deleted. -* @property {boolean} isMine=true - Is true if you created the action during your current Interface session, -* false otherwise. Read-only. -* @property {boolean} ::no-motion-state - Is present when the entity hasn't been registered with the physics engine yet (e.g., -* if the action hasn't been properly configured), otherwise undefined. Read-only. -* @property {boolean} ::active - Is true when the action is modifying the entity's motion, false -* otherwise. Is present once the entity has been registered with the physics engine, otherwise undefined. -* Read-only. -* @property {Entities.PhysicsMotionType} ::motion-type - How the entity moves with the action. Is present once the entity has -* been registered with the physics engine, otherwise undefined. Read-only. -* -* @see The different action types have additional arguments as follows: -* @see {@link Entities.ActionArguments-FarGrab|ActionArguments-FarGrab} -* @see {@link Entities.ActionArguments-Hold|ActionArguments-Hold} -* @see {@link Entities.ActionArguments-Offset|ActionArguments-Offset} -* @see {@link Entities.ActionArguments-Tractor|ActionArguments-Tractor} -* @see {@link Entities.ActionArguments-TravelOriented|ActionArguments-TravelOriented} -* @see {@link Entities.ActionArguments-Hinge|ActionArguments-Hinge} -* @see {@link Entities.ActionArguments-Slider|ActionArguments-Slider} -* @see {@link Entities.ActionArguments-ConeTwist|ActionArguments-ConeTwist} -* @see {@link Entities.ActionArguments-BallSocket|ActionArguments-BallSocket} -*/ + * Different entity action types have different arguments: some common to all actions (listed in the table) and some specific + * to each {@link Entities.ActionType|ActionType} (linked to below). The arguments are accessed as an object of property names + * and values. + * + * @typedef {object} Entities.ActionArguments + * @property {Entities.ActionType} type - The type of action. + * @property {string} tag="" - A string that a script can use for its own purposes. + * @property {number} ttl=0 - How long the action should exist, in seconds, before it is automatically deleted. A value of + * 0 means that the action should not be deleted. + * @property {boolean} isMine=true - Is true if the action was created during the current client session, + * false otherwise. Read-only. + * @property {boolean} ::no-motion-state - Is present when the entity hasn't been registered with the physics engine yet (e.g., + * if the action hasn't been properly configured), otherwise undefined. Read-only. + * @property {boolean} ::active - Is true when the action is modifying the entity's motion, false + * otherwise. Is present once the entity has been registered with the physics engine, otherwise undefined. + * Read-only. + * @property {Entities.PhysicsMotionType} ::motion-type - How the entity moves with the action. Is present once the entity has + * been registered with the physics engine, otherwise undefined. Read-only. + * + * @comment The different action types have additional arguments as follows: + * @see {@link Entities.ActionArguments-FarGrab|ActionArguments-FarGrab} + * @see {@link Entities.ActionArguments-Hold|ActionArguments-Hold} + * @see {@link Entities.ActionArguments-Offset|ActionArguments-Offset} + * @see {@link Entities.ActionArguments-Tractor|ActionArguments-Tractor} + * @see {@link Entities.ActionArguments-TravelOriented|ActionArguments-TravelOriented} + * @see {@link Entities.ActionArguments-Hinge|ActionArguments-Hinge} + * @see {@link Entities.ActionArguments-Slider|ActionArguments-Slider} + * @see {@link Entities.ActionArguments-ConeTwist|ActionArguments-ConeTwist} + * @see {@link Entities.ActionArguments-BallSocket|ActionArguments-BallSocket} + */ // Note: The "type" property is set in EntityItem::getActionArguments(). QVariantMap ObjectDynamic::getArguments() { QVariantMap arguments; diff --git a/libraries/physics/src/PhysicalEntitySimulation.cpp b/libraries/physics/src/PhysicalEntitySimulation.cpp index f532d5209f..0a08aaa28d 100644 --- a/libraries/physics/src/PhysicalEntitySimulation.cpp +++ b/libraries/physics/src/PhysicalEntitySimulation.cpp @@ -309,20 +309,28 @@ void PhysicalEntitySimulation::buildMotionStatesForEntitiesThatNeedThem() { SetOfEntities::iterator entityItr = _entitiesToAddToPhysics.begin(); while (entityItr != _entitiesToAddToPhysics.end()) { EntityItemPointer entity = (*entityItr); - assert(!entity->getPhysicsInfo()); if (entity->isDead()) { prepareEntityForDelete(entity); entityItr = _entitiesToAddToPhysics.erase(entityItr); - } else if (!entity->shouldBePhysical()) { - // this entity should no longer be on _entitiesToAddToPhysics + continue; + } + if (entity->getPhysicsInfo()) { entityItr = _entitiesToAddToPhysics.erase(entityItr); + continue; + } + if (!entity->shouldBePhysical()) { + // this entity should no longer be on _entitiesToAddToPhysics if (entity->isMovingRelativeToParent()) { SetOfEntities::iterator itr = _simpleKinematicEntities.find(entity); if (itr == _simpleKinematicEntities.end()) { _simpleKinematicEntities.insert(entity); } } - } else if (entity->isReadyToComputeShape()) { + entityItr = _entitiesToAddToPhysics.erase(entityItr); + continue; + } + + if (entity->isReadyToComputeShape()) { ShapeRequest shapeRequest(entity); ShapeRequests::iterator requestItr = _shapeRequests.find(shapeRequest); if (requestItr == _shapeRequests.end()) { @@ -332,18 +340,7 @@ void PhysicalEntitySimulation::buildMotionStatesForEntitiesThatNeedThem() { uint32_t requestCount = ObjectMotionState::getShapeManager()->getWorkRequestCount(); btCollisionShape* shape = const_cast(ObjectMotionState::getShapeManager()->getShape(shapeInfo)); if (shape) { - EntityMotionState* motionState = static_cast(entity->getPhysicsInfo()); - if (!motionState) { - buildMotionState(shape, entity); - } else { - // Is it possible to fall in here? - // entity shouldn't be on _entitiesToAddToPhysics list if it already has a motionState. - // but just in case... - motionState->setShape(shape); - motionState->setRegion(_space->getRegion(entity->getSpaceIndex())); - _physicalObjects.insert(motionState); - _incomingChanges.insert(motionState); - } + buildMotionState(shape, entity); } else if (requestCount != ObjectMotionState::getShapeManager()->getWorkRequestCount()) { // shape doesn't exist but a new worker has been spawned to build it --> add to shapeRequests and wait shapeRequest.shapeHash = shapeInfo.getHash(); @@ -354,6 +351,7 @@ void PhysicalEntitySimulation::buildMotionStatesForEntitiesThatNeedThem() { } entityItr = _entitiesToAddToPhysics.erase(entityItr); } else { + // skip for later ++entityItr; } } diff --git a/libraries/physics/src/ThreadSafeDynamicsWorld.cpp b/libraries/physics/src/ThreadSafeDynamicsWorld.cpp index f6189121a9..00b8a71831 100644 --- a/libraries/physics/src/ThreadSafeDynamicsWorld.cpp +++ b/libraries/physics/src/ThreadSafeDynamicsWorld.cpp @@ -181,7 +181,7 @@ void ThreadSafeDynamicsWorld::drawConnectedSpheres(btIDebugDraw* drawer, btScala btVector3 xAxis = direction.cross(btVector3(0.0f, 1.0f, 0.0f)); xAxis = xAxis.length() < EPSILON ? btVector3(1.0f, 0.0f, 0.0f) : xAxis.normalize(); btVector3 zAxis = xAxis.cross(btVector3(0.0f, 1.0f, 0.0f)); - zAxis = (direction.normalize().getY() < EPSILON) ? btVector3(0.0f, 1.0f, 0.0f) : zAxis.normalize(); + zAxis = (direction.length2() < EPSILON || direction.normalize().getY() < EPSILON) ? btVector3(0.0f, 1.0f, 0.0f) : zAxis.normalize(); float fullCircle = 2.0f * PI; for (float i = 0; i < fullCircle; i += stepRadians) { float x1 = btSin(btScalar(i)) * radius1; diff --git a/libraries/platform/CMakeLists.txt b/libraries/platform/CMakeLists.txt index 70f3157e1e..55203d45a4 100644 --- a/libraries/platform/CMakeLists.txt +++ b/libraries/platform/CMakeLists.txt @@ -5,3 +5,11 @@ link_hifi_libraries(shared) GroupSources("src") target_json() + +if (APPLE) + # link in required OS X frameworks and include the right GL headers + find_library(OpenGL OpenGL) + find_library(AppKit AppKit) + + target_link_libraries(${TARGET_NAME} ${OpenGL} ${AppKit}) +endif () diff --git a/libraries/platform/src/platform/Platform.h b/libraries/platform/src/platform/Platform.h index 7f73ff4ff4..9405c77ae0 100644 --- a/libraries/platform/src/platform/Platform.h +++ b/libraries/platform/src/platform/Platform.h @@ -27,12 +27,13 @@ json getGPU(int index); int getNumDisplays(); json getDisplay(int index); - -int getNumMemories(); -json getMemory(int index); + +json getMemory(); json getComputer(); +json getAll(); + } // namespace platform #endif // hifi_platform_h diff --git a/libraries/platform/src/platform/PlatformKeys.h b/libraries/platform/src/platform/PlatformKeys.h index fd29b2ff7f..1008c5ca4b 100644 --- a/libraries/platform/src/platform/PlatformKeys.h +++ b/libraries/platform/src/platform/PlatformKeys.h @@ -9,6 +9,9 @@ #define hifi_platform_PlatformKeys_h namespace platform { namespace keys{ + // "UNKNOWN" + extern const char* UNKNOWN; + namespace cpu { extern const char* vendor; extern const char* vendor_Intel; @@ -36,8 +39,9 @@ namespace platform { namespace keys{ extern const char* coordsTop; extern const char* coordsBottom; } + namespace memory { extern const char* memTotal; - + } namespace computer { extern const char* OS; extern const char* OS_WINDOWS; @@ -45,6 +49,8 @@ namespace platform { namespace keys{ extern const char* OS_LINUX; extern const char* OS_ANDROID; + extern const char* OSVersion; + extern const char* vendor; extern const char* vendor_Apple; @@ -52,6 +58,14 @@ namespace platform { namespace keys{ extern const char* profileTier; } - } } // namespace plaform::keys + + // Keys for categories used in json returned by getAll() + extern const char* CPUS; + extern const char* GPUS; + extern const char* DISPLAYS; + extern const char* MEMORY; + extern const char* COMPUTER; + +} } // namespace plaform::keys #endif diff --git a/libraries/platform/src/platform/backend/AndroidPlatform.cpp b/libraries/platform/src/platform/backend/AndroidPlatform.cpp index ee5a7e39b9..b0a4c5e67b 100644 --- a/libraries/platform/src/platform/backend/AndroidPlatform.cpp +++ b/libraries/platform/src/platform/backend/AndroidPlatform.cpp @@ -9,39 +9,45 @@ #include "AndroidPlatform.h" #include "../PlatformKeys.h" #include +#include using namespace platform; -void AndroidInstance::enumerateCpu() { +void AndroidInstance::enumerateCpus() { json cpu; cpu[keys::cpu::vendor] = ""; cpu[keys::cpu::model] = ""; cpu[keys::cpu::clockSpeed] = ""; cpu[keys::cpu::numCores] = 0; - _cpu.push_back(cpu); + + _cpus.push_back(cpu); } -void AndroidInstance::enumerateGpu() { +void AndroidInstance::enumerateGpus() { GPUIdent* ident = GPUIdent::getInstance(); json gpu = {}; - gpu[keys::gpu::vendor] = ident->getName().toUtf8().constData(); gpu[keys::gpu::model] = ident->getName().toUtf8().constData(); + gpu[keys::gpu::vendor] = findGPUVendorInDescription(gpu[keys::gpu::model].get()); gpu[keys::gpu::videoMemory] = ident->getMemory(); gpu[keys::gpu::driver] = ident->getDriver().toUtf8().constData(); - _gpu.push_back(gpu); - _display = ident->getOutput(); + _gpus.push_back(gpu); + _displays = ident->getOutput(); } void AndroidInstance::enumerateMemory() { json ram = {}; - ram[keys::memTotal]=0; - _memory.push_back(ram); + ram[keys::memory::memTotal]=0; + _memory = ram; } void AndroidInstance::enumerateComputer(){ _computer[keys::computer::OS] = keys::computer::OS_ANDROID; _computer[keys::computer::vendor] = ""; _computer[keys::computer::model] = ""; + + auto sysInfo = QSysInfo(); + + _computer[keys::computer::OSVersion] = sysInfo.kernelVersion().toStdString(); } diff --git a/libraries/platform/src/platform/backend/AndroidPlatform.h b/libraries/platform/src/platform/backend/AndroidPlatform.h index d1496383c0..6592b3519d 100644 --- a/libraries/platform/src/platform/backend/AndroidPlatform.h +++ b/libraries/platform/src/platform/backend/AndroidPlatform.h @@ -15,10 +15,10 @@ namespace platform { class AndroidInstance : public Instance { public: - void enumerateCpu() override; + void enumerateCpus() override; + void enumerateGpus() override; void enumerateMemory() override; - void enumerateGpu() override; - void enumerateComputer () override; + void enumerateComputer() override; }; } // namespace platform diff --git a/libraries/platform/src/platform/backend/LinuxPlatform.cpp b/libraries/platform/src/platform/backend/LinuxPlatform.cpp index 356df27e0a..61501669cb 100644 --- a/libraries/platform/src/platform/backend/LinuxPlatform.cpp +++ b/libraries/platform/src/platform/backend/LinuxPlatform.cpp @@ -13,36 +13,37 @@ #include #include #include +#include using namespace platform; -void LinuxInstance::enumerateCpu() { +void LinuxInstance::enumerateCpus() { json cpu = {}; cpu[keys::cpu::vendor] = CPUIdent::Vendor(); cpu[keys::cpu::model] = CPUIdent::Brand(); cpu[keys::cpu::numCores] = std::thread::hardware_concurrency(); - _cpu.push_back(cpu); + _cpus.push_back(cpu); } -void LinuxInstance::enumerateGpu() { +void LinuxInstance::enumerateGpus() { GPUIdent* ident = GPUIdent::getInstance(); json gpu = {}; - gpu[keys::gpu::vendor] = ident->getName().toUtf8().constData(); gpu[keys::gpu::model] = ident->getName().toUtf8().constData(); + gpu[keys::gpu::vendor] = findGPUVendorInDescription(gpu[keys::gpu::model].get()); gpu[keys::gpu::videoMemory] = ident->getMemory(); gpu[keys::gpu::driver] = ident->getDriver().toUtf8().constData(); - _gpu.push_back(gpu); - _display = ident->getOutput(); + _gpus.push_back(gpu); + _displays = ident->getOutput(); } void LinuxInstance::enumerateMemory() { json ram = {}; - ram[keys::memTotal]=0; + ram[keys::memory::memTotal]=0; - _memory.push_back(ram); + _memory = ram; } void LinuxInstance::enumerateComputer(){ @@ -50,5 +51,9 @@ void LinuxInstance::enumerateComputer(){ _computer[keys::computer::OS] = keys::computer::OS_LINUX; _computer[keys::computer::vendor] = ""; _computer[keys::computer::model] = ""; + + auto sysInfo = QSysInfo(); + + _computer[keys::computer::OSVersion] = sysInfo.kernelVersion().toStdString(); } diff --git a/libraries/platform/src/platform/backend/LinuxPlatform.h b/libraries/platform/src/platform/backend/LinuxPlatform.h index 1629101f41..2f2529db7c 100644 --- a/libraries/platform/src/platform/backend/LinuxPlatform.h +++ b/libraries/platform/src/platform/backend/LinuxPlatform.h @@ -15,10 +15,10 @@ namespace platform { class LinuxInstance : public Instance { public: - void enumerateCpu() override; + void enumerateCpus() override; + void enumerateGpus() override; void enumerateMemory() override; - void enumerateGpu() override; - void enumerateComputer () override; + void enumerateComputer() override; }; } // namespace platform diff --git a/libraries/platform/src/platform/backend/MACOSPlatform.cpp b/libraries/platform/src/platform/backend/MACOSPlatform.cpp index 7dbc403783..cacbd06816 100644 --- a/libraries/platform/src/platform/backend/MACOSPlatform.cpp +++ b/libraries/platform/src/platform/backend/MACOSPlatform.cpp @@ -21,32 +21,33 @@ #include #include +#include #endif using namespace platform; -void MACOSInstance::enumerateCpu() { +void MACOSInstance::enumerateCpus() { json cpu = {}; cpu[keys::cpu::vendor] = CPUIdent::Vendor(); cpu[keys::cpu::model] = CPUIdent::Brand(); cpu[keys::cpu::numCores] = std::thread::hardware_concurrency(); - _cpu.push_back(cpu); + _cpus.push_back(cpu); } -void MACOSInstance::enumerateGpu() { +void MACOSInstance::enumerateGpus() { #ifdef Q_OS_MAC GPUIdent* ident = GPUIdent::getInstance(); json gpu = {}; - gpu[keys::gpu::vendor] = ident->getName().toUtf8().constData(); gpu[keys::gpu::model] = ident->getName().toUtf8().constData(); + gpu[keys::gpu::vendor] = findGPUVendorInDescription(gpu[keys::gpu::model].get()); gpu[keys::gpu::videoMemory] = ident->getMemory(); gpu[keys::gpu::driver] = ident->getDriver().toUtf8().constData(); - _gpu.push_back(gpu); + _gpus.push_back(gpu); #endif @@ -101,7 +102,7 @@ void MACOSInstance::enumerateDisplays() { display["modeWidth"] = displayModeWidth; display["modeHeight"] = displayModeHeight; - _display.push_back(display); + _displays.push_back(display); #endif } @@ -111,9 +112,9 @@ void MACOSInstance::enumerateMemory() { #ifdef Q_OS_MAC long pages = sysconf(_SC_PHYS_PAGES); long page_size = sysconf(_SC_PAGE_SIZE); - ram[keys::memTotal] = pages * page_size; + ram[keys::memory::memTotal] = pages * page_size; #endif - _memory.push_back(ram); + _memory = ram; } void MACOSInstance::enumerateComputer(){ @@ -133,5 +134,9 @@ void MACOSInstance::enumerateComputer(){ free(model); #endif + + auto sysInfo = QSysInfo(); + + _computer[keys::computer::OSVersion] = sysInfo.kernelVersion().toStdString(); } diff --git a/libraries/platform/src/platform/backend/MACOSPlatform.h b/libraries/platform/src/platform/backend/MACOSPlatform.h index 4a257d8be5..e893dda739 100644 --- a/libraries/platform/src/platform/backend/MACOSPlatform.h +++ b/libraries/platform/src/platform/backend/MACOSPlatform.h @@ -15,11 +15,11 @@ namespace platform { class MACOSInstance : public Instance { public: - void enumerateCpu() override; - void enumerateMemory() override; - void enumerateGpu() override; + void enumerateCpus() override; + void enumerateGpus() override; void enumerateDisplays() override; - void enumerateComputer () override; + void enumerateMemory() override; + void enumerateComputer() override; }; } // namespace platform diff --git a/libraries/platform/src/platform/backend/Platform.cpp b/libraries/platform/src/platform/backend/Platform.cpp index 8e9fda30ed..dba41ce121 100644 --- a/libraries/platform/src/platform/backend/Platform.cpp +++ b/libraries/platform/src/platform/backend/Platform.cpp @@ -11,6 +11,8 @@ #include "../PlatformKeys.h" namespace platform { namespace keys { + const char* UNKNOWN = "UNKNOWN"; + namespace cpu { const char* vendor = "vendor"; const char* vendor_Intel = "Intel"; @@ -38,8 +40,9 @@ namespace platform { namespace keys { const char* coordsTop = "coordinatestop"; const char* coordsBottom = "coordinatesbottom"; } - const char* memTotal = "memTotal"; - + namespace memory { + const char* memTotal = "memTotal"; + } namespace computer { const char* OS = "OS"; const char* OS_WINDOWS = "WINDOWS"; @@ -47,6 +50,8 @@ namespace platform { namespace keys { const char* OS_LINUX = "LINUX"; const char* OS_ANDROID = "ANDROID"; + const char* OSVersion = "OSVersion"; + const char* vendor = "vendor"; const char* vendor_Apple = "Apple"; @@ -54,6 +59,12 @@ namespace platform { namespace keys { const char* profileTier = "profileTier"; } + + const char* CPUS = "cpus"; + const char* GPUS = "gpus"; + const char* DISPLAYS = "displays"; + const char* MEMORY = "memory"; + const char* COMPUTER = "computer"; }} #include @@ -117,14 +128,14 @@ json platform::getDisplay(int index) { return _instance->getDisplay(index); } -int platform::getNumMemories() { - return _instance->getNumMemories(); +json platform::getMemory() { + return _instance->getMemory(); } -json platform::getMemory(int index) { - return _instance->getMemory(index); -} - -json platform::getComputer(){ +json platform::getComputer() { return _instance->getComputer(); } + +json platform::getAll() { + return _instance->getAll(); +} diff --git a/libraries/platform/src/platform/backend/PlatformInstance.cpp b/libraries/platform/src/platform/backend/PlatformInstance.cpp index 3dd3e5f592..41786bca1f 100644 --- a/libraries/platform/src/platform/backend/PlatformInstance.cpp +++ b/libraries/platform/src/platform/backend/PlatformInstance.cpp @@ -16,10 +16,10 @@ using namespace platform; bool Instance::enumeratePlatform() { enumerateComputer(); - enumerateCpu(); - enumerateGpu(); - enumerateDisplays(); enumerateMemory(); + enumerateCpus(); + enumerateGpus(); + enumerateDisplays(); // And profile the platform and put the tier in "computer" _computer[keys::computer::profileTier] = Profiler::TierNames[Profiler::profilePlatform()]; @@ -28,55 +28,42 @@ bool Instance::enumeratePlatform() { } json Instance::getCPU(int index) { - assert(index <(int) _cpu.size()); - if (index >= (int)_cpu.size()) + assert(index <(int) _cpus.size()); + if (index >= (int)_cpus.size()) return json(); - return _cpu.at(index); -} - -//These are ripe for template.. will work on that next -json Instance::getMemory(int index) { - assert(index <(int) _memory.size()); - if(index >= (int)_memory.size()) - return json(); - - return _memory.at(index); + return _cpus.at(index); } json Instance::getGPU(int index) { - assert(index <(int) _gpu.size()); + assert(index <(int) _gpus.size()); - if (index >=(int) _gpu.size()) + if (index >=(int) _gpus.size()) return json(); - return _gpu.at(index); + return _gpus.at(index); } json Instance::getDisplay(int index) { - assert(index <(int) _display.size()); + assert(index <(int) _displays.size()); - if (index >=(int) _display.size()) + if (index >=(int) _displays.size()) return json(); - return _display.at(index); + return _displays.at(index); } Instance::~Instance() { - if (_cpu.size() > 0) { - _cpu.clear(); + if (_cpus.size() > 0) { + _cpus.clear(); } - if (_memory.size() > 0) { - _memory.clear(); + if (_gpus.size() > 0) { + _gpus.clear(); } - if (_gpu.size() > 0) { - _gpu.clear(); - } - - if (_display.size() > 0) { - _display.clear(); + if (_displays.size() > 0) { + _displays.clear(); } } @@ -106,17 +93,53 @@ json Instance::listAllKeys() { keys::display::coordsTop, keys::display::coordsBottom, - keys::memTotal, + keys::memory::memTotal, keys::computer::OS, keys::computer::OS_WINDOWS, keys::computer::OS_MACOS, keys::computer::OS_LINUX, keys::computer::OS_ANDROID, + keys::computer::OSVersion, keys::computer::vendor, keys::computer::vendor_Apple, keys::computer::model, - keys::computer::profileTier + keys::computer::profileTier, + + keys::CPUS, + keys::GPUS, + keys::DISPLAYS, + keys::MEMORY, + keys::COMPUTER, }}); return allKeys; } + +const char* Instance::findGPUVendorInDescription(const std::string& description) { + // intel integrated graphics + if (description.find(keys::gpu::vendor_Intel) != std::string::npos) { + return keys::gpu::vendor_Intel; + } + // AMD gpu + else if ((description.find(keys::gpu::vendor_AMD) != std::string::npos) || (description.find("Radeon") != std::string::npos)) { + return keys::gpu::vendor_AMD; + } + // NVIDIA gpu + else if (description.find(keys::gpu::vendor_NVIDIA) != std::string::npos) { + return keys::gpu::vendor_NVIDIA; + } else { + return keys::UNKNOWN; + } +} + +json Instance::getAll() { + json all = {}; + + all[keys::COMPUTER] = _computer; + all[keys::MEMORY] = _memory; + all[keys::CPUS] = _cpus; + all[keys::GPUS] = _gpus; + all[keys::DISPLAYS] = _displays; + + return all; +} diff --git a/libraries/platform/src/platform/backend/PlatformInstance.h b/libraries/platform/src/platform/backend/PlatformInstance.h index 95eb2ef25e..b7983446f5 100644 --- a/libraries/platform/src/platform/backend/PlatformInstance.h +++ b/libraries/platform/src/platform/backend/PlatformInstance.h @@ -19,36 +19,39 @@ class Instance { public: bool virtual enumeratePlatform(); - int getNumCPUs() { return (int)_cpu.size(); } + int getNumCPUs() { return (int)_cpus.size(); } json getCPU(int index); - int getNumGPUs() { return (int)_gpu.size(); } + int getNumGPUs() { return (int)_gpus.size(); } json getGPU(int index); - int getNumMemories() { return (int)_memory.size(); } - json getMemory(int index); - - int getNumDisplays() { return (int)_display.size(); } + int getNumDisplays() { return (int)_displays.size(); } json getDisplay(int index); + json getMemory() { return _memory; } + + json getComputer() { return _computer; } - json getComputer() {return _computer;} - - void virtual enumerateCpu()=0; - void virtual enumerateMemory()=0; - void virtual enumerateGpu()=0; + json getAll(); + + void virtual enumerateCpus()=0; + void virtual enumerateGpus()=0; void virtual enumerateDisplays() {} + void virtual enumerateMemory() = 0; void virtual enumerateComputer()=0; virtual ~Instance(); static json listAllKeys(); + // Helper function to filter the vendor name out of the description of a GPU + static const char* findGPUVendorInDescription(const std::string& description); + protected: - std::vector _cpu; - std::vector _memory; - std::vector _gpu; - std::vector _display; + std::vector _cpus; + std::vector _gpus; + std::vector _displays; + json _memory; json _computer; }; diff --git a/libraries/platform/src/platform/backend/WINPlatform.cpp b/libraries/platform/src/platform/backend/WINPlatform.cpp index e34d87d853..9cf01ce4e0 100644 --- a/libraries/platform/src/platform/backend/WINPlatform.cpp +++ b/libraries/platform/src/platform/backend/WINPlatform.cpp @@ -16,32 +16,33 @@ #ifdef Q_OS_WIN #include +#include #endif using namespace platform; -void WINInstance::enumerateCpu() { +void WINInstance::enumerateCpus() { json cpu = {}; cpu[keys::cpu::vendor] = CPUIdent::Vendor(); cpu[keys::cpu::model] = CPUIdent::Brand(); cpu[keys::cpu::numCores] = std::thread::hardware_concurrency(); - _cpu.push_back(cpu); + _cpus.push_back(cpu); } -void WINInstance::enumerateGpu() { +void WINInstance::enumerateGpus() { GPUIdent* ident = GPUIdent::getInstance(); json gpu = {}; - gpu[keys::gpu::vendor] = ident->getName().toUtf8().constData(); gpu[keys::gpu::model] = ident->getName().toUtf8().constData(); + gpu[keys::gpu::vendor] = findGPUVendorInDescription(gpu[keys::gpu::model].get()); gpu[keys::gpu::videoMemory] = ident->getMemory(); gpu[keys::gpu::driver] = ident->getDriver().toUtf8().constData(); - _gpu.push_back(gpu); - _display = ident->getOutput(); + _gpus.push_back(gpu); + _displays = ident->getOutput(); } void WINInstance::enumerateMemory() { @@ -52,9 +53,9 @@ void WINInstance::enumerateMemory() { statex.dwLength = sizeof(statex); GlobalMemoryStatusEx(&statex); int totalRam = statex.ullTotalPhys / 1024 / 1024; - ram[platform::keys::memTotal] = totalRam; + ram[platform::keys::memory::memTotal] = totalRam; #endif - _memory.push_back(ram); + _memory = ram; } void WINInstance::enumerateComputer(){ @@ -62,5 +63,8 @@ void WINInstance::enumerateComputer(){ _computer[keys::computer::vendor] = ""; _computer[keys::computer::model] = ""; + auto sysInfo = QSysInfo(); + + _computer[keys::computer::OSVersion] = sysInfo.kernelVersion().toStdString(); } diff --git a/libraries/platform/src/platform/backend/WINPlatform.h b/libraries/platform/src/platform/backend/WINPlatform.h index e540335d94..cb6d3f482f 100644 --- a/libraries/platform/src/platform/backend/WINPlatform.h +++ b/libraries/platform/src/platform/backend/WINPlatform.h @@ -15,9 +15,9 @@ namespace platform { class WINInstance : public Instance { public: - void enumerateCpu() override; + void enumerateCpus() override; + void enumerateGpus() override; void enumerateMemory() override; - void enumerateGpu() override; void enumerateComputer () override; }; } // namespace platform diff --git a/libraries/plugins/src/plugins/DisplayPlugin.cpp b/libraries/plugins/src/plugins/DisplayPlugin.cpp index 47503e8f85..2fe3d5fbea 100644 --- a/libraries/plugins/src/plugins/DisplayPlugin.cpp +++ b/libraries/plugins/src/plugins/DisplayPlugin.cpp @@ -35,15 +35,6 @@ void DisplayPlugin::waitForPresent() { } } -std::function DisplayPlugin::getHUDOperator() { - std::function hudOperator; - { - QMutexLocker locker(&_presentMutex); - hudOperator = _hudOperator; - } - return hudOperator; -} - glm::mat4 HmdDisplay::getEyeToHeadTransform(Eye eye) const { static const glm::mat4 xform; return xform; diff --git a/libraries/plugins/src/plugins/DisplayPlugin.h b/libraries/plugins/src/plugins/DisplayPlugin.h index 9dc1d7002d..ca4e3bc392 100644 --- a/libraries/plugins/src/plugins/DisplayPlugin.h +++ b/libraries/plugins/src/plugins/DisplayPlugin.h @@ -147,14 +147,6 @@ public: virtual void submitFrame(const gpu::FramePointer& newFrame) = 0; virtual void captureFrame(const std::string& outputName) const { } - virtual float getRenderResolutionScale() const { - return _renderResolutionScale; - } - - void setRenderResolutionScale(float renderResolutionScale) { - _renderResolutionScale = renderResolutionScale; - } - // The size of the rendering target (may be larger than the device size due to distortion) virtual glm::uvec2 getRecommendedRenderSize() const = 0; @@ -213,13 +205,12 @@ public: void waitForPresent(); float getAveragePresentTime() { return _movingAveragePresent.average / (float)USECS_PER_MSEC; } // in msec - std::function getHUDOperator(); - static const QString& MENU_PATH(); // for updating plugin-related commands. Mimics the input plugin. virtual void pluginUpdate() = 0; + virtual std::function getHUDOperator() { return nullptr; } virtual StencilMaskMode getStencilMaskMode() const { return StencilMaskMode::NONE; } using StencilMaskMeshOperator = std::function; virtual StencilMaskMeshOperator getStencilMaskMeshOperator() { return nullptr; } @@ -234,12 +225,8 @@ protected: gpu::ContextPointer _gpuContext; - std::function _hudOperator { std::function() }; - MovingAverage _movingAveragePresent; - float _renderResolutionScale { 1.0f }; - private: QMutex _presentMutex; QWaitCondition _presentCondition; diff --git a/libraries/pointers/src/PickManager.cpp b/libraries/pointers/src/PickManager.cpp index 0cf5f90e3d..aadfbc41a0 100644 --- a/libraries/pointers/src/PickManager.cpp +++ b/libraries/pointers/src/PickManager.cpp @@ -122,22 +122,22 @@ void PickManager::update() { // FIXME: give each type its own expiry // Each type will update at least one pick, regardless of the expiry { - PROFILE_RANGE(picks, "StylusPicks"); + PROFILE_RANGE_EX(picks, "StylusPicks", 0xffff0000, (uint64_t)_totalPickCounts[PickQuery::Stylus]); PerformanceTimer perfTimer("StylusPicks"); _updatedPickCounts[PickQuery::Stylus] = _stylusPickCacheOptimizer.update(cachedPicks[PickQuery::Stylus], _nextPickToUpdate[PickQuery::Stylus], expiry, false); } { - PROFILE_RANGE(picks, "RayPicks"); + PROFILE_RANGE_EX(picks, "RayPicks", 0xffff0000, (uint64_t)_totalPickCounts[PickQuery::Ray]); PerformanceTimer perfTimer("RayPicks"); _updatedPickCounts[PickQuery::Ray] = _rayPickCacheOptimizer.update(cachedPicks[PickQuery::Ray], _nextPickToUpdate[PickQuery::Ray], expiry, shouldPickHUD); } { - PROFILE_RANGE(picks, "ParabolaPick"); - PerformanceTimer perfTimer("ParabolaPick"); + PROFILE_RANGE_EX(picks, "ParabolaPicks", 0xffff0000, (uint64_t)_totalPickCounts[PickQuery::Parabola]); + PerformanceTimer perfTimer("ParabolaPicks"); _updatedPickCounts[PickQuery::Parabola] = _parabolaPickCacheOptimizer.update(cachedPicks[PickQuery::Parabola], _nextPickToUpdate[PickQuery::Parabola], expiry, shouldPickHUD); } { - PROFILE_RANGE(picks, "CollisoinPicks"); + PROFILE_RANGE_EX(picks, "CollisionPicks", 0xffff0000, (uint64_t)_totalPickCounts[PickQuery::Collision]); PerformanceTimer perfTimer("CollisionPicks"); _updatedPickCounts[PickQuery::Collision] = _collisionPickCacheOptimizer.update(cachedPicks[PickQuery::Collision], _nextPickToUpdate[PickQuery::Collision], expiry, false); } diff --git a/libraries/qml/src/qml/impl/RenderEventHandler.cpp b/libraries/qml/src/qml/impl/RenderEventHandler.cpp index a1edfd6789..cc9fe34edc 100644 --- a/libraries/qml/src/qml/impl/RenderEventHandler.cpp +++ b/libraries/qml/src/qml/impl/RenderEventHandler.cpp @@ -49,8 +49,8 @@ bool RenderEventHandler::event(QEvent* e) { return QObject::event(e); } -RenderEventHandler::RenderEventHandler(SharedObject* shared, QThread* targetThread) - : _shared(shared) { +RenderEventHandler::RenderEventHandler(SharedObject* shared, QThread* targetThread) : + _shared(shared) { // Create the GL canvas in the same thread as the share canvas if (!_canvas.create(SharedObject::getSharedContext())) { qFatal("Unable to create new offscreen GL context"); @@ -136,7 +136,8 @@ void RenderEventHandler::qmlRender(bool sceneGraphSync) { resize(); - { + + if (_currentSize != QSize()) { PROFILE_RANGE(render_qml_gl, "render"); GLuint texture = SharedObject::getTextureCache().acquireTexture(_currentSize); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, _fbo); @@ -146,7 +147,7 @@ void RenderEventHandler::qmlRender(bool sceneGraphSync) { glClear(GL_COLOR_BUFFER_BIT); } else { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - _shared->_quickWindow->setRenderTarget(_fbo, _currentSize); + _shared->setRenderTarget(_fbo, _currentSize); _shared->_renderControl->render(); } _shared->_lastRenderTime = usecTimestampNow(); @@ -179,7 +180,7 @@ void RenderEventHandler::onQuit() { _fbo = 0; } - _shared->shutdownRendering(_canvas, _currentSize); + _shared->shutdownRendering(_currentSize); _canvas.doneCurrent(); } _canvas.moveToThreadWithContext(qApp->thread()); diff --git a/libraries/qml/src/qml/impl/SharedObject.cpp b/libraries/qml/src/qml/impl/SharedObject.cpp index b72f37481b..55788c8a02 100644 --- a/libraries/qml/src/qml/impl/SharedObject.cpp +++ b/libraries/qml/src/qml/impl/SharedObject.cpp @@ -78,7 +78,6 @@ SharedObject::SharedObject() { QObject::connect(qApp, &QCoreApplication::aboutToQuit, this, &SharedObject::onAboutToQuit); } - SharedObject::~SharedObject() { // After destroy returns, the rendering thread should be gone destroy(); @@ -173,7 +172,6 @@ void SharedObject::setRootItem(QQuickItem* rootItem) { QObject::connect(_renderControl, &QQuickRenderControl::renderRequested, this, &SharedObject::requestRender); QObject::connect(_renderControl, &QQuickRenderControl::sceneChanged, this, &SharedObject::requestRenderSync); #endif - } void SharedObject::destroy() { @@ -210,7 +208,7 @@ void SharedObject::destroy() { } // Block until the rendering thread has stopped // FIXME this is undesirable because this is blocking the main thread, - // but I haven't found a reliable way to do this only at application + // but I haven't found a reliable way to do this only at application // shutdown if (_renderThread) { _renderThread->wait(); @@ -220,10 +218,8 @@ void SharedObject::destroy() { #endif } - #define SINGLE_QML_ENGINE 0 - #if SINGLE_QML_ENGINE static QQmlEngine* globalEngine{ nullptr }; static size_t globalEngineRefCount{ 0 }; @@ -344,6 +340,11 @@ void SharedObject::setSize(const QSize& size) { #endif } +void SharedObject::setMaxFps(uint8_t maxFps) { + QMutexLocker locker(&_mutex); + _maxFps = maxFps; +} + bool SharedObject::preRender(bool sceneGraphSync) { #ifndef DISABLE_QML QMutexLocker lock(&_mutex); @@ -370,9 +371,9 @@ bool SharedObject::preRender(bool sceneGraphSync) { return true; } -void SharedObject::shutdownRendering(OffscreenGLCanvas& canvas, const QSize& size) { +void SharedObject::shutdownRendering(const QSize& size) { QMutexLocker locker(&_mutex); - if (size != QSize(0, 0)) { + if (size != QSize()) { getTextureCache().releaseSize(size); if (_latestTextureAndFence.first) { getTextureCache().releaseTexture(_latestTextureAndFence); @@ -380,19 +381,17 @@ void SharedObject::shutdownRendering(OffscreenGLCanvas& canvas, const QSize& siz } #ifndef DISABLE_QML _renderControl->invalidate(); - canvas.doneCurrent(); #endif wake(); } -bool SharedObject::isQuit() { +bool SharedObject::isQuit() const { QMutexLocker locker(&_mutex); return _quit; } void SharedObject::requestRender() { - // Don't queue multiple renders - if (_renderRequested) { + if (_quit) { return; } _renderRequested = true; @@ -402,18 +401,13 @@ void SharedObject::requestRenderSync() { if (_quit) { return; } - - { - QMutexLocker lock(&_mutex); - _syncRequested = true; - } - - requestRender(); + _renderRequested = true; + _syncRequested = true; } bool SharedObject::fetchTexture(TextureAndFence& textureAndFence) { QMutexLocker locker(&_mutex); - if (0 == _latestTextureAndFence.first) { + if (!_latestTextureAndFence.first) { return false; } textureAndFence = { 0, 0 }; @@ -421,8 +415,7 @@ bool SharedObject::fetchTexture(TextureAndFence& textureAndFence) { return true; } -void hifi::qml::impl::SharedObject::addToDeletionList(QObject * object) -{ +void SharedObject::addToDeletionList(QObject* object) { _deletionList.append(QPointer(object)); } @@ -469,11 +462,9 @@ void SharedObject::onRender() { return; } - QMutexLocker lock(&_mutex); if (_syncRequested) { - lock.unlock(); _renderControl->polishItems(); - lock.relock(); + QMutexLocker lock(&_mutex); QCoreApplication::postEvent(_renderObject, new OffscreenEvent(OffscreenEvent::RenderSync)); // sync and render request, main and render threads must be synchronized wait(); @@ -494,13 +485,11 @@ void SharedObject::onTimer() { { QMutexLocker locker(&_mutex); // Don't queue more than one frame at a time - if (0 != _latestTextureAndFence.first) { + if (_latestTextureAndFence.first) { return; } - } - { - if (_maxFps == 0) { + if (!_maxFps) { return; } auto minRenderInterval = USECS_PER_SECOND / _maxFps; diff --git a/libraries/qml/src/qml/impl/SharedObject.h b/libraries/qml/src/qml/impl/SharedObject.h index c9c0ef7bd0..50c56ad714 100644 --- a/libraries/qml/src/qml/impl/SharedObject.h +++ b/libraries/qml/src/qml/impl/SharedObject.h @@ -16,7 +16,6 @@ #include "TextureCache.h" - class QWindow; class QTimer; class QQuickWindow; @@ -24,7 +23,6 @@ class QQuickItem; class QOpenGLContext; class QQmlEngine; class QQmlContext; -class OffscreenGLCanvas; namespace hifi { namespace qml { @@ -51,11 +49,11 @@ public: void create(OffscreenSurface* surface); void setRootItem(QQuickItem* rootItem); void destroy(); - bool isQuit(); + bool isQuit() const; QSize getSize() const; void setSize(const QSize& size); - void setMaxFps(uint8_t maxFps) { _maxFps = maxFps; } + void setMaxFps(uint8_t maxFps); QQuickWindow* getWindow() { return _quickWindow; } QQuickItem* getRootItem() { return _rootItem; } @@ -72,7 +70,7 @@ private: bool event(QEvent* e) override; bool preRender(bool sceneGraphSync); - void shutdownRendering(OffscreenGLCanvas& canvas, const QSize& size); + void shutdownRendering(const QSize& size); // Called by the render event handler, from the render thread void initializeRenderControl(QOpenGLContext* context); void releaseTextureAndFence(); @@ -94,31 +92,30 @@ private: QList> _deletionList; // Texture management - TextureAndFence _latestTextureAndFence{ 0, 0 }; - QQuickItem* _item{ nullptr }; - QQuickItem* _rootItem{ nullptr }; - QQuickWindow* _quickWindow{ nullptr }; - QQmlContext* _qmlContext{ nullptr }; + TextureAndFence _latestTextureAndFence { 0, 0 }; + QQuickItem* _rootItem { nullptr }; + QQuickWindow* _quickWindow { nullptr }; + QQmlContext* _qmlContext { nullptr }; mutable QMutex _mutex; QWaitCondition _cond; #ifndef DISABLE_QML - QWindow* _proxyWindow{ nullptr }; - RenderControl* _renderControl{ nullptr }; - RenderEventHandler* _renderObject{ nullptr }; + QWindow* _proxyWindow { nullptr }; + RenderControl* _renderControl { nullptr }; + RenderEventHandler* _renderObject { nullptr }; - QTimer* _renderTimer{ nullptr }; - QThread* _renderThread{ nullptr }; + QTimer* _renderTimer { nullptr }; + QThread* _renderThread { nullptr }; #endif - uint64_t _lastRenderTime{ 0 }; - QSize _size{ 100, 100 }; - uint8_t _maxFps{ 60 }; + uint64_t _lastRenderTime { 0 }; + QSize _size { 100, 100 }; + uint8_t _maxFps { 60 }; - bool _renderRequested{ false }; - bool _syncRequested{ false }; - bool _quit{ false }; - bool _paused{ false }; + bool _renderRequested { false }; + bool _syncRequested { false }; + bool _quit { false }; + bool _paused { false }; }; } // namespace impl diff --git a/libraries/qml/src/qml/impl/TextureCache.h b/libraries/qml/src/qml/impl/TextureCache.h index c146d0bdbf..29f88955a4 100644 --- a/libraries/qml/src/qml/impl/TextureCache.h +++ b/libraries/qml/src/qml/impl/TextureCache.h @@ -35,9 +35,8 @@ public: using Size = uint64_t; struct TextureSet { - Size textureSize; // The number of surfaces with this size - size_t clientCount{ 0 }; + size_t clientCount { 0 }; ValueList returnedTextures; }; @@ -66,7 +65,7 @@ private: std::unordered_map _textureSizes; Mutex _mutex; std::list _returnedTextures; - size_t _totalTextureUsage{ 0 }; + size_t _totalTextureUsage { 0 }; }; }}} // namespace hifi::qml::impl diff --git a/libraries/recording/src/recording/ClipCache.cpp b/libraries/recording/src/recording/ClipCache.cpp index bc20e4d8eb..0fc65a2d79 100644 --- a/libraries/recording/src/recording/ClipCache.cpp +++ b/libraries/recording/src/recording/ClipCache.cpp @@ -18,7 +18,13 @@ using namespace recording; NetworkClipLoader::NetworkClipLoader(const QUrl& url) : Resource(url), - _clip(std::make_shared(url)) {} + _clip(std::make_shared(url)) { + if (url.isEmpty()) { + _loaded = false; + _startedLoading = false; + _failedToLoad = true; + } +} void NetworkClip::init(const QByteArray& clipData) { _clipData = clipData; diff --git a/libraries/render-utils/src/DeferredBufferWrite.slh b/libraries/render-utils/src/DeferredBufferWrite.slh index fc9310a520..de4581d66e 100644 --- a/libraries/render-utils/src/DeferredBufferWrite.slh +++ b/libraries/render-utils/src/DeferredBufferWrite.slh @@ -37,7 +37,6 @@ void packDeferredFragment(vec3 normal, float alpha, vec3 albedo, float roughness _fragColor0 = vec4(albedo, mix(packShadedMetallic(metallic), packScatteringMetallic(metallic), check)); _fragColor1 = vec4(packNormal(normal), clamp(roughness, 0.0, 1.0)); _fragColor2 = vec4(mix(emissive, vec3(scattering), check), occlusion); - _fragColor3 = vec4(isEmissiveEnabled() * emissive, 1.0); } @@ -49,7 +48,6 @@ void packDeferredFragmentLightmap(vec3 normal, float alpha, vec3 albedo, float r _fragColor0 = vec4(albedo, packLightmappedMetallic(metallic)); _fragColor1 = vec4(packNormal(normal), clamp(roughness, 0.0, 1.0)); _fragColor2 = vec4(isLightmapEnabled() * lightmap, 1.0); - _fragColor3 = vec4(isLightmapEnabled() * lightmap * albedo, 1.0); } @@ -59,7 +57,7 @@ void packDeferredFragmentUnlit(vec3 normal, float alpha, vec3 color) { } _fragColor0 = vec4(color, packUnlit()); _fragColor1 = vec4(packNormal(normal), 1.0); - // _fragColor2 = vec4(vec3(0.0), 1.0); + _fragColor2 = vec4(vec3(0.0), 1.0); _fragColor3 = vec4(color, 1.0); } @@ -69,7 +67,8 @@ void packDeferredFragmentTranslucent(vec3 normal, float alpha, vec3 albedo, floa } _fragColor0 = vec4(albedo.rgb, alpha); _fragColor1 = vec4(packNormal(normal), clamp(roughness, 0.0, 1.0)); - + _fragColor2 = vec4(vec3(0.0), 1.0); + _fragColor3 = vec4(0.0); } <@endif@> diff --git a/libraries/render-utils/src/DeferredLightingEffect.cpp b/libraries/render-utils/src/DeferredLightingEffect.cpp index 82b7f3102a..3ab9340906 100644 --- a/libraries/render-utils/src/DeferredLightingEffect.cpp +++ b/libraries/render-utils/src/DeferredLightingEffect.cpp @@ -41,33 +41,17 @@ namespace gr { using namespace render; -struct LightLocations { - bool shadowTransform{ false }; - void initialize(const gpu::ShaderPointer& program) { - shadowTransform = program->getReflection().validUniformBuffer(ru::Buffer::ShadowParams); - } -}; - -static void loadLightProgram(int programId, bool lightVolume, gpu::PipelinePointer& program, LightLocationsPtr& locations); +static void loadLightProgram(int programId, bool lightVolume, gpu::PipelinePointer& program); void DeferredLightingEffect::init() { - _directionalAmbientSphereLightLocations = std::make_shared(); - _directionalSkyboxLightLocations = std::make_shared(); + loadLightProgram(shader::render_utils::program::directional_ambient_light, false, _directionalAmbientSphereLight); + loadLightProgram(shader::render_utils::program::directional_skybox_light, false, _directionalSkyboxLight); - _directionalAmbientSphereLightShadowLocations = std::make_shared(); - _directionalSkyboxLightShadowLocations = std::make_shared(); + loadLightProgram(shader::render_utils::program::directional_ambient_light_shadow, false, _directionalAmbientSphereLightShadow); + loadLightProgram(shader::render_utils::program::directional_skybox_light_shadow, false, _directionalSkyboxLightShadow); - _localLightLocations = std::make_shared(); - _localLightOutlineLocations = std::make_shared(); - - loadLightProgram(shader::render_utils::program::directional_ambient_light, false, _directionalAmbientSphereLight, _directionalAmbientSphereLightLocations); - loadLightProgram(shader::render_utils::program::directional_skybox_light, false, _directionalSkyboxLight, _directionalSkyboxLightLocations); - - loadLightProgram(shader::render_utils::program::directional_ambient_light_shadow, false, _directionalAmbientSphereLightShadow, _directionalAmbientSphereLightShadowLocations); - loadLightProgram(shader::render_utils::program::directional_skybox_light_shadow, false, _directionalSkyboxLightShadow, _directionalSkyboxLightShadowLocations); - - loadLightProgram(shader::render_utils::program::local_lights_shading, true, _localLight, _localLightLocations); - loadLightProgram(shader::render_utils::program::local_lights_drawOutline, true, _localLightOutline, _localLightOutlineLocations); + loadLightProgram(shader::render_utils::program::local_lights_shading, true, _localLight); + loadLightProgram(shader::render_utils::program::local_lights_drawOutline, true, _localLightOutline); } // FIXME: figure out how to move lightFrame into a varying in GeometryCache and RenderPipelines @@ -123,15 +107,9 @@ void DeferredLightingEffect::unsetLocalLightsBatch(gpu::Batch& batch) { batch.setUniformBuffer(ru::Buffer::LightClusterFrustumGrid, nullptr); } -static gpu::ShaderPointer makeLightProgram(int programId, LightLocationsPtr& locations) { +static void loadLightProgram(int programId, bool lightVolume, gpu::PipelinePointer& pipeline) { + gpu::ShaderPointer program = gpu::Shader::createProgram(programId); - locations->initialize(program); - return program; -} - -static void loadLightProgram(int programId, bool lightVolume, gpu::PipelinePointer& pipeline, LightLocationsPtr& locations) { - - gpu::ShaderPointer program = makeLightProgram(programId, locations); auto state = std::make_shared(); state->setColorWriteMask(true, true, true, false); @@ -295,44 +273,6 @@ graphics::MeshPointer DeferredLightingEffect::getSpotLightMesh() { return _spotLightMesh; } -gpu::FramebufferPointer PreparePrimaryFramebuffer::createFramebuffer(const char* name, const glm::uvec2& frameSize) { - gpu::FramebufferPointer framebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create(name)); - auto colorFormat = gpu::Element::COLOR_SRGBA_32; - - auto defaultSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR); - auto primaryColorTexture = gpu::Texture::createRenderBuffer(colorFormat, frameSize.x, frameSize.y, gpu::Texture::SINGLE_MIP, defaultSampler); - - framebuffer->setRenderBuffer(0, primaryColorTexture); - - auto depthFormat = gpu::Element(gpu::SCALAR, gpu::UINT32, gpu::DEPTH_STENCIL); // Depth24_Stencil8 texel format - auto primaryDepthTexture = gpu::Texture::createRenderBuffer(depthFormat, frameSize.x, frameSize.y, gpu::Texture::SINGLE_MIP, defaultSampler); - - framebuffer->setDepthStencilBuffer(primaryDepthTexture, depthFormat); - - return framebuffer; -} - -void PreparePrimaryFramebuffer::configure(const Config& config) { - _resolutionScale = config.resolutionScale; -} - -void PreparePrimaryFramebuffer::run(const RenderContextPointer& renderContext, Output& primaryFramebuffer) { - glm::uvec2 frameSize(renderContext->args->_viewport.z, renderContext->args->_viewport.w); - glm::uvec2 scaledFrameSize(glm::vec2(frameSize) * _resolutionScale); - - // Resizing framebuffers instead of re-building them seems to cause issues with threaded - // rendering - if (!_primaryFramebuffer || _primaryFramebuffer->getSize() != scaledFrameSize) { - _primaryFramebuffer = createFramebuffer("deferredPrimary", scaledFrameSize); - } - - primaryFramebuffer = _primaryFramebuffer; - - // Set viewport for the rest of the scaled passes - renderContext->args->_viewport.z = scaledFrameSize.x; - renderContext->args->_viewport.w = scaledFrameSize.y; -} - void PrepareDeferred::run(const RenderContextPointer& renderContext, const Inputs& inputs, Outputs& outputs) { auto args = renderContext->args; @@ -456,7 +396,6 @@ void RenderDeferredSetup::run(const render::RenderContextPointer& renderContext, // Setup the global directional pass pipeline auto program = deferredLightingEffect->_directionalSkyboxLight; - LightLocationsPtr locations = deferredLightingEffect->_directionalSkyboxLightLocations; { if (keyLightCastShadows) { @@ -464,20 +403,16 @@ void RenderDeferredSetup::run(const render::RenderContextPointer& renderContext, // otherwise use the ambient sphere version if (hasAmbientMap) { program = deferredLightingEffect->_directionalSkyboxLightShadow; - locations = deferredLightingEffect->_directionalSkyboxLightShadowLocations; } else { program = deferredLightingEffect->_directionalAmbientSphereLightShadow; - locations = deferredLightingEffect->_directionalAmbientSphereLightShadowLocations; } } else { // If the keylight has an ambient Map then use the Skybox version of the pass // otherwise use the ambient sphere version if (hasAmbientMap) { program = deferredLightingEffect->_directionalSkyboxLight; - locations = deferredLightingEffect->_directionalSkyboxLightLocations; } else { program = deferredLightingEffect->_directionalAmbientSphereLight; - locations = deferredLightingEffect->_directionalAmbientSphereLightLocations; } } diff --git a/libraries/render-utils/src/DeferredLightingEffect.h b/libraries/render-utils/src/DeferredLightingEffect.h index 1cc6ca4767..4779376410 100644 --- a/libraries/render-utils/src/DeferredLightingEffect.h +++ b/libraries/render-utils/src/DeferredLightingEffect.h @@ -37,10 +37,6 @@ #include "SubsurfaceScattering.h" #include "AmbientOcclusionEffect.h" - -struct LightLocations; -using LightLocationsPtr = std::shared_ptr; - // THis is where we currently accumulate the local lights, let s change that sooner than later class DeferredLightingEffect : public Dependency { SINGLETON_DEPENDENCY @@ -72,51 +68,12 @@ private: gpu::PipelinePointer _localLight; gpu::PipelinePointer _localLightOutline; - LightLocationsPtr _directionalSkyboxLightLocations; - LightLocationsPtr _directionalAmbientSphereLightLocations; - - LightLocationsPtr _directionalSkyboxLightShadowLocations; - LightLocationsPtr _directionalAmbientSphereLightShadowLocations; - - LightLocationsPtr _localLightLocations; - LightLocationsPtr _localLightOutlineLocations; - friend class LightClusteringPass; friend class RenderDeferredSetup; friend class RenderDeferredLocals; friend class RenderDeferredCleanup; }; -class PreparePrimaryFramebufferConfig : public render::Job::Config { - Q_OBJECT - Q_PROPERTY(float resolutionScale MEMBER resolutionScale NOTIFY dirty) -public: - - float resolutionScale{ 1.0f }; - -signals: - void dirty(); -}; - -class PreparePrimaryFramebuffer { -public: - - using Output = gpu::FramebufferPointer; - using Config = PreparePrimaryFramebufferConfig; - using JobModel = render::Job::ModelO; - - PreparePrimaryFramebuffer(float resolutionScale = 1.0f) : _resolutionScale{resolutionScale} {} - void configure(const Config& config); - void run(const render::RenderContextPointer& renderContext, Output& primaryFramebuffer); - - gpu::FramebufferPointer _primaryFramebuffer; - float _resolutionScale{ 1.0f }; - -private: - - static gpu::FramebufferPointer createFramebuffer(const char* name, const glm::uvec2& size); -}; - class PrepareDeferred { public: // Inputs: primaryFramebuffer and lightingModel diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 64a46f3c1e..74cf1ffa39 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -1691,10 +1691,7 @@ public: } }; - -using packBlendshapeOffsetTo = void(glm::uvec4& packed, const BlendshapeOffsetUnpacked& unpacked); - -void packBlendshapeOffsetTo_Pos_F32_3xSN10_Nor_3xSN10_Tan_3xSN10(glm::uvec4& packed, const BlendshapeOffsetUnpacked& unpacked) { +static void packBlendshapeOffsetTo_Pos_F32_3xSN10_Nor_3xSN10_Tan_3xSN10(glm::uvec4& packed, const BlendshapeOffsetUnpacked& unpacked) { float len = glm::compMax(glm::abs(unpacked.positionOffset)); glm::vec3 normalizedPos(unpacked.positionOffset); if (len > 0.0f) { @@ -1711,6 +1708,37 @@ void packBlendshapeOffsetTo_Pos_F32_3xSN10_Nor_3xSN10_Tan_3xSN10(glm::uvec4& pac ); } +static void packBlendshapeOffsets_ref(BlendshapeOffsetUnpacked* unpacked, BlendshapeOffsetPacked* packed, int size) { + for (int i = 0; i < size; ++i) { + packBlendshapeOffsetTo_Pos_F32_3xSN10_Nor_3xSN10_Tan_3xSN10((*packed).packedPosNorTan, (*unpacked)); + ++unpacked; + ++packed; + } +} + +#if defined(_M_IX86) || defined(_M_X64) || defined(__i386__) || defined(__x86_64__) +// +// Runtime CPU dispatch +// +#include + +void packBlendshapeOffsets_AVX2(float (*unpacked)[9], uint32_t (*packed)[4], int size); + +static void packBlendshapeOffsets(BlendshapeOffsetUnpacked* unpacked, BlendshapeOffsetPacked* packed, int size) { + static bool _cpuSupportsAVX2 = cpuSupportsAVX2(); + if (_cpuSupportsAVX2) { + static_assert(sizeof(BlendshapeOffsetUnpacked) == 9 * sizeof(float), "struct BlendshapeOffsetUnpacked size doesn't match."); + static_assert(sizeof(BlendshapeOffsetPacked) == 4 * sizeof(uint32_t), "struct BlendshapeOffsetPacked size doesn't match."); + packBlendshapeOffsets_AVX2((float(*)[9])unpacked, (uint32_t(*)[4])packed, size); + } else { + packBlendshapeOffsets_ref(unpacked, packed, size); + } +} + +#else // portable reference code +static auto& packBlendshapeOffsets = packBlendshapeOffsets_ref; +#endif + class Blender : public QRunnable { public: @@ -1735,21 +1763,28 @@ Blender::Blender(ModelPointer model, HFMModel::ConstPointer hfmModel, int blendN void Blender::run() { DETAILED_PROFILE_RANGE_EX(simulation_animation, __FUNCTION__, 0xFFFF0000, 0, { { "url", _model->getURL().toString() } }); int numBlendshapeOffsets = 0; // number of offsets required for all meshes. + int maxBlendshapeOffsets = 0; // number of offsets in the largest mesh. int numMeshes = 0; // number of meshes in this model. for (auto meshIter = _hfmModel->meshes.cbegin(); meshIter != _hfmModel->meshes.cend(); ++meshIter) { numMeshes++; + if (meshIter->blendshapes.isEmpty()) { + continue; + } int numVertsInMesh = meshIter->vertices.size(); numBlendshapeOffsets += numVertsInMesh; + maxBlendshapeOffsets = std::max(maxBlendshapeOffsets, numVertsInMesh); } - // all elements are default constructed to zero offsets. - QVector packedBlendshapeOffsets(numBlendshapeOffsets); - QVector unpackedBlendshapeOffsets(numBlendshapeOffsets); - - // allocate the required size + // allocate the required sizes QVector blendedMeshSizes; blendedMeshSizes.reserve(numMeshes); + QVector packedBlendshapeOffsets; + packedBlendshapeOffsets.resize(numBlendshapeOffsets); + + QVector unpackedBlendshapeOffsets; + unpackedBlendshapeOffsets.resize(maxBlendshapeOffsets); // reuse for all meshes + int offset = 0; for (auto meshIter = _hfmModel->meshes.cbegin(); meshIter != _hfmModel->meshes.cend(); ++meshIter) { if (meshIter->blendshapes.isEmpty()) { @@ -1759,6 +1794,9 @@ void Blender::run() { int numVertsInMesh = meshIter->vertices.size(); blendedMeshSizes.push_back(numVertsInMesh); + // initialize offsets to zero + memset(unpackedBlendshapeOffsets.data(), 0, numVertsInMesh * sizeof(BlendshapeOffsetUnpacked)); + // for each blendshape in this mesh, accumulate the offsets into unpackedBlendshapeOffsets. const float NORMAL_COEFFICIENT_SCALE = 0.01f; for (int i = 0, n = qMin(_blendshapeCoefficients.size(), meshIter->blendshapes.size()); i < n; i++) { @@ -1773,7 +1811,7 @@ void Blender::run() { for (int j = 0; j < blendshape.indices.size(); ++j) { int index = blendshape.indices.at(j); - auto& currentBlendshapeOffset = unpackedBlendshapeOffsets[offset + index]; + auto& currentBlendshapeOffset = unpackedBlendshapeOffsets[index]; currentBlendshapeOffset.positionOffset += blendshape.vertices.at(j) * vertexCoefficient; currentBlendshapeOffset.normalOffset += blendshape.normals.at(j) * normalCoefficient; if (j < blendshape.tangents.size()) { @@ -1781,20 +1819,15 @@ void Blender::run() { } } } + + // convert unpackedBlendshapeOffsets into packedBlendshapeOffsets for the gpu. + auto unpacked = unpackedBlendshapeOffsets.data(); + auto packed = packedBlendshapeOffsets.data() + offset; + packBlendshapeOffsets(unpacked, packed, numVertsInMesh); + offset += numVertsInMesh; } - - // convert unpackedBlendshapeOffsets into packedBlendshapeOffsets for the gpu. - // FIXME it feels like we could be more effectively using SIMD here - { - auto unpacked = unpackedBlendshapeOffsets.data(); - auto packed = packedBlendshapeOffsets.data(); - for (int i = 0; i < unpackedBlendshapeOffsets.size(); ++i) { - packBlendshapeOffsetTo_Pos_F32_3xSN10_Nor_3xSN10_Tan_3xSN10((*packed).packedPosNorTan, (*unpacked)); - ++unpacked; - ++packed; - } - } + Q_ASSERT(offset == numBlendshapeOffsets); // post the result to the ModelBlender, which will dispatch to the model if still alive QMetaObject::invokeMethod(DependencyManager::get().data(), "setBlendedVertices", diff --git a/libraries/render-utils/src/RenderCommonTask.cpp b/libraries/render-utils/src/RenderCommonTask.cpp index 18532b7a66..e5de6ccd27 100644 --- a/libraries/render-utils/src/RenderCommonTask.cpp +++ b/libraries/render-utils/src/RenderCommonTask.cpp @@ -106,34 +106,6 @@ void DrawLayered3D::run(const RenderContextPointer& renderContext, const Inputs& } } -void CompositeHUD::run(const RenderContextPointer& renderContext, const gpu::FramebufferPointer& inputs) { - assert(renderContext->args); - assert(renderContext->args->_context); - - // We do not want to render HUD elements in secondary camera - if (renderContext->args->_renderMode == RenderArgs::RenderMode::SECONDARY_CAMERA_RENDER_MODE) { - return; - } - - // Grab the HUD texture -#if !defined(DISABLE_QML) - gpu::doInBatch("CompositeHUD", renderContext->args->_context, [&](gpu::Batch& batch) { - glm::mat4 projMat; - Transform viewMat; - renderContext->args->getViewFrustum().evalProjectionMatrix(projMat); - renderContext->args->getViewFrustum().evalViewTransform(viewMat); - batch.setProjectionTransform(projMat); - batch.setViewTransform(viewMat, true); - if (inputs) { - batch.setFramebuffer(inputs); - } - if (renderContext->args->_hudOperator) { - renderContext->args->_hudOperator(batch, renderContext->args->_hudTexture, renderContext->args->_renderMode == RenderArgs::RenderMode::MIRROR_RENDER_MODE); - } - }); -#endif -} - void Blit::run(const RenderContextPointer& renderContext, const gpu::FramebufferPointer& srcFramebuffer) { assert(renderContext->args); assert(renderContext->args->_context); @@ -205,6 +177,34 @@ void Blit::run(const RenderContextPointer& renderContext, const gpu::Framebuffer }); } +void NewOrDefaultFramebuffer::run(const render::RenderContextPointer& renderContext, const Input& input, Output& output) { + RenderArgs* args = renderContext->args; + // auto frameSize = input; + glm::uvec2 frameSize(args->_viewport.z, args->_viewport.w); + output.reset(); + + // First if the default Framebuffer is the correct size then use it + auto destBlitFbo = args->_blitFramebuffer; + if (destBlitFbo && destBlitFbo->getSize() == frameSize) { + output = destBlitFbo; + return; + } + + // Else use the lodal Framebuffer + if (_outputFramebuffer && _outputFramebuffer->getSize() != frameSize) { + _outputFramebuffer.reset(); + } + + if (!_outputFramebuffer) { + _outputFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("newFramebuffer.out")); + auto colorFormat = gpu::Element::COLOR_SRGBA_32; + auto defaultSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR); + auto colorTexture = gpu::Texture::createRenderBuffer(colorFormat, frameSize.x, frameSize.y, gpu::Texture::SINGLE_MIP, defaultSampler); + _outputFramebuffer->setRenderBuffer(0, colorTexture); + } + + output = _outputFramebuffer; +} void ResolveFramebuffer::run(const render::RenderContextPointer& renderContext, const Inputs& inputs, Outputs& outputs) { RenderArgs* args = renderContext->args; @@ -235,42 +235,6 @@ void ResolveFramebuffer::run(const render::RenderContextPointer& renderContext, }); } -void ResolveNewFramebuffer::run(const render::RenderContextPointer& renderContext, const Inputs& inputs, Outputs& outputs) { - RenderArgs* args = renderContext->args; - auto srcFbo = inputs; - outputs.reset(); - - // Check valid src - if (!srcFbo) { - return; - } - - // Check valid size for sr and dest - auto frameSize(srcFbo->getSize()); - - // Resizing framebuffers instead of re-building them seems to cause issues with threaded rendering - if (_outputFramebuffer && _outputFramebuffer->getSize() != frameSize) { - _outputFramebuffer.reset(); - } - - if (!_outputFramebuffer) { - _outputFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("resolvedNew.out")); - auto colorFormat = gpu::Element::COLOR_SRGBA_32; - auto defaultSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR); - auto colorTexture = gpu::Texture::createRenderBuffer(colorFormat, frameSize.x, frameSize.y, gpu::Texture::SINGLE_MIP, defaultSampler); - _outputFramebuffer->setRenderBuffer(0, colorTexture); - } - - gpu::Vec4i rectSrc; - rectSrc.z = frameSize.x; - rectSrc.w = frameSize.y; - gpu::doInBatch("ResolveNew", args->_context, [&](gpu::Batch& batch) { batch.blit(srcFbo, rectSrc, _outputFramebuffer, rectSrc); }); - - outputs = _outputFramebuffer; -} - - - void ExtractFrustums::run(const render::RenderContextPointer& renderContext, const Inputs& inputs, Outputs& output) { assert(renderContext->args); assert(renderContext->args->_context); diff --git a/libraries/render-utils/src/RenderCommonTask.h b/libraries/render-utils/src/RenderCommonTask.h index 4f72600d34..756445a30f 100644 --- a/libraries/render-utils/src/RenderCommonTask.h +++ b/libraries/render-utils/src/RenderCommonTask.h @@ -13,6 +13,8 @@ #include "LightStage.h" #include "LightingModel.h" + + class BeginGPURangeTimer { public: using JobModel = render::Job::ModelO; @@ -75,16 +77,6 @@ protected: bool _opaquePass { true }; }; -class CompositeHUD { -public: - // IF specified the input Framebuffer is actively set by the batch of this job before calling the HUDOperator. - // If not, the current Framebuffer is left unchanged. - //using Inputs = gpu::FramebufferPointer; - using JobModel = render::Job::ModelI; - - void run(const render::RenderContextPointer& renderContext, const gpu::FramebufferPointer& inputs); -}; - class Blit { public: using JobModel = render::Job::ModelI; @@ -92,6 +84,16 @@ public: void run(const render::RenderContextPointer& renderContext, const gpu::FramebufferPointer& srcFramebuffer); }; +class NewOrDefaultFramebuffer { +public: + using Input = glm::uvec2; + using Output = gpu::FramebufferPointer; + using JobModel = render::Job::ModelIO; + + void run(const render::RenderContextPointer& renderContext, const Input& input, Output& output); +private: + gpu::FramebufferPointer _outputFramebuffer; +}; class ResolveFramebuffer { public: @@ -102,18 +104,6 @@ public: void run(const render::RenderContextPointer& renderContext, const Inputs& source, Outputs& dest); }; -class ResolveNewFramebuffer { -public: - using Inputs = gpu::FramebufferPointer; - using Outputs = gpu::FramebufferPointer; - using JobModel = render::Job::ModelIO; - - void run(const render::RenderContextPointer& renderContext, const Inputs& source, Outputs& dest); -private: - gpu::FramebufferPointer _outputFramebuffer; -}; - - class ExtractFrustums { public: diff --git a/libraries/render-utils/src/RenderDeferredTask.cpp b/libraries/render-utils/src/RenderDeferredTask.cpp index 624869bbf5..e513fb7282 100644 --- a/libraries/render-utils/src/RenderDeferredTask.cpp +++ b/libraries/render-utils/src/RenderDeferredTask.cpp @@ -47,6 +47,7 @@ #include "FadeEffect.h" #include "BloomStage.h" #include "RenderUtilsLogging.h" +#include "RenderHUDLayerTask.h" #include "AmbientOcclusionEffect.h" #include "AntialiasingEffect.h" @@ -96,11 +97,8 @@ RenderDeferredTask::RenderDeferredTask() void RenderDeferredTask::configure(const Config& config) { // Propagate resolution scale to sub jobs who need it auto preparePrimaryBufferConfig = config.getConfig("PreparePrimaryBuffer"); - auto upsamplePrimaryBufferConfig = config.getConfig("PrimaryBufferUpscale"); assert(preparePrimaryBufferConfig); - assert(upsamplePrimaryBufferConfig); - preparePrimaryBufferConfig->setProperty("resolutionScale", config.resolutionScale); - upsamplePrimaryBufferConfig->setProperty("factor", 1.0f / config.resolutionScale); + preparePrimaryBufferConfig->setResolutionScale(config.resolutionScale); } void RenderDeferredTask::build(JobModel& task, const render::Varying& input, render::Varying& output) { @@ -232,8 +230,6 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren task.addJob("DrawInFrontOpaque", inFrontOpaquesInputs, true); task.addJob("DrawInFrontTransparent", inFrontTransparentsInputs, false); - const auto toneAndPostRangeTimer = task.addJob("BeginToneAndPostRangeTimer", "PostToneLayeredAntialiasing"); - // AA job before bloom to limit flickering const auto antialiasingInputs = Antialiasing::Inputs(deferredFrameTransform, lightingFramebuffer, linearDepthTarget, velocityBuffer).asVarying(); task.addJob("Antialiasing", antialiasingInputs); @@ -243,8 +239,8 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren task.addJob("Bloom", bloomInputs); // Lighting Buffer ready for tone mapping - const auto toneMappingInputs = ToneMappingDeferred::Inputs(lightingFramebuffer, scaledPrimaryFramebuffer).asVarying(); - task.addJob("ToneMapping", toneMappingInputs); + const auto toneMappingInputs = ToneMappingDeferred::Input(lightingFramebuffer, scaledPrimaryFramebuffer).asVarying(); + const auto toneMappedBuffer = task.addJob("ToneMapping", toneMappingInputs); // Debugging task is happening in the "over" layer after tone mapping and just before HUD { // Debug the bounds of the rendered items, still look at the zbuffer @@ -255,21 +251,11 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren } // Upscale to finale resolution - const auto primaryFramebuffer = task.addJob("PrimaryBufferUpscale", scaledPrimaryFramebuffer); + const auto primaryFramebuffer = task.addJob("PrimaryBufferUpscale", toneMappedBuffer); - // Composite the HUD and HUD overlays - task.addJob("HUD", primaryFramebuffer); - - const auto nullJitter = Varying(glm::vec2(0.0f, 0.0f)); - const auto hudOpaquesInputs = DrawLayered3D::Inputs(hudOpaque, lightingModel, nullJitter).asVarying(); - const auto hudTransparentsInputs = DrawLayered3D::Inputs(hudTransparent, lightingModel, nullJitter).asVarying(); - task.addJob("DrawHUDOpaque", hudOpaquesInputs, true); - task.addJob("DrawHUDTransparent", hudTransparentsInputs, false); - - task.addJob("ToneAndPostRangeTimer", toneAndPostRangeTimer); - - // Blit! - task.addJob("Blit", primaryFramebuffer); + // HUD Layer + const auto renderHUDLayerInputs = RenderHUDLayerTask::Input(primaryFramebuffer, lightingModel, hudOpaque, hudTransparent).asVarying(); + task.addJob("RenderHUDLayer", renderHUDLayerInputs); } RenderDeferredTaskDebug::RenderDeferredTaskDebug() { @@ -435,6 +421,44 @@ void RenderDeferredTaskDebug::build(JobModel& task, const render::Varying& input } +gpu::FramebufferPointer PreparePrimaryFramebuffer::createFramebuffer(const char* name, const glm::uvec2& frameSize) { + gpu::FramebufferPointer framebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create(name)); + auto colorFormat = gpu::Element::COLOR_SRGBA_32; + + auto defaultSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR); + auto primaryColorTexture = gpu::Texture::createRenderBuffer(colorFormat, frameSize.x, frameSize.y, gpu::Texture::SINGLE_MIP, defaultSampler); + + framebuffer->setRenderBuffer(0, primaryColorTexture); + + auto depthFormat = gpu::Element(gpu::SCALAR, gpu::UINT32, gpu::DEPTH_STENCIL); // Depth24_Stencil8 texel format + auto primaryDepthTexture = gpu::Texture::createRenderBuffer(depthFormat, frameSize.x, frameSize.y, gpu::Texture::SINGLE_MIP, defaultSampler); + + framebuffer->setDepthStencilBuffer(primaryDepthTexture, depthFormat); + + return framebuffer; +} + +void PreparePrimaryFramebuffer::configure(const Config& config) { + _resolutionScale = config.getResolutionScale(); +} + +void PreparePrimaryFramebuffer::run(const RenderContextPointer& renderContext, Output& primaryFramebuffer) { + glm::uvec2 frameSize(renderContext->args->_viewport.z, renderContext->args->_viewport.w); + glm::uvec2 scaledFrameSize(glm::vec2(frameSize) * _resolutionScale); + + // Resizing framebuffers instead of re-building them seems to cause issues with threaded + // rendering + if (!_primaryFramebuffer || _primaryFramebuffer->getSize() != scaledFrameSize) { + _primaryFramebuffer = createFramebuffer("deferredPrimary", scaledFrameSize); + } + + primaryFramebuffer = _primaryFramebuffer; + + // Set viewport for the rest of the scaled passes + renderContext->args->_viewport.z = scaledFrameSize.x; + renderContext->args->_viewport.w = scaledFrameSize.y; +} + void RenderTransparentDeferred::run(const RenderContextPointer& renderContext, const Inputs& inputs) { assert(renderContext->args); diff --git a/libraries/render-utils/src/RenderDeferredTask.h b/libraries/render-utils/src/RenderDeferredTask.h index 3eb1153928..969094488e 100644 --- a/libraries/render-utils/src/RenderDeferredTask.h +++ b/libraries/render-utils/src/RenderDeferredTask.h @@ -149,4 +149,42 @@ public: private: }; + +class PreparePrimaryFramebufferConfig : public render::Job::Config { + Q_OBJECT + Q_PROPERTY(float resolutionScale WRITE setResolutionScale READ getResolutionScale) +public: + float getResolutionScale() const { return resolutionScale; } + void setResolutionScale(float scale) { + const float SCALE_RANGE_MIN = 0.1f; + const float SCALE_RANGE_MAX = 2.0f; + resolutionScale = std::max(SCALE_RANGE_MIN, std::min(SCALE_RANGE_MAX, scale)); + } + +signals: + void dirty(); + +protected: + float resolutionScale{ 1.0f }; +}; + +class PreparePrimaryFramebuffer { +public: + + using Output = gpu::FramebufferPointer; + using Config = PreparePrimaryFramebufferConfig; + using JobModel = render::Job::ModelO; + + PreparePrimaryFramebuffer(float resolutionScale = 1.0f) : _resolutionScale{ resolutionScale } {} + void configure(const Config& config); + void run(const render::RenderContextPointer& renderContext, Output& primaryFramebuffer); + + gpu::FramebufferPointer _primaryFramebuffer; + float _resolutionScale{ 1.0f }; + +private: + + static gpu::FramebufferPointer createFramebuffer(const char* name, const glm::uvec2& size); +}; + #endif // hifi_RenderDeferredTask_h diff --git a/libraries/render-utils/src/RenderForwardTask.cpp b/libraries/render-utils/src/RenderForwardTask.cpp index de55f3f4ff..d65ad18aa1 100755 --- a/libraries/render-utils/src/RenderForwardTask.cpp +++ b/libraries/render-utils/src/RenderForwardTask.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include @@ -32,6 +33,7 @@ #include "FramebufferCache.h" #include "TextureCache.h" #include "RenderCommonTask.h" +#include "RenderHUDLayerTask.h" namespace ru { using render_utils::slot::texture::Texture; @@ -47,6 +49,13 @@ using namespace render; extern void initForwardPipelines(ShapePlumber& plumber); +void RenderForwardTask::configure(const Config& config) { + // Propagate resolution scale to sub jobs who need it + auto preparePrimaryBufferConfig = config.getConfig("PreparePrimaryBuffer"); + assert(preparePrimaryBufferConfig); + preparePrimaryBufferConfig->setResolutionScale(config.resolutionScale); +} + void RenderForwardTask::build(JobModel& task, const render::Varying& input, render::Varying& output) { task.addJob("SetRenderMethodTask", render::Args::FORWARD); @@ -87,16 +96,19 @@ void RenderForwardTask::build(JobModel& task, const render::Varying& input, rend // First job, alter faded fadeEffect->build(task, opaques); - // Prepare objects shared by several jobs - const auto deferredFrameTransform = task.addJob("DeferredFrameTransform"); // GPU jobs: Start preparing the main framebuffer - const auto framebuffer = task.addJob("PrepareFramebuffer"); + const auto scaledPrimaryFramebuffer = task.addJob("PreparePrimaryBuffer"); - task.addJob("PrepareForward", lightFrame); + // Prepare deferred, generate the shared Deferred Frame Transform. Only valid with the scaled frame buffer + const auto deferredFrameTransform = task.addJob("DeferredFrameTransform"); + + // Prepare Forward Framebuffer pass + const auto prepareForwardInputs = PrepareForward::Inputs(scaledPrimaryFramebuffer, lightFrame).asVarying(); + task.addJob("PrepareForward", prepareForwardInputs); // draw a stencil mask in hidden regions of the framebuffer. - task.addJob("PrepareStencil", framebuffer); + task.addJob("PrepareStencil", scaledPrimaryFramebuffer); // Draw opaques forward const auto opaqueInputs = DrawForward::Inputs(opaques, lightingModel).asVarying(); @@ -128,94 +140,103 @@ void RenderForwardTask::build(JobModel& task, const render::Varying& input, rend task.addJob("DrawZoneStack", debugZoneInputs); } - // Just resolve the msaa - const auto resolveInputs = - ResolveFramebuffer::Inputs(framebuffer, static_cast(nullptr)).asVarying(); - const auto resolvedFramebuffer = task.addJob("Resolve", resolveInputs); - //auto resolvedFramebuffer = task.addJob("Resolve", framebuffer); - #if defined(Q_OS_ANDROID) + + // Just resolve the msaa + const auto resolveInputs = ResolveFramebuffer::Inputs(scaledPrimaryFramebuffer, static_cast(nullptr)).asVarying(); + const auto resolvedFramebuffer = task.addJob("Resolve", resolveInputs); + + const auto toneMappedBuffer = resolvedFramebuffer; #else + const auto newResolvedFramebuffer = task.addJob("MakeResolvingFramebuffer"); + + + // Just resolve the msaa + const auto resolveInputs = ResolveFramebuffer::Inputs(scaledPrimaryFramebuffer, newResolvedFramebuffer).asVarying(); + const auto resolvedFramebuffer = task.addJob("Resolve", resolveInputs); + // Lighting Buffer ready for tone mapping // Forward rendering on GLES doesn't support tonemapping to and from the same FBO, so we specify // the output FBO as null, which causes the tonemapping to target the blit framebuffer - const auto toneMappingInputs = ToneMappingDeferred::Inputs(resolvedFramebuffer, static_cast(nullptr)).asVarying(); - task.addJob("ToneMapping", toneMappingInputs); + const auto toneMappingInputs = ToneMappingDeferred::Input(resolvedFramebuffer, resolvedFramebuffer).asVarying(); + const auto toneMappedBuffer = task.addJob("ToneMapping", toneMappingInputs); + #endif - // Layered Overlays - // Composite the HUD and HUD overlays - task.addJob("HUD", resolvedFramebuffer); + // Upscale to finale resolution + const auto primaryFramebuffer = task.addJob("PrimaryBufferUpscale", toneMappedBuffer); - const auto hudOpaquesInputs = DrawLayered3D::Inputs(hudOpaque, lightingModel, nullJitter).asVarying(); - const auto hudTransparentsInputs = DrawLayered3D::Inputs(hudTransparent, lightingModel, nullJitter).asVarying(); - task.addJob("DrawHUDOpaque", hudOpaquesInputs, true); - task.addJob("DrawHUDTransparent", hudTransparentsInputs, false); - - // Disable blit because we do tonemapping and compositing directly to the blit FBO - // Blit! - // task.addJob("Blit", framebuffer); + // HUD Layer + const auto renderHUDLayerInputs = RenderHUDLayerTask::Input(primaryFramebuffer, lightingModel, hudOpaque, hudTransparent).asVarying(); + task.addJob("RenderHUDLayer", renderHUDLayerInputs); } -void PrepareFramebuffer::configure(const Config& config) { +gpu::FramebufferPointer PreparePrimaryFramebufferMSAA::createFramebuffer(const char* name, const glm::uvec2& frameSize, int numSamples) { + gpu::FramebufferPointer framebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create(name)); + + auto defaultSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR); + + auto colorFormat = gpu::Element::COLOR_SRGBA_32; + auto colorTexture = + gpu::Texture::createRenderBufferMultisample(colorFormat, frameSize.x, frameSize.y, numSamples, defaultSampler); + framebuffer->setRenderBuffer(0, colorTexture); + + auto depthFormat = gpu::Element(gpu::SCALAR, gpu::UINT32, gpu::DEPTH_STENCIL); // Depth24_Stencil8 texel format + auto depthTexture = + gpu::Texture::createRenderBufferMultisample(depthFormat, frameSize.x, frameSize.y, numSamples, defaultSampler); + framebuffer->setDepthStencilBuffer(depthTexture, depthFormat); + + return framebuffer; +} + +void PreparePrimaryFramebufferMSAA::configure(const Config& config) { + _resolutionScale = config.getResolutionScale(); _numSamples = config.getNumSamples(); } -void PrepareFramebuffer::run(const RenderContextPointer& renderContext, gpu::FramebufferPointer& framebuffer) { +void PreparePrimaryFramebufferMSAA::run(const RenderContextPointer& renderContext, gpu::FramebufferPointer& framebuffer) { glm::uvec2 frameSize(renderContext->args->_viewport.z, renderContext->args->_viewport.w); + glm::uvec2 scaledFrameSize(glm::vec2(frameSize) * _resolutionScale); // Resizing framebuffers instead of re-building them seems to cause issues with threaded rendering - if (_framebuffer && (_framebuffer->getSize() != frameSize || _framebuffer->getNumSamples() != _numSamples)) { - _framebuffer.reset(); + if (!_framebuffer || (_framebuffer->getSize() != scaledFrameSize) || (_framebuffer->getNumSamples() != _numSamples)) { + _framebuffer = createFramebuffer("forward", scaledFrameSize, _numSamples); } - if (!_framebuffer) { - _framebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("forward")); - - int numSamples = _numSamples; - - auto colorFormat = gpu::Element::COLOR_SRGBA_32; - auto defaultSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR); - auto colorTexture = - gpu::Texture::createRenderBufferMultisample(colorFormat, frameSize.x, frameSize.y, numSamples, defaultSampler); - _framebuffer->setRenderBuffer(0, colorTexture); - - auto depthFormat = gpu::Element(gpu::SCALAR, gpu::UINT32, gpu::DEPTH_STENCIL); // Depth24_Stencil8 texel format - auto depthTexture = - gpu::Texture::createRenderBufferMultisample(depthFormat, frameSize.x, frameSize.y, numSamples, defaultSampler); - _framebuffer->setDepthStencilBuffer(depthTexture, depthFormat); - } - - auto args = renderContext->args; - gpu::doInBatch("PrepareFramebuffer::run", args->_context, [&](gpu::Batch& batch) { - batch.enableStereo(false); - batch.setViewportTransform(args->_viewport); - batch.setStateScissorRect(args->_viewport); - - batch.setFramebuffer(_framebuffer); - batch.clearFramebuffer(gpu::Framebuffer::BUFFER_COLOR0 | gpu::Framebuffer::BUFFER_DEPTH | - gpu::Framebuffer::BUFFER_STENCIL, - vec4(vec3(0), 0), 1.0, 0, true); - }); - framebuffer = _framebuffer; + + // Set viewport for the rest of the scaled passes + renderContext->args->_viewport.z = scaledFrameSize.x; + renderContext->args->_viewport.w = scaledFrameSize.y; } void PrepareForward::run(const RenderContextPointer& renderContext, const Inputs& inputs) { RenderArgs* args = renderContext->args; + auto primaryFramebuffer = inputs.get0(); + auto lightStageFrame = inputs.get1(); + gpu::doInBatch("RenderForward::Draw::run", args->_context, [&](gpu::Batch& batch) { args->_batch = &batch; + batch.enableStereo(false); + batch.setViewportTransform(args->_viewport); + batch.setStateScissorRect(args->_viewport); + + batch.setFramebuffer(primaryFramebuffer); + batch.clearFramebuffer(gpu::Framebuffer::BUFFER_COLOR0 | gpu::Framebuffer::BUFFER_DEPTH | + gpu::Framebuffer::BUFFER_STENCIL, + vec4(vec3(0), 0), 1.0, 0, true); + graphics::LightPointer keySunLight; auto lightStage = args->_scene->getStage(); if (lightStage) { - keySunLight = lightStage->getCurrentKeyLight(*inputs); + keySunLight = lightStage->getCurrentKeyLight(*lightStageFrame); } graphics::LightPointer keyAmbiLight; if (lightStage) { - keyAmbiLight = lightStage->getCurrentAmbientLight(*inputs); + keyAmbiLight = lightStage->getCurrentAmbientLight(*lightStageFrame); } if (keySunLight) { diff --git a/libraries/render-utils/src/RenderForwardTask.h b/libraries/render-utils/src/RenderForwardTask.h index 40d004ddb2..baf7f66c6c 100755 --- a/libraries/render-utils/src/RenderForwardTask.h +++ b/libraries/render-utils/src/RenderForwardTask.h @@ -17,39 +17,59 @@ #include "AssembleLightingStageTask.h" #include "LightingModel.h" +class RenderForwardTaskConfig : public render::Task::Config { + Q_OBJECT + Q_PROPERTY(float resolutionScale MEMBER resolutionScale NOTIFY dirty) +public: + float resolutionScale{ 1.f }; + +signals: + void dirty(); +}; + class RenderForwardTask { public: using Input = render::VaryingSet3; - using JobModel = render::Task::ModelI; + using Config = RenderForwardTaskConfig; + using JobModel = render::Task::ModelI; RenderForwardTask() {} + void configure(const Config& config); void build(JobModel& task, const render::Varying& input, render::Varying& output); }; -class PrepareFramebufferConfig : public render::Job::Config { +class PreparePrimaryFramebufferMSAAConfig : public render::Job::Config { Q_OBJECT - Q_PROPERTY(int numSamples WRITE setNumSamples READ getNumSamples NOTIFY dirty) + Q_PROPERTY(float resolutionScale WRITE setResolutionScale READ getResolutionScale) + Q_PROPERTY(int numSamples WRITE setNumSamples READ getNumSamples) public: + float getResolutionScale() const { return resolutionScale; } + void setResolutionScale(float scale) { + const float SCALE_RANGE_MIN = 0.1f; + const float SCALE_RANGE_MAX = 2.0f; + resolutionScale = std::max(SCALE_RANGE_MIN, std::min(SCALE_RANGE_MAX, scale)); + } + int getNumSamples() const { return numSamples; } void setNumSamples(int num) { numSamples = std::max(1, std::min(32, num)); - emit dirty(); } signals: void dirty(); protected: + float resolutionScale{ 1.0f }; int numSamples{ 4 }; }; -class PrepareFramebuffer { +class PreparePrimaryFramebufferMSAA { public: - using Inputs = gpu::FramebufferPointer; - using Config = PrepareFramebufferConfig; - using JobModel = render::Job::ModelO; + using Output = gpu::FramebufferPointer; + using Config = PreparePrimaryFramebufferMSAAConfig; + using JobModel = render::Job::ModelO; void configure(const Config& config); void run(const render::RenderContextPointer& renderContext, @@ -57,12 +77,15 @@ public: private: gpu::FramebufferPointer _framebuffer; + float _resolutionScale{ 1.0f }; int _numSamples; + + static gpu::FramebufferPointer createFramebuffer(const char* name, const glm::uvec2& frameSize, int numSamples); }; class PrepareForward { public: - using Inputs = LightStage::FramePointer; + using Inputs = render::VaryingSet2 ; using JobModel = render::Job::ModelI; void run(const render::RenderContextPointer& renderContext, diff --git a/libraries/render-utils/src/RenderHUDLayerTask.cpp b/libraries/render-utils/src/RenderHUDLayerTask.cpp new file mode 100644 index 0000000000..ac7a867366 --- /dev/null +++ b/libraries/render-utils/src/RenderHUDLayerTask.cpp @@ -0,0 +1,60 @@ +// +// Created by Sam Gateau on 2019/06/14 +// Copyright 2013-2019 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 "RenderHUDLayerTask.h" + +#include +#include "RenderCommonTask.h" + +using namespace render; + +void CompositeHUD::run(const RenderContextPointer& renderContext, const gpu::FramebufferPointer& inputs) { + assert(renderContext->args); + assert(renderContext->args->_context); + + // We do not want to render HUD elements in secondary camera + if (nsightActive() || renderContext->args->_renderMode == RenderArgs::RenderMode::SECONDARY_CAMERA_RENDER_MODE) { + return; + } + + // Grab the HUD texture +#if !defined(DISABLE_QML) + gpu::doInBatch("CompositeHUD", renderContext->args->_context, [&](gpu::Batch& batch) { + glm::mat4 projMat; + Transform viewMat; + renderContext->args->getViewFrustum().evalProjectionMatrix(projMat); + renderContext->args->getViewFrustum().evalViewTransform(viewMat); + batch.setProjectionTransform(projMat); + batch.setViewTransform(viewMat, true); + if (inputs) { + batch.setFramebuffer(inputs); + } + if (renderContext->args->_hudOperator) { + renderContext->args->_hudOperator(batch, renderContext->args->_hudTexture, renderContext->args->_renderMode == RenderArgs::RenderMode::MIRROR_RENDER_MODE); + } + }); +#endif +} + +void RenderHUDLayerTask::build(JobModel& task, const render::Varying& input, render::Varying& output) { + const auto& inputs = input.get(); + + const auto& primaryFramebuffer = inputs[0]; + const auto& lightingModel = inputs[1]; + const auto& hudOpaque = inputs[2]; + const auto& hudTransparent = inputs[3]; + + // Composite the HUD and HUD overlays + task.addJob("HUD", primaryFramebuffer); + + // And HUD Layer objects + const auto nullJitter = Varying(glm::vec2(0.0f, 0.0f)); + const auto hudOpaquesInputs = DrawLayered3D::Inputs(hudOpaque, lightingModel, nullJitter).asVarying(); + const auto hudTransparentsInputs = DrawLayered3D::Inputs(hudTransparent, lightingModel, nullJitter).asVarying(); + task.addJob("DrawHUDOpaque", hudOpaquesInputs, true); + task.addJob("DrawHUDTransparent", hudTransparentsInputs, false); +} diff --git a/libraries/render-utils/src/RenderHUDLayerTask.h b/libraries/render-utils/src/RenderHUDLayerTask.h new file mode 100644 index 0000000000..78cd009636 --- /dev/null +++ b/libraries/render-utils/src/RenderHUDLayerTask.h @@ -0,0 +1,34 @@ +// +// Created by Sam Gateau on 2019/06/14 +// Copyright 2013-2019 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_RenderHUDLayerTask_h +#define hifi_RenderHUDLayerTask_h + +#include "LightingModel.h" + + +class CompositeHUD { +public: + // IF specified the input Framebuffer is actively set by the batch of this job before calling the HUDOperator. + // If not, the current Framebuffer is left unchanged. + //using Inputs = gpu::FramebufferPointer; + using JobModel = render::Job::ModelI; + + void run(const render::RenderContextPointer& renderContext, const gpu::FramebufferPointer& inputs); +}; + +class RenderHUDLayerTask { +public: + // Framebuffer where to draw, lighting model, opaque items, transparent items + using Input = render::VaryingSet4; + using JobModel = render::Task::ModelI; + + void build(JobModel& task, const render::Varying& input, render::Varying& output); +}; + +#endif // hifi_RenderHUDLayerTask_h \ No newline at end of file diff --git a/libraries/render-utils/src/ToneMappingEffect.cpp b/libraries/render-utils/src/ToneMappingEffect.cpp index 64a2adb5d4..b7cc5d3d80 100644 --- a/libraries/render-utils/src/ToneMappingEffect.cpp +++ b/libraries/render-utils/src/ToneMappingEffect.cpp @@ -47,14 +47,13 @@ void ToneMappingEffect::setToneCurve(ToneCurve curve) { } } -void ToneMappingEffect::render(RenderArgs* args, const gpu::TexturePointer& lightingBuffer, const gpu::FramebufferPointer& requestedDestinationFramebuffer) { +void ToneMappingEffect::render(RenderArgs* args, const gpu::TexturePointer& lightingBuffer, const gpu::FramebufferPointer& destinationFramebuffer) { if (!_blitLightBuffer) { init(args); } - - auto destinationFramebuffer = requestedDestinationFramebuffer; - if (!destinationFramebuffer) { - destinationFramebuffer = args->_blitFramebuffer; + + if (!lightingBuffer || !destinationFramebuffer) { + return; } auto framebufferSize = glm::ivec2(lightingBuffer->getDimensions()); @@ -83,9 +82,15 @@ void ToneMappingDeferred::configure(const Config& config) { _toneMappingEffect.setToneCurve((ToneMappingEffect::ToneCurve)config.curve); } -void ToneMappingDeferred::run(const render::RenderContextPointer& renderContext, const Inputs& inputs) { +void ToneMappingDeferred::run(const render::RenderContextPointer& renderContext, const Input& input, Output& output) { + + auto lightingBuffer = input.get0()->getRenderBuffer(0); + auto destFbo = input.get1(); + + if (!destFbo) { + destFbo = renderContext->args->_blitFramebuffer; + } - auto lightingBuffer = inputs.get0()->getRenderBuffer(0); - auto destFbo = inputs.get1(); _toneMappingEffect.render(renderContext->args, lightingBuffer, destFbo); + output = destFbo; } diff --git a/libraries/render-utils/src/ToneMappingEffect.h b/libraries/render-utils/src/ToneMappingEffect.h index 69694b13f5..faf6e514e9 100644 --- a/libraries/render-utils/src/ToneMappingEffect.h +++ b/libraries/render-utils/src/ToneMappingEffect.h @@ -82,12 +82,13 @@ signals: class ToneMappingDeferred { public: // Inputs: lightingFramebuffer, destinationFramebuffer - using Inputs = render::VaryingSet2; + using Input = render::VaryingSet2; + using Output = gpu::FramebufferPointer; using Config = ToneMappingConfig; - using JobModel = render::Job::ModelI; + using JobModel = render::Job::ModelIO; void configure(const Config& config); - void run(const render::RenderContextPointer& renderContext, const Inputs& inputs); + void run(const render::RenderContextPointer& renderContext, const Input& input, Output& output); ToneMappingEffect _toneMappingEffect; }; diff --git a/libraries/render-utils/src/deferred_light.slv b/libraries/render-utils/src/deferred_light.slv index 164fd9fb3b..2a68aa0e27 100644 --- a/libraries/render-utils/src/deferred_light.slv +++ b/libraries/render-utils/src/deferred_light.slv @@ -18,7 +18,7 @@ layout(location=RENDER_UTILS_ATTR_TEXCOORD01) out vec4 _texCoord01; void main(void) { const float depth = 1.0; - const vec4 UNIT_QUAD[4] = vec4[4]( + const mat4 UNIT_QUAD = mat4( vec4(-1.0, -1.0, depth, 1.0), vec4(1.0, -1.0, depth, 1.0), vec4(-1.0, 1.0, depth, 1.0), @@ -26,7 +26,7 @@ void main(void) { ); vec4 pos = UNIT_QUAD[gl_VertexID]; - _texCoord01.xy = (pos.xy + 1.0) * 0.5; + _texCoord01 = vec4((pos.xy + 1.0) * 0.5, 0.0, 0.0); gl_Position = pos; } diff --git a/libraries/render/src/render/Args.h b/libraries/render/src/render/Args.h index 7821692a60..1798208981 100644 --- a/libraries/render/src/render/Args.h +++ b/libraries/render/src/render/Args.h @@ -124,20 +124,20 @@ namespace render { DebugFlags _debugFlags { RENDER_DEBUG_NONE }; gpu::Batch* _batch = nullptr; - uint32_t _globalShapeKey{ 0 }; - uint32_t _itemShapeKey{ 0 }; - bool _enableTexturing{ true }; - bool _enableBlendshape{ true }; - bool _enableSkinning{ true }; + uint32_t _globalShapeKey { 0 }; + uint32_t _itemShapeKey { 0 }; + bool _enableTexturing { true }; + bool _enableBlendshape { true }; + bool _enableSkinning { true }; - bool _enableFade{ false }; + bool _enableFade { false }; RenderDetails _details; render::ScenePointer _scene; int8_t _cameraMode { -1 }; - std::function _hudOperator; - gpu::TexturePointer _hudTexture; + std::function _hudOperator { nullptr }; + gpu::TexturePointer _hudTexture { nullptr }; bool _takingSnapshot { false }; StencilMaskMode _stencilMaskMode { StencilMaskMode::NONE }; diff --git a/libraries/render/src/render/ResampleTask.cpp b/libraries/render/src/render/ResampleTask.cpp index ed4d0ddfd0..3e9bfec8da 100644 --- a/libraries/render/src/render/ResampleTask.cpp +++ b/libraries/render/src/render/ResampleTask.cpp @@ -135,3 +135,43 @@ void Upsample::run(const RenderContextPointer& renderContext, const gpu::Framebu args->_viewport = viewport; } } + +gpu::PipelinePointer UpsampleToBlitFramebuffer::_pipeline; + +void UpsampleToBlitFramebuffer::run(const RenderContextPointer& renderContext, const Input& input, gpu::FramebufferPointer& resampledFrameBuffer) { + assert(renderContext->args); + assert(renderContext->args->hasViewFrustum()); + RenderArgs* args = renderContext->args; + auto sourceFramebuffer = input; + + resampledFrameBuffer = args->_blitFramebuffer; + + if (resampledFrameBuffer != sourceFramebuffer) { + if (!_pipeline) { + gpu::ShaderPointer program = gpu::Shader::createProgram(shader::gpu::program::drawTransformUnitQuadTextureOpaque); + gpu::StatePointer state = gpu::StatePointer(new gpu::State()); + state->setDepthTest(gpu::State::DepthTest(false, false)); + _pipeline = gpu::Pipeline::create(program, state); + } + const auto bufferSize = resampledFrameBuffer->getSize(); + glm::ivec4 viewport{ 0, 0, bufferSize.x, bufferSize.y }; + + gpu::doInBatch("Upsample::run", args->_context, [&](gpu::Batch& batch) { + batch.enableStereo(false); + + batch.setFramebuffer(resampledFrameBuffer); + + batch.setViewportTransform(viewport); + batch.setProjectionTransform(glm::mat4()); + batch.resetViewTransform(); + batch.setPipeline(_pipeline); + + batch.setModelTransform(gpu::Framebuffer::evalSubregionTexcoordTransform(bufferSize, viewport)); + batch.setResourceTexture(0, sourceFramebuffer->getRenderBuffer(0)); + batch.draw(gpu::TRIANGLE_STRIP, 4); + }); + + // Set full final viewport + args->_viewport = viewport; + } +} diff --git a/libraries/render/src/render/ResampleTask.h b/libraries/render/src/render/ResampleTask.h index 25f9c6a3e9..e62b76e6d0 100644 --- a/libraries/render/src/render/ResampleTask.h +++ b/libraries/render/src/render/ResampleTask.h @@ -67,6 +67,20 @@ namespace render { gpu::FramebufferPointer getResampledFrameBuffer(const gpu::FramebufferPointer& sourceFramebuffer); }; + + class UpsampleToBlitFramebuffer { + public: + using Input = gpu::FramebufferPointer; + using JobModel = Job::ModelIO; + + UpsampleToBlitFramebuffer() {} + + void run(const RenderContextPointer& renderContext, const Input& input, gpu::FramebufferPointer& resampledFrameBuffer); + + protected: + + static gpu::PipelinePointer _pipeline; + }; } #endif // hifi_render_ResampleTask_h diff --git a/libraries/script-engine/src/RecordingScriptingInterface.cpp b/libraries/script-engine/src/RecordingScriptingInterface.cpp index f3bcbe6bb7..e57d80274a 100644 --- a/libraries/script-engine/src/RecordingScriptingInterface.cpp +++ b/libraries/script-engine/src/RecordingScriptingInterface.cpp @@ -93,7 +93,7 @@ void RecordingScriptingInterface::loadRecording(const QString& url, QScriptValue // when clip load fails, call the callback with the URL and failure boolean connect(clipLoader.data(), &recording::NetworkClipLoader::failed, callback.engine(), [this, weakClipLoader, url, callback](QNetworkReply::NetworkError error) mutable { - qCDebug(scriptengine) << "Failed to load recording from" << url; + qCDebug(scriptengine) << "Failed to load recording from\"" << url << '"'; if (callback.isFunction()) { QScriptValueList args { false, url }; diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 9d29f32931..02dcde3695 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -397,15 +397,20 @@ void ScriptEngine::executeOnScriptThread(std::function function, const Q } void ScriptEngine::waitTillDoneRunning() { + // Engine should be stopped already, but be defensive + stop(); + auto workerThread = thread(); - + + if (workerThread == QThread::currentThread()) { + qCWarning(scriptengine) << "ScriptEngine::waitTillDoneRunning called, but the script is on the same thread:" << getFilename(); + return; + } + if (_isThreaded && workerThread) { // We should never be waiting (blocking) on our own thread assert(workerThread != QThread::currentThread()); - // Engine should be stopped already, but be defensive - stop(); - auto startedWaiting = usecTimestampNow(); while (workerThread->isRunning()) { // If the final evaluation takes too long, then tell the script engine to stop running @@ -2200,6 +2205,7 @@ void ScriptEngine::loadEntityScript(const EntityItemID& entityID, const QString& * lifetime: 300 // Delete after 5 minutes. * }); */ +// The JSDoc is for the callEntityScriptMethod() call in this method. // since all of these operations can be asynch we will always do the actual work in the response handler // for the download void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, const QString& scriptOrURL, const QString& contents, bool isURL, bool success , const QString& status) { @@ -2384,8 +2390,10 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co *

Note: Can only be connected to via this.unoad = function () { ... } in the entity script.

*
ValueDimensionsNotes
Available in:Client Entity ScriptsServer Entity Scripts
* @function Entities.unload + * @param {Uuid} entityID - The ID of the entity that the script is running in. * @returns {Signal} */ +// The JSDoc is for the callEntityScriptMethod() call in this method. void ScriptEngine::unloadEntityScript(const EntityItemID& entityID, bool shouldRemoveFromMap) { if (QThread::currentThread() != thread()) { #ifdef THREAD_DEBUGGING diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index a2b16ed178..52ece63dc2 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -318,6 +318,7 @@ public: /**jsdoc * Adds a function to the list of functions called when an entity event occurs on a particular entity. + *

See also, the {@link Entities} API.

* @function Script.addEventHandler * @param {Uuid} entityID - The ID of the entity. * @param {Script.EntityEvent} eventName - The name of the entity event. @@ -341,6 +342,7 @@ public: /**jsdoc * Removes a function from the list of functions called when an entity event occurs on a particular entity. + *

See also, the {@link Entities} API.

* @function Script.removeEventHandler * @param {Uuid} entityID - The ID of the entity. * @param {Script.EntityEvent} eventName - The name of the entity event. diff --git a/libraries/script-engine/src/ScriptEngines.cpp b/libraries/script-engine/src/ScriptEngines.cpp index 25c330e3fe..17f2aea9a5 100644 --- a/libraries/script-engine/src/ScriptEngines.cpp +++ b/libraries/script-engine/src/ScriptEngines.cpp @@ -114,7 +114,7 @@ QUrl expandScriptUrl(const QUrl& rawScriptURL) { url = QUrl::fromLocalFile(fileInfo.canonicalFilePath()); QUrl defaultScriptsLoc = PathUtils::defaultScriptsLocation(); - if (!defaultScriptsLoc.isParentOf(url)) { + if (!defaultScriptsLoc.isParentOf(url) && defaultScriptsLoc != url) { qCWarning(scriptengine) << "Script.include() ignoring file path" << "-- outside of standard libraries: " << url.path() diff --git a/libraries/shared/src/BillboardMode.h b/libraries/shared/src/BillboardMode.h index 700127aff1..aa00ce34aa 100644 --- a/libraries/shared/src/BillboardMode.h +++ b/libraries/shared/src/BillboardMode.h @@ -21,7 +21,7 @@ * "none"The entity will not be billboarded. * "yaw"The entity will yaw, but not pitch, to face the camera. Its actual rotation will be * ignored. - * "full"The entity will be billboarded to face the camera. Its actual rotation will be + * "full"The entity will yaw and pitch to face the camera. Its actual rotation will be * ignored. * * diff --git a/libraries/shared/src/ComponentMode.cpp b/libraries/shared/src/ComponentMode.cpp index 5b3a42e9ae..42ef820e2a 100644 --- a/libraries/shared/src/ComponentMode.cpp +++ b/libraries/shared/src/ComponentMode.cpp @@ -8,6 +8,22 @@ #include "ComponentMode.h" +/**jsdoc + *

How an effect is applied in a {@link Entities.EntityProperties-Zone|Zone} entity.

+ * + * + * + * + * + * + * + * + * + *
ValueDescription
"inherit"The effect from any enclosing zone continues into this zone.
"disabled"The effect — from any enclosing zone and this zone — is disabled in + * this zone.
"enabled"The effect from this zone is enabled, overriding the effect from any enclosing + * zone.
+ * @typedef {string} Entities.ComponentMode + */ const char* componentModeNames[] = { "inherit", "disabled", @@ -22,6 +38,20 @@ QString ComponentModeHelpers::getNameForComponentMode(ComponentMode mode) { return componentModeNames[(int)mode]; } +/**jsdoc + *

The priority of updates from avatars in a zone to other clients.

+ * + * + * + * + * + * + * + * + * + *
ValueDescription
"inherit"The update priority from any enclosing zone continues into this zone.
"crowd"The update priority in this zone is the normal priority.
"hero"Avatars in this zone have an increased update priority.
+ * @typedef {string} Entities.AvatarPriorityMode + */ const char* avatarPriorityModeNames[] = { "inherit", "crowd", diff --git a/libraries/shared/src/GPUIdent.cpp b/libraries/shared/src/GPUIdent.cpp index f092a56c17..c195b2ec3a 100644 --- a/libraries/shared/src/GPUIdent.cpp +++ b/libraries/shared/src/GPUIdent.cpp @@ -282,12 +282,13 @@ GPUIdent* GPUIdent::ensureQuery(const QString& vendor, const QString& renderer) if (!validAdapterList.empty()) { for (auto outy = adapterToOutputs.begin(); outy != adapterToOutputs.end(); ++outy) { - AdapterEntry entry = *outy; + AdapterEntry entry = *outy; for (auto test = entry.second.begin(); test != entry.second.end(); ++test) { - + std::wstring wDeviceName(test->DeviceName); + std::string deviceName(wDeviceName.begin(), wDeviceName.end()); + nlohmann::json output = {}; - output["description"] = entry.first.first.Description; - output["deviceName"]= test->DeviceName; + output["model"] = deviceName; output["coordinatesleft"] = test->DesktopCoordinates.left; output["coordinatesright"] = test->DesktopCoordinates.right; output["coordinatestop"] = test->DesktopCoordinates.top; diff --git a/libraries/shared/src/GizmoType.h b/libraries/shared/src/GizmoType.h index fc047b257d..20870158bc 100644 --- a/libraries/shared/src/GizmoType.h +++ b/libraries/shared/src/GizmoType.h @@ -12,16 +12,16 @@ #include "QString" /**jsdoc - *

Controls how the Gizmo behaves and renders

+ *

A {@link Entities.EntityProperties-Gizmo|Gizmo} entity may be one of the following types:

* * * * * - * + * * *
ValueDescription
ringA ring gizmo.
"ring"A ring gizmo.
- * @typedef {string} GizmoType + * @typedef {string} Entities.GizmoType */ enum GizmoType { diff --git a/libraries/shared/src/PointerEvent.cpp b/libraries/shared/src/PointerEvent.cpp index 69d3f28d80..54374a934c 100644 --- a/libraries/shared/src/PointerEvent.cpp +++ b/libraries/shared/src/PointerEvent.cpp @@ -66,7 +66,7 @@ void PointerEvent::setButton(Button button) { } /**jsdoc - * A PointerEvent defines a 2D or 3D mouse or similar pointer event. + * A 2D or 3D mouse or similar pointer event. * @typedef {object} PointerEvent * @property {string} type - The type of event: "Press", "DoublePress", "Release", or * "Move". diff --git a/libraries/shared/src/Preferences.h b/libraries/shared/src/Preferences.h index 9f84d9c8b7..9147117792 100644 --- a/libraries/shared/src/Preferences.h +++ b/libraries/shared/src/Preferences.h @@ -115,48 +115,39 @@ protected: const Lambda _triggerHandler; }; - -template -class TypedPreference : public Preference { -public: - using Getter = std::function; - using Setter = std::function; - - TypedPreference(const QString& category, const QString& name, Getter getter, Setter setter) - : Preference(category, name), _getter(getter), _setter(setter) { } - - T getValue() const { return _value; } - void setValue(const T& value) { if (_value != value) { _value = value; emitValueChanged(); } } - void load() override { _value = _getter(); } - void save() const override { - T oldValue = _getter(); - if (_value != oldValue) { - _setter(_value); - } - } - -protected: - T _value; - const Getter _getter; - const Setter _setter; -}; - -class BoolPreference : public TypedPreference { +class BoolPreference : public Preference { Q_OBJECT Q_PROPERTY(bool value READ getValue WRITE setValue NOTIFY valueChanged) public: + using Getter = std::function; + using Setter = std::function; + BoolPreference(const QString& category, const QString& name, Getter getter, Setter setter) - : TypedPreference(category, name, getter, setter) { } + : Preference(category, name), _getter(getter), _setter(setter) { } + + bool getValue() const { return _value; } + void setValue(const bool& value) { if (_value != value) { _value = value; emitValueChanged(); } } + void load() override { _value = _getter(); } + void save() const override { + bool oldValue = _getter(); + if (_value != oldValue) { + _setter(_value); + } + } signals: void valueChanged(); protected: + bool _value; + const Getter _getter; + const Setter _setter; + void emitValueChanged() override { emit valueChanged(); } }; -class FloatPreference : public TypedPreference { +class FloatPreference : public Preference { Q_OBJECT Q_PROPERTY(float value READ getValue WRITE setValue NOTIFY valueChanged) Q_PROPERTY(float min READ getMin CONSTANT) @@ -165,8 +156,21 @@ class FloatPreference : public TypedPreference { Q_PROPERTY(float decimals READ getDecimals CONSTANT) public: + using Getter = std::function; + using Setter = std::function; + FloatPreference(const QString& category, const QString& name, Getter getter, Setter setter) - : TypedPreference(category, name, getter, setter) { } + : Preference(category, name), _getter(getter), _setter(setter) { } + + float getValue() const { return _value; } + void setValue(const float& value) { if (_value != value) { _value = value; emitValueChanged(); } } + void load() override { _value = _getter(); } + void save() const override { + float oldValue = _getter(); + if (_value != oldValue) { + _setter(_value); + } + } float getMin() const { return _min; } void setMin(float min) { _min = min; }; @@ -186,14 +190,17 @@ signals: protected: void emitValueChanged() override { emit valueChanged(); } + float _value; + const Getter _getter; + const Setter _setter; + float _decimals { 0 }; float _min { 0 }; float _max { 1 }; float _step { 0.1f }; }; - -class IntPreference : public TypedPreference { +class IntPreference : public Preference { Q_OBJECT Q_PROPERTY(float value READ getValue WRITE setValue NOTIFY valueChanged) Q_PROPERTY(float min READ getMin CONSTANT) @@ -202,8 +209,21 @@ class IntPreference : public TypedPreference { Q_PROPERTY(int decimals READ getDecimals CONSTANT) public: + using Getter = std::function; + using Setter = std::function; + IntPreference(const QString& category, const QString& name, Getter getter, Setter setter) - : TypedPreference(category, name, getter, setter) { } + : Preference(category, name), _getter(getter), _setter(setter) { } + + int getValue() const { return _value; } + void setValue(const int& value) { if (_value != value) { _value = value; emitValueChanged(); } } + void load() override { _value = _getter(); } + void save() const override { + int oldValue = _getter(); + if (_value != oldValue) { + _setter(_value); + } + } float getMin() const { return _min; } void setMin(float min) { _min = min; }; @@ -221,6 +241,10 @@ signals: void valueChanged(); protected: + int _value; + const Getter _getter; + const Setter _setter; + void emitValueChanged() override { emit valueChanged(); } int _min { std::numeric_limits::min() }; @@ -229,19 +253,37 @@ protected: int _decimals { 0 }; }; -class StringPreference : public TypedPreference { +class StringPreference : public Preference { Q_OBJECT Q_PROPERTY(QString value READ getValue WRITE setValue NOTIFY valueChanged) public: + using Getter = std::function; + using Setter = std::function; + StringPreference(const QString& category, const QString& name, Getter getter, Setter setter) - : TypedPreference(category, name, getter, setter) { } + : Preference(category, name), _getter(getter), _setter(setter) { } + + + QString getValue() const { return _value; } + void setValue(const QString& value) { if (_value != value) { _value = value; emitValueChanged(); } } + void load() override { _value = _getter(); } + void save() const override { + QString oldValue = _getter(); + if (_value != oldValue) { + _setter(_value); + } + } signals: void valueChanged(); protected: void emitValueChanged() override { emit valueChanged(); } + + QString _value; + const Getter _getter; + const Setter _setter; }; class SliderPreference : public FloatPreference { @@ -303,7 +345,7 @@ public: ComboBoxPreference(const QString& category, const QString& name, Getter getter, Setter setter) : EditPreference(category, name, getter, setter) { } Type getType() override { return ComboBox; } - + const QStringList& getItems() { return _items; } void setItems(const QStringList& items) { _items = items; } @@ -342,6 +384,9 @@ class CheckPreference : public BoolPreference { Q_OBJECT Q_PROPERTY(bool indented READ getIndented CONSTANT) public: + using Getter = std::function; + using Setter = std::function; + CheckPreference(const QString& category, const QString& name, Getter getter, Setter setter) : BoolPreference(category, name, getter, setter) { } Type getType() override { return Checkbox; } diff --git a/libraries/shared/src/PrimitiveMode.h b/libraries/shared/src/PrimitiveMode.h index 6072f24fb9..421b940aa1 100644 --- a/libraries/shared/src/PrimitiveMode.h +++ b/libraries/shared/src/PrimitiveMode.h @@ -12,17 +12,17 @@ #include "QString" /**jsdoc - *

How the geometry of the entity is rendered.

+ *

How the geometry of an entity is rendered.

* * * * * - * - * + * + * * *
ValueDescription
solidThe entity will be drawn as a solid shape.
linesThe entity will be drawn as wireframe.
"solid"The entity is drawn as a solid shape.
"lines"The entity is drawn as wireframe.
- * @typedef {string} PrimitiveMode + * @typedef {string} Entities.PrimitiveMode */ enum class PrimitiveMode { diff --git a/libraries/shared/src/PrioritySortUtil.h b/libraries/shared/src/PrioritySortUtil.h index d6a740231c..e4a258a065 100644 --- a/libraries/shared/src/PrioritySortUtil.h +++ b/libraries/shared/src/PrioritySortUtil.h @@ -45,7 +45,7 @@ namespace PrioritySortUtil { class PriorityQueue { public: PriorityQueue() = delete; - PriorityQueue(const ConicalViewFrustums& views) : _views(views) { } + PriorityQueue(const ConicalViewFrustums& views) : _views(views), _usecCurrentTime(usecTimestampNow()) { } PriorityQueue(const ConicalViewFrustums& views, float angularWeight, float centerWeight, float ageWeight) : _views(views), _angularWeight(angularWeight), _centerWeight(centerWeight), _ageWeight(ageWeight) , _usecCurrentTime(usecTimestampNow()) { diff --git a/libraries/shared/src/PulseMode.h b/libraries/shared/src/PulseMode.h index 7c823db68c..4e398cbbaa 100644 --- a/libraries/shared/src/PulseMode.h +++ b/libraries/shared/src/PulseMode.h @@ -18,12 +18,12 @@ * ValueDescription * * - * noneNo pulsing. - * inPulse in phase with the pulse period. - * outPulse out of phase with the pulse period. + * "none"No pulsing. + * "in"Pulse in phase with the pulse period. + * "out"Pulse out of phase with the pulse period. * * - * @typedef {string} PulseMode + * @typedef {string} Entities.PulseMode */ enum class PulseMode { diff --git a/libraries/shared/src/RegisteredMetaTypes.h b/libraries/shared/src/RegisteredMetaTypes.h index 2ca7863ec6..5b8aa34fd9 100644 --- a/libraries/shared/src/RegisteredMetaTypes.h +++ b/libraries/shared/src/RegisteredMetaTypes.h @@ -122,43 +122,43 @@ glm::vec3 vec3FromVariant(const QVariant &object, bool& valid); glm::vec3 vec3FromVariant(const QVariant &object); /**jsdoc -* A color vector. See also the {@link Vec3(0)|Vec3} object. -* -* @typedef {object} Color -* @property {number} red - Red component value. Integer in the range 0 - 255. Synonyms: r, x. -* @property {number} green - Green component value. Integer in the range 0 - 255. Synonyms: g, y. -* @property {number} blue - Blue component value. Integer in the range 0 - 255. Synonyms: b, z. -* @example Colors can be set in multiple ways and modified with their aliases, but still stringify in the same way -* Entities.editEntity(, { color: { x: 1, y: 2, z: 3 }}); // { red: 1, green: 2, blue: 3 } -* Entities.editEntity(, { color: { r: 4, g: 5, b: 6 }}); // { red: 4, green: 5, blue: 6 } -* Entities.editEntity(, { color: { red: 7, green: 8, blue: 9 }}); // { red: 7, green: 8, blue: 9 } -* Entities.editEntity(, { color: [10, 11, 12] }); // { red: 10, green: 11, blue: 12 } -* Entities.editEntity(, { color: 13 }); // { red: 13, green: 13, blue: 13 } -* var color = Entities.getEntityProperties().color; // { red: 13, green: 13, blue: 13 } -* color.g = 14; // { red: 13, green: 14, blue: 13 } -* color.blue = 15; // { red: 13, green: 14, blue: 15 } -* Entities.editEntity(, { color: "red"}); // { red: 255, green: 0, blue: 0 } -* Entities.editEntity(, { color: "#00FF00"}); // { red: 0, green: 255, blue: 0 } -*/ + * A color vector. See also the {@link Vec3(0)|Vec3} object. + * + * @typedef {object} Color + * @property {number} red - Red component value. Integer in the range 0 - 255. Synonyms: r, x. + * @property {number} green - Green component value. Integer in the range 0 - 255. Synonyms: g, y. + * @property {number} blue - Blue component value. Integer in the range 0 - 255. Synonyms: b, z. + * @example Colors can be set in multiple ways and modified with their aliases, but still stringify in the same way + * Entities.editEntity(, { color: { x: 1, y: 2, z: 3 }}); // { red: 1, green: 2, blue: 3 } + * Entities.editEntity(, { color: { r: 4, g: 5, b: 6 }}); // { red: 4, green: 5, blue: 6 } + * Entities.editEntity(, { color: { red: 7, green: 8, blue: 9 }}); // { red: 7, green: 8, blue: 9 } + * Entities.editEntity(, { color: [10, 11, 12] }); // { red: 10, green: 11, blue: 12 } + * Entities.editEntity(, { color: 13 }); // { red: 13, green: 13, blue: 13 } + * var color = Entities.getEntityProperties().color; // { red: 13, green: 13, blue: 13 } + * color.g = 14; // { red: 13, green: 14, blue: 13 } + * color.blue = 15; // { red: 13, green: 14, blue: 15 } + * Entities.editEntity(, { color: "red"}); // { red: 255, green: 0, blue: 0 } + * Entities.editEntity(, { color: "#00FF00"}); // { red: 0, green: 255, blue: 0 } + */ /**jsdoc -* A color vector. See also the {@link Vec3(0)|Vec3} object. -* -* @typedef {object} ColorFloat -* @property {number} red - Red component value. Real in the range 0 - 255. Synonyms: r, x. -* @property {number} green - Green component value. Real in the range 0 - 255. Synonyms: g, y. -* @property {number} blue - Blue component value. Real in the range 0 - 255. Synonyms: b, z. -* @example ColorFloats can be set in multiple ways and modified with their aliases, but still stringify in the same way -* Entities.editEntity(, { color: { x: 1, y: 2, z: 3 }}); // { red: 1, green: 2, blue: 3 } -* Entities.editEntity(, { color: { r: 4, g: 5, b: 6 }}); // { red: 4, green: 5, blue: 6 } -* Entities.editEntity(, { color: { red: 7, green: 8, blue: 9 }}); // { red: 7, green: 8, blue: 9 } -* Entities.editEntity(, { color: [10, 11, 12] }); // { red: 10, green: 11, blue: 12 } -* Entities.editEntity(, { color: 13 }); // { red: 13, green: 13, blue: 13 } -* var color = Entities.getEntityProperties().color; // { red: 13, green: 13, blue: 13 } -* color.g = 14; // { red: 13, green: 14, blue: 13 } -* color.blue = 15; // { red: 13, green: 14, blue: 15 } -* Entities.editEntity(, { color: "red"}); // { red: 255, green: 0, blue: 0 } -* Entities.editEntity(, { color: "#00FF00"}); // { red: 0, green: 255, blue: 0 } -*/ + * A color vector with real values. Values may also be null. See also the {@link Vec3(0)|Vec3} object. + * + * @typedef {object} ColorFloat + * @property {number} red - Red component value. Real in the range 0 - 255. Synonyms: r, x. + * @property {number} green - Green component value. Real in the range 0 - 255. Synonyms: g, y. + * @property {number} blue - Blue component value. Real in the range 0 - 255. Synonyms: b, z. + * @example ColorFloats can be set in multiple ways and modified with their aliases, but still stringify in the same way + * Entities.editEntity(, { color: { x: 1, y: 2, z: 3 }}); // { red: 1, green: 2, blue: 3 } + * Entities.editEntity(, { color: { r: 4, g: 5, b: 6 }}); // { red: 4, green: 5, blue: 6 } + * Entities.editEntity(, { color: { red: 7, green: 8, blue: 9 }}); // { red: 7, green: 8, blue: 9 } + * Entities.editEntity(, { color: [10, 11, 12] }); // { red: 10, green: 11, blue: 12 } + * Entities.editEntity(, { color: 13 }); // { red: 13, green: 13, blue: 13 } + * var color = Entities.getEntityProperties().color; // { red: 13, green: 13, blue: 13 } + * color.g = 14; // { red: 13, green: 14, blue: 13 } + * color.blue = 15; // { red: 13, green: 14, blue: 15 } + * Entities.editEntity(, { color: "red"}); // { red: 255, green: 0, blue: 0 } + * Entities.editEntity(, { color: "#00FF00"}); // { red: 0, green: 255, blue: 0 } + */ QScriptValue u8vec3ToScriptValue(QScriptEngine* engine, const glm::u8vec3& vec3); QScriptValue u8vec3ColorToScriptValue(QScriptEngine* engine, const glm::u8vec3& vec3); void u8vec3FromScriptValue(const QScriptValue& object, glm::u8vec3& vec3); diff --git a/libraries/shared/src/RenderLayer.h b/libraries/shared/src/RenderLayer.h index b5bf885616..c95ceb192c 100644 --- a/libraries/shared/src/RenderLayer.h +++ b/libraries/shared/src/RenderLayer.h @@ -12,18 +12,18 @@ #include "QString" /**jsdoc - *

In which layer an entity is rendered.

+ *

A layer in which an entity may be rendered.

* * * * * - * - * - * + * + * + * * *
ValueDescription
worldThe entity will be drawn in the world with everything else.
frontThe entity will be drawn on top of the world layer, but behind the HUD sphere.
hudThe entity will be drawn on top of other layers and the HUD sphere.
"world"The entity is drawn in the world with everything else.
"front"The entity is drawn on top of the world layer but behind the HUD sphere.
"hud"The entity is drawn on top of other layers and the HUD sphere.
- * @typedef {string} RenderLayer + * @typedef {string} Entities.RenderLayer */ enum class RenderLayer { diff --git a/libraries/shared/src/ShapeInfo.cpp b/libraries/shared/src/ShapeInfo.cpp index 00c9b883aa..c60d1c2574 100644 --- a/libraries/shared/src/ShapeInfo.cpp +++ b/libraries/shared/src/ShapeInfo.cpp @@ -17,7 +17,7 @@ #include "NumericalConstants.h" // for MILLIMETERS_PER_METER /**jsdoc - *

A ShapeType defines the shape used for collisions or zones.

+ *

Defines the shape used for collisions or zones.

* * * diff --git a/libraries/shared/src/SpatiallyNestable.cpp b/libraries/shared/src/SpatiallyNestable.cpp index f49921f272..8387270905 100644 --- a/libraries/shared/src/SpatiallyNestable.cpp +++ b/libraries/shared/src/SpatiallyNestable.cpp @@ -1348,7 +1348,20 @@ SpatiallyNestablePointer SpatiallyNestable::findByID(QUuid id, bool& success) { return parentWP.lock(); } - +/**jsdoc + *

An in-world item may be one of the following types:

+ *
ValueDescription
+ * + * + * + * + * + * + * + * + *
ValueDescription
"entity"The item is an entity.
"avatar"The item is an avatar.
"unknown"The item cannot be found.
+ * @typedef {string} Entities.NestableType + */ QString SpatiallyNestable::nestableTypeToString(NestableType nestableType) { switch(nestableType) { case NestableType::Entity: diff --git a/libraries/shared/src/WebInputMode.h b/libraries/shared/src/WebInputMode.h index d97ccef519..4f43691ac9 100644 --- a/libraries/shared/src/WebInputMode.h +++ b/libraries/shared/src/WebInputMode.h @@ -12,14 +12,14 @@ #include "QString" /**jsdoc - *

Controls how the web surface processed PointerEvents

+ *

Specifies how a web surface processes events.

* * * * * - * - * + * + * * *
ValueDescription
touchEvents are processed as touch events.
mouseEvents are processed as mouse events.
"touch"Events are processed as touch events.
"mouse"Events are processed as mouse events.
* @typedef {string} WebInputMode diff --git a/libraries/shared/src/avx2/BlendshapePacking_avx2.cpp b/libraries/shared/src/avx2/BlendshapePacking_avx2.cpp new file mode 100644 index 0000000000..1fea60a315 --- /dev/null +++ b/libraries/shared/src/avx2/BlendshapePacking_avx2.cpp @@ -0,0 +1,285 @@ +// +// BlendshapePacking_avx2.cpp +// +// Created by Ken Cooke on 6/22/19. +// Copyright 2019 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 +// + +#ifdef __AVX2__ + +#include +#include + +void packBlendshapeOffsets_AVX2(float (*unpacked)[9], uint32_t (*packed)[4], int size) { + + int i = 0; + for (; i < size - 7; i += 8) { // blocks of 8 + + // + // deinterleave (8x9 to 9x8 matrix transpose) + // + __m256 s0 = _mm256_insertf128_ps(_mm256_castps128_ps256(_mm_loadu_ps(&unpacked[i+0][0])), _mm_loadu_ps(&unpacked[i+4][0]), 1); + __m256 s1 = _mm256_insertf128_ps(_mm256_castps128_ps256(_mm_loadu_ps(&unpacked[i+1][0])), _mm_loadu_ps(&unpacked[i+5][0]), 1); + __m256 s2 = _mm256_insertf128_ps(_mm256_castps128_ps256(_mm_loadu_ps(&unpacked[i+2][0])), _mm_loadu_ps(&unpacked[i+6][0]), 1); + __m256 s3 = _mm256_insertf128_ps(_mm256_castps128_ps256(_mm_loadu_ps(&unpacked[i+3][0])), _mm_loadu_ps(&unpacked[i+7][0]), 1); + __m256 s4 = _mm256_insertf128_ps(_mm256_castps128_ps256(_mm_loadu_ps(&unpacked[i+0][4])), _mm_loadu_ps(&unpacked[i+4][4]), 1); + __m256 s5 = _mm256_insertf128_ps(_mm256_castps128_ps256(_mm_loadu_ps(&unpacked[i+1][4])), _mm_loadu_ps(&unpacked[i+5][4]), 1); + __m256 s6 = _mm256_insertf128_ps(_mm256_castps128_ps256(_mm_loadu_ps(&unpacked[i+2][4])), _mm_loadu_ps(&unpacked[i+6][4]), 1); + __m256 s7 = _mm256_insertf128_ps(_mm256_castps128_ps256(_mm_loadu_ps(&unpacked[i+3][4])), _mm_loadu_ps(&unpacked[i+7][4]), 1); + + __m256 t0 = _mm256_unpacklo_ps(s0, s1); + __m256 t1 = _mm256_unpackhi_ps(s0, s1); + __m256 t2 = _mm256_unpacklo_ps(s2, s3); + __m256 t3 = _mm256_unpackhi_ps(s2, s3); + __m256 t4 = _mm256_unpacklo_ps(s4, s5); + __m256 t5 = _mm256_unpackhi_ps(s4, s5); + __m256 t6 = _mm256_unpacklo_ps(s6, s7); + __m256 t7 = _mm256_unpackhi_ps(s6, s7); + + __m256 px = _mm256_shuffle_ps(t0, t2, _MM_SHUFFLE(1,0,1,0)); + __m256 py = _mm256_shuffle_ps(t0, t2, _MM_SHUFFLE(3,2,3,2)); + __m256 pz = _mm256_shuffle_ps(t1, t3, _MM_SHUFFLE(1,0,1,0)); + __m256 nx = _mm256_shuffle_ps(t1, t3, _MM_SHUFFLE(3,2,3,2)); + __m256 ny = _mm256_shuffle_ps(t4, t6, _MM_SHUFFLE(1,0,1,0)); + __m256 nz = _mm256_shuffle_ps(t4, t6, _MM_SHUFFLE(3,2,3,2)); + __m256 tx = _mm256_shuffle_ps(t5, t7, _MM_SHUFFLE(1,0,1,0)); + __m256 ty = _mm256_shuffle_ps(t5, t7, _MM_SHUFFLE(3,2,3,2)); + + __m256 tz = _mm256_i32gather_ps(unpacked[i+0], _mm256_setr_epi32(8,17,26,35,44,53,62,71), sizeof(float)); + + // abs(pos) + __m256 apx = _mm256_andnot_ps(_mm256_set1_ps(-0.0f), px); + __m256 apy = _mm256_andnot_ps(_mm256_set1_ps(-0.0f), py); + __m256 apz = _mm256_andnot_ps(_mm256_set1_ps(-0.0f), pz); + + // len = compMax(abs(pos)) + __m256 len = _mm256_max_ps(_mm256_max_ps(apx, apy), apz); + + // detect zeros + __m256 mask = _mm256_cmp_ps(len, _mm256_setzero_ps(), _CMP_EQ_OQ); + + // rcp = 1.0f / len + __m256 rcp = _mm256_div_ps(_mm256_set1_ps(1.0f), len); + + // replace +inf with 1.0f + rcp = _mm256_blendv_ps(rcp, _mm256_set1_ps(1.0f), mask); + len = _mm256_blendv_ps(len, _mm256_set1_ps(1.0f), mask); + + // pos *= 1.0f / len + px = _mm256_mul_ps(px, rcp); + py = _mm256_mul_ps(py, rcp); + pz = _mm256_mul_ps(pz, rcp); + + // clamp(vec, -1.0f, 1.0f) + px = _mm256_min_ps(_mm256_max_ps(px, _mm256_set1_ps(-1.0f)), _mm256_set1_ps(1.0f)); + py = _mm256_min_ps(_mm256_max_ps(py, _mm256_set1_ps(-1.0f)), _mm256_set1_ps(1.0f)); + pz = _mm256_min_ps(_mm256_max_ps(pz, _mm256_set1_ps(-1.0f)), _mm256_set1_ps(1.0f)); + nx = _mm256_min_ps(_mm256_max_ps(nx, _mm256_set1_ps(-1.0f)), _mm256_set1_ps(1.0f)); + ny = _mm256_min_ps(_mm256_max_ps(ny, _mm256_set1_ps(-1.0f)), _mm256_set1_ps(1.0f)); + nz = _mm256_min_ps(_mm256_max_ps(nz, _mm256_set1_ps(-1.0f)), _mm256_set1_ps(1.0f)); + tx = _mm256_min_ps(_mm256_max_ps(tx, _mm256_set1_ps(-1.0f)), _mm256_set1_ps(1.0f)); + ty = _mm256_min_ps(_mm256_max_ps(ty, _mm256_set1_ps(-1.0f)), _mm256_set1_ps(1.0f)); + tz = _mm256_min_ps(_mm256_max_ps(tz, _mm256_set1_ps(-1.0f)), _mm256_set1_ps(1.0f)); + + // vec *= 511.0f + px = _mm256_mul_ps(px, _mm256_set1_ps(511.0f)); + py = _mm256_mul_ps(py, _mm256_set1_ps(511.0f)); + pz = _mm256_mul_ps(pz, _mm256_set1_ps(511.0f)); + nx = _mm256_mul_ps(nx, _mm256_set1_ps(511.0f)); + ny = _mm256_mul_ps(ny, _mm256_set1_ps(511.0f)); + nz = _mm256_mul_ps(nz, _mm256_set1_ps(511.0f)); + tx = _mm256_mul_ps(tx, _mm256_set1_ps(511.0f)); + ty = _mm256_mul_ps(ty, _mm256_set1_ps(511.0f)); + tz = _mm256_mul_ps(tz, _mm256_set1_ps(511.0f)); + + // veci = lrint(vec) & 03ff + __m256i pxi = _mm256_and_si256(_mm256_cvtps_epi32(px), _mm256_set1_epi32(0x3ff)); + __m256i pyi = _mm256_and_si256(_mm256_cvtps_epi32(py), _mm256_set1_epi32(0x3ff)); + __m256i pzi = _mm256_and_si256(_mm256_cvtps_epi32(pz), _mm256_set1_epi32(0x3ff)); + __m256i nxi = _mm256_and_si256(_mm256_cvtps_epi32(nx), _mm256_set1_epi32(0x3ff)); + __m256i nyi = _mm256_and_si256(_mm256_cvtps_epi32(ny), _mm256_set1_epi32(0x3ff)); + __m256i nzi = _mm256_and_si256(_mm256_cvtps_epi32(nz), _mm256_set1_epi32(0x3ff)); + __m256i txi = _mm256_and_si256(_mm256_cvtps_epi32(tx), _mm256_set1_epi32(0x3ff)); + __m256i tyi = _mm256_and_si256(_mm256_cvtps_epi32(ty), _mm256_set1_epi32(0x3ff)); + __m256i tzi = _mm256_and_si256(_mm256_cvtps_epi32(tz), _mm256_set1_epi32(0x3ff)); + + // pack = (xi << 0) | (yi << 10) | (zi << 20); + __m256i li = _mm256_castps_si256(len); // length + __m256i pi = _mm256_or_si256(_mm256_or_si256(pxi, _mm256_slli_epi32(pyi, 10)), _mm256_slli_epi32(pzi, 20)); // position + __m256i ni = _mm256_or_si256(_mm256_or_si256(nxi, _mm256_slli_epi32(nyi, 10)), _mm256_slli_epi32(nzi, 20)); // normal + __m256i ti = _mm256_or_si256(_mm256_or_si256(txi, _mm256_slli_epi32(tyi, 10)), _mm256_slli_epi32(tzi, 20)); // tangent + + // + // interleave (4x4 matrix transpose) + // + __m256i u0 = _mm256_unpacklo_epi32(li, pi); + __m256i u1 = _mm256_unpackhi_epi32(li, pi); + __m256i u2 = _mm256_unpacklo_epi32(ni, ti); + __m256i u3 = _mm256_unpackhi_epi32(ni, ti); + + __m256i v0 = _mm256_unpacklo_epi64(u0, u2); + __m256i v1 = _mm256_unpackhi_epi64(u0, u2); + __m256i v2 = _mm256_unpacklo_epi64(u1, u3); + __m256i v3 = _mm256_unpackhi_epi64(u1, u3); + + __m256i w0 = _mm256_permute2f128_si256(v0, v1, 0x20); + __m256i w1 = _mm256_permute2f128_si256(v2, v3, 0x20); + __m256i w2 = _mm256_permute2f128_si256(v0, v1, 0x31); + __m256i w3 = _mm256_permute2f128_si256(v2, v3, 0x31); + + // store pack x 8 + _mm256_storeu_si256((__m256i*)packed[i+0], w0); + _mm256_storeu_si256((__m256i*)packed[i+2], w1); + _mm256_storeu_si256((__m256i*)packed[i+4], w2); + _mm256_storeu_si256((__m256i*)packed[i+6], w3); + } + + if (i < size) { // remainder + int rem = size - i; + + // + // deinterleave (8x9 to 9x8 matrix transpose) + // + __m256 s0 = _mm256_setzero_ps(); + __m256 s1 = _mm256_setzero_ps(); + __m256 s2 = _mm256_setzero_ps(); + __m256 s3 = _mm256_setzero_ps(); + __m256 s4 = _mm256_setzero_ps(); + __m256 s5 = _mm256_setzero_ps(); + __m256 s6 = _mm256_setzero_ps(); + __m256 s7 = _mm256_setzero_ps(); + + switch (rem) { + case 7: s6 = _mm256_loadu_ps(unpacked[i+6]); + case 6: s5 = _mm256_loadu_ps(unpacked[i+5]); + case 5: s4 = _mm256_loadu_ps(unpacked[i+4]); + case 4: s3 = _mm256_loadu_ps(unpacked[i+3]); + case 3: s2 = _mm256_loadu_ps(unpacked[i+2]); + case 2: s1 = _mm256_loadu_ps(unpacked[i+1]); + case 1: s0 = _mm256_loadu_ps(unpacked[i+0]); + } + + __m256 t0 = _mm256_unpacklo_ps(s0, s1); + __m256 t1 = _mm256_unpackhi_ps(s0, s1); + __m256 t2 = _mm256_unpacklo_ps(s2, s3); + __m256 t3 = _mm256_unpackhi_ps(s2, s3); + __m256 t4 = _mm256_unpacklo_ps(s4, s5); + __m256 t5 = _mm256_unpackhi_ps(s4, s5); + __m256 t6 = _mm256_unpacklo_ps(s6, s7); + __m256 t7 = _mm256_unpackhi_ps(s6, s7); + + s0 = _mm256_shuffle_ps(t0, t2, _MM_SHUFFLE(1,0,1,0)); + s1 = _mm256_shuffle_ps(t0, t2, _MM_SHUFFLE(3,2,3,2)); + s2 = _mm256_shuffle_ps(t1, t3, _MM_SHUFFLE(1,0,1,0)); + s3 = _mm256_shuffle_ps(t1, t3, _MM_SHUFFLE(3,2,3,2)); + s4 = _mm256_shuffle_ps(t4, t6, _MM_SHUFFLE(1,0,1,0)); + s5 = _mm256_shuffle_ps(t4, t6, _MM_SHUFFLE(3,2,3,2)); + s6 = _mm256_shuffle_ps(t5, t7, _MM_SHUFFLE(1,0,1,0)); + s7 = _mm256_shuffle_ps(t5, t7, _MM_SHUFFLE(3,2,3,2)); + + __m256 px = _mm256_permute2f128_ps(s0, s4, 0x20); + __m256 py = _mm256_permute2f128_ps(s1, s5, 0x20); + __m256 pz = _mm256_permute2f128_ps(s2, s6, 0x20); + __m256 nx = _mm256_permute2f128_ps(s3, s7, 0x20); + __m256 ny = _mm256_permute2f128_ps(s0, s4, 0x31); + __m256 nz = _mm256_permute2f128_ps(s1, s5, 0x31); + __m256 tx = _mm256_permute2f128_ps(s2, s6, 0x31); + __m256 ty = _mm256_permute2f128_ps(s3, s7, 0x31); + + __m256i loadmask = _mm256_cvtepi8_epi32(_mm_cvtsi64_si128(0xffffffffffffffffULL >> (64 - 8 * rem))); + __m256 tz = _mm256_mask_i32gather_ps(_mm256_setzero_ps(), unpacked[i+0], _mm256_setr_epi32(8,17,26,35,44,53,62,71), + _mm256_castsi256_ps(loadmask), sizeof(float)); + // abs(pos) + __m256 apx = _mm256_andnot_ps(_mm256_set1_ps(-0.0f), px); + __m256 apy = _mm256_andnot_ps(_mm256_set1_ps(-0.0f), py); + __m256 apz = _mm256_andnot_ps(_mm256_set1_ps(-0.0f), pz); + + // len = compMax(abs(pos)) + __m256 len = _mm256_max_ps(_mm256_max_ps(apx, apy), apz); + + // detect zeros + __m256 mask = _mm256_cmp_ps(len, _mm256_setzero_ps(), _CMP_EQ_OQ); + + // rcp = 1.0f / len + __m256 rcp = _mm256_div_ps(_mm256_set1_ps(1.0f), len); + + // replace +inf with 1.0f + rcp = _mm256_blendv_ps(rcp, _mm256_set1_ps(1.0f), mask); + len = _mm256_blendv_ps(len, _mm256_set1_ps(1.0f), mask); + + // pos *= 1.0f / len + px = _mm256_mul_ps(px, rcp); + py = _mm256_mul_ps(py, rcp); + pz = _mm256_mul_ps(pz, rcp); + + // clamp(vec, -1.0f, 1.0f) + px = _mm256_min_ps(_mm256_max_ps(px, _mm256_set1_ps(-1.0f)), _mm256_set1_ps(1.0f)); + py = _mm256_min_ps(_mm256_max_ps(py, _mm256_set1_ps(-1.0f)), _mm256_set1_ps(1.0f)); + pz = _mm256_min_ps(_mm256_max_ps(pz, _mm256_set1_ps(-1.0f)), _mm256_set1_ps(1.0f)); + nx = _mm256_min_ps(_mm256_max_ps(nx, _mm256_set1_ps(-1.0f)), _mm256_set1_ps(1.0f)); + ny = _mm256_min_ps(_mm256_max_ps(ny, _mm256_set1_ps(-1.0f)), _mm256_set1_ps(1.0f)); + nz = _mm256_min_ps(_mm256_max_ps(nz, _mm256_set1_ps(-1.0f)), _mm256_set1_ps(1.0f)); + tx = _mm256_min_ps(_mm256_max_ps(tx, _mm256_set1_ps(-1.0f)), _mm256_set1_ps(1.0f)); + ty = _mm256_min_ps(_mm256_max_ps(ty, _mm256_set1_ps(-1.0f)), _mm256_set1_ps(1.0f)); + tz = _mm256_min_ps(_mm256_max_ps(tz, _mm256_set1_ps(-1.0f)), _mm256_set1_ps(1.0f)); + + // vec *= 511.0f + px = _mm256_mul_ps(px, _mm256_set1_ps(511.0f)); + py = _mm256_mul_ps(py, _mm256_set1_ps(511.0f)); + pz = _mm256_mul_ps(pz, _mm256_set1_ps(511.0f)); + nx = _mm256_mul_ps(nx, _mm256_set1_ps(511.0f)); + ny = _mm256_mul_ps(ny, _mm256_set1_ps(511.0f)); + nz = _mm256_mul_ps(nz, _mm256_set1_ps(511.0f)); + tx = _mm256_mul_ps(tx, _mm256_set1_ps(511.0f)); + ty = _mm256_mul_ps(ty, _mm256_set1_ps(511.0f)); + tz = _mm256_mul_ps(tz, _mm256_set1_ps(511.0f)); + + // veci = lrint(vec) & 03ff + __m256i pxi = _mm256_and_si256(_mm256_cvtps_epi32(px), _mm256_set1_epi32(0x3ff)); + __m256i pyi = _mm256_and_si256(_mm256_cvtps_epi32(py), _mm256_set1_epi32(0x3ff)); + __m256i pzi = _mm256_and_si256(_mm256_cvtps_epi32(pz), _mm256_set1_epi32(0x3ff)); + __m256i nxi = _mm256_and_si256(_mm256_cvtps_epi32(nx), _mm256_set1_epi32(0x3ff)); + __m256i nyi = _mm256_and_si256(_mm256_cvtps_epi32(ny), _mm256_set1_epi32(0x3ff)); + __m256i nzi = _mm256_and_si256(_mm256_cvtps_epi32(nz), _mm256_set1_epi32(0x3ff)); + __m256i txi = _mm256_and_si256(_mm256_cvtps_epi32(tx), _mm256_set1_epi32(0x3ff)); + __m256i tyi = _mm256_and_si256(_mm256_cvtps_epi32(ty), _mm256_set1_epi32(0x3ff)); + __m256i tzi = _mm256_and_si256(_mm256_cvtps_epi32(tz), _mm256_set1_epi32(0x3ff)); + + // pack = (xi << 0) | (yi << 10) | (zi << 20); + __m256i li = _mm256_castps_si256(len); // length + __m256i pi = _mm256_or_si256(_mm256_or_si256(pxi, _mm256_slli_epi32(pyi, 10)), _mm256_slli_epi32(pzi, 20)); // position + __m256i ni = _mm256_or_si256(_mm256_or_si256(nxi, _mm256_slli_epi32(nyi, 10)), _mm256_slli_epi32(nzi, 20)); // normal + __m256i ti = _mm256_or_si256(_mm256_or_si256(txi, _mm256_slli_epi32(tyi, 10)), _mm256_slli_epi32(tzi, 20)); // tangent + + // + // interleave (4x4 matrix transpose) + // + __m256i u0 = _mm256_unpacklo_epi32(li, pi); + __m256i u1 = _mm256_unpackhi_epi32(li, pi); + __m256i u2 = _mm256_unpacklo_epi32(ni, ti); + __m256i u3 = _mm256_unpackhi_epi32(ni, ti); + + __m256i v0 = _mm256_unpacklo_epi64(u0, u2); + __m256i v1 = _mm256_unpackhi_epi64(u0, u2); + __m256i v2 = _mm256_unpacklo_epi64(u1, u3); + __m256i v3 = _mm256_unpackhi_epi64(u1, u3); + + // store pack x 8 + switch (rem) { + case 7: _mm_storeu_si128((__m128i*)packed[i+6], _mm256_extractf128_si256(v2, 1)); + case 6: _mm_storeu_si128((__m128i*)packed[i+5], _mm256_extractf128_si256(v1, 1)); + case 5: _mm_storeu_si128((__m128i*)packed[i+4], _mm256_extractf128_si256(v0, 1)); + case 4: _mm_storeu_si128((__m128i*)packed[i+3], _mm256_castsi256_si128(v3)); + case 3: _mm_storeu_si128((__m128i*)packed[i+2], _mm256_castsi256_si128(v2)); + case 2: _mm_storeu_si128((__m128i*)packed[i+1], _mm256_castsi256_si128(v1)); + case 1: _mm_storeu_si128((__m128i*)packed[i+0], _mm256_castsi256_si128(v0)); + } + } + + _mm256_zeroupper(); +} + +#endif diff --git a/libraries/ui/src/DockWidget.cpp b/libraries/ui/src/DockWidget.cpp index 3bcd479d61..44bac1f670 100644 --- a/libraries/ui/src/DockWidget.cpp +++ b/libraries/ui/src/DockWidget.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include @@ -28,6 +29,7 @@ DockWidget::DockWidget(const QString& title, QWidget* parent) : QDockWidget(titl auto offscreenUi = DependencyManager::get(); auto qmlEngine = offscreenUi->getSurfaceContext()->engine(); _quickView = std::shared_ptr(new QQuickView(qmlEngine, nullptr), quickViewDeleter); + _quickView->setFormat(getDefaultOpenGLSurfaceFormat()); QWidget* widget = QWidget::createWindowContainer(_quickView.get()); setWidget(widget); QWidget* headerWidget = new QWidget(); diff --git a/plugins/oculus/src/OculusDisplayPlugin.cpp b/plugins/oculus/src/OculusDisplayPlugin.cpp index df01591639..161266c4af 100644 --- a/plugins/oculus/src/OculusDisplayPlugin.cpp +++ b/plugins/oculus/src/OculusDisplayPlugin.cpp @@ -163,7 +163,7 @@ void OculusDisplayPlugin::hmdPresent() { batch.setStateScissorRect(ivec4(uvec2(), _outputFramebuffer->getSize())); batch.resetViewTransform(); batch.setProjectionTransform(mat4()); - batch.setPipeline(_presentPipeline); + batch.setPipeline(_SRGBToLinearPipeline); batch.setResourceTexture(0, _compositeFramebuffer->getRenderBuffer(0)); batch.draw(gpu::TRIANGLE_STRIP, 4); }); diff --git a/script-archive/light_modifier/README.md b/script-archive/light_modifier/README.md index f23bd25dda..d78fe3ef41 100644 --- a/script-archive/light_modifier/README.md +++ b/script-archive/light_modifier/README.md @@ -18,7 +18,7 @@ When you run the lightLoader.js script, several scripts will be loaded: - visiblePanel.js (the transparent panel) - closeButton.js (for closing the ui) - ../libraries/lightOverlayManager.js (shows 2d overlays for lights in the world) -- ../libraries/entitySelectionTool.js (visualizes volume of the lights) +- ../../scripts/system/create/entitySelectionTool/entitySelectionTool.js (visualizes volume of the lights) Current sliders are (top to bottom): red diff --git a/script-archive/light_modifier/lightModifier.js b/script-archive/light_modifier/lightModifier.js index 0c691e2b2e..35cdfb95ce 100644 --- a/script-archive/light_modifier/lightModifier.js +++ b/script-archive/light_modifier/lightModifier.js @@ -33,8 +33,8 @@ var PARENT_SCRIPT_URL = Script.resolvePath('lightParent.js?' + Math.random(0 - 1 if (SHOW_OVERLAYS === true) { - Script.include('../libraries/gridTool.js'); - Script.include('../libraries/entitySelectionTool.js?' + Math.random(0 - 100)); + Script.include('../../scripts/system/libraries/gridTool.js'); + Script.include('../../scripts/system/create/entitySelectionTool/entitySelectionTool.js?' + Math.random(0 - 100)); Script.include('../libraries/lightOverlayManager.js'); var grid = Grid(); diff --git a/scripts/defaultScripts.js b/scripts/defaultScripts.js index e392680df9..607ee3165b 100644 --- a/scripts/defaultScripts.js +++ b/scripts/defaultScripts.js @@ -27,7 +27,7 @@ var DEFAULT_SCRIPTS_COMBINED = [ "system/marketplaces/marketplaces.js", "system/notifications.js", "system/commerce/wallet.js", - "system/edit.js", + "system/create/edit.js", "system/dialTone.js", "system/firstPersonHMD.js", "system/tablet-ui/tabletUI.js", @@ -36,7 +36,7 @@ var DEFAULT_SCRIPTS_COMBINED = [ "system/audioMuteOverlay.js" ]; var DEFAULT_SCRIPTS_SEPARATE = [ - "system/controllers/controllerScripts.js", + "system/controllers/controllerScripts.js" //"system/chat.js" ]; diff --git a/scripts/developer/utilities/lib/prop/PropGroup.qml b/scripts/developer/utilities/lib/prop/PropGroup.qml index 22facb71c6..848ff8b288 100644 --- a/scripts/developer/utilities/lib/prop/PropGroup.qml +++ b/scripts/developer/utilities/lib/prop/PropGroup.qml @@ -17,6 +17,8 @@ PropFolderPanel { Global { id: global } id: root + property var rootObject: {} + property alias propItemsPanel: root.panelFrameContent // Prop Group is designed to author an array of ProItems, they are defined with an array of the tuplets describing each individual item: @@ -79,12 +81,24 @@ PropFolderPanel { }) } break; case 'object': { - var component = Qt.createComponent("PropItem.qml"); + console.log('Item is an object, create PropGroup: ' + JSON.stringify(proItem.object[proItem.property])); + var itemRootObject = proItem.object[proItem.property]; + var itemLabel = proItem.property; + var itemDepth = root.indentDepth + 1; + if (Array.isArray(itemRootObject)) { + if (objectItem.length > 1) { + itemLabel = itemLabel + " " + objectItem.length + } else { + itemLabel = itemLabel + " " + objectItem.length + itemRootObject = itemRootObject[0]; + } + } + var component = Qt.createComponent("PropGroup.qml"); component.createObject(propItemsContainer, { - "label": proItem.property, - "object": proItem.object, - "property": proItem.property, - }) + "label": itemLabel, + "rootObject":itemRootObject, + "indentDepth": itemDepth, + }) } break; case 'printLabel': { var component = Qt.createComponent("PropItem.qml"); @@ -110,19 +124,46 @@ PropFolderPanel { function populateFromObjectProps(object) { var propsModel = [] - var props = Object.keys(object); + if (Array.isArray(object)) { + if (object.length <= 1) { + object = object[0]; + } + } + + var props = Object.keys(object); for (var p in props) { var o = {}; o["object"] = object o["property"] = props[p]; // o["readOnly"] = true; - o["type"] = "string"; - propsModel.push(o) + + var thePropThing = object[props[p]]; + if ((thePropThing !== undefined) && (thePropThing !== null)) { + var theType = typeof(thePropThing) + switch(theType) { + case 'object': { + o["type"] = "object"; + propsModel.push(o) + } break; + default: { + o["type"] = "string"; + propsModel.push(o) + } break; + } + + } else { + o["type"] = "string"; + propsModel.push(o) + } } + root.updatePropItems(root.propItemsPanel, propsModel); } Component.onCompleted: { + if (root.rootObject !== null) { + populateFromObjectProps(root.rootObject) + } } } diff --git a/scripts/developer/utilities/lib/prop/PropScalar.qml b/scripts/developer/utilities/lib/prop/PropScalar.qml index 33e1bfc958..ae86c6ef81 100644 --- a/scripts/developer/utilities/lib/prop/PropScalar.qml +++ b/scripts/developer/utilities/lib/prop/PropScalar.qml @@ -20,17 +20,22 @@ PropItem { property bool integral: false property var numDigits: 2 - property alias valueVar : sliderControl.value + property alias min: sliderControl.minimumValue property alias max: sliderControl.maximumValue property bool showValue: true - signal valueChanged(real value) - Component.onCompleted: { - valueVar = root.valueVarGetter(); - } + } + + property var sourceValueVar: root.valueVarGetter() + + function applyValueVarFromWidgets(value) { + if (!root.readOnly) { + root.valueVarSetter(value) + } + } PropLabel { id: valueLabel @@ -42,7 +47,7 @@ PropItem { horizontalAlignment: global.valueTextAlign height: global.slimHeight - text: root.valueVarGetter().toFixed(root.integral ? 0 : root.numDigits) + text: root.sourceValueVar.toFixed(root.integral ? 0 : root.numDigits) background: Rectangle { color: global.color @@ -59,8 +64,8 @@ PropItem { anchors.left: valueLabel.right anchors.right: root.right anchors.verticalCenter: root.verticalCenter - - onValueChanged: { if (!root.readOnly) { root.valueVarSetter(value)} } + value: root.sourceValueVar + onValueChanged: { applyValueVarFromWidgets(value) } } diff --git a/scripts/developer/utilities/render/luci/Platform.qml b/scripts/developer/utilities/render/luci/Platform.qml index 9c5ffda070..eaa4766b32 100644 --- a/scripts/developer/utilities/render/luci/Platform.qml +++ b/scripts/developer/utilities/render/luci/Platform.qml @@ -23,46 +23,31 @@ Column { id: computer label: "Computer" isUnfold: true - - Component.onCompleted: { - computer.populateFromObjectProps(JSON.parse(PlatformInfo.getComputer())) - } + rootObject:JSON.parse(PlatformInfo.getComputer()) } Prop.PropGroup { id: cpu label: "CPU" isUnfold: true - - Component.onCompleted: { - cpu.populateFromObjectProps(JSON.parse(PlatformInfo.getCPU(0))) - } + rootObject:JSON.parse(PlatformInfo.getPlatform()).cpus } Prop.PropGroup { id: memory label: "Memory" isUnfold: true - - Component.onCompleted: { - memory.populateFromObjectProps(JSON.parse(PlatformInfo.getMemory())) - } + rootObject:JSON.parse(PlatformInfo.getMemory()) } Prop.PropGroup { id: gpu label: "GPU" isUnfold: true - - Component.onCompleted: { - gpu.populateFromObjectProps(JSON.parse(PlatformInfo.getGPU(0))) - } + rootObject:JSON.parse(PlatformInfo.getPlatform()).gpus } Prop.PropGroup { id: display label: "Display" isUnfold: true - - Component.onCompleted: { - display.populateFromObjectProps(JSON.parse(PlatformInfo.getDisplay(0))) - } + rootObject:JSON.parse(PlatformInfo.getPlatform()).displays } } diff --git a/scripts/developer/utilities/render/luci/RenderSettings.qml b/scripts/developer/utilities/render/luci/RenderSettings.qml index 906c117b3a..bd76964070 100644 --- a/scripts/developer/utilities/render/luci/RenderSettings.qml +++ b/scripts/developer/utilities/render/luci/RenderSettings.qml @@ -30,5 +30,12 @@ Column { object: Render property: "shadowsEnabled" } + Prop.PropScalar { + label: "Viewport Resolution Scale" + object: Render + property: "viewportResolutionScale" + min: 0.25 + max: 1.5 + } } diff --git a/scripts/simplifiedUI/defaultScripts.js b/scripts/simplifiedUI/defaultScripts.js index 3213303f2b..ccefcc3a95 100644 --- a/scripts/simplifiedUI/defaultScripts.js +++ b/scripts/simplifiedUI/defaultScripts.js @@ -11,12 +11,10 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -var DEFAULT_SCRIPTS_PATH_PREFIX = ScriptDiscoveryService.defaultScriptsPath + "/"; - var DEFAULT_SCRIPTS_SEPARATE = [ - DEFAULT_SCRIPTS_PATH_PREFIX + "system/controllers/controllerScripts.js", - DEFAULT_SCRIPTS_PATH_PREFIX + "ui/simplifiedUI.js" + "system/controllers/controllerScripts.js", + "ui/simplifiedUI.js" ]; function loadSeparateDefaults() { for (var i in DEFAULT_SCRIPTS_SEPARATE) { @@ -26,9 +24,9 @@ function loadSeparateDefaults() { var DEFAULT_SCRIPTS_COMBINED = [ - DEFAULT_SCRIPTS_PATH_PREFIX + "system/request-service.js", - DEFAULT_SCRIPTS_PATH_PREFIX + "system/progress.js", - DEFAULT_SCRIPTS_PATH_PREFIX + "system/away.js" + "system/request-service.js", + "system/progress.js", + "system/away.js" ]; function runDefaultsTogether() { for (var i in DEFAULT_SCRIPTS_COMBINED) { diff --git a/scripts/simplifiedUI/system/assets/animations/Cheering.fbx b/scripts/simplifiedUI/system/assets/animations/Cheering.fbx new file mode 100644 index 0000000000..8787bf4bd8 Binary files /dev/null and b/scripts/simplifiedUI/system/assets/animations/Cheering.fbx differ diff --git a/scripts/simplifiedUI/system/assets/animations/Clapping.fbx b/scripts/simplifiedUI/system/assets/animations/Clapping.fbx new file mode 100644 index 0000000000..d05b41866d Binary files /dev/null and b/scripts/simplifiedUI/system/assets/animations/Clapping.fbx differ diff --git a/scripts/simplifiedUI/system/assets/animations/Crying.fbx b/scripts/simplifiedUI/system/assets/animations/Crying.fbx new file mode 100644 index 0000000000..2e60ba2450 Binary files /dev/null and b/scripts/simplifiedUI/system/assets/animations/Crying.fbx differ diff --git a/scripts/simplifiedUI/system/assets/animations/Dancing.fbx b/scripts/simplifiedUI/system/assets/animations/Dancing.fbx new file mode 100644 index 0000000000..7759d273b7 Binary files /dev/null and b/scripts/simplifiedUI/system/assets/animations/Dancing.fbx differ diff --git a/scripts/simplifiedUI/system/assets/animations/Fall.fbx b/scripts/simplifiedUI/system/assets/animations/Fall.fbx new file mode 100644 index 0000000000..627e909bb4 Binary files /dev/null and b/scripts/simplifiedUI/system/assets/animations/Fall.fbx differ diff --git a/scripts/simplifiedUI/system/assets/animations/Love.fbx b/scripts/simplifiedUI/system/assets/animations/Love.fbx new file mode 100644 index 0000000000..159ccafd04 Binary files /dev/null and b/scripts/simplifiedUI/system/assets/animations/Love.fbx differ diff --git a/scripts/simplifiedUI/system/assets/animations/Pointing.fbx b/scripts/simplifiedUI/system/assets/animations/Pointing.fbx new file mode 100644 index 0000000000..da3c9bbeca Binary files /dev/null and b/scripts/simplifiedUI/system/assets/animations/Pointing.fbx differ diff --git a/scripts/simplifiedUI/system/assets/animations/Sit1.fbx b/scripts/simplifiedUI/system/assets/animations/Sit1.fbx new file mode 100644 index 0000000000..db75219980 Binary files /dev/null and b/scripts/simplifiedUI/system/assets/animations/Sit1.fbx differ diff --git a/scripts/simplifiedUI/system/assets/animations/Sit2.fbx b/scripts/simplifiedUI/system/assets/animations/Sit2.fbx new file mode 100644 index 0000000000..400b599794 Binary files /dev/null and b/scripts/simplifiedUI/system/assets/animations/Sit2.fbx differ diff --git a/scripts/simplifiedUI/system/assets/animations/Sit3.fbx b/scripts/simplifiedUI/system/assets/animations/Sit3.fbx new file mode 100644 index 0000000000..174fd75c4e Binary files /dev/null and b/scripts/simplifiedUI/system/assets/animations/Sit3.fbx differ diff --git a/scripts/simplifiedUI/system/assets/animations/Surprised.fbx b/scripts/simplifiedUI/system/assets/animations/Surprised.fbx new file mode 100644 index 0000000000..49362605b3 Binary files /dev/null and b/scripts/simplifiedUI/system/assets/animations/Surprised.fbx differ diff --git a/scripts/simplifiedUI/system/assets/animations/Waving.fbx b/scripts/simplifiedUI/system/assets/animations/Waving.fbx new file mode 100644 index 0000000000..e2442f64f4 Binary files /dev/null and b/scripts/simplifiedUI/system/assets/animations/Waving.fbx differ diff --git a/scripts/system/assets/data/createAppTooltips.json b/scripts/simplifiedUI/system/assets/data/createAppTooltips.json similarity index 100% rename from scripts/system/assets/data/createAppTooltips.json rename to scripts/simplifiedUI/system/assets/data/createAppTooltips.json diff --git a/scripts/simplifiedUI/system/assets/images/Overlay-Viz-blank.png b/scripts/simplifiedUI/system/assets/images/Overlay-Viz-blank.png new file mode 100644 index 0000000000..bbbf44f7a9 Binary files /dev/null and b/scripts/simplifiedUI/system/assets/images/Overlay-Viz-blank.png differ diff --git a/scripts/simplifiedUI/system/assets/images/Particle-Sprite-Smoke-1.png b/scripts/simplifiedUI/system/assets/images/Particle-Sprite-Smoke-1.png new file mode 100644 index 0000000000..78c9b3da4a Binary files /dev/null and b/scripts/simplifiedUI/system/assets/images/Particle-Sprite-Smoke-1.png differ diff --git a/scripts/simplifiedUI/system/assets/images/close-small-light.svg b/scripts/simplifiedUI/system/assets/images/close-small-light.svg new file mode 100644 index 0000000000..f9edf95fca --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/close-small-light.svg @@ -0,0 +1,14 @@ + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/grabsprite-3.png b/scripts/simplifiedUI/system/assets/images/grabsprite-3.png new file mode 100644 index 0000000000..4ecc772a41 Binary files /dev/null and b/scripts/simplifiedUI/system/assets/images/grabsprite-3.png differ diff --git a/scripts/simplifiedUI/system/assets/images/ignore-target.svg b/scripts/simplifiedUI/system/assets/images/ignore-target.svg new file mode 100644 index 0000000000..3d685139ec --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/ignore-target.svg @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/kick-target.svg b/scripts/simplifiedUI/system/assets/images/kick-target.svg new file mode 100644 index 0000000000..21cb3a5462 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/kick-target.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/lock.svg b/scripts/simplifiedUI/system/assets/images/lock.svg new file mode 100644 index 0000000000..bb9658de00 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/lock.svg @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/lod-a.svg b/scripts/simplifiedUI/system/assets/images/lod-a.svg new file mode 100644 index 0000000000..6845e0ff78 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/lod-a.svg @@ -0,0 +1,46 @@ + + + +image/svg+xml \ No newline at end of file diff --git a/scripts/simplifiedUI/system/assets/images/lod-i.svg b/scripts/simplifiedUI/system/assets/images/lod-i.svg new file mode 100644 index 0000000000..f909f3b495 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/lod-i.svg @@ -0,0 +1,46 @@ + + + +image/svg+xml \ No newline at end of file diff --git a/scripts/simplifiedUI/system/assets/images/luci-a.svg b/scripts/simplifiedUI/system/assets/images/luci-a.svg new file mode 100644 index 0000000000..40e1481eba --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/luci-a.svg @@ -0,0 +1,10 @@ + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/luci-i.svg b/scripts/simplifiedUI/system/assets/images/luci-i.svg new file mode 100644 index 0000000000..2d73339908 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/luci-i.svg @@ -0,0 +1,13 @@ + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/materials/GridPattern.json b/scripts/simplifiedUI/system/assets/images/materials/GridPattern.json new file mode 100644 index 0000000000..468b709ea4 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/materials/GridPattern.json @@ -0,0 +1,13 @@ +{ + "materialVersion": 1, + "materials": { + "albedo": [ + 0.0, + 0.0, + 7.0 + ], + "unlit": true, + "opacity": 0.4, + "albedoMap": "GridPattern.png" + } +} diff --git a/scripts/simplifiedUI/system/assets/images/materials/GridPattern.png b/scripts/simplifiedUI/system/assets/images/materials/GridPattern.png new file mode 100644 index 0000000000..2ecc7f8570 Binary files /dev/null and b/scripts/simplifiedUI/system/assets/images/materials/GridPattern.png differ diff --git a/scripts/simplifiedUI/system/assets/images/min-max-toggle.svg b/scripts/simplifiedUI/system/assets/images/min-max-toggle.svg new file mode 100644 index 0000000000..1699cc705d --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/min-max-toggle.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/mute-target.svg b/scripts/simplifiedUI/system/assets/images/mute-target.svg new file mode 100644 index 0000000000..1ed642c79e --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/mute-target.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/progress-bar-2k.svg b/scripts/simplifiedUI/system/assets/images/progress-bar-2k.svg new file mode 100644 index 0000000000..45758c7c68 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/progress-bar-2k.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/progress-bar-4k.svg b/scripts/simplifiedUI/system/assets/images/progress-bar-4k.svg new file mode 100644 index 0000000000..609ab9610b --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/progress-bar-4k.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/progress-bar-text.svg b/scripts/simplifiedUI/system/assets/images/progress-bar-text.svg new file mode 100644 index 0000000000..05ebb3f637 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/progress-bar-text.svg @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/run.svg b/scripts/simplifiedUI/system/assets/images/run.svg new file mode 100644 index 0000000000..0957166346 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/run.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/scripts/simplifiedUI/system/assets/images/textures/dirt.jpeg b/scripts/simplifiedUI/system/assets/images/textures/dirt.jpeg new file mode 100644 index 0000000000..694e4f3c9a Binary files /dev/null and b/scripts/simplifiedUI/system/assets/images/textures/dirt.jpeg differ diff --git a/scripts/simplifiedUI/system/assets/images/textures/grass.png b/scripts/simplifiedUI/system/assets/images/textures/grass.png new file mode 100644 index 0000000000..d51fe0cf28 Binary files /dev/null and b/scripts/simplifiedUI/system/assets/images/textures/grass.png differ diff --git a/scripts/simplifiedUI/system/assets/images/tools/add-remove-friends.svg b/scripts/simplifiedUI/system/assets/images/tools/add-remove-friends.svg new file mode 100644 index 0000000000..6ee9ce842d --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/add-remove-friends.svg @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/tools/assets-01.svg b/scripts/simplifiedUI/system/assets/images/tools/assets-01.svg new file mode 100644 index 0000000000..d7bdd1d7f0 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/assets-01.svg @@ -0,0 +1,186 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/tools/bubble.svg b/scripts/simplifiedUI/system/assets/images/tools/bubble.svg new file mode 100644 index 0000000000..064b7734a9 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/bubble.svg @@ -0,0 +1,275 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/tools/cube-01.svg b/scripts/simplifiedUI/system/assets/images/tools/cube-01.svg new file mode 100644 index 0000000000..f8cf96e4f2 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/cube-01.svg @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/tools/directory.svg b/scripts/simplifiedUI/system/assets/images/tools/directory.svg new file mode 100644 index 0000000000..09f3c14bf0 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/directory.svg @@ -0,0 +1,255 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/tools/edit.svg b/scripts/simplifiedUI/system/assets/images/tools/edit.svg new file mode 100644 index 0000000000..f65e0cd84d --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/edit.svg @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/tools/help.svg b/scripts/simplifiedUI/system/assets/images/tools/help.svg new file mode 100644 index 0000000000..b7fa8ca5cd --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/help.svg @@ -0,0 +1,110 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/tools/ignore.svg b/scripts/simplifiedUI/system/assets/images/tools/ignore.svg new file mode 100644 index 0000000000..f315c5f249 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/ignore.svg @@ -0,0 +1,177 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/tools/kick.svg b/scripts/simplifiedUI/system/assets/images/tools/kick.svg new file mode 100644 index 0000000000..1eed6e7f43 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/kick.svg @@ -0,0 +1,140 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/tools/light-01.svg b/scripts/simplifiedUI/system/assets/images/tools/light-01.svg new file mode 100644 index 0000000000..4573c7d636 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/light-01.svg @@ -0,0 +1,112 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/tools/market.svg b/scripts/simplifiedUI/system/assets/images/tools/market.svg new file mode 100644 index 0000000000..0cec030933 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/market.svg @@ -0,0 +1,147 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/tools/mic.svg b/scripts/simplifiedUI/system/assets/images/tools/mic.svg new file mode 100644 index 0000000000..3a7c183bc4 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/mic.svg @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/tools/min-max-toggle.svg b/scripts/simplifiedUI/system/assets/images/tools/min-max-toggle.svg new file mode 100644 index 0000000000..1699cc705d --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/min-max-toggle.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/tools/model-01.svg b/scripts/simplifiedUI/system/assets/images/tools/model-01.svg new file mode 100644 index 0000000000..e760d74d5c --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/model-01.svg @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/tools/particle-01.svg b/scripts/simplifiedUI/system/assets/images/tools/particle-01.svg new file mode 100644 index 0000000000..cfcfb0ea7f --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/particle-01.svg @@ -0,0 +1,161 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/tools/people.svg b/scripts/simplifiedUI/system/assets/images/tools/people.svg new file mode 100644 index 0000000000..5fedfefa75 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/people.svg @@ -0,0 +1,161 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/tools/polyvox.svg b/scripts/simplifiedUI/system/assets/images/tools/polyvox.svg new file mode 100644 index 0000000000..69f1e978ff --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/polyvox.svg @@ -0,0 +1,39 @@ + + + + +polyvox + + + +voxels + + diff --git a/scripts/simplifiedUI/system/assets/images/tools/snap.svg b/scripts/simplifiedUI/system/assets/images/tools/snap.svg new file mode 100644 index 0000000000..c540f307ae --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/snap.svg @@ -0,0 +1,109 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/tools/sphere-01.svg b/scripts/simplifiedUI/system/assets/images/tools/sphere-01.svg new file mode 100644 index 0000000000..975199c8da --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/sphere-01.svg @@ -0,0 +1,108 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/tools/sphere-add.svg b/scripts/simplifiedUI/system/assets/images/tools/sphere-add.svg new file mode 100644 index 0000000000..59ca489b5f --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/sphere-add.svg @@ -0,0 +1,77 @@ + + + +image/svg+xmladd + \ No newline at end of file diff --git a/scripts/simplifiedUI/system/assets/images/tools/sphere-delete.svg b/scripts/simplifiedUI/system/assets/images/tools/sphere-delete.svg new file mode 100644 index 0000000000..6997654500 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/sphere-delete.svg @@ -0,0 +1,76 @@ + + + +image/svg+xmldelete + \ No newline at end of file diff --git a/scripts/simplifiedUI/system/assets/images/tools/steam-invite.svg b/scripts/simplifiedUI/system/assets/images/tools/steam-invite.svg new file mode 100644 index 0000000000..ce225cca68 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/steam-invite.svg @@ -0,0 +1,112 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/tools/switch.svg b/scripts/simplifiedUI/system/assets/images/tools/switch.svg new file mode 100644 index 0000000000..e67a9aac04 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/switch.svg @@ -0,0 +1,167 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/tools/text-01.svg b/scripts/simplifiedUI/system/assets/images/tools/text-01.svg new file mode 100644 index 0000000000..d33d66d4a5 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/text-01.svg @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/tools/upload-01.svg b/scripts/simplifiedUI/system/assets/images/tools/upload-01.svg new file mode 100644 index 0000000000..149b10f3bc --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/upload-01.svg @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/tools/voxel-add.svg b/scripts/simplifiedUI/system/assets/images/tools/voxel-add.svg new file mode 100644 index 0000000000..8e6e2c5b35 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/voxel-add.svg @@ -0,0 +1,104 @@ + + + +image/svg+xmladd + \ No newline at end of file diff --git a/scripts/simplifiedUI/system/assets/images/tools/voxel-delete.svg b/scripts/simplifiedUI/system/assets/images/tools/voxel-delete.svg new file mode 100644 index 0000000000..0b0d0b9787 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/voxel-delete.svg @@ -0,0 +1,103 @@ + + + +image/svg+xmldelete + \ No newline at end of file diff --git a/scripts/simplifiedUI/system/assets/images/tools/voxel-terrain.svg b/scripts/simplifiedUI/system/assets/images/tools/voxel-terrain.svg new file mode 100644 index 0000000000..e5ed16dbcd --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/voxel-terrain.svg @@ -0,0 +1,101 @@ + + + +image/svg+xmlterrain + \ No newline at end of file diff --git a/scripts/simplifiedUI/system/assets/images/tools/voxels.svg b/scripts/simplifiedUI/system/assets/images/tools/voxels.svg new file mode 100644 index 0000000000..a0dbd63d45 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/voxels.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/tools/web-01.svg b/scripts/simplifiedUI/system/assets/images/tools/web-01.svg new file mode 100644 index 0000000000..903b3ac819 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/web-01.svg @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/tools/zone-01.svg b/scripts/simplifiedUI/system/assets/images/tools/zone-01.svg new file mode 100644 index 0000000000..29d17e5187 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/tools/zone-01.svg @@ -0,0 +1,131 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/images/unlock.svg b/scripts/simplifiedUI/system/assets/images/unlock.svg new file mode 100644 index 0000000000..789a8b0ed5 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/unlock.svg @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/models/Avatar-Overlay-v1.fbx b/scripts/simplifiedUI/system/assets/models/Avatar-Overlay-v1.fbx new file mode 100644 index 0000000000..db710702f2 Binary files /dev/null and b/scripts/simplifiedUI/system/assets/models/Avatar-Overlay-v1.fbx differ diff --git a/scripts/simplifiedUI/system/assets/models/Bubble-v14.fbx b/scripts/simplifiedUI/system/assets/models/Bubble-v14.fbx new file mode 100644 index 0000000000..c7d3122f00 Binary files /dev/null and b/scripts/simplifiedUI/system/assets/models/Bubble-v14.fbx differ diff --git a/scripts/simplifiedUI/system/assets/models/black-sphere.fbx b/scripts/simplifiedUI/system/assets/models/black-sphere.fbx new file mode 100644 index 0000000000..2e6dea233f Binary files /dev/null and b/scripts/simplifiedUI/system/assets/models/black-sphere.fbx differ diff --git a/scripts/simplifiedUI/system/assets/models/miniTabletBlank.fbx b/scripts/simplifiedUI/system/assets/models/miniTabletBlank.fbx new file mode 100644 index 0000000000..a2faa2a80a Binary files /dev/null and b/scripts/simplifiedUI/system/assets/models/miniTabletBlank.fbx differ diff --git a/scripts/simplifiedUI/system/assets/models/oculusSensorv11.fbx b/scripts/simplifiedUI/system/assets/models/oculusSensorv11.fbx new file mode 100644 index 0000000000..52fadc77dc Binary files /dev/null and b/scripts/simplifiedUI/system/assets/models/oculusSensorv11.fbx differ diff --git a/scripts/simplifiedUI/system/assets/models/teleport-cancel.fbx b/scripts/simplifiedUI/system/assets/models/teleport-cancel.fbx new file mode 100644 index 0000000000..1c12e28159 Binary files /dev/null and b/scripts/simplifiedUI/system/assets/models/teleport-cancel.fbx differ diff --git a/scripts/simplifiedUI/system/assets/models/teleport-destination.fbm/Teleportation-Destination-Texture2.png b/scripts/simplifiedUI/system/assets/models/teleport-destination.fbm/Teleportation-Destination-Texture2.png new file mode 100644 index 0000000000..eb9addcfca Binary files /dev/null and b/scripts/simplifiedUI/system/assets/models/teleport-destination.fbm/Teleportation-Destination-Texture2.png differ diff --git a/scripts/simplifiedUI/system/assets/models/teleport-destination.fbx b/scripts/simplifiedUI/system/assets/models/teleport-destination.fbx new file mode 100644 index 0000000000..5fdb0d56af Binary files /dev/null and b/scripts/simplifiedUI/system/assets/models/teleport-destination.fbx differ diff --git a/scripts/simplifiedUI/system/assets/models/teleport-seat.fbx b/scripts/simplifiedUI/system/assets/models/teleport-seat.fbx new file mode 100644 index 0000000000..cd7a9abc7e Binary files /dev/null and b/scripts/simplifiedUI/system/assets/models/teleport-seat.fbx differ diff --git a/scripts/simplifiedUI/system/assets/models/teleportationSpotBasev8.fbx b/scripts/simplifiedUI/system/assets/models/teleportationSpotBasev8.fbx new file mode 100644 index 0000000000..d651575dea Binary files /dev/null and b/scripts/simplifiedUI/system/assets/models/teleportationSpotBasev8.fbx differ diff --git a/scripts/simplifiedUI/system/assets/models/trackingSpacev18.fbx b/scripts/simplifiedUI/system/assets/models/trackingSpacev18.fbx new file mode 100644 index 0000000000..16597eb285 Binary files /dev/null and b/scripts/simplifiedUI/system/assets/models/trackingSpacev18.fbx differ diff --git a/scripts/simplifiedUI/system/assets/sounds/bubble.wav b/scripts/simplifiedUI/system/assets/sounds/bubble.wav new file mode 100644 index 0000000000..fd23a235d4 Binary files /dev/null and b/scripts/simplifiedUI/system/assets/sounds/bubble.wav differ diff --git a/scripts/simplifiedUI/system/assets/sounds/button-click.wav b/scripts/simplifiedUI/system/assets/sounds/button-click.wav new file mode 100644 index 0000000000..30a097ce45 Binary files /dev/null and b/scripts/simplifiedUI/system/assets/sounds/button-click.wav differ diff --git a/scripts/simplifiedUI/system/assets/sounds/button-hover.wav b/scripts/simplifiedUI/system/assets/sounds/button-hover.wav new file mode 100644 index 0000000000..cd76d0174c Binary files /dev/null and b/scripts/simplifiedUI/system/assets/sounds/button-hover.wav differ diff --git a/scripts/simplifiedUI/system/assets/sounds/countdown-tick.wav b/scripts/simplifiedUI/system/assets/sounds/countdown-tick.wav new file mode 100644 index 0000000000..015e1f642e Binary files /dev/null and b/scripts/simplifiedUI/system/assets/sounds/countdown-tick.wav differ diff --git a/scripts/simplifiedUI/system/assets/sounds/entitySnap.wav b/scripts/simplifiedUI/system/assets/sounds/entitySnap.wav new file mode 100644 index 0000000000..4584f3dcaa Binary files /dev/null and b/scripts/simplifiedUI/system/assets/sounds/entitySnap.wav differ diff --git a/scripts/simplifiedUI/system/assets/sounds/finish-recording.wav b/scripts/simplifiedUI/system/assets/sounds/finish-recording.wav new file mode 100644 index 0000000000..f224049f97 Binary files /dev/null and b/scripts/simplifiedUI/system/assets/sounds/finish-recording.wav differ diff --git a/scripts/simplifiedUI/system/assets/sounds/goodbye.wav b/scripts/simplifiedUI/system/assets/sounds/goodbye.wav new file mode 100644 index 0000000000..bd6c51a5c0 Binary files /dev/null and b/scripts/simplifiedUI/system/assets/sounds/goodbye.wav differ diff --git a/scripts/simplifiedUI/system/assets/sounds/hello.wav b/scripts/simplifiedUI/system/assets/sounds/hello.wav new file mode 100644 index 0000000000..6269dab5db Binary files /dev/null and b/scripts/simplifiedUI/system/assets/sounds/hello.wav differ diff --git a/scripts/simplifiedUI/system/assets/sounds/notification-general1.raw b/scripts/simplifiedUI/system/assets/sounds/notification-general1.raw new file mode 100644 index 0000000000..be81fa15c5 Binary files /dev/null and b/scripts/simplifiedUI/system/assets/sounds/notification-general1.raw differ diff --git a/scripts/simplifiedUI/system/assets/sounds/notification-general2.raw b/scripts/simplifiedUI/system/assets/sounds/notification-general2.raw new file mode 100644 index 0000000000..58f0bac19c Binary files /dev/null and b/scripts/simplifiedUI/system/assets/sounds/notification-general2.raw differ diff --git a/scripts/simplifiedUI/system/assets/sounds/rezzing.wav b/scripts/simplifiedUI/system/assets/sounds/rezzing.wav new file mode 100644 index 0000000000..3c059aecdf Binary files /dev/null and b/scripts/simplifiedUI/system/assets/sounds/rezzing.wav differ diff --git a/scripts/simplifiedUI/system/assets/sounds/short1.wav b/scripts/simplifiedUI/system/assets/sounds/short1.wav new file mode 100644 index 0000000000..fb03f5dd49 Binary files /dev/null and b/scripts/simplifiedUI/system/assets/sounds/short1.wav differ diff --git a/scripts/simplifiedUI/system/assets/sounds/start-recording.wav b/scripts/simplifiedUI/system/assets/sounds/start-recording.wav new file mode 100644 index 0000000000..71c69f3372 Binary files /dev/null and b/scripts/simplifiedUI/system/assets/sounds/start-recording.wav differ diff --git a/scripts/simplifiedUI/system/attachedEntitiesManager.js b/scripts/simplifiedUI/system/attachedEntitiesManager.js new file mode 100644 index 0000000000..061e27f595 --- /dev/null +++ b/scripts/simplifiedUI/system/attachedEntitiesManager.js @@ -0,0 +1,285 @@ +// +// attachedEntitiesManager.js +// +// Created by Seth Alves on 2016-1-20 +// Copyright 2016 High Fidelity, Inc. +// +// This script handles messages from the grab script related to wearables, and interacts with a doppelganger. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +Script.include("libraries/utils.js"); + +var DEFAULT_WEARABLE_DATA = { + joints: {} +}; + + +var MINIMUM_DROP_DISTANCE_FROM_JOINT = 0.8; +var ATTACHED_ENTITY_SEARCH_DISTANCE = 10.0; +var ATTACHED_ENTITIES_SETTINGS_KEY = "ATTACHED_ENTITIES"; +var DRESSING_ROOM_DISTANCE = 2.0; +var SHOW_TOOL_BAR = false; + +// tool bar + +if (SHOW_TOOL_BAR) { + var BUTTON_SIZE = 64; + var PADDING = 6; + Script.include(["libraries/toolBars.js"]); + + var toolBar = new ToolBar(0, 0, ToolBar.HORIZONTAL, "highfidelity.attachedEntities.toolbar"); + var lockButton = toolBar.addTool({ + width: BUTTON_SIZE, + height: BUTTON_SIZE, + imageURL: Script.resolvePath("assets/images/lock.svg"), + color: { + red: 255, + green: 255, + blue: 255 + }, + alpha: 1, + visible: true + }, false); +} + + +function mousePressEvent(event) { + var clickedOverlay = Overlays.getOverlayAtPoint({ + x: event.x, + y: event.y + }); + + if (lockButton === toolBar.clicked(clickedOverlay)) { + manager.toggleLocked(); + } +} + +function scriptEnding() { + if (SHOW_TOOL_BAR) { + toolBar.cleanup(); + } +} + +if (SHOW_TOOL_BAR) { + Controller.mousePressEvent.connect(mousePressEvent); +} +Script.scriptEnding.connect(scriptEnding); + + + +// attached entites + + +function AttachedEntitiesManager() { + var clothingLocked = false; + + this.subscribeToMessages = function() { + Messages.subscribe('Hifi-Object-Manipulation'); + Messages.messageReceived.connect(this.handleWearableMessages); + } + + this.handleWearableMessages = function(channel, message, sender) { + if (channel !== 'Hifi-Object-Manipulation') { + return; + } + + var parsedMessage = null; + + try { + parsedMessage = JSON.parse(message); + } catch (e) { + print('error parsing wearable message'); + return; + } + + if (parsedMessage.action === 'update' || + parsedMessage.action === 'loaded') { + // ignore + } else if (parsedMessage.action === 'release') { + manager.handleEntityRelease(parsedMessage.grabbedEntity, parsedMessage.joint) + // manager.saveAttachedEntities(); + } else if (parsedMessage.action === 'equip') { + // manager.saveAttachedEntities(); + } else { + print('attachedEntitiesManager -- unknown actions: ' + parsedMessage.action); + } + } + + this.handleEntityRelease = function(grabbedEntity, releasedFromJoint) { + // if this is still equipped, just rewrite the position information. + var grabData = getEntityCustomData('grabKey', grabbedEntity, {}); + + var allowedJoints = getEntityCustomData('wearable', grabbedEntity, DEFAULT_WEARABLE_DATA).joints; + + var props = Entities.getEntityProperties(grabbedEntity, ["position", "parentID", "parentJointIndex"]); + if (props.parentID === Uuid.NULL || props.parentID === MyAvatar.sessionUUID) { + var bestJointName = ""; + var bestJointIndex = -1; + var bestJointDistance = 0; + var bestJointOffset = null; + for (var jointName in allowedJoints) { + if ((releasedFromJoint == "LeftHand" || releasedFromJoint == "RightHand") && + (jointName == "LeftHand" || jointName == "RightHand")) { + // don't auto-attach to a hand if a hand just dropped something + continue; + } + var jointIndex = MyAvatar.getJointIndex(jointName); + if (jointIndex >= 0) { + var jointPosition = MyAvatar.getJointPosition(jointIndex); + var distanceFromJoint = Vec3.distance(jointPosition, props.position); + if (distanceFromJoint <= MINIMUM_DROP_DISTANCE_FROM_JOINT) { + if (bestJointIndex == -1 || distanceFromJoint < bestJointDistance) { + bestJointName = jointName; + bestJointIndex = jointIndex; + bestJointDistance = distanceFromJoint; + bestJointOffset = allowedJoints[jointName]; + } + } + } + } + + if (bestJointIndex != -1) { + var wearProps = Entities.getEntityProperties(grabbedEntity); + wearProps.parentID = MyAvatar.sessionUUID; + wearProps.parentJointIndex = bestJointIndex; + delete wearProps.localPosition; + delete wearProps.localRotation; + var updatePresets = false; + if (bestJointOffset && bestJointOffset.constructor === Array) { + if (!clothingLocked || bestJointOffset.length < 2) { + // we're unlocked or this thing didn't have a preset position, so update it + updatePresets = true; + } else { + // don't snap the entity to the preferred position if unlocked + wearProps.localPosition = bestJointOffset[0]; + wearProps.localRotation = bestJointOffset[1]; + } + } + + Entities.deleteEntity(grabbedEntity); + //the true boolean here after add entity adds it as an 'avatar entity', which can travel with you from server to server. + + var newEntity = Entities.addEntity(wearProps, true); + + if (updatePresets) { + this.updateRelativeOffsets(newEntity); + } + } else if (props.parentID != Uuid.NULL) { + // drop the entity and set it to have no parent (not on the avatar), unless it's being equipped in a hand. + if (props.parentID === MyAvatar.sessionUUID && + (props.parentJointIndex == MyAvatar.getJointIndex("RightHand") || + props.parentJointIndex == MyAvatar.getJointIndex("LeftHand"))) { + // this is equipped on a hand -- don't clear the parent. + } else { + var wearProps = Entities.getEntityProperties(grabbedEntity); + wearProps.parentID = Uuid.NULL; + wearProps.parentJointIndex = -1; + delete wearProps.id; + delete wearProps.created; + delete wearProps.age; + delete wearProps.ageAsText; + delete wearProps.naturalDimensions; + delete wearProps.naturalPosition; + delete wearProps.actionData; + delete wearProps.sittingPoints; + delete wearProps.boundingBox; + delete wearProps.avatarEntity; + delete wearProps.owningAvatarID; + delete wearProps.localPosition; + delete wearProps.localRotation; + Entities.deleteEntity(grabbedEntity); + Entities.addEntity(wearProps); + } + } + } + } + + this.updateRelativeOffsets = function(entityID) { + // save the preferred (current) relative position and rotation into the user-data of the entity + var props = Entities.getEntityProperties(entityID); + if (props.parentID == MyAvatar.sessionUUID) { + grabData = getEntityCustomData('grabKey', entityID, {}); + var wearableData = getEntityCustomData('wearable', entityID, DEFAULT_WEARABLE_DATA); + var currentJointName = MyAvatar.getJointNames()[props.parentJointIndex]; + wearableData.joints[currentJointName] = [props.localPosition, props.localRotation]; + setEntityCustomData('wearable', entityID, wearableData); + return true; + } + return false; + } + + // this.saveAttachedEntities = function() { + // print("--- saving attached entities ---"); + // saveData = []; + // var nearbyEntities = Entities.findEntities(MyAvatar.position, ATTACHED_ENTITY_SEARCH_DISTANCE); + // for (i = 0; i < nearbyEntities.length; i++) { + // var entityID = nearbyEntities[i]; + // if (this.updateRelativeOffsets(entityID)) { + // var props = Entities.getEntityProperties(entityID); // refresh, because updateRelativeOffsets changed them + // this.scrubProperties(props); + // saveData.push(props); + // } + // } + // Settings.setValue(ATTACHED_ENTITIES_SETTINGS_KEY, JSON.stringify(saveData)); + // } + + // this.scrubProperties = function(props) { + // var toScrub = ["queryAACube", "position", "rotation", + // "created", "ageAsText", "naturalDimensions", + // "naturalPosition", "velocity", "acceleration", + // "angularVelocity", "boundingBox"]; + // toScrub.forEach(function(propertyName) { + // delete props[propertyName]; + // }); + // // if the userData has a grabKey, clear old state + // if ("userData" in props) { + // try { + // parsedUserData = JSON.parse(props.userData); + // if ("grabKey" in parsedUserData) { + // parsedUserData.grabKey.refCount = 0; + // delete parsedUserData.grabKey["avatarId"]; + // props["userData"] = JSON.stringify(parsedUserData); + // } + // } catch (e) { + // } + // } + // } + + // this.loadAttachedEntities = function(grabbedEntity) { + // print("--- loading attached entities ---"); + // jsonAttachmentData = Settings.getValue(ATTACHED_ENTITIES_SETTINGS_KEY); + // var loadData = []; + // try { + // loadData = JSON.parse(jsonAttachmentData); + // } catch (e) { + // print('error parsing saved attachment data'); + // return; + // } + + // for (i = 0; i < loadData.length; i ++) { + // var savedProps = loadData[ i ]; + // var currentProps = Entities.getEntityProperties(savedProps.id); + // if (currentProps.id == savedProps.id && + // // TODO -- also check that parentJointIndex matches? + // currentProps.parentID == MyAvatar.sessionUUID) { + // // entity is already in-world. TODO -- patch it up? + // continue; + // } + // this.scrubProperties(savedProps); + // delete savedProps["id"]; + // savedProps.parentID = MyAvatar.sessionUUID; // this will change between sessions + // var loadedEntityID = Entities.addEntity(savedProps, true); + + // Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({ + // action: 'loaded', + // grabbedEntity: loadedEntityID + // })); + // } + // } +} + +var manager = new AttachedEntitiesManager(); +manager.subscribeToMessages(); \ No newline at end of file diff --git a/scripts/simplifiedUI/system/audio.js b/scripts/simplifiedUI/system/audio.js new file mode 100644 index 0000000000..a161b40ffd --- /dev/null +++ b/scripts/simplifiedUI/system/audio.js @@ -0,0 +1,92 @@ +"use strict"; + +// +// audio.js +// +// Created by Howard Stearns on 2 Jun 2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */ + +(function() { // BEGIN LOCAL_SCOPE + +var TABLET_BUTTON_NAME = "AUDIO"; +var HOME_BUTTON_TEXTURE = "http://hifi-content.s3.amazonaws.com/alan/dev/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-root.png"; +var AUDIO_QML_SOURCE = "hifi/audio/Audio.qml"; + +var MUTE_ICONS = { + icon: "icons/tablet-icons/mic-mute-i.svg", + activeIcon: "icons/tablet-icons/mic-mute-a.svg" +}; + +var UNMUTE_ICONS = { + icon: "icons/tablet-icons/mic-unmute-i.svg", + activeIcon: "icons/tablet-icons/mic-unmute-a.svg" +}; +var PTT_ICONS = { + icon: "icons/tablet-icons/mic-ptt-i.svg", + activeIcon: "icons/tablet-icons/mic-ptt-a.svg" +}; + +function onMuteToggled() { + if (Audio.pushToTalk) { + button.editProperties(PTT_ICONS); + } else if (Audio.muted) { + button.editProperties(MUTE_ICONS); + } else { + button.editProperties(UNMUTE_ICONS); + } +} + +var onAudioScreen = false; + +function onClicked() { + if (onAudioScreen) { + // for toolbar-mode: go back to home screen, this will close the window. + tablet.gotoHomeScreen(); + } else { + if (HMD.tabletID) { + Entities.editEntity(HMD.tabletID, { textures: JSON.stringify({ "tex.close": HOME_BUTTON_TEXTURE }) }); + } + tablet.loadQMLSource(AUDIO_QML_SOURCE); + } +} + +function onScreenChanged(type, url) { + onAudioScreen = (type === "QML" && url === AUDIO_QML_SOURCE); + // for toolbar mode: change button to active when window is first openend, false otherwise. + button.editProperties({isActive: onAudioScreen}); +} + +var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); +var button = tablet.addButton({ + icon: Audio.pushToTalk ? PTT_ICONS.icon : Audio.muted ? MUTE_ICONS.icon : UNMUTE_ICONS.icon, + activeIcon: Audio.pushToTalk ? PTT_ICONS.activeIcon : Audio.muted ? MUTE_ICONS.activeIcon : UNMUTE_ICONS.activeIcon, + text: TABLET_BUTTON_NAME, + sortOrder: 1 +}); + +onMuteToggled(); + +button.clicked.connect(onClicked); +tablet.screenChanged.connect(onScreenChanged); +Audio.mutedChanged.connect(onMuteToggled); +Audio.pushToTalkChanged.connect(onMuteToggled); +HMD.displayModeChanged.connect(onMuteToggled); + +Script.scriptEnding.connect(function () { + if (onAudioScreen) { + tablet.gotoHomeScreen(); + } + button.clicked.disconnect(onClicked); + tablet.screenChanged.disconnect(onScreenChanged); + Audio.mutedChanged.disconnect(onMuteToggled); + Audio.pushToTalkChanged.disconnect(onMuteToggled); + HMD.displayModeChanged.disconnect(onMuteToggled); + tablet.removeButton(button); +}); + +}()); // END LOCAL_SCOPE diff --git a/scripts/simplifiedUI/system/audioMuteOverlay.js b/scripts/simplifiedUI/system/audioMuteOverlay.js new file mode 100644 index 0000000000..9acc5ab123 --- /dev/null +++ b/scripts/simplifiedUI/system/audioMuteOverlay.js @@ -0,0 +1,130 @@ +// +// audioMuteOverlay.js +// +// client script that creates an overlay to provide mute feedback +// +// Created by Triplelexx on 17/03/09 +// Reworked by Seth Alves on 2019-2-17 +// 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 +// + +"use strict"; + +/* global Audio, Script, Overlays, Quat, MyAvatar, HMD */ + +(function() { // BEGIN LOCAL_SCOPE + + var lastShortTermInputLoudness = 0.0; + var lastLongTermInputLoudness = 0.0; + var sampleRate = 8.0; // Hz + + var shortTermAttackTC = Math.exp(-1.0 / (sampleRate * 0.500)); // 500 milliseconds attack + var shortTermReleaseTC = Math.exp(-1.0 / (sampleRate * 1.000)); // 1000 milliseconds release + + var longTermAttackTC = Math.exp(-1.0 / (sampleRate * 5.0)); // 5 second attack + var longTermReleaseTC = Math.exp(-1.0 / (sampleRate * 10.0)); // 10 seconds release + + var activationThreshold = 0.05; // how much louder short-term needs to be than long-term to trigger warning + + var holdReset = 2.0 * sampleRate; // 2 seconds hold + var holdCount = 0; + var warningOverlayID = null; + var pollInterval = null; + var warningText = "Muted"; + + function showWarning() { + if (warningOverlayID) { + return; + } + + if (HMD.active) { + warningOverlayID = Overlays.addOverlay("text3d", { + name: "Muted-Warning", + localPosition: { x: 0.0, y: -0.45, z: -1.0 }, + localOrientation: Quat.fromVec3Degrees({ x: 0.0, y: 0.0, z: 0.0, w: 1.0 }), + text: warningText, + textAlpha: 1, + textColor: { red: 226, green: 51, blue: 77 }, + backgroundAlpha: 0, + lineHeight: 0.042, + dimensions: { x: 0.11, y: 0.05 }, + visible: true, + ignoreRayIntersection: true, + drawInFront: true, + grabbable: false, + parentID: MyAvatar.SELF_ID, + parentJointIndex: MyAvatar.getJointIndex("_CAMERA_MATRIX") + }); + } + } + + function hideWarning() { + if (!warningOverlayID) { + return; + } + Overlays.deleteOverlay(warningOverlayID); + warningOverlayID = null; + } + + function startPoll() { + if (pollInterval) { + return; + } + pollInterval = Script.setInterval(function() { + var shortTermInputLoudness = Audio.inputLevel; + var longTermInputLoudness = shortTermInputLoudness; + + var shortTc = (shortTermInputLoudness > lastShortTermInputLoudness) ? shortTermAttackTC : shortTermReleaseTC; + var longTc = (longTermInputLoudness > lastLongTermInputLoudness) ? longTermAttackTC : longTermReleaseTC; + + shortTermInputLoudness += shortTc * (lastShortTermInputLoudness - shortTermInputLoudness); + longTermInputLoudness += longTc * (lastLongTermInputLoudness - longTermInputLoudness); + + lastShortTermInputLoudness = shortTermInputLoudness; + lastLongTermInputLoudness = longTermInputLoudness; + + if (shortTermInputLoudness > lastLongTermInputLoudness + activationThreshold) { + holdCount = holdReset; + } else { + holdCount = Math.max(holdCount - 1, 0); + } + + if (holdCount > 0) { + showWarning(); + } else { + hideWarning(); + } + }, 1000.0 / sampleRate); + } + + function stopPoll() { + if (!pollInterval) { + return; + } + Script.clearInterval(pollInterval); + pollInterval = null; + hideWarning(); + } + + function startOrStopPoll() { + if (Audio.warnWhenMuted && Audio.muted) { + startPoll(); + } else { + stopPoll(); + } + } + + function cleanup() { + stopPoll(); + } + + Script.scriptEnding.connect(cleanup); + + startOrStopPoll(); + Audio.mutedChanged.connect(startOrStopPoll); + Audio.warnWhenMutedChanged.connect(startOrStopPoll); + +}()); // END LOCAL_SCOPE diff --git a/scripts/simplifiedUI/system/audioScope.js b/scripts/simplifiedUI/system/audioScope.js new file mode 100644 index 0000000000..81d8e8fbd4 --- /dev/null +++ b/scripts/simplifiedUI/system/audioScope.js @@ -0,0 +1,95 @@ +"use strict"; +// +// audioScope.js +// scripts/system/ +// +// Created by Brad Hefta-Gaub on 3/10/2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +/* global Script, Tablet, AudioScope, Audio */ + +(function () { // BEGIN LOCAL_SCOPE + + var scopeVisibile = AudioScope.getVisible(); + var scopePaused = AudioScope.getPause(); + var autoPause = false; + + var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + var showScopeButton = tablet.addButton({ + icon: "icons/tablet-icons/scope.svg", + text: "Audio Scope", + isActive: scopeVisibile + }); + + var scopePauseImage = "icons/tablet-icons/scope-pause.svg"; + var scopePlayImage = "icons/tablet-icons/scope-play.svg"; + + var pauseScopeButton = tablet.addButton({ + icon: scopePaused ? scopePlayImage : scopePauseImage, + text: scopePaused ? "Unpause" : "Pause", + isActive: scopePaused + }); + + var autoPauseScopeButton = tablet.addButton({ + icon: "icons/tablet-icons/scope-auto.svg", + text: "Auto Pause", + isActive: autoPause + }); + + function setScopePause(paused) { + scopePaused = paused; + pauseScopeButton.editProperties({ + isActive: scopePaused, + icon: scopePaused ? scopePlayImage : scopePauseImage, + text: scopePaused ? "Unpause" : "Pause" + }); + AudioScope.setPause(scopePaused); + } + + showScopeButton.clicked.connect(function () { + // toggle button active state + scopeVisibile = !scopeVisibile; + showScopeButton.editProperties({ + isActive: scopeVisibile + }); + + AudioScope.setVisible(scopeVisibile); + }); + + pauseScopeButton.clicked.connect(function () { + // toggle button active state + setScopePause(!scopePaused); + }); + + autoPauseScopeButton.clicked.connect(function () { + // toggle button active state + autoPause = !autoPause; + autoPauseScopeButton.editProperties({ + isActive: autoPause, + text: autoPause ? "Auto Pause" : "Manual" + }); + }); + + Script.scriptEnding.connect(function () { + tablet.removeButton(showScopeButton); + tablet.removeButton(pauseScopeButton); + tablet.removeButton(autoPauseScopeButton); + }); + + Audio.noiseGateOpened.connect(function(){ + if (autoPause) { + setScopePause(false); + } + }); + + Audio.noiseGateClosed.connect(function(){ + // noise gate closed + if (autoPause) { + setScopePause(true); + } + }); + +}()); // END LOCAL_SCOPE \ No newline at end of file diff --git a/scripts/simplifiedUI/system/avatarFinderBeacon.js b/scripts/simplifiedUI/system/avatarFinderBeacon.js new file mode 100644 index 0000000000..00f3d15fbb --- /dev/null +++ b/scripts/simplifiedUI/system/avatarFinderBeacon.js @@ -0,0 +1,93 @@ +// avatarFinderBeacon.js +// +// Created by Thijs Wenker on 12/7/16 +// Copyright 2016 High Fidelity, Inc. +// +// Shows 2km long red beams for each avatar outside of the 20 meter radius of your avatar, tries to ignore AC Agents. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + +var MIN_DISPLAY_DISTANCE = 20.0; // meters +var BEAM_COLOR = {red: 255, green: 0, blue: 0}; +var SHOW_THROUGH_WALLS = false; +var BEACON_LENGTH = 2000.0; // meters +var TRY_TO_IGNORE_AC_AGENTS = true; + +var HALF_BEACON_LENGTH = BEACON_LENGTH / 2.0; + +var beacons = {}; + +// List of .fst files used by AC scripts, that should be ignored in the script in case TRY_TO_IGNORE_AC_AGENTS is enabled +var POSSIBLE_AC_AVATARS = [ + 'http://hifi-content.s3.amazonaws.com/ozan/dev/avatars/invisible_avatar/invisible_avatar.fst', + 'http://hifi-content.s3.amazonaws.com/ozan/dev/avatars/camera_man/pod/_latest/camera_man_pod.fst' +]; + +AvatarFinderBeacon = function(avatar) { + var visible = false; + var avatarSessionUUID = avatar.sessionUUID; + this.overlay = Overlays.addOverlay('line3d', { + color: BEAM_COLOR, + dashed: false, + start: Vec3.sum(avatar.position, {x: 0, y: -HALF_BEACON_LENGTH, z: 0}), + end: Vec3.sum(avatar.position, {x: 0, y: HALF_BEACON_LENGTH, z: 0}), + rotation: {x: 0, y: 0, z: 0, w: 1}, + visible: visible, + drawInFront: SHOW_THROUGH_WALLS, + ignoreRayIntersection: true, + parentID: avatarSessionUUID, + parentJointIndex: -2 + }); + this.cleanup = function() { + Overlays.deleteOverlay(this.overlay); + }; + this.shouldShow = function() { + return Vec3.distance(MyAvatar.position, avatar.position) >= MIN_DISPLAY_DISTANCE; + }; + this.update = function() { + avatar = AvatarList.getAvatar(avatarSessionUUID); + Overlays.editOverlay(this.overlay, { + visible: this.shouldShow() + }); + }; +}; + +function updateBeacon(avatarSessionUUID) { + if (!(avatarSessionUUID in beacons)) { + var avatar = AvatarList.getAvatar(avatarSessionUUID); + if (TRY_TO_IGNORE_AC_AGENTS + && (POSSIBLE_AC_AVATARS.indexOf(avatar.skeletonModelURL) !== -1 || Vec3.length(avatar.position) === 0.0)) { + return; + } + beacons[avatarSessionUUID] = new AvatarFinderBeacon(avatar); + return; + } + beacons[avatarSessionUUID].update(); +} + +Window.domainChanged.connect(function () { + beacons = {}; +}); + +Script.update.connect(function() { + AvatarList.getAvatarIdentifiers().forEach(function(avatarSessionUUID) { + updateBeacon(avatarSessionUUID); + }); +}); + +AvatarList.avatarRemovedEvent.connect(function(avatarSessionUUID) { + if (avatarSessionUUID in beacons) { + beacons[avatarSessionUUID].cleanup(); + delete beacons[avatarSessionUUID]; + } +}); + +Script.scriptEnding.connect(function() { + for (var sessionUUID in beacons) { + if (!beacons.hasOwnProperty(sessionUUID)) { + return; + } + beacons[sessionUUID].cleanup(); + } +}); diff --git a/scripts/simplifiedUI/system/avatarapp.js b/scripts/simplifiedUI/system/avatarapp.js new file mode 100644 index 0000000000..6439d30023 --- /dev/null +++ b/scripts/simplifiedUI/system/avatarapp.js @@ -0,0 +1,663 @@ +"use strict"; +/*jslint vars:true, plusplus:true, forin:true*/ +/*global Tablet, Script, Entities, MyAvatar, Camera, Quat, HMD, Account, UserActivityLogger, Messages, print, + AvatarBookmarks, ContextOverlay, AddressManager +*/ +/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */ +// +// avatarapp.js +// +// Created by Alexander Ivash on April 30, 2018 +// Copyright 2016 High Fidelity, Inc +// +// Distributed under the Apache License, Version 2.0 +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +(function() { // BEGIN LOCAL_SCOPE + +var AVATARAPP_QML_SOURCE = "hifi/AvatarApp.qml"; +Script.include("/~/system/libraries/controllers.js"); + +// constants from AvatarBookmarks.h +var ENTRY_AVATAR_URL = "avatarUrl"; +var ENTRY_AVATAR_ENTITIES = "avatarEntites"; +var ENTRY_AVATAR_SCALE = "avatarScale"; + +function executeLater(callback) { + Script.setTimeout(callback, 300); +} + +function isWearable(avatarEntity) { + return avatarEntity.properties.visible === true && + (avatarEntity.properties.parentID === MyAvatar.sessionUUID || avatarEntity.properties.parentID === MyAvatar.SELF_ID); +} + +function getMyAvatarWearables() { + var entitiesArray = MyAvatar.getAvatarEntitiesVariant(); + var wearablesArray = []; + + for (var i = 0; i < entitiesArray.length; ++i) { + var entity = entitiesArray[i]; + if (!isWearable(entity)) { + continue; + } + + var localRotation = entity.properties.localRotation; + entity.properties.localRotationAngles = Quat.safeEulerAngles(localRotation); + wearablesArray.push(entity); + } + + return wearablesArray; +} + +function getMyAvatar() { + var avatar = {}; + avatar[ENTRY_AVATAR_URL] = MyAvatar.skeletonModelURL; + avatar[ENTRY_AVATAR_SCALE] = MyAvatar.getAvatarScale(); + avatar[ENTRY_AVATAR_ENTITIES] = getMyAvatarWearables(); + return avatar; +} + +function getMyAvatarSettings() { + return { + dominantHand: MyAvatar.getDominantHand(), + hmdAvatarAlignmentType: MyAvatar.getHmdAvatarAlignmentType(), + collisionsEnabled: MyAvatar.getCollisionsEnabled(), + otherAvatarsCollisionsEnabled: MyAvatar.getOtherAvatarsCollisionsEnabled(), + collisionSoundUrl : MyAvatar.collisionSoundURL, + animGraphUrl: MyAvatar.getAnimGraphUrl(), + animGraphOverrideUrl : MyAvatar.getAnimGraphOverrideUrl(), + }; +} + +function updateAvatarWearables(avatar, callback, wearablesOverride) { + executeLater(function() { + var wearables = wearablesOverride ? wearablesOverride : getMyAvatarWearables(); + avatar[ENTRY_AVATAR_ENTITIES] = wearables; + + sendToQml({'method' : 'wearablesUpdated', 'wearables' : wearables}); + sendToQml({ 'method' : 'wearablesFrozenChanged', 'wearablesFrozen' : getWearablesFrozen()}); + + if(callback) + callback(); + }); +} + +var adjustWearables = { + opened : false, + cameraMode : '', + setOpened : function(value) { + if(this.opened !== value) { + if(value) { + this.cameraMode = Camera.mode; + + if(!HMD.active) { + Camera.mode = 'mirror'; + } + } else { + Camera.mode = this.cameraMode; + } + + this.opened = value; + } + } +}; + +var currentAvatarWearablesBackup = null; +var currentAvatar = null; +var currentAvatarSettings = getMyAvatarSettings(); + +var notifyScaleChanged = true; +function onTargetScaleChanged() { + if(currentAvatar.scale !== MyAvatar.getAvatarScale()) { + currentAvatar.scale = MyAvatar.getAvatarScale(); + if(notifyScaleChanged) { + sendToQml({'method' : 'scaleChanged', 'value' : currentAvatar.scale}); + } + } +} + +function onSkeletonModelURLChanged() { + if(currentAvatar || (currentAvatar.skeletonModelURL !== MyAvatar.skeletonModelURL)) { + fromQml({'method' : 'getAvatars'}); + } +} + +function onDominantHandChanged(dominantHand) { + if(currentAvatarSettings.dominantHand !== dominantHand) { + currentAvatarSettings.dominantHand = dominantHand; + sendToQml({'method' : 'settingChanged', 'name' : 'dominantHand', 'value' : dominantHand}); + } +} + +function onHmdAvatarAlignmentTypeChanged(type) { + if (currentAvatarSettings.hmdAvatarAlignmentType !== type) { + currentAvatarSettings.hmdAvatarAlignmentType = type; + sendToQml({'method' : 'settingChanged', 'name' : 'hmdAvatarAlignmentType', 'value' : type}); + } +} + +function onCollisionsEnabledChanged(enabled) { + if(currentAvatarSettings.collisionsEnabled !== enabled) { + currentAvatarSettings.collisionsEnabled = enabled; + sendToQml({'method' : 'settingChanged', 'name' : 'collisionsEnabled', 'value' : enabled}); + } +} + +function onOtherAvatarsCollisionsEnabledChanged(enabled) { + if (currentAvatarSettings.otherAvatarsCollisionsEnabled !== enabled) { + currentAvatarSettings.otherAvatarsCollisionsEnabled = enabled; + sendToQml({ 'method': 'settingChanged', 'name': 'otherAvatarsCollisionsEnabled', 'value': enabled }); + } +} + +function onNewCollisionSoundUrl(url) { + if(currentAvatarSettings.collisionSoundUrl !== url) { + currentAvatarSettings.collisionSoundUrl = url; + sendToQml({'method' : 'settingChanged', 'name' : 'collisionSoundUrl', 'value' : url}); + } +} + +function onAnimGraphUrlChanged(url) { + if (currentAvatarSettings.animGraphUrl !== url) { + currentAvatarSettings.animGraphUrl = url; + sendToQml({ 'method': 'settingChanged', 'name': 'animGraphUrl', 'value': currentAvatarSettings.animGraphUrl }); + + if (currentAvatarSettings.animGraphOverrideUrl !== MyAvatar.getAnimGraphOverrideUrl()) { + currentAvatarSettings.animGraphOverrideUrl = MyAvatar.getAnimGraphOverrideUrl(); + sendToQml({ 'method': 'settingChanged', 'name': 'animGraphOverrideUrl', + 'value': currentAvatarSettings.animGraphOverrideUrl }); + } + } +} + +var selectedAvatarEntityID = null; +var grabbedAvatarEntityChangeNotifier = null; + +var MARKETPLACE_PURCHASES_QML_PATH = "hifi/commerce/wallet/Wallet.qml"; +var MARKETPLACE_URL = Account.metaverseServerURL + "/marketplace"; +var MARKETPLACES_INJECT_SCRIPT_URL = Script.resolvePath("html/js/marketplacesInject.js"); + +function getWearablesFrozen() { + var wearablesFrozen = true; + var wearablesArray = getMyAvatarWearables(); + wearablesArray.forEach(function(wearable) { + if (isGrabbable(wearable.id)) { + wearablesFrozen = false; + } + }); + + return wearablesFrozen; +} + +function freezeWearables() { + var wearablesArray = getMyAvatarWearables(); + wearablesArray.forEach(function(wearable) { + setGrabbable(wearable.id, false); + }); +} + +function unfreezeWearables() { + var wearablesArray = getMyAvatarWearables(); + wearablesArray.forEach(function(wearable) { + setGrabbable(wearable.id, true); + }); +} + + +function fromQml(message) { // messages are {method, params}, like json-rpc. See also sendToQml. + switch (message.method) { + case 'getAvatars': + currentAvatar = getMyAvatar(); + currentAvatarSettings = getMyAvatarSettings(); + + message.data = { + 'bookmarks' : AvatarBookmarks.getBookmarks(), + 'displayName' : MyAvatar.displayName, + 'currentAvatar' : currentAvatar, + 'currentAvatarSettings' : currentAvatarSettings + }; + + for(var bookmarkName in message.data.bookmarks) { + var bookmark = message.data.bookmarks[bookmarkName]; + + if (bookmark.avatarEntites) { + bookmark.avatarEntites.forEach(function(avatarEntity) { + avatarEntity.properties.localRotationAngles = Quat.safeEulerAngles(avatarEntity.properties.localRotation); + }); + } + } + + sendToQml(message); + break; + case 'selectAvatar': + Entities.addingWearable.disconnect(onAddingWearable); + Entities.deletingWearable.disconnect(onDeletingWearable); + AvatarBookmarks.loadBookmark(message.name); + Entities.addingWearable.connect(onAddingWearable); + Entities.deletingWearable.connect(onDeletingWearable); + sendToQml({ 'method' : 'wearablesFrozenChanged', 'wearablesFrozen' : getWearablesFrozen()}); + break; + case 'deleteAvatar': + AvatarBookmarks.removeBookmark(message.name); + break; + case 'addAvatar': + AvatarBookmarks.addBookmark(message.name); + break; + case 'adjustWearable': + if(message.properties.localRotationAngles) { + message.properties.localRotation = Quat.fromVec3Degrees(message.properties.localRotationAngles); + } + + Entities.editEntity(message.entityID, message.properties); + message.properties = Entities.getEntityProperties(message.entityID, Object.keys(message.properties)); + + if(message.properties.localRotation) { + message.properties.localRotationAngles = Quat.safeEulerAngles(message.properties.localRotation); + } + + sendToQml({'method' : 'wearableUpdated', 'entityID' : message.entityID, wearableIndex : message.wearableIndex, properties : message.properties, updateUI : false}); + break; + case 'adjustWearablesOpened': + currentAvatarWearablesBackup = getMyAvatarWearables(); + adjustWearables.setOpened(true); + unfreezeWearables(); + + Entities.mousePressOnEntity.connect(onSelectedEntity); + Messages.subscribe('Hifi-Object-Manipulation'); + Messages.messageReceived.connect(handleWearableMessages); + break; + case 'adjustWearablesClosed': + if(!message.save) { + // revert changes using snapshot of wearables + if(currentAvatarWearablesBackup !== null) { + AvatarBookmarks.updateAvatarEntities(currentAvatarWearablesBackup); + updateAvatarWearables(currentAvatar, null, currentAvatarWearablesBackup); + } + } else { + sendToQml({'method' : 'updateAvatarInBookmarks'}); + } + + adjustWearables.setOpened(false); + ensureWearableSelected(null); + Entities.mousePressOnEntity.disconnect(onSelectedEntity); + Messages.messageReceived.disconnect(handleWearableMessages); + Messages.unsubscribe('Hifi-Object-Manipulation'); + break; + case 'addWearable': + + var joints = MyAvatar.getJointNames(); + var hipsIndex = -1; + + for(var i = 0; i < joints.length; ++i) { + if(joints[i] === 'Hips') { + hipsIndex = i; + break; + } + } + + var properties = { + name: "Custom wearable", + type: "Model", + modelURL: message.url, + parentID: MyAvatar.sessionUUID, + relayParentJoints: false, + parentJointIndex: hipsIndex + }; + + Entities.addingWearable.disconnect(onAddingWearable); + var entityID = Entities.addEntity(properties, true); + Entities.addingWearable.connect(onAddingWearable); + + updateAvatarWearables(currentAvatar, function() { + onSelectedEntity(entityID); + }); + break; + case 'selectWearable': + ensureWearableSelected(message.entityID); + break; + case 'deleteWearable': + + Entities.deletingWearable.disconnect(onDeletingWearable); + Entities.deleteEntity(message.entityID); + Entities.deletingWearable.connect(onDeletingWearable); + + updateAvatarWearables(currentAvatar); + break; + case 'changeDisplayName': + if (MyAvatar.displayName !== message.displayName) { + MyAvatar.displayName = message.displayName; + UserActivityLogger.palAction("display_name_change", message.displayName); + } + break; + case 'applyExternalAvatar': + var currentAvatarURL = MyAvatar.getFullAvatarURLFromPreferences(); + if(currentAvatarURL !== message.avatarURL) { + MyAvatar.useFullAvatarURL(message.avatarURL); + sendToQml({'method' : 'externalAvatarApplied', 'avatarURL' : message.avatarURL}); + } + break; + case 'navigate': + var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + if(message.url.indexOf('app://') === 0) { + if (message.url === 'app://marketplace') { + tablet.gotoWebScreen(MARKETPLACE_URL, MARKETPLACES_INJECT_SCRIPT_URL); + } else if (message.url === 'app://purchases') { + tablet.pushOntoStack(MARKETPLACE_PURCHASES_QML_PATH); + } + + } else if(message.url.indexOf('hifi://') === 0) { + AddressManager.handleLookupString(message.url, false); + } else if(message.url.indexOf('https://') === 0 || message.url.indexOf('http://') === 0) { + tablet.gotoWebScreen(message.url, MARKETPLACES_INJECT_SCRIPT_URL); + } + + break; + case 'setScale': + notifyScaleChanged = false; + MyAvatar.setAvatarScale(message.avatarScale); + currentAvatar.avatarScale = message.avatarScale; + notifyScaleChanged = true; + break; + case 'revertScale': + MyAvatar.setAvatarScale(message.avatarScale); + currentAvatar.avatarScale = message.avatarScale; + break; + case 'saveSettings': + MyAvatar.setAvatarScale(message.avatarScale); + currentAvatar.avatarScale = message.avatarScale; + + MyAvatar.setDominantHand(message.settings.dominantHand); + MyAvatar.setHmdAvatarAlignmentType(message.settings.hmdAvatarAlignmentType); + MyAvatar.setOtherAvatarsCollisionsEnabled(message.settings.otherAvatarsCollisionsEnabled); + MyAvatar.setCollisionsEnabled(message.settings.collisionsEnabled); + MyAvatar.collisionSoundURL = message.settings.collisionSoundUrl; + MyAvatar.setAnimGraphOverrideUrl(message.settings.animGraphOverrideUrl); + + currentAvatarSettings = getMyAvatarSettings(); + break; + case 'toggleWearablesFrozen': + var wearablesFrozen = getWearablesFrozen(); + wearablesFrozen = !wearablesFrozen; + if (wearablesFrozen) { + freezeWearables(); + } else { + unfreezeWearables(); + } + sendToQml({'method' : 'wearablesFrozenChanged', 'wearablesFrozen' : wearablesFrozen}); + break; + default: + print('Unrecognized message from AvatarApp.qml'); + } +} + +function isGrabbable(entityID) { + if(entityID === null) { + return false; + } + + var properties = Entities.getEntityProperties(entityID, ['avatarEntity', 'grab.grabbable']); + if (properties.avatarEntity) { + return properties.grab.grabbable; + } + + return false; +} + +function setGrabbable(entityID, grabbable) { + var properties = Entities.getEntityProperties(entityID, ['avatarEntity', 'grab.grabbable']); + if (properties.avatarEntity && properties.grab.grabbable != grabbable) { + var editProps = { grab: { grabbable: grabbable }}; + Entities.editEntity(entityID, editProps); + sendToQml({ 'method' : 'wearablesFrozenChanged', 'wearablesFrozen' : getWearablesFrozen()}); + } +} + +function ensureWearableSelected(entityID) { + if(selectedAvatarEntityID !== entityID) { + if(grabbedAvatarEntityChangeNotifier !== null) { + Script.clearInterval(grabbedAvatarEntityChangeNotifier); + grabbedAvatarEntityChangeNotifier = null; + } + selectedAvatarEntityID = entityID; + return true; + } + + return false; +} + +function isEntityBeingWorn(entityID) { + return Entities.getEntityProperties(entityID, 'parentID').parentID === MyAvatar.sessionUUID; +} + +function onSelectedEntity(entityID, pointerEvent) { + if(selectedAvatarEntityID !== entityID && isEntityBeingWorn(entityID)) + { + if(ensureWearableSelected(entityID)) { + sendToQml({'method' : 'selectAvatarEntity', 'entityID' : selectedAvatarEntityID}); + } + } +} + +function onAddingWearable(entityID) { + updateAvatarWearables(currentAvatar, function() { + sendToQml({'method' : 'updateAvatarInBookmarks'}); + }); + sendToQml({ 'method' : 'wearablesFrozenChanged', 'wearablesFrozen' : getWearablesFrozen()}); +} + +function onDeletingWearable(entityID) { + updateAvatarWearables(currentAvatar, function() { + sendToQml({'method' : 'updateAvatarInBookmarks'}); + }); + sendToQml({ 'method' : 'wearablesFrozenChanged', 'wearablesFrozen' : getWearablesFrozen()}); +} + +function handleWearableMessages(channel, message, sender) { + if (channel !== 'Hifi-Object-Manipulation') { + return; + } + + var parsedMessage = null; + + try { + parsedMessage = JSON.parse(message); + } catch (e) { + return; + } + + var entityID = parsedMessage.grabbedEntity; + + var updateWearable = function() { + // for some reasons Entities.getEntityProperties returns more than was asked.. + var propertyNames = ['localPosition', 'localRotation', 'dimensions', 'naturalDimensions']; + var entityProperties = Entities.getEntityProperties(selectedAvatarEntityID, propertyNames); + var properties = {}; + + propertyNames.forEach(function(propertyName) { + properties[propertyName] = entityProperties[propertyName]; + }); + + properties.localRotationAngles = Quat.safeEulerAngles(properties.localRotation); + sendToQml({'method' : 'wearableUpdated', 'entityID' : selectedAvatarEntityID, + 'wearableIndex' : -1, 'properties' : properties, updateUI : true}); + + }; + + if(parsedMessage.action === 'grab') { + if(selectedAvatarEntityID !== entityID) { + ensureWearableSelected(entityID); + sendToQml({'method' : 'selectAvatarEntity', 'entityID' : selectedAvatarEntityID}); + } + + grabbedAvatarEntityChangeNotifier = Script.setInterval(updateWearable, 1000); + } else if(parsedMessage.action === 'release') { + if(grabbedAvatarEntityChangeNotifier !== null) { + Script.clearInterval(grabbedAvatarEntityChangeNotifier); + grabbedAvatarEntityChangeNotifier = null; + updateWearable(); + } + } +} + +function sendToQml(message) { + tablet.sendToQml(message); +} + +function onBookmarkLoaded(bookmarkName) { + executeLater(function() { + currentAvatar = getMyAvatar(); + sendToQml({'method' : 'bookmarkLoaded', 'data' : {'name' : bookmarkName, 'currentAvatar' : currentAvatar} }); + }); +} + +function onBookmarkDeleted(bookmarkName) { + sendToQml({'method' : 'bookmarkDeleted', 'name' : bookmarkName}); +} + +function onBookmarkAdded(bookmarkName) { + var bookmark = AvatarBookmarks.getBookmark(bookmarkName); + bookmark.avatarEntites.forEach(function(avatarEntity) { + avatarEntity.properties.localRotationAngles = Quat.safeEulerAngles(avatarEntity.properties.localRotation); + }); + + sendToQml({ 'method': 'bookmarkAdded', 'bookmarkName': bookmarkName, 'bookmark': bookmark }); +} + +// +// Manage the connection between the button and the window. +// +var button; +var buttonName = "AVATAR"; +var tablet = null; + +function startup() { + tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + button = tablet.addButton({ + text: buttonName, + icon: "icons/tablet-icons/avatar-i.svg", + activeIcon: "icons/tablet-icons/avatar-a.svg", + sortOrder: 7 + }); + button.clicked.connect(onTabletButtonClicked); + tablet.screenChanged.connect(onTabletScreenChanged); +} + +startup(); + +var isWired = false; +function off() { + if(adjustWearables.opened) { + adjustWearables.setOpened(false); + ensureWearableSelected(null); + Entities.mousePressOnEntity.disconnect(onSelectedEntity); + + Messages.messageReceived.disconnect(handleWearableMessages); + Messages.unsubscribe('Hifi-Object-Manipulation'); + } + + if (isWired) { // It is not ok to disconnect these twice, hence guard. + isWired = false; + + AvatarBookmarks.bookmarkLoaded.disconnect(onBookmarkLoaded); + AvatarBookmarks.bookmarkDeleted.disconnect(onBookmarkDeleted); + AvatarBookmarks.bookmarkAdded.disconnect(onBookmarkAdded); + + Entities.addingWearable.disconnect(onAddingWearable); + Entities.deletingWearable.disconnect(onDeletingWearable); + MyAvatar.skeletonModelURLChanged.disconnect(onSkeletonModelURLChanged); + MyAvatar.dominantHandChanged.disconnect(onDominantHandChanged); + MyAvatar.hmdAvatarAlignmentTypeChanged.disconnect(onHmdAvatarAlignmentTypeChanged); + MyAvatar.collisionsEnabledChanged.disconnect(onCollisionsEnabledChanged); + MyAvatar.otherAvatarsCollisionsEnabledChanged.disconnect(onOtherAvatarsCollisionsEnabledChanged); + MyAvatar.newCollisionSoundURL.disconnect(onNewCollisionSoundUrl); + MyAvatar.animGraphUrlChanged.disconnect(onAnimGraphUrlChanged); + MyAvatar.targetScaleChanged.disconnect(onTargetScaleChanged); + } +} + +function on() { + + if (!isWired) { // It is not ok to connect these twice, hence guard. + isWired = true; + + AvatarBookmarks.bookmarkLoaded.connect(onBookmarkLoaded); + AvatarBookmarks.bookmarkDeleted.connect(onBookmarkDeleted); + AvatarBookmarks.bookmarkAdded.connect(onBookmarkAdded); + + Entities.addingWearable.connect(onAddingWearable); + Entities.deletingWearable.connect(onDeletingWearable); + MyAvatar.skeletonModelURLChanged.connect(onSkeletonModelURLChanged); + MyAvatar.dominantHandChanged.connect(onDominantHandChanged); + MyAvatar.hmdAvatarAlignmentTypeChanged.connect(onHmdAvatarAlignmentTypeChanged); + MyAvatar.collisionsEnabledChanged.connect(onCollisionsEnabledChanged); + MyAvatar.otherAvatarsCollisionsEnabledChanged.connect(onOtherAvatarsCollisionsEnabledChanged); + MyAvatar.newCollisionSoundURL.connect(onNewCollisionSoundUrl); + MyAvatar.animGraphUrlChanged.connect(onAnimGraphUrlChanged); + MyAvatar.targetScaleChanged.connect(onTargetScaleChanged); + } +} + +function onTabletButtonClicked() { + if (onAvatarAppScreen) { + // for toolbar-mode: go back to home screen, this will close the window. + tablet.gotoHomeScreen(); + } else { + ContextOverlay.enabled = false; + tablet.loadQMLSource(AVATARAPP_QML_SOURCE); + } +} +var hasEventBridge = false; +function wireEventBridge(on) { + if (on) { + if (!hasEventBridge) { + tablet.fromQml.connect(fromQml); + hasEventBridge = true; + } + } else { + if (hasEventBridge) { + tablet.fromQml.disconnect(fromQml); + hasEventBridge = false; + } + } +} + +var onAvatarAppScreen = false; +function onTabletScreenChanged(type, url) { + var onAvatarAppScreenNow = (type === "QML" && url === AVATARAPP_QML_SOURCE); + wireEventBridge(onAvatarAppScreenNow); + // for toolbar mode: change button to active when window is first openend, false otherwise. + button.editProperties({isActive: onAvatarAppScreenNow}); + + if (!onAvatarAppScreen && onAvatarAppScreenNow) { + on(); + } else if(onAvatarAppScreen && !onAvatarAppScreenNow) { + off(); + } + + onAvatarAppScreen = onAvatarAppScreenNow; + + if(onAvatarAppScreenNow) { + sendToQml({ 'method' : 'initialize', 'data' : { jointNames : MyAvatar.getJointNames() }}); + sendToQml({ 'method' : 'wearablesFrozenChanged', 'wearablesFrozen' : getWearablesFrozen()}); + } +} + +function shutdown() { + if (onAvatarAppScreen) { + tablet.gotoHomeScreen(); + } + button.clicked.disconnect(onTabletButtonClicked); + tablet.removeButton(button); + tablet.screenChanged.disconnect(onTabletScreenChanged); + + off(); +} + +// +// Cleanup. +// +Script.scriptEnding.connect(shutdown); + +}()); // END LOCAL_SCOPE diff --git a/scripts/simplifiedUI/system/bubble.js b/scripts/simplifiedUI/system/bubble.js new file mode 100644 index 0000000000..eca3b3dcd4 --- /dev/null +++ b/scripts/simplifiedUI/system/bubble.js @@ -0,0 +1,206 @@ +"use strict"; + +// +// bubble.js +// scripts/system/ +// +// Created by Brad Hefta-Gaub on 11/18/2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +/* global Script, Users, Overlays, AvatarList, Controller, Camera, getControllerWorldLocation, UserActivityLogger */ + +(function () { // BEGIN LOCAL_SCOPE + var button; + // Used for animating and disappearing the bubble + var bubbleOverlayTimestamp; + // Used for rate limiting the bubble sound + var lastBubbleSoundTimestamp = 0; + // Affects bubble height + var BUBBLE_HEIGHT_SCALE = 0.15; + // The bubble model itself + var bubbleOverlay = Overlays.addOverlay("model", { + url: Script.resolvePath("assets/models/Bubble-v14.fbx"), // If you'd like to change the model, modify this line (and the dimensions below) + dimensions: { x: MyAvatar.sensorToWorldScale, y: 0.75 * MyAvatar.sensorToWorldScale, z: MyAvatar.sensorToWorldScale }, + position: { x: MyAvatar.position.x, y: -MyAvatar.scale * 2 + MyAvatar.position.y + MyAvatar.scale * BUBBLE_HEIGHT_SCALE, z: MyAvatar.position.z }, + rotation: Quat.multiply(MyAvatar.orientation, Quat.fromVec3Degrees({x: 0.0, y: 180.0, z: 0.0})), + scale: { x: 2 , y: MyAvatar.scale * 0.5 + 0.5, z: 2 }, + visible: false, + ignoreRayIntersection: true + }); + // The bubble activation sound + var bubbleActivateSound = SoundCache.getSound(Script.resolvePath("assets/sounds/bubble.wav")); + // Is the update() function connected? + var updateConnected = false; + + var BUBBLE_VISIBLE_DURATION_MS = 3000; + var BUBBLE_RAISE_ANIMATION_DURATION_MS = 750; + var BUBBLE_SOUND_RATE_LIMIT_MS = 15000; + + // Hides the bubble model overlay + function hideOverlays() { + Overlays.editOverlay(bubbleOverlay, { + visible: false + }); + } + + // Make the bubble overlay visible, set its position, and play the sound + function createOverlays() { + var nowTimestamp = Date.now(); + if (nowTimestamp - lastBubbleSoundTimestamp >= BUBBLE_SOUND_RATE_LIMIT_MS) { + Audio.playSound(bubbleActivateSound, { + position: { x: MyAvatar.position.x, y: MyAvatar.position.y, z: MyAvatar.position.z }, + localOnly: true, + volume: 0.2 + }); + lastBubbleSoundTimestamp = nowTimestamp; + } + hideOverlays(); + if (updateConnected === true) { + updateConnected = false; + Script.update.disconnect(update); + } + + Overlays.editOverlay(bubbleOverlay, { + dimensions: { + x: MyAvatar.sensorToWorldScale, + y: 0.75 * MyAvatar.sensorToWorldScale, + z: MyAvatar.sensorToWorldScale + }, + position: { + x: MyAvatar.position.x, + y: -MyAvatar.scale * 2 + MyAvatar.position.y + MyAvatar.scale * BUBBLE_HEIGHT_SCALE, + z: MyAvatar.position.z + }, + rotation: Quat.multiply(MyAvatar.orientation, Quat.fromVec3Degrees({x: 0.0, y: 180.0, z: 0.0})), + scale: { + x: 2 , + y: MyAvatar.scale * 0.5 + 0.5 , + z: 2 + }, + visible: true + }); + bubbleOverlayTimestamp = nowTimestamp; + Script.update.connect(update); + updateConnected = true; + } + + // Called from the C++ scripting interface to show the bubble overlay + function enteredIgnoreRadius() { + createOverlays(); + UserActivityLogger.privacyShieldActivated(); + } + + // Used to set the state of the bubble HUD button + function writeButtonProperties(parameter) { + button.editProperties({isActive: parameter}); + } + + // The bubble script's update function + function update() { + var timestamp = Date.now(); + var delay = (timestamp - bubbleOverlayTimestamp); + var overlayAlpha = 1.0 - (delay / BUBBLE_VISIBLE_DURATION_MS); + if (overlayAlpha > 0) { + if (delay < BUBBLE_RAISE_ANIMATION_DURATION_MS) { + Overlays.editOverlay(bubbleOverlay, { + dimensions: { + x: MyAvatar.sensorToWorldScale, + y: 0.75 * MyAvatar.sensorToWorldScale, + z: MyAvatar.sensorToWorldScale + }, + // Quickly raise the bubble from the ground up + position: { + x: MyAvatar.position.x, + y: (-((BUBBLE_RAISE_ANIMATION_DURATION_MS - delay) / BUBBLE_RAISE_ANIMATION_DURATION_MS)) * MyAvatar.scale * 2 + MyAvatar.position.y + MyAvatar.scale * BUBBLE_HEIGHT_SCALE, + z: MyAvatar.position.z + }, + rotation: Quat.multiply(MyAvatar.orientation, Quat.fromVec3Degrees({x: 0.0, y: 180.0, z: 0.0})), + scale: { + x: 2 , + y: ((1 - ((BUBBLE_RAISE_ANIMATION_DURATION_MS - delay) / BUBBLE_RAISE_ANIMATION_DURATION_MS)) * MyAvatar.scale * 0.5 + 0.5), + z: 2 + } + }); + } else { + // Keep the bubble in place for a couple seconds + Overlays.editOverlay(bubbleOverlay, { + dimensions: { + x: MyAvatar.sensorToWorldScale, + y: 0.75 * MyAvatar.sensorToWorldScale, + z: MyAvatar.sensorToWorldScale + }, + position: { + x: MyAvatar.position.x, + y: MyAvatar.position.y + MyAvatar.scale * BUBBLE_HEIGHT_SCALE, + z: MyAvatar.position.z + }, + rotation: Quat.multiply(MyAvatar.orientation, Quat.fromVec3Degrees({x: 0.0, y: 180.0, z: 0.0})), + scale: { + x: 2, + y: MyAvatar.scale * 0.5 + 0.5 , + z: 2 + } + }); + } + } else { + hideOverlays(); + if (updateConnected === true) { + Script.update.disconnect(update); + updateConnected = false; + } + } + } + + // When the space bubble is toggled... + // NOTE: the c++ calls this with just the first param -- we added a second + // just for not logging the initial state of the bubble when we startup. + function onBubbleToggled(enabled, doNotLog) { + writeButtonProperties(enabled); + if (doNotLog !== true) { + UserActivityLogger.privacyShieldToggled(enabled); + } + if (enabled) { + createOverlays(); + } else { + hideOverlays(); + if (updateConnected === true) { + Script.update.disconnect(update); + updateConnected = false; + } + } + } + + // Setup the bubble button + var buttonName = "SHIELD"; + var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + button = tablet.addButton({ + icon: "icons/tablet-icons/bubble-i.svg", + activeIcon: "icons/tablet-icons/bubble-a.svg", + text: buttonName, + sortOrder: 4 + }); + + onBubbleToggled(Users.getIgnoreRadiusEnabled(), true); // pass in true so we don't log this initial one in the UserActivity table + + button.clicked.connect(Users.toggleIgnoreRadius); + Users.ignoreRadiusEnabledChanged.connect(onBubbleToggled); + Users.enteredIgnoreRadius.connect(enteredIgnoreRadius); + + // Cleanup the tablet button and overlays when script is stopped + Script.scriptEnding.connect(function () { + button.clicked.disconnect(Users.toggleIgnoreRadius); + if (tablet) { + tablet.removeButton(button); + } + Users.ignoreRadiusEnabledChanged.disconnect(onBubbleToggled); + Users.enteredIgnoreRadius.disconnect(enteredIgnoreRadius); + Overlays.deleteOverlay(bubbleOverlay); + if (updateConnected === true) { + Script.update.disconnect(update); + } + }); + +}()); // END LOCAL_SCOPE diff --git a/scripts/simplifiedUI/system/chat.js b/scripts/simplifiedUI/system/chat.js new file mode 100644 index 0000000000..749665f3d8 --- /dev/null +++ b/scripts/simplifiedUI/system/chat.js @@ -0,0 +1,1010 @@ +"use strict"; + +// Chat.js +// By Don Hopkins (dhopkins@donhopkins.com) +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +(function() { + + var webPageURL = Script.resolvePath("html/ChatPage.html"); // URL of tablet web page. + var randomizeWebPageURL = true; // Set to true for debugging. + var lastWebPageURL = ""; // Last random URL of tablet web page. + var onChatPage = false; // True when chat web page is opened. + var webHandlerConnected = false; // True when the web handler has been connected. + var channelName = "Chat"; // Unique name for channel that we listen to. + var tabletButtonName = "CHAT"; // Tablet button label. + var tabletButtonIcon = "icons/tablet-icons/menu-i.svg"; // Icon for chat button. + var tabletButtonActiveIcon = "icons/tablet-icons/menu-a.svg"; // Active icon for chat button. + var tabletButton = null; // The button we create in the tablet. + var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); // The awesome tablet. + var chatLog = []; // Array of chat messages in the form of [avatarID, displayName, message, data]. + var avatarIdentifiers = {}; // Map of avatar ids to dict of identifierParams. + var speechBubbleShowing = false; // Is the speech bubble visible? + var speechBubbleMessage = null; // The message shown in the speech bubble. + var speechBubbleData = null; // The data of the speech bubble message. + var speechBubbleTextID = null; // The id of the speech bubble local text entity. + var speechBubbleTimer = null; // The timer to pop down the speech bubble. + var speechBubbleParams = null; // The params used to create or edit the speech bubble. + + // Persistent variables saved in the Settings. + var chatName = ''; // The user's name shown in chat. + var chatLogMaxSize = 100; // The maximum number of chat messages we remember. + var sendTyping = true; // Send typing begin and end notification. + var identifyAvatarDuration = 10; // How long to leave the avatar identity line up, in seconds. + var identifyAvatarLineColor = { red: 0, green: 255, blue: 0 }; // The color of the avatar identity line. + var identifyAvatarMyJointName = 'Head'; // My bone from which to draw the avatar identity line. + var identifyAvatarYourJointName = 'Head'; // Your bone to which to draw the avatar identity line. + var speechBubbleDuration = 10; // How long to leave the speech bubble up, in seconds. + var speechBubbleTextColor = {red: 255, green: 255, blue: 255}; // The text color of the speech bubble. + var speechBubbleBackgroundColor = {red: 0, green: 0, blue: 0}; // The background color of the speech bubble. + var speechBubbleOffset = {x: 0, y: 0.3, z: 0.0}; // The offset from the joint to whic the speech bubble is attached. + var speechBubbleJointName = 'Head'; // The name of the joint to which the speech bubble is attached. + var speechBubbleLineHeight = 0.05; // The height of a line of text in the speech bubble. + var SPEECH_BUBBLE_MAX_WIDTH = 1; // meters + + var textSizeOverlay = Overlays.addOverlay("text3d", { + position: MyAvatar.position, + lineHeight: speechBubbleLineHeight, + leftMargin: 0, + topMargin: 0, + rightMargin: 0, + bottomMargin: 0, + ignoreRayIntersection: true, + visible: false + }); + + // Load the persistent variables from the Settings, with defaults. + function loadSettings() { + chatName = Settings.getValue('Chat_chatName', MyAvatar.displayName); + if (!chatName) { + chatName = randomAvatarName(); + } + chatLogMaxSize = Settings.getValue('Chat_chatLogMaxSize', 100); + sendTyping = Settings.getValue('Chat_sendTyping', true); + identifyAvatarDuration = Settings.getValue('Chat_identifyAvatarDuration', 10); + identifyAvatarLineColor = Settings.getValue('Chat_identifyAvatarLineColor', { red: 0, green: 255, blue: 0 }); + identifyAvatarMyJointName = Settings.getValue('Chat_identifyAvatarMyJointName', 'Head'); + identifyAvatarYourJointName = Settings.getValue('Chat_identifyAvatarYourJointName', 'Head'); + speechBubbleDuration = Settings.getValue('Chat_speechBubbleDuration', 10); + speechBubbleTextColor = Settings.getValue('Chat_speechBubbleTextColor', {red: 255, green: 255, blue: 255}); + speechBubbleBackgroundColor = Settings.getValue('Chat_speechBubbleBackgroundColor', {red: 0, green: 0, blue: 0}); + speechBubbleOffset = Settings.getValue('Chat_speechBubbleOffset', {x: 0.0, y: 0.3, z:0.0}); + speechBubbleJointName = Settings.getValue('Chat_speechBubbleJointName', 'Head'); + speechBubbleLineHeight = Settings.getValue('Chat_speechBubbleLineHeight', 0.05); + Overlays.editOverlay(textSizeOverlay, { + lineHeight: speechBubbleLineHeight + }); + + saveSettings(); + } + + // Save the persistent variables to the Settings. + function saveSettings() { + Settings.setValue('Chat_chatName', chatName); + Settings.setValue('Chat_chatLogMaxSize', chatLogMaxSize); + Settings.setValue('Chat_sendTyping', sendTyping); + Settings.setValue('Chat_identifyAvatarDuration', identifyAvatarDuration); + Settings.setValue('Chat_identifyAvatarLineColor', identifyAvatarLineColor); + Settings.setValue('Chat_identifyAvatarMyJointName', identifyAvatarMyJointName); + Settings.setValue('Chat_identifyAvatarYourJointName', identifyAvatarYourJointName); + Settings.setValue('Chat_speechBubbleDuration', speechBubbleDuration); + Settings.setValue('Chat_speechBubbleTextColor', speechBubbleTextColor); + Settings.setValue('Chat_speechBubbleBackgroundColor', speechBubbleBackgroundColor); + Settings.setValue('Chat_speechBubbleOffset', speechBubbleOffset); + Settings.setValue('Chat_speechBubbleJointName', speechBubbleJointName); + Settings.setValue('Chat_speechBubbleLineHeight', speechBubbleLineHeight); + } + + // Reset the Settings and persistent variables to the defaults. + function resetSettings() { + Settings.setValue('Chat_chatName', null); + Settings.setValue('Chat_chatLogMaxSize', null); + Settings.setValue('Chat_sendTyping', null); + Settings.setValue('Chat_identifyAvatarDuration', null); + Settings.setValue('Chat_identifyAvatarLineColor', null); + Settings.setValue('Chat_identifyAvatarMyJointName', null); + Settings.setValue('Chat_identifyAvatarYourJointName', null); + Settings.setValue('Chat_speechBubbleDuration', null); + Settings.setValue('Chat_speechBubbleTextColor', null); + Settings.setValue('Chat_speechBubbleBackgroundColor', null); + Settings.setValue('Chat_speechBubbleOffset', null); + Settings.setValue('Chat_speechBubbleJointName', null); + Settings.setValue('Chat_speechBubbleLineHeight', null); + + loadSettings(); + } + + // Update anything that might depend on the settings. + function updateSettings() { + updateSpeechBubble(); + trimChatLog(); + updateChatPage(); + } + + // Trim the chat log so it is no longer than chatLogMaxSize lines. + function trimChatLog() { + if (chatLog.length > chatLogMaxSize) { + chatLog.splice(0, chatLogMaxSize - chatLog.length); + } + } + + // Clear the local chat log. + function clearChatLog() { + //print("clearChatLog"); + chatLog = []; + updateChatPage(); + } + + // We got a chat message from the channel. + // Trim the chat log, save the latest message in the chat log, + // and show the message on the tablet, if the chat page is showing. + function handleTransmitChatMessage(avatarID, displayName, message, data) { + //print("receiveChat", "avatarID", avatarID, "displayName", displayName, "message", message, "data", data); + + trimChatLog(); + chatLog.push([avatarID, displayName, message, data]); + + if (onChatPage) { + tablet.emitScriptEvent( + JSON.stringify({ + type: "ReceiveChatMessage", + avatarID: avatarID, + displayName: displayName, + message: message, + data: data + })); + } + } + + // Trim the chat log, save the latest log message in the chat log, + // and show the message on the tablet, if the chat page is showing. + function logMessage(message, data) { + //print("logMessage", message, data); + + trimChatLog(); + chatLog.push([null, null, message, data]); + + if (onChatPage) { + tablet.emitScriptEvent( + JSON.stringify({ + type: "LogMessage", + message: message, + data: data + })); + } + } + + // An empty chat message was entered. + // Hide our speech bubble. + function emptyChatMessage(data) { + popDownSpeechBubble(); + } + + // Notification that we typed a keystroke. + function type() { + //print("type"); + } + + // Notification that we began typing. + // Notify everyone that we started typing. + function beginTyping() { + //print("beginTyping"); + if (!sendTyping) { + return; + } + + Messages.sendMessage( + channelName, + JSON.stringify({ + type: 'AvatarBeginTyping', + avatarID: MyAvatar.sessionUUID, + displayName: chatName + })); + } + + // Notification that somebody started typing. + function handleAvatarBeginTyping(avatarID, displayName) { + //print("handleAvatarBeginTyping:", "avatarID", avatarID, displayName); + } + + // Notification that we stopped typing. + // Notify everyone that we stopped typing. + function endTyping() { + //print("endTyping"); + if (!sendTyping) { + return; + } + + Messages.sendMessage( + channelName, + JSON.stringify({ + type: 'AvatarEndTyping', + avatarID: MyAvatar.sessionUUID, + displayName: chatName + })); + } + + // Notification that somebody stopped typing. + function handleAvatarEndTyping(avatarID, displayName) { + //print("handleAvatarEndTyping:", "avatarID", avatarID, displayName); + } + + // Identify an avatar by drawing a line from our head to their head. + // If the avatar is our own, then just draw a line up into the sky. + function identifyAvatar(yourAvatarID) { + //print("identifyAvatar", yourAvatarID); + + unidentifyAvatars(); + + var myAvatarID = MyAvatar.sessionUUID; + var myJointIndex = MyAvatar.getJointIndex(identifyAvatarMyJointName); + var myJointRotation = + Quat.multiply( + MyAvatar.orientation, + MyAvatar.getAbsoluteJointRotationInObjectFrame(myJointIndex)); + var myJointPosition = + Vec3.sum( + MyAvatar.position, + Vec3.multiplyQbyV( + MyAvatar.orientation, + MyAvatar.getAbsoluteJointTranslationInObjectFrame(myJointIndex))); + + var yourJointIndex = -1; + var yourJointPosition; + + if (yourAvatarID == myAvatarID) { + + // You pointed at your own name, so draw a line up from your head. + + yourJointPosition = { + x: myJointPosition.x, + y: myJointPosition.y + 1000.0, + z: myJointPosition.z + }; + + } else { + + // You pointed at somebody else's name, so draw a line from your head to their head. + + var yourAvatar = AvatarList.getAvatar(yourAvatarID); + if (!yourAvatar) { + return; + } + + yourJointIndex = yourAvatar.getJointIndex(identifyAvatarMyJointName) + + var yourJointRotation = + Quat.multiply( + yourAvatar.orientation, + yourAvatar.getAbsoluteJointRotationInObjectFrame(yourJointIndex)); + yourJointPosition = + Vec3.sum( + yourAvatar.position, + Vec3.multiplyQbyV( + yourAvatar.orientation, + yourAvatar.getAbsoluteJointTranslationInObjectFrame(yourJointIndex))); + + } + + var identifierParams = { + parentID: myAvatarID, + parentJointIndex: myJointIndex, + lifetime: identifyAvatarDuration, + start: myJointPosition, + endParentID: yourAvatarID, + endParentJointIndex: yourJointIndex, + end: yourJointPosition, + color: identifyAvatarLineColor, + alpha: 1 + }; + + avatarIdentifiers[yourAvatarID] = identifierParams; + + identifierParams.lineID = Overlays.addOverlay("line3d", identifierParams); + + //print("ADDOVERLAY lineID", lineID, "myJointPosition", JSON.stringify(myJointPosition), "yourJointPosition", JSON.stringify(yourJointPosition), "lineData", JSON.stringify(lineData)); + + identifierParams.timer = + Script.setTimeout(function() { + //print("DELETEOVERLAY lineID"); + unidentifyAvatar(yourAvatarID); + }, identifyAvatarDuration * 1000); + + } + + // Stop identifying an avatar. + function unidentifyAvatar(yourAvatarID) { + //print("unidentifyAvatar", yourAvatarID); + + var identifierParams = avatarIdentifiers[yourAvatarID]; + if (!identifierParams) { + return; + } + + if (identifierParams.timer) { + Script.clearTimeout(identifierParams.timer); + } + + if (identifierParams.lineID) { + Overlays.deleteOverlay(identifierParams.lineID); + } + + delete avatarIdentifiers[yourAvatarID]; + } + + // Stop identifying all avatars. + function unidentifyAvatars() { + var ids = []; + + for (var avatarID in avatarIdentifiers) { + ids.push(avatarID); + } + + for (var i = 0, n = ids.length; i < n; i++) { + var avatarID = ids[i]; + unidentifyAvatar(avatarID); + } + + } + + // Turn to face another avatar. + function faceAvatar(yourAvatarID, displayName) { + //print("faceAvatar:", yourAvatarID, displayName); + + var myAvatarID = MyAvatar.sessionUUID; + if (yourAvatarID == myAvatarID) { + // You clicked on yourself. + return; + } + + var yourAvatar = AvatarList.getAvatar(yourAvatarID); + if (!yourAvatar) { + logMessage(displayName + ' is not here!', null); + return; + } + + // Project avatar positions to the floor and get the direction between those points, + // then face my avatar towards your avatar. + var yourPosition = yourAvatar.position; + yourPosition.y = 0; + var myPosition = MyAvatar.position; + myPosition.y = 0; + var myOrientation = Quat.lookAtSimple(myPosition, yourPosition); + MyAvatar.orientation = myOrientation; + } + + // Make a hopefully unique random anonymous avatar name. + function randomAvatarName() { + return 'Anon_' + Math.floor(Math.random() * 1000000); + } + + // Change the avatar size to bigger. + function biggerSize() { + //print("biggerSize"); + logMessage("Increasing avatar size", null); + MyAvatar.increaseSize(); + } + + // Change the avatar size to smaller. + function smallerSize() { + //print("smallerSize"); + logMessage("Decreasing avatar size", null); + MyAvatar.decreaseSize(); + } + + // Set the avatar size to normal. + function normalSize() { + //print("normalSize"); + logMessage("Resetting avatar size to normal!", null); + MyAvatar.resetSize(); + } + + // Send out a "Who" message, including our avatarID as myAvatarID, + // which will be sent in the response, so we can tell the reply + // is to our request. + function transmitWho() { + //print("transmitWho"); + logMessage("Who is here?", null); + Messages.sendMessage( + channelName, + JSON.stringify({ + type: 'Who', + myAvatarID: MyAvatar.sessionUUID + })); + } + + // Send a reply to a "Who" message, with a friendly message, + // our avatarID and our displayName. myAvatarID is the id + // of the avatar who send the Who message, to whom we're + // responding. + function handleWho(myAvatarID) { + var avatarID = MyAvatar.sessionUUID; + if (myAvatarID == avatarID) { + // Don't reply to myself. + return; + } + + var message = "I'm here!"; + var data = {}; + + Messages.sendMessage( + channelName, + JSON.stringify({ + type: 'ReplyWho', + myAvatarID: myAvatarID, + avatarID: avatarID, + displayName: chatName, + message: message, + data: data + })); + } + + // Receive the reply to a "Who" message. Ignore it unless we were the one + // who sent it out (if myAvatarIS is our avatar's id). + function handleReplyWho(myAvatarID, avatarID, displayName, message, data) { + if (myAvatarID != MyAvatar.sessionUUID) { + return; + } + + handleTransmitChatMessage(avatarID, displayName, message, data); + } + + // Handle input form the user, possibly multiple lines separated by newlines. + // Each line may be a chat command starting with "/", or a chat message. + function handleChatMessage(message, data) { + + var messageLines = message.trim().split('\n'); + + for (var i = 0, n = messageLines.length; i < n; i++) { + var messageLine = messageLines[i]; + + if (messageLine.substr(0, 1) == '/') { + handleChatCommand(messageLine, data); + } else { + transmitChatMessage(messageLine, data); + } + } + + } + + // Handle a chat command prefixed by "/". + function handleChatCommand(message, data) { + + var commandLine = message.substr(1); + var tokens = commandLine.trim().split(' '); + var command = tokens[0]; + var rest = commandLine.substr(command.length + 1).trim(); + + //print("commandLine", commandLine, "command", command, "tokens", tokens, "rest", rest); + + switch (command) { + + case '?': + case 'help': + logMessage('Type "/?" or "/help" for help', null); + logMessage('Type "/name " to set your chat name, or "/name" to use your display name. If your display name is not defined, a random name will be used.', null); + logMessage('Type "/close" to close your overhead chat message.', null); + logMessage('Type "/say " to display a new message.', null); + logMessage('Type "/clear" to clear your chat log.', null); + logMessage('Type "/who" to ask who is in the chat session.', null); + logMessage('Type "/bigger", "/smaller" or "/normal" to change your avatar size.', null); + break; + + case 'name': + if (rest == '') { + if (MyAvatar.displayName) { + chatName = MyAvatar.displayName; + saveSettings(); + logMessage('Your chat name has been set to your display name "' + chatName + '".', null); + } else { + chatName = randomAvatarName(); + saveSettings(); + logMessage('Your avatar\'s display name is not defined, so your chat name has been set to "' + chatName + '".', null); + } + } else { + chatName = rest; + saveSettings(); + logMessage('Your chat name has been set to "' + chatName + '".', null); + } + break; + + case 'close': + popDownSpeechBubble(); + logMessage('Overhead chat message closed.', null); + break; + + case 'say': + if (rest == '') { + emptyChatMessage(data); + } else { + transmitChatMessage(rest, data); + } + break; + + case 'who': + transmitWho(); + break; + + case 'clear': + clearChatLog(); + break; + + case 'bigger': + biggerSize(); + break; + + case 'smaller': + smallerSize(); + break; + + case 'normal': + normalSize(); + break; + + case 'resetsettings': + resetSettings(); + updateSettings(); + break; + + case 'speechbubbleheight': + var y = parseInt(rest); + if (!isNaN(y)) { + speechBubbleOffset.y = y; + } + saveSettings(); + updateSettings(); + break; + + case 'speechbubbleduration': + var duration = parseFloat(rest); + if (!isNaN(duration)) { + speechBubbleDuration = duration; + } + saveSettings(); + updateSettings(); + break; + + default: + logMessage('Unknown chat command. Type "/help" or "/?" for help.', null); + break; + + } + + } + + // Send out a chat message to everyone. + function transmitChatMessage(message, data) { + //print("transmitChatMessage", 'avatarID', avatarID, 'displayName', displayName, 'message', message, 'data', data); + + popUpSpeechBubble(message, data); + + Messages.sendMessage( + channelName, + JSON.stringify({ + type: 'TransmitChatMessage', + avatarID: MyAvatar.sessionUUID, + displayName: chatName, + message: message, + data: data + })); + + } + + // Show the speech bubble. + function popUpSpeechBubble(message, data) { + //print("popUpSpeechBubble", message, data); + + popDownSpeechBubble(); + + speechBubbleShowing = true; + speechBubbleMessage = message; + speechBubbleData = data; + + updateSpeechBubble(); + + if (speechBubbleDuration > 0) { + speechBubbleTimer = Script.setTimeout( + function () { + popDownSpeechBubble(); + }, + speechBubbleDuration * 1000); + } + } + + // Update the speech bubble. + // This is factored out so we can update an existing speech bubble if any settings change. + function updateSpeechBubble() { + if (!speechBubbleShowing) { + return; + } + + var jointIndex = MyAvatar.getJointIndex(speechBubbleJointName); + var dimensions = { + x: 100.0, + y: 100.0, + z: 0.1 + }; + + speechBubbleParams = { + type: "Text", + lifetime: speechBubbleDuration, + parentID: MyAvatar.sessionUUID, + jointIndex: jointIndex, + dimensions: dimensions, + lineHeight: speechBubbleLineHeight, + leftMargin: 0, + topMargin: 0, + rightMargin: 0, + bottomMargin: 0, + faceCamera: true, + drawInFront: true, + ignoreRayIntersection: true, + text: speechBubbleMessage, + textColor: speechBubbleTextColor, + color: speechBubbleTextColor, + backgroundColor: speechBubbleBackgroundColor + }; + + // Only overlay text3d has a way to measure the text, not entities. + // So we make a temporary one just for measuring text, then delete it. + var speechBubbleTextOverlayID = Overlays.addOverlay("text3d", speechBubbleParams); + var textSize = Overlays.textSize(textSizeOverlay, speechBubbleMessage); + try { + Overlays.deleteOverlay(speechBubbleTextOverlayID); + } catch (e) {} + + //print("updateSpeechBubble:", "speechBubbleMessage", speechBubbleMessage, "textSize", textSize.width, textSize.height); + + var fudge = 0.02; + + var width = textSize.width + fudge; + var height = speechBubbleLineHeight + fudge; + + if (textSize.width >= SPEECH_BUBBLE_MAX_WIDTH) { + var numLines = Math.ceil(width); + height = speechBubbleLineHeight * numLines + fudge; + width = SPEECH_BUBBLE_MAX_WIDTH; + } + + dimensions = { + x: width, + y: height, + z: 0.1 + }; + speechBubbleParams.dimensions = dimensions; + + var headRotation = + Quat.multiply( + MyAvatar.orientation, + MyAvatar.getAbsoluteJointRotationInObjectFrame(jointIndex)); + var headPosition = + Vec3.sum( + MyAvatar.position, + Vec3.multiplyQbyV( + MyAvatar.orientation, + MyAvatar.getAbsoluteJointTranslationInObjectFrame(jointIndex))); + var rotatedOffset = + Vec3.multiplyQbyV( + headRotation, + speechBubbleOffset); + var position = + Vec3.sum( + headPosition, + rotatedOffset); + position.y += height / 2; // offset based on half of bubble height + speechBubbleParams.position = position; + + if (!speechBubbleTextID) { + speechBubbleTextID = + Entities.addEntity(speechBubbleParams, true); + } else { + Entities.editEntity(speechBubbleTextID, speechBubbleParams); + } + + //print("speechBubbleTextID:", speechBubbleTextID, "speechBubbleParams", JSON.stringify(speechBubbleParams)); + } + + // Hide the speech bubble. + function popDownSpeechBubble() { + cancelSpeechBubbleTimer(); + + speechBubbleShowing = false; + + //print("popDownSpeechBubble speechBubbleTextID", speechBubbleTextID); + + if (speechBubbleTextID) { + try { + Entities.deleteEntity(speechBubbleTextID); + } catch (e) {} + speechBubbleTextID = null; + } + } + + // Cancel the speech bubble popup timer. + function cancelSpeechBubbleTimer() { + if (speechBubbleTimer) { + Script.clearTimeout(speechBubbleTimer); + speechBubbleTimer = null; + } + } + + // Show the tablet web page and connect the web handler. + function showTabletWebPage() { + var url = Script.resolvePath(webPageURL); + if (randomizeWebPageURL) { + url += '?rand=' + Math.random(); + } + lastWebPageURL = url; + onChatPage = true; + tablet.gotoWebScreen(lastWebPageURL); + // Connect immediately so we don't miss anything. + connectWebHandler(); + } + + // Update the tablet web page with the chat log. + function updateChatPage() { + if (!onChatPage) { + return; + } + + tablet.emitScriptEvent( + JSON.stringify({ + type: "Update", + chatLog: chatLog + })); + } + + function onChatMessageReceived(channel, message, senderID) { + + // Ignore messages to any other channel than mine. + if (channel != channelName) { + return; + } + + // Parse the message and pull out the message parameters. + var messageData = JSON.parse(message); + var messageType = messageData.type; + + //print("MESSAGE", message); + //print("MESSAGEDATA", messageData, JSON.stringify(messageData)); + + switch (messageType) { + + case 'TransmitChatMessage': + handleTransmitChatMessage(messageData.avatarID, messageData.displayName, messageData.message, messageData.data); + break; + + case 'AvatarBeginTyping': + handleAvatarBeginTyping(messageData.avatarID, messageData.displayName); + break; + + case 'AvatarEndTyping': + handleAvatarEndTyping(messageData.avatarID, messageData.displayName); + break; + + case 'Who': + handleWho(messageData.myAvatarID); + break; + + case 'ReplyWho': + handleReplyWho(messageData.myAvatarID, messageData.avatarID, messageData.displayName, messageData.message, messageData.data); + break; + + default: + print("onChatMessageReceived: unknown messageType", messageType, "message", message); + break; + + } + + } + + // Handle events from the tablet web page. + function onWebEventReceived(event) { + if (!onChatPage) { + return; + } + + //print("onWebEventReceived: event", event); + + var eventData = JSON.parse(event); + var eventType = eventData.type; + + switch (eventType) { + + case 'Ready': + updateChatPage(); + break; + + case 'Update': + updateChatPage(); + break; + + case 'HandleChatMessage': + var message = eventData.message; + var data = eventData.data; + //print("onWebEventReceived: HandleChatMessage:", 'message', message, 'data', data); + handleChatMessage(message, data); + break; + + case 'PopDownSpeechBubble': + popDownSpeechBubble(); + break; + + case 'EmptyChatMessage': + emptyChatMessage(); + break; + + case 'Type': + type(); + break; + + case 'BeginTyping': + beginTyping(); + break; + + case 'EndTyping': + endTyping(); + break; + + case 'IdentifyAvatar': + identifyAvatar(eventData.avatarID); + break; + + case 'UnidentifyAvatar': + unidentifyAvatar(eventData.avatarID); + break; + + case 'FaceAvatar': + faceAvatar(eventData.avatarID, eventData.displayName); + break; + + case 'ClearChatLog': + clearChatLog(); + break; + + case 'Who': + transmitWho(); + break; + + case 'Bigger': + biggerSize(); + break; + + case 'Smaller': + smallerSize(); + break; + + case 'Normal': + normalSize(); + break; + + default: + print("onWebEventReceived: unexpected eventType", eventType); + break; + + } + } + + function onScreenChanged(type, url) { + //print("onScreenChanged", "type", type, "url", url, "lastWebPageURL", lastWebPageURL); + + if ((type === "Web") && + (url === lastWebPageURL)) { + if (!onChatPage) { + onChatPage = true; + connectWebHandler(); + } + } else { + if (onChatPage) { + onChatPage = false; + disconnectWebHandler(); + } + } + + } + + function connectWebHandler() { + if (webHandlerConnected) { + return; + } + + try { + tablet.webEventReceived.connect(onWebEventReceived); + } catch (e) { + print("connectWebHandler: error connecting: " + e); + return; + } + + webHandlerConnected = true; + //print("connectWebHandler connected"); + + updateChatPage(); + } + + function disconnectWebHandler() { + if (!webHandlerConnected) { + return; + } + + try { + tablet.webEventReceived.disconnect(onWebEventReceived); + } catch (e) { + print("disconnectWebHandler: error disconnecting web handler: " + e); + return; + } + webHandlerConnected = false; + + //print("disconnectWebHandler: disconnected"); + } + + // Show the tablet web page when the chat button on the tablet is clicked. + function onTabletButtonClicked() { + showTabletWebPage(); + } + + // Shut down the chat application when the tablet button is destroyed. + function onTabletButtonDestroyed() { + shutDown(); + } + + // Start up the chat application. + function startUp() { + //print("startUp"); + + loadSettings(); + + tabletButton = tablet.addButton({ + icon: tabletButtonIcon, + activeIcon: tabletButtonActiveIcon, + text: tabletButtonName + }); + + Messages.subscribe(channelName); + + tablet.screenChanged.connect(onScreenChanged); + + Messages.messageReceived.connect(onChatMessageReceived); + + tabletButton.clicked.connect(onTabletButtonClicked); + + Script.scriptEnding.connect(onTabletButtonDestroyed); + + logMessage('Type "/?" or "/help" for help with chat.', null); + + //print("Added chat button to tablet."); + } + + // Shut down the chat application. + function shutDown() { + //print("shutDown"); + + popDownSpeechBubble(); + unidentifyAvatars(); + disconnectWebHandler(); + + Overlays.deleteOverlay(textSizeOverlay); + + if (onChatPage) { + tablet.gotoHomeScreen(); + onChatPage = false; + } + + tablet.screenChanged.disconnect(onScreenChanged); + + Messages.messageReceived.disconnect(onChatMessageReceived); + + // Clean up the tablet button we made. + tabletButton.clicked.disconnect(onTabletButtonClicked); + tablet.removeButton(tabletButton); + tabletButton = null; + + //print("Removed chat button from tablet."); + } + + // Kick off the chat application! + startUp(); + +}()); diff --git a/scripts/simplifiedUI/system/clickToAvatarApp.js b/scripts/simplifiedUI/system/clickToAvatarApp.js new file mode 100644 index 0000000000..8024f595b5 --- /dev/null +++ b/scripts/simplifiedUI/system/clickToAvatarApp.js @@ -0,0 +1,7 @@ +(function () { + var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + this.clickDownOnEntity = function (entityID, mouseEvent) { + tablet.loadQMLSource("hifi/AvatarApp.qml"); + }; +} +); diff --git a/scripts/simplifiedUI/system/commerce/wallet.js b/scripts/simplifiedUI/system/commerce/wallet.js new file mode 100644 index 0000000000..86806fd8b4 --- /dev/null +++ b/scripts/simplifiedUI/system/commerce/wallet.js @@ -0,0 +1,737 @@ +"use strict"; +/* jslint vars:true, plusplus:true, forin:true */ +/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */ +// +// wallet.js +// +// Created by Zach Fox on 2017-08-17 +// 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 +// + +/* global getConnectionData getControllerWorldLocation openLoginWindow WalletScriptingInterface */ + +(function () { // BEGIN LOCAL_SCOPE +Script.include("/~/system/libraries/accountUtils.js"); +Script.include("/~/system/libraries/connectionUtils.js"); +var AppUi = Script.require('appUi'); + +var MARKETPLACE_URL = Account.metaverseServerURL + "/marketplace"; + +// BEGIN AVATAR SELECTOR LOGIC +var UNSELECTED_COLOR = { red: 0x1F, green: 0xC6, blue: 0xA6 }; +var SELECTED_COLOR = { red: 0xF3, green: 0x91, blue: 0x29 }; +var HOVER_COLOR = { red: 0xD0, green: 0xD0, blue: 0xD0 }; + +var overlays = {}; // Keeps track of all our extended overlay data objects, keyed by target identifier. + +function ExtendedOverlay(key, type, properties) { // A wrapper around overlays to store the key it is associated with. + overlays[key] = this; + this.key = key; + this.selected = false; + this.hovering = false; + this.activeOverlay = Overlays.addOverlay(type, properties); // We could use different overlays for (un)selected... +} +// Instance methods: +ExtendedOverlay.prototype.deleteOverlay = function () { // remove display and data of this overlay + Overlays.deleteOverlay(this.activeOverlay); + delete overlays[this.key]; +}; + +ExtendedOverlay.prototype.editOverlay = function (properties) { // change display of this overlay + Overlays.editOverlay(this.activeOverlay, properties); +}; + +function color(selected, hovering) { + var base = hovering ? HOVER_COLOR : selected ? SELECTED_COLOR : UNSELECTED_COLOR; + function scale(component) { + return component; + } + return { red: scale(base.red), green: scale(base.green), blue: scale(base.blue) }; +} +// so we don't have to traverse the overlays to get the last one +var lastHoveringId = 0; +ExtendedOverlay.prototype.hover = function (hovering) { + this.hovering = hovering; + if (this.key === lastHoveringId) { + if (hovering) { + return; + } + lastHoveringId = 0; + } + this.editOverlay({ color: color(this.selected, hovering) }); + if (hovering) { + // un-hover the last hovering overlay + if (lastHoveringId && lastHoveringId !== this.key) { + ExtendedOverlay.get(lastHoveringId).hover(false); + } + lastHoveringId = this.key; + } +}; +ExtendedOverlay.prototype.select = function (selected) { + if (this.selected === selected) { + return; + } + + this.editOverlay({ color: color(selected, this.hovering) }); + this.selected = selected; +}; +// Class methods: +var selectedId = false; +ExtendedOverlay.isSelected = function (id) { + return selectedId === id; +}; +ExtendedOverlay.get = function (key) { // answer the extended overlay data object associated with the given avatar identifier + return overlays[key]; +}; +ExtendedOverlay.some = function (iterator) { // Bails early as soon as iterator returns truthy. + var key; + for (key in overlays) { + if (iterator(ExtendedOverlay.get(key))) { + return; + } + } +}; +ExtendedOverlay.unHover = function () { // calls hover(false) on lastHoveringId (if any) + if (lastHoveringId) { + ExtendedOverlay.get(lastHoveringId).hover(false); + } +}; + +// hit(overlay) on the one overlay intersected by pickRay, if any. +// noHit() if no ExtendedOverlay was intersected (helps with hover) +ExtendedOverlay.applyPickRay = function (pickRay, hit, noHit) { + // Depends on nearer coverOverlays to extend closer to us than farther ones. + var pickedOverlay = Overlays.findRayIntersection(pickRay); + if (!pickedOverlay.intersects) { + if (noHit) { + return noHit(); + } + return; + } + ExtendedOverlay.some(function (overlay) { // See if pickedOverlay is one of ours. + if ((overlay.activeOverlay) === pickedOverlay.overlayID) { + hit(overlay); + return true; + } + }); +}; + +function addAvatarNode(id) { + return new ExtendedOverlay(id, "sphere", { + drawInFront: true, + solid: true, + alpha: 0.8, + color: color(false, false), + ignoreRayIntersection: false + }); +} + +var pingPong = true; +var OVERLAY_SCALE = 0.032; +function updateOverlays() { + var eye = Camera.position; + AvatarList.getAvatarIdentifiers().forEach(function (id) { + if (!id) { + return; // don't update ourself, or avatars we're not interested in + } + var avatar = AvatarList.getAvatar(id); + if (!avatar) { + return; // will be deleted below if there had been an overlay. + } + var overlay = ExtendedOverlay.get(id); + if (!overlay) { // For now, we're treating this as a temporary loss, as from the personal space bubble. Add it back. + overlay = addAvatarNode(id); + } + var target = avatar.position; + var distance = Vec3.distance(target, eye); + var offset = 0.2; + // get diff between target and eye (a vector pointing to the eye from avatar position) + var diff = Vec3.subtract(target, eye); + var headIndex = avatar.getJointIndex("Head"); // base offset on 1/2 distance from hips to head if we can + if (headIndex > 0) { + offset = avatar.getAbsoluteJointTranslationInObjectFrame(headIndex).y / 2; + } + + // move a bit in front, towards the camera + target = Vec3.subtract(target, Vec3.multiply(Vec3.normalize(diff), offset)); + + // now bump it up a bit + target.y = target.y + offset; + + overlay.ping = pingPong; + overlay.editOverlay({ + color: color(ExtendedOverlay.isSelected(id), overlay.hovering), + position: target, + dimensions: OVERLAY_SCALE * distance + }); + }); + pingPong = !pingPong; + ExtendedOverlay.some(function (overlay) { // Remove any that weren't updated. (User is gone.) + if (overlay.ping === pingPong) { + overlay.deleteOverlay(); + } + }); +} +function removeOverlays() { + selectedId = false; + lastHoveringId = 0; + ExtendedOverlay.some(function (overlay) { + overlay.deleteOverlay(); + }); +} + +// +// Clicks. +// +function usernameFromIDReply(id, username, machineFingerprint, isAdmin) { + if (selectedId === id) { + var message = { + method: 'updateSelectedRecipientUsername', + userName: username === "" ? "unknown username" : username + }; + ui.sendMessage(message); + } +} +function handleClick(pickRay) { + ExtendedOverlay.applyPickRay(pickRay, function (overlay) { + var nextSelectedStatus = !overlay.selected; + var avatarId = overlay.key; + selectedId = nextSelectedStatus ? avatarId : false; + if (nextSelectedStatus) { + Users.requestUsernameFromID(avatarId); + } + var message = { + method: 'selectRecipient', + id: avatarId, + isSelected: nextSelectedStatus, + displayName: '"' + AvatarList.getAvatar(avatarId).sessionDisplayName + '"', + userName: '' + }; + ui.sendMessage(message); + + ExtendedOverlay.some(function (overlay) { + var id = overlay.key; + var selected = ExtendedOverlay.isSelected(id); + overlay.select(selected); + }); + + return true; + }); +} +function handleMouseEvent(mousePressEvent) { // handleClick if we get one. + if (!mousePressEvent.isLeftButton) { + return; + } + handleClick(Camera.computePickRay(mousePressEvent.x, mousePressEvent.y)); +} +function handleMouseMove(pickRay) { // given the pickRay, just do the hover logic + ExtendedOverlay.applyPickRay(pickRay, function (overlay) { + overlay.hover(true); + }, function () { + ExtendedOverlay.unHover(); + }); +} + +// handy global to keep track of which hand is the mouse (if any) +var currentHandPressed = 0; +var TRIGGER_CLICK_THRESHOLD = 0.85; +var TRIGGER_PRESS_THRESHOLD = 0.05; + +function handleMouseMoveEvent(event) { // find out which overlay (if any) is over the mouse position + var pickRay; + if (HMD.active) { + if (currentHandPressed !== 0) { + pickRay = controllerComputePickRay(currentHandPressed); + } else { + // nothing should hover, so + ExtendedOverlay.unHover(); + return; + } + } else { + pickRay = Camera.computePickRay(event.x, event.y); + } + handleMouseMove(pickRay); +} +function handleTriggerPressed(hand, value) { + // The idea is if you press one trigger, it is the one + // we will consider the mouse. Even if the other is pressed, + // we ignore it until this one is no longer pressed. + var isPressed = value > TRIGGER_PRESS_THRESHOLD; + if (currentHandPressed === 0) { + currentHandPressed = isPressed ? hand : 0; + return; + } + if (currentHandPressed === hand) { + currentHandPressed = isPressed ? hand : 0; + return; + } + // otherwise, the other hand is still triggered + // so do nothing. +} + +// We get mouseMoveEvents from the handControllers, via handControllerPointer. +// But we don't get mousePressEvents. +var triggerMapping = Controller.newMapping(Script.resolvePath('') + '-click'); +var triggerPressMapping = Controller.newMapping(Script.resolvePath('') + '-press'); +function controllerComputePickRay(hand) { + var controllerPose = getControllerWorldLocation(hand, true); + if (controllerPose.valid) { + return { origin: controllerPose.position, direction: Quat.getUp(controllerPose.orientation) }; + } +} +function makeClickHandler(hand) { + return function (clicked) { + if (clicked > TRIGGER_CLICK_THRESHOLD) { + var pickRay = controllerComputePickRay(hand); + handleClick(pickRay); + } + }; +} +function makePressHandler(hand) { + return function (value) { + handleTriggerPressed(hand, value); + }; +} +triggerMapping.from(Controller.Standard.RTClick).peek().to(makeClickHandler(Controller.Standard.RightHand)); +triggerMapping.from(Controller.Standard.LTClick).peek().to(makeClickHandler(Controller.Standard.LeftHand)); +triggerPressMapping.from(Controller.Standard.RT).peek().to(makePressHandler(Controller.Standard.RightHand)); +triggerPressMapping.from(Controller.Standard.LT).peek().to(makePressHandler(Controller.Standard.LeftHand)); +// END AVATAR SELECTOR LOGIC + +var sendMoneyRecipient; +var sendMoneyParticleEffectUpdateTimer; +var particleEffectTimestamp; +var sendMoneyParticleEffect; +var SEND_MONEY_PARTICLE_TIMER_UPDATE = 250; +var SEND_MONEY_PARTICLE_EMITTING_DURATION = 3000; +var SEND_MONEY_PARTICLE_LIFETIME_SECONDS = 8; +var SEND_MONEY_PARTICLE_PROPERTIES = { + accelerationSpread: { x: 0, y: 0, z: 0 }, + alpha: 1, + alphaFinish: 1, + alphaSpread: 0, + alphaStart: 1, + azimuthFinish: 0, + azimuthStart: -6, + color: { red: 143, green: 5, blue: 255 }, + colorFinish: { red: 255, green: 0, blue: 204 }, + colorSpread: { red: 0, green: 0, blue: 0 }, + colorStart: { red: 0, green: 136, blue: 255 }, + emitAcceleration: { x: 0, y: 0, z: 0 }, // Immediately gets updated to be accurate + emitDimensions: { x: 0, y: 0, z: 0 }, + emitOrientation: { x: 0, y: 0, z: 0 }, + emitRate: 4, + emitSpeed: 2.1, + emitterShouldTrail: true, + isEmitting: 1, + lifespan: SEND_MONEY_PARTICLE_LIFETIME_SECONDS + 1, // Immediately gets updated to be accurate + lifetime: SEND_MONEY_PARTICLE_LIFETIME_SECONDS + 1, + maxParticles: 20, + name: 'hfc-particles', + particleRadius: 0.2, + polarFinish: 0, + polarStart: 0, + radiusFinish: 0.05, + radiusSpread: 0, + radiusStart: 0.2, + speedSpread: 0, + textures: "http://hifi-content.s3.amazonaws.com/alan/dev/Particles/Bokeh-Particle-HFC.png", + type: 'ParticleEffect' +}; + +var MS_PER_SEC = 1000; +function updateSendMoneyParticleEffect() { + var timestampNow = Date.now(); + if ((timestampNow - particleEffectTimestamp) > (SEND_MONEY_PARTICLE_LIFETIME_SECONDS * MS_PER_SEC)) { + deleteSendMoneyParticleEffect(); + return; + } else if ((timestampNow - particleEffectTimestamp) > SEND_MONEY_PARTICLE_EMITTING_DURATION) { + Entities.editEntity(sendMoneyParticleEffect, { + isEmitting: 0 + }); + } else if (sendMoneyParticleEffect) { + var recipientPosition = AvatarList.getAvatar(sendMoneyRecipient).position; + var distance = Vec3.distance(recipientPosition, MyAvatar.position); + var accel = Vec3.subtract(recipientPosition, MyAvatar.position); + accel.y -= 3.0; + var life = Math.sqrt(2 * distance / Vec3.length(accel)); + Entities.editEntity(sendMoneyParticleEffect, { + emitAcceleration: accel, + lifespan: life + }); + } +} + +function deleteSendMoneyParticleEffect() { + if (sendMoneyParticleEffectUpdateTimer) { + Script.clearInterval(sendMoneyParticleEffectUpdateTimer); + sendMoneyParticleEffectUpdateTimer = null; + } + if (sendMoneyParticleEffect) { + sendMoneyParticleEffect = Entities.deleteEntity(sendMoneyParticleEffect); + } + sendMoneyRecipient = null; +} + +function onUsernameChanged() { + if (ui.checkIsOpen()) { + ui.open(WALLET_QML_SOURCE); + } +} + +var MARKETPLACE_QML_PATH = "hifi/commerce/marketplace/Marketplace.qml"; +function openMarketplace(optionalItem) { + ui.open(MARKETPLACE_QML_PATH); + + if (optionalItem) { + ui.tablet.sendToQml({ + method: 'updateMarketplaceQMLItem', + params: { itemId: optionalItem } + }); + } +} + +function setCertificateInfo(itemCertificateId) { + ui.tablet.sendToQml({ + method: 'inspectionCertificate_setCertificateId', + entityId: "", + certificateId: itemCertificateId + }); +} + +// Function Name: fromQml() +// +// Description: +// -Called when a message is received from SpectatorCamera.qml. The "message" argument is what is sent from the QML +// in the format "{method, params}", like json-rpc. See also sendToQml(). +function fromQml(message) { + switch (message.method) { + case 'passphrasePopup_cancelClicked': + case 'needsLogIn_cancelClicked': + ui.close(); + break; + case 'walletSetup_cancelClicked': + switch (message.referrer) { + case '': // User clicked "Wallet" app + case undefined: + case null: + ui.close(); + break; + case 'purchases': + case 'marketplace cta': + case 'mainPage': + openMarketplace(); + break; + default: + openMarketplace(); + break; + } + break; + case 'needsLogIn_loginClicked': + openLoginWindow(); + break; + case 'disableHmdPreview': + break; // do nothing here, handled in marketplaces.js + case 'maybeEnableHmdPreview': + break; // do nothing here, handled in marketplaces.js + case 'transactionHistory_linkClicked': + openMarketplace(message.itemId); + break; + case 'goToMarketplaceMainPage': + openMarketplace(); + break; + case 'goToMarketplaceItemPage': + openMarketplace(message.itemId); + break; + case 'refreshConnections': + print('Refreshing Connections...'); + getConnectionData(false); + break; + case 'enable_ChooseRecipientNearbyMode': + if (!isUpdateOverlaysWired) { + Script.update.connect(updateOverlays); + isUpdateOverlaysWired = true; + } + break; + case 'disable_ChooseRecipientNearbyMode': + if (isUpdateOverlaysWired) { + Script.update.disconnect(updateOverlays); + isUpdateOverlaysWired = false; + } + removeOverlays(); + break; + case 'sendAsset_sendPublicly': + deleteSendMoneyParticleEffect(); + sendMoneyRecipient = message.recipient; + var props = SEND_MONEY_PARTICLE_PROPERTIES; + props.parentID = MyAvatar.sessionUUID; + props.position = MyAvatar.position; + props.position.y += 0.2; + if (message.effectImage) { + props.textures = message.effectImage; + } + sendMoneyParticleEffect = Entities.addEntity(props, true); + particleEffectTimestamp = Date.now(); + updateSendMoneyParticleEffect(); + sendMoneyParticleEffectUpdateTimer = + Script.setInterval(updateSendMoneyParticleEffect, SEND_MONEY_PARTICLE_TIMER_UPDATE); + break; + case 'transactionHistory_goToBank': + if (Account.metaverseServerURL.indexOf("staging") >= 0) { + Window.location = "hifi://hifiqa-master-metaverse-staging"; // So that we can test in staging. + } else { + Window.location = "hifi://BankOfHighFidelity"; + } + break; + case 'purchases_updateWearables': + var currentlyWornWearables = []; + var ATTACHMENT_SEARCH_RADIUS = 100; // meters (just in case) + + var nearbyEntities = Entities.findEntitiesByType('Model', MyAvatar.position, ATTACHMENT_SEARCH_RADIUS); + + for (var i = 0; i < nearbyEntities.length; i++) { + var currentProperties = Entities.getEntityProperties( + nearbyEntities[i], ['certificateID', 'editionNumber', 'parentID'] + ); + if (currentProperties.parentID === MyAvatar.sessionUUID) { + currentlyWornWearables.push({ + entityID: nearbyEntities[i], + entityCertID: currentProperties.certificateID, + entityEdition: currentProperties.editionNumber + }); + } + } + + ui.tablet.sendToQml({ method: 'updateWearables', wornWearables: currentlyWornWearables }); + break; + case 'purchases_walletNotSetUp': + ui.tablet.sendToQml({ + method: 'updateWalletReferrer', + referrer: "purchases" + }); + break; + case 'purchases_openGoTo': + ui.open("hifi/tablet/TabletAddressDialog.qml"); + break; + case 'purchases_itemInfoClicked': + var itemId = message.itemId; + if (itemId && itemId !== "") { + openMarketplace(itemId); + } + break; + case 'purchases_itemCertificateClicked': + setCertificateInfo(message.itemCertificateId); + break; + case 'clearShouldShowDotHistory': + shouldShowDotHistory = false; + ui.messagesWaiting(shouldShowDotUpdates || shouldShowDotHistory); + break; + case 'clearShouldShowDotUpdates': + shouldShowDotUpdates = false; + ui.messagesWaiting(shouldShowDotUpdates || shouldShowDotHistory); + break; + case 'http.request': + // Handled elsewhere, don't log. + break; + case 'closeSendAsset': + ui.close(); + break; + default: + print('wallet.js: Unrecognized message from QML'); + } +} + +var isWired = false; +function walletOpened() { + Users.usernameFromIDReply.connect(usernameFromIDReply); + Controller.mousePressEvent.connect(handleMouseEvent); + Controller.mouseMoveEvent.connect(handleMouseMoveEvent); + triggerMapping.enable(); + triggerPressMapping.enable(); + isWired = true; + + if (shouldShowDotHistory) { + ui.sendMessage({ + method: 'updateRecentActivityMessageLight', + messagesWaiting: shouldShowDotHistory + }); + } +} + +function walletClosed() { + off(); +} + +function notificationDataProcessPageUpdates(data) { + return data.data.updates; +} + +function notificationDataProcessPageHistory(data) { + return data.data.history; +} + +var shouldShowDotUpdates = false; +function notificationPollCallbackUpdates(updatesArray) { + shouldShowDotUpdates = updatesArray.length > 0; + ui.messagesWaiting(shouldShowDotUpdates || shouldShowDotHistory); + + if (updatesArray.length > 0) { + var message; + if (!ui.notificationInitialCallbackMade[0]) { + message = updatesArray.length + " of your purchased items " + + (updatesArray.length === 1 ? "has an update " : "have updates ") + + "available. Open INVENTORY to update."; + ui.notificationDisplayBanner(message); + + ui.notificationPollCaresAboutSince[0] = true; + } else { + for (var i = 0; i < updatesArray.length; i++) { + message = "Update available for \"" + + updatesArray[i].base_item_title + "\"." + + "Open INVENTORY to update."; + ui.notificationDisplayBanner(message); + } + } + } +} +var shouldShowDotHistory = false; +function notificationPollCallbackHistory(historyArray) { + if (!ui.isOpen) { + var notificationCount = historyArray.length; + shouldShowDotHistory = shouldShowDotHistory || notificationCount > 0; + ui.messagesWaiting(shouldShowDotUpdates || shouldShowDotHistory); + + if (notificationCount > 0) { + var message; + if (!ui.notificationInitialCallbackMade[1]) { + message = "You have " + notificationCount + " unread recent " + + "transaction" + (notificationCount === 1 ? "" : "s") + ". Open INVENTORY to see all activity."; + ui.notificationDisplayBanner(message); + } else { + for (var i = 0; i < notificationCount; i++) { + var historyMessage = historyArray[i].message; + var sanitizedHistoryMessage = historyMessage.replace(/<\/?[^>]+(>|$)/g, ""); + message = '"' + sanitizedHistoryMessage + '" ' + + "Open INVENTORY to see all activity."; + ui.notificationDisplayBanner(message); + } + } + } + } +} + +function isReturnedDataEmptyUpdates(data) { + var updatesArray = data.data.updates; + return updatesArray.length === 0; +} + +function isReturnedDataEmptyHistory(data) { + var historyArray = data.data.history; + return historyArray.length === 0; +} + +var DEVELOPER_MENU = "Developer"; +var MARKETPLACE_ITEM_TESTER_LABEL = "Marketplace Item Tester"; +var MARKETPLACE_ITEM_TESTER_QML_SOURCE = "hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml"; +function installMarketplaceItemTester() { + if (!Menu.menuExists(DEVELOPER_MENU)) { + Menu.addMenu(DEVELOPER_MENU); + } + if (!Menu.menuItemExists(DEVELOPER_MENU, MARKETPLACE_ITEM_TESTER_LABEL)) { + Menu.addMenuItem({ + menuName: DEVELOPER_MENU, + menuItemName: MARKETPLACE_ITEM_TESTER_LABEL, + isCheckable: false + }); + } + + Menu.menuItemEvent.connect(function (menuItem) { + if (menuItem === MARKETPLACE_ITEM_TESTER_LABEL) { + ui.open(MARKETPLACE_ITEM_TESTER_QML_SOURCE); + } + }); +} + +function uninstallMarketplaceItemTester() { + if (Menu.menuExists(DEVELOPER_MENU) && + Menu.menuItemExists(DEVELOPER_MENU, MARKETPLACE_ITEM_TESTER_LABEL) + ) { + Menu.removeMenuItem(DEVELOPER_MENU, MARKETPLACE_ITEM_TESTER_LABEL); + } +} + +var BUTTON_NAME = "INVENTORY"; +var WALLET_QML_SOURCE = "hifi/commerce/wallet/Wallet.qml"; +var SENDASSET_QML_SOURCE = "hifi/commerce/common/sendAsset/SendAsset.qml"; +var NOTIFICATION_POLL_TIMEOUT = 300000; +var ui; +function startup() { + var notificationPollEndpointArray = ["/api/v1/commerce/available_updates?per_page=10"]; + var notificationPollTimeoutMsArray = [NOTIFICATION_POLL_TIMEOUT]; + var notificationDataProcessPageArray = [notificationDataProcessPageUpdates]; + var notificationPollCallbackArray = [notificationPollCallbackUpdates]; + var notificationPollStopPaginatingConditionMetArray = [isReturnedDataEmptyUpdates]; + var notificationPollCaresAboutSinceArray = [false]; + + if (!WalletScriptingInterface.limitedCommerce) { + notificationPollEndpointArray[1] = "/api/v1/commerce/history?per_page=10"; + notificationPollTimeoutMsArray[1] = NOTIFICATION_POLL_TIMEOUT; + notificationDataProcessPageArray[1] = notificationDataProcessPageHistory; + notificationPollCallbackArray[1] = notificationPollCallbackHistory; + notificationPollStopPaginatingConditionMetArray[1] = isReturnedDataEmptyHistory; + notificationPollCaresAboutSinceArray[1] = true; + } + + ui = new AppUi({ + buttonName: BUTTON_NAME, + sortOrder: 10, + home: WALLET_QML_SOURCE, + additionalAppScreens: SENDASSET_QML_SOURCE, + onOpened: walletOpened, + onClosed: walletClosed, + onMessage: fromQml, + notificationPollEndpoint: notificationPollEndpointArray, + notificationPollTimeoutMs: notificationPollTimeoutMsArray, + notificationDataProcessPage: notificationDataProcessPageArray, + notificationPollCallback: notificationPollCallbackArray, + notificationPollStopPaginatingConditionMet: notificationPollStopPaginatingConditionMetArray, + notificationPollCaresAboutSince: notificationPollCaresAboutSinceArray + }); + GlobalServices.myUsernameChanged.connect(onUsernameChanged); + installMarketplaceItemTester(); +} + +var isUpdateOverlaysWired = false; +function off() { + if (isWired) { + Users.usernameFromIDReply.disconnect(usernameFromIDReply); + Controller.mousePressEvent.disconnect(handleMouseEvent); + Controller.mouseMoveEvent.disconnect(handleMouseMoveEvent); + triggerMapping.disable(); + triggerPressMapping.disable(); + isWired = false; + } + + if (isUpdateOverlaysWired) { + Script.update.disconnect(updateOverlays); + isUpdateOverlaysWired = false; + } + removeOverlays(); +} + +function shutdown() { + GlobalServices.myUsernameChanged.disconnect(onUsernameChanged); + deleteSendMoneyParticleEffect(); + uninstallMarketplaceItemTester(); + off(); +} + +// +// Run the functions. +// +startup(); +Script.scriptEnding.connect(shutdown); +}()); // END LOCAL_SCOPE diff --git a/scripts/simplifiedUI/system/controllers/handTouch.js b/scripts/simplifiedUI/system/controllers/handTouch.js index c706d054c1..5939c6e3d2 100644 --- a/scripts/simplifiedUI/system/controllers/handTouch.js +++ b/scripts/simplifiedUI/system/controllers/handTouch.js @@ -17,7 +17,8 @@ (function () { var LEAP_MOTION_NAME = "LeapMotion"; - var handTouchEnabled = true; + // Hand touch is disabled due to twitchy finger bug when walking near walls or tables. see BUGZ-154. + var handTouchEnabled = false; var leapMotionEnabled = Controller.getRunningInputDeviceNames().indexOf(LEAP_MOTION_NAME) >= 0; var MSECONDS_AFTER_LOAD = 2000; var updateFingerWithIndex = 0; diff --git a/interface/resources/qml/hifi/tablet/Edit.qml b/scripts/simplifiedUI/system/create/Edit.qml similarity index 100% rename from interface/resources/qml/hifi/tablet/Edit.qml rename to scripts/simplifiedUI/system/create/Edit.qml diff --git a/interface/resources/qml/hifi/tablet/EditEntityList.qml b/scripts/simplifiedUI/system/create/EditEntityList.qml similarity index 87% rename from interface/resources/qml/hifi/tablet/EditEntityList.qml rename to scripts/simplifiedUI/system/create/EditEntityList.qml index 2afaa8cd82..94935c7bb5 100644 --- a/interface/resources/qml/hifi/tablet/EditEntityList.qml +++ b/scripts/simplifiedUI/system/create/EditEntityList.qml @@ -1,8 +1,8 @@ import QtQuick 2.7 import QtQuick.Controls 2.2 import QtWebChannel 1.0 -import "../../controls" -import "../toolbars" +import controls 1.0 +import hifi.toolbars 1.0 import QtGraphicalEffects 1.0 import controlsUit 1.0 as HifiControls import stylesUit 1.0 diff --git a/interface/resources/qml/hifi/tablet/EditTabButton.qml b/scripts/simplifiedUI/system/create/EditTabButton.qml similarity index 100% rename from interface/resources/qml/hifi/tablet/EditTabButton.qml rename to scripts/simplifiedUI/system/create/EditTabButton.qml diff --git a/interface/resources/qml/hifi/tablet/EditTabView.qml b/scripts/simplifiedUI/system/create/EditTabView.qml similarity index 94% rename from interface/resources/qml/hifi/tablet/EditTabView.qml rename to scripts/simplifiedUI/system/create/EditTabView.qml index 87db317b17..7e8789487c 100644 --- a/interface/resources/qml/hifi/tablet/EditTabView.qml +++ b/scripts/simplifiedUI/system/create/EditTabView.qml @@ -1,8 +1,8 @@ import QtQuick 2.7 import QtQuick.Controls 2.2 import QtWebChannel 1.0 -import "../../controls" -import "../toolbars" +import controls 1.0 +import hifi.toolbars 1.0 import QtGraphicalEffects 1.0 import controlsUit 1.0 as HifiControls import stylesUit 1.0 @@ -72,7 +72,7 @@ TabBar { NewEntityButton { - icon: "icons/create-icons/94-model-01.svg" + icon: "create-icons/94-model-01.svg" text: "MODEL" onClicked: { editRoot.sendToScript({ @@ -84,7 +84,7 @@ TabBar { } NewEntityButton { - icon: "icons/create-icons/21-cube-01.svg" + icon: "create-icons/21-cube-01.svg" text: "SHAPE" onClicked: { editRoot.sendToScript({ @@ -96,7 +96,7 @@ TabBar { } NewEntityButton { - icon: "icons/create-icons/24-light-01.svg" + icon: "create-icons/24-light-01.svg" text: "LIGHT" onClicked: { editRoot.sendToScript({ @@ -108,7 +108,7 @@ TabBar { } NewEntityButton { - icon: "icons/create-icons/20-text-01.svg" + icon: "create-icons/20-text-01.svg" text: "TEXT" onClicked: { editRoot.sendToScript({ @@ -120,7 +120,7 @@ TabBar { } NewEntityButton { - icon: "icons/create-icons/image.svg" + icon: "create-icons/image.svg" text: "IMAGE" onClicked: { editRoot.sendToScript({ @@ -132,7 +132,7 @@ TabBar { } NewEntityButton { - icon: "icons/create-icons/25-web-1-01.svg" + icon: "create-icons/25-web-1-01.svg" text: "WEB" onClicked: { editRoot.sendToScript({ @@ -144,7 +144,7 @@ TabBar { } NewEntityButton { - icon: "icons/create-icons/23-zone-01.svg" + icon: "create-icons/23-zone-01.svg" text: "ZONE" onClicked: { editRoot.sendToScript({ @@ -156,7 +156,7 @@ TabBar { } NewEntityButton { - icon: "icons/create-icons/90-particles-01.svg" + icon: "create-icons/90-particles-01.svg" text: "PARTICLE" onClicked: { editRoot.sendToScript({ @@ -168,7 +168,7 @@ TabBar { } NewEntityButton { - icon: "icons/create-icons/126-material-01.svg" + icon: "create-icons/126-material-01.svg" text: "MATERIAL" onClicked: { editRoot.sendToScript({ diff --git a/interface/resources/qml/hifi/tablet/EditTools.qml b/scripts/simplifiedUI/system/create/EditTools.qml similarity index 95% rename from interface/resources/qml/hifi/tablet/EditTools.qml rename to scripts/simplifiedUI/system/create/EditTools.qml index 976e98cd50..468935b287 100644 --- a/interface/resources/qml/hifi/tablet/EditTools.qml +++ b/scripts/simplifiedUI/system/create/EditTools.qml @@ -37,7 +37,7 @@ StackView { } function pushSource(path) { - var item = Qt.createComponent(Qt.resolvedUrl("../../" + path)); + var item = Qt.createComponent(Qt.resolvedUrl(path)); editRoot.push(item, itemProperties, StackView.Immediate); editRoot.currentItem.sendToScript.connect(editRoot.sendToScript); diff --git a/interface/resources/qml/hifi/tablet/EditToolsTabView.qml b/scripts/simplifiedUI/system/create/EditToolsTabView.qml similarity index 94% rename from interface/resources/qml/hifi/tablet/EditToolsTabView.qml rename to scripts/simplifiedUI/system/create/EditToolsTabView.qml index 1f062815b6..a333acc586 100644 --- a/interface/resources/qml/hifi/tablet/EditToolsTabView.qml +++ b/scripts/simplifiedUI/system/create/EditToolsTabView.qml @@ -1,8 +1,8 @@ import QtQuick 2.7 import QtQuick.Controls 2.2 import QtWebChannel 1.0 -import "../../controls" -import "../toolbars" +import controls 1.0 +import hifi.toolbars 1.0 import QtGraphicalEffects 1.0 import controlsUit 1.0 as HifiControls import stylesUit 1.0 @@ -78,7 +78,7 @@ TabBar { NewEntityButton { - icon: "icons/create-icons/94-model-01.svg" + icon: "create-icons/94-model-01.svg" text: "MODEL" onClicked: { editRoot.sendToScript({ @@ -90,7 +90,7 @@ TabBar { } NewEntityButton { - icon: "icons/create-icons/21-cube-01.svg" + icon: "create-icons/21-cube-01.svg" text: "SHAPE" onClicked: { editRoot.sendToScript({ @@ -102,7 +102,7 @@ TabBar { } NewEntityButton { - icon: "icons/create-icons/24-light-01.svg" + icon: "create-icons/24-light-01.svg" text: "LIGHT" onClicked: { editRoot.sendToScript({ @@ -114,7 +114,7 @@ TabBar { } NewEntityButton { - icon: "icons/create-icons/20-text-01.svg" + icon: "create-icons/20-text-01.svg" text: "TEXT" onClicked: { editRoot.sendToScript({ @@ -126,7 +126,7 @@ TabBar { } NewEntityButton { - icon: "icons/create-icons/image.svg" + icon: "create-icons/image.svg" text: "IMAGE" onClicked: { editRoot.sendToScript({ @@ -138,7 +138,7 @@ TabBar { } NewEntityButton { - icon: "icons/create-icons/25-web-1-01.svg" + icon: "create-icons/25-web-1-01.svg" text: "WEB" onClicked: { editRoot.sendToScript({ @@ -150,7 +150,7 @@ TabBar { } NewEntityButton { - icon: "icons/create-icons/23-zone-01.svg" + icon: "create-icons/23-zone-01.svg" text: "ZONE" onClicked: { editRoot.sendToScript({ @@ -162,7 +162,7 @@ TabBar { } NewEntityButton { - icon: "icons/create-icons/90-particles-01.svg" + icon: "create-icons/90-particles-01.svg" text: "PARTICLE" onClicked: { editRoot.sendToScript({ @@ -174,7 +174,7 @@ TabBar { } NewEntityButton { - icon: "icons/create-icons/126-material-01.svg" + icon: "create-icons/126-material-01.svg" text: "MATERIAL" onClicked: { editRoot.sendToScript({ diff --git a/interface/resources/qml/hifi/tablet/EntityList.qml b/scripts/simplifiedUI/system/create/EntityList.qml similarity index 100% rename from interface/resources/qml/hifi/tablet/EntityList.qml rename to scripts/simplifiedUI/system/create/EntityList.qml diff --git a/interface/resources/qml/hifi/tablet/NewEntityButton.qml b/scripts/simplifiedUI/system/create/NewEntityButton.qml similarity index 92% rename from interface/resources/qml/hifi/tablet/NewEntityButton.qml rename to scripts/simplifiedUI/system/create/NewEntityButton.qml index 1952ef7ee8..9c210ac95a 100644 --- a/interface/resources/qml/hifi/tablet/NewEntityButton.qml +++ b/scripts/simplifiedUI/system/create/NewEntityButton.qml @@ -6,7 +6,7 @@ Item { id: newEntityButton property var uuid; property string text: "ENTITY" - property string icon: "icons/edit-icon.svg" + property string icon: Path.resources + "icons/edit-icon.svg" property string activeText: newEntityButton.text property string activeIcon: newEntityButton.icon property bool isActive: false @@ -47,14 +47,6 @@ Item { anchors.topMargin: 0 } - function urlHelper(src) { - if (src.match(/\bhttp/)) { - return src; - } else { - return "../../../" + src; - } - } - Rectangle { id: buttonOutline color: "#00000000" @@ -96,7 +88,7 @@ Item { anchors.bottomMargin: 5 anchors.horizontalCenter: parent.horizontalCenter fillMode: Image.Stretch - source: newEntityButton.urlHelper(newEntityButton.icon) + source: newEntityButton.icon } ColorOverlay { diff --git a/interface/resources/qml/hifi/tablet/NewMaterialDialog.qml b/scripts/simplifiedUI/system/create/NewMaterialDialog.qml similarity index 99% rename from interface/resources/qml/hifi/tablet/NewMaterialDialog.qml rename to scripts/simplifiedUI/system/create/NewMaterialDialog.qml index dde372648b..75570327e0 100644 --- a/interface/resources/qml/hifi/tablet/NewMaterialDialog.qml +++ b/scripts/simplifiedUI/system/create/NewMaterialDialog.qml @@ -15,7 +15,7 @@ import QtQuick.Dialogs 1.2 as OriginalDialogs import stylesUit 1.0 import controlsUit 1.0 -import "../dialogs" +import dialogs 1.0 Rectangle { id: newMaterialDialog diff --git a/interface/resources/qml/hifi/tablet/NewMaterialWindow.qml b/scripts/simplifiedUI/system/create/NewMaterialWindow.qml similarity index 100% rename from interface/resources/qml/hifi/tablet/NewMaterialWindow.qml rename to scripts/simplifiedUI/system/create/NewMaterialWindow.qml diff --git a/interface/resources/qml/hifi/tablet/NewModelDialog.qml b/scripts/simplifiedUI/system/create/NewModelDialog.qml similarity index 99% rename from interface/resources/qml/hifi/tablet/NewModelDialog.qml rename to scripts/simplifiedUI/system/create/NewModelDialog.qml index 9540979479..1ded00d701 100644 --- a/interface/resources/qml/hifi/tablet/NewModelDialog.qml +++ b/scripts/simplifiedUI/system/create/NewModelDialog.qml @@ -14,7 +14,7 @@ import QtQuick.Dialogs 1.2 as OriginalDialogs import stylesUit 1.0 import controlsUit 1.0 -import "../dialogs" +import dialogs 1.0 Rectangle { id: newModelDialog diff --git a/interface/resources/qml/hifi/tablet/NewModelWindow.qml b/scripts/simplifiedUI/system/create/NewModelWindow.qml similarity index 100% rename from interface/resources/qml/hifi/tablet/NewModelWindow.qml rename to scripts/simplifiedUI/system/create/NewModelWindow.qml diff --git a/interface/resources/icons/create-icons/126-material-01.svg b/scripts/simplifiedUI/system/create/create-icons/126-material-01.svg similarity index 100% rename from interface/resources/icons/create-icons/126-material-01.svg rename to scripts/simplifiedUI/system/create/create-icons/126-material-01.svg diff --git a/interface/resources/icons/create-icons/20-text-01.svg b/scripts/simplifiedUI/system/create/create-icons/20-text-01.svg similarity index 100% rename from interface/resources/icons/create-icons/20-text-01.svg rename to scripts/simplifiedUI/system/create/create-icons/20-text-01.svg diff --git a/interface/resources/icons/create-icons/21-cube-01.svg b/scripts/simplifiedUI/system/create/create-icons/21-cube-01.svg similarity index 100% rename from interface/resources/icons/create-icons/21-cube-01.svg rename to scripts/simplifiedUI/system/create/create-icons/21-cube-01.svg diff --git a/interface/resources/icons/create-icons/22-sphere-01.svg b/scripts/simplifiedUI/system/create/create-icons/22-sphere-01.svg similarity index 100% rename from interface/resources/icons/create-icons/22-sphere-01.svg rename to scripts/simplifiedUI/system/create/create-icons/22-sphere-01.svg diff --git a/interface/resources/icons/create-icons/23-zone-01.svg b/scripts/simplifiedUI/system/create/create-icons/23-zone-01.svg similarity index 100% rename from interface/resources/icons/create-icons/23-zone-01.svg rename to scripts/simplifiedUI/system/create/create-icons/23-zone-01.svg diff --git a/interface/resources/icons/create-icons/24-light-01.svg b/scripts/simplifiedUI/system/create/create-icons/24-light-01.svg similarity index 100% rename from interface/resources/icons/create-icons/24-light-01.svg rename to scripts/simplifiedUI/system/create/create-icons/24-light-01.svg diff --git a/interface/resources/icons/create-icons/25-web-1-01.svg b/scripts/simplifiedUI/system/create/create-icons/25-web-1-01.svg similarity index 100% rename from interface/resources/icons/create-icons/25-web-1-01.svg rename to scripts/simplifiedUI/system/create/create-icons/25-web-1-01.svg diff --git a/interface/resources/icons/create-icons/90-particles-01.svg b/scripts/simplifiedUI/system/create/create-icons/90-particles-01.svg similarity index 100% rename from interface/resources/icons/create-icons/90-particles-01.svg rename to scripts/simplifiedUI/system/create/create-icons/90-particles-01.svg diff --git a/interface/resources/icons/create-icons/94-model-01.svg b/scripts/simplifiedUI/system/create/create-icons/94-model-01.svg similarity index 100% rename from interface/resources/icons/create-icons/94-model-01.svg rename to scripts/simplifiedUI/system/create/create-icons/94-model-01.svg diff --git a/interface/resources/icons/create-icons/image.svg b/scripts/simplifiedUI/system/create/create-icons/image.svg similarity index 100% rename from interface/resources/icons/create-icons/image.svg rename to scripts/simplifiedUI/system/create/create-icons/image.svg diff --git a/scripts/simplifiedUI/system/dialTone.js b/scripts/simplifiedUI/system/dialTone.js new file mode 100644 index 0000000000..7c0a5b250d --- /dev/null +++ b/scripts/simplifiedUI/system/dialTone.js @@ -0,0 +1,36 @@ +"use strict"; + +// +// dialTone.js +// examples +// +// Created by Stephen Birarda on 06/08/15. +// Added disconnect HRS 6/11/15. +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +(function() { // BEGIN LOCAL_SCOPE + +// setup the local sound we're going to use +var connectSound = SoundCache.getSound(Script.resolvePath("assets/sounds/hello.wav")); +var disconnectSound = SoundCache.getSound(Script.resolvePath("assets/sounds/goodbye.wav")); +var micMutedSound = SoundCache.getSound(Script.resolvePath("assets/sounds/goodbye.wav")); + +// setup the options needed for that sound +var soundOptions = { + localOnly: true +}; + +// play the sound locally once we get the first audio packet from a mixer +Audio.receivedFirstPacket.connect(function(){ + Audio.playSound(connectSound, soundOptions); +}); + +Audio.disconnected.connect(function(){ + Audio.playSound(disconnectSound, soundOptions); +}); + +}()); // END LOCAL_SCOPE diff --git a/scripts/simplifiedUI/system/directory.js b/scripts/simplifiedUI/system/directory.js new file mode 100644 index 0000000000..f84429ab95 --- /dev/null +++ b/scripts/simplifiedUI/system/directory.js @@ -0,0 +1,134 @@ +// +// directory.js +// examples +// +// Created by David Rowe on 8 Jun 2015 +// Copyright 2015 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 +// + +Script.include([ + "libraries/toolBars.js", +]); + +var toolIconUrl = Script.resolvePath("assets/images/tools/"); + +var DIRECTORY_WINDOW_URL = Account.metaverseServerURL + "/directory"; +var directoryWindow = new OverlayWebWindow({ + title: 'Directory', + source: "about:blank", + width: 900, + height: 700, + visible: false +}); + +var toolHeight = 50; +var toolWidth = 50; +var TOOLBAR_MARGIN_Y = 0; + + +function showDirectory() { + directoryWindow.setURL(DIRECTORY_WINDOW_URL); + directoryWindow.setVisible(true); +} + +function hideDirectory() { + directoryWindow.setVisible(false); + directoryWindow.setURL("about:blank"); +} + +function toggleDirectory() { + if (directoryWindow.visible) { + hideDirectory(); + } else { + showDirectory(); + } +} + +var toolBar = (function() { + var that = {}, + toolBar, + browseDirectoryButton; + + function initialize() { + toolBar = new ToolBar(0, 0, ToolBar.HORIZONTAL, "highfidelity.directory.toolbar", function(windowDimensions, toolbar) { + return { + x: windowDimensions.x / 2, + y: windowDimensions.y + }; + }, { + x: -2 * toolWidth, + y: -TOOLBAR_MARGIN_Y - toolHeight + }); + browseDirectoryButton = toolBar.addTool({ + imageURL: toolIconUrl + "directory.svg", + subImage: { + x: 0, + y: Tool.IMAGE_WIDTH, + width: Tool.IMAGE_WIDTH, + height: Tool.IMAGE_HEIGHT + }, + width: toolWidth, + height: toolHeight, + alpha: 0.9, + visible: true + }); + + toolBar.showTool(browseDirectoryButton, true); + } + + var browseDirectoryButtonDown = false; + that.mousePressEvent = function(event) { + var clickedOverlay, + url, + file; + + if (!event.isLeftButton) { + // if another mouse button than left is pressed ignore it + return false; + } + + clickedOverlay = Overlays.getOverlayAtPoint({ + x: event.x, + y: event.y + }); + + + + if (browseDirectoryButton === toolBar.clicked(clickedOverlay)) { + toggleDirectory(); + return true; + } + + return false; + }; + + that.mouseReleaseEvent = function(event) { + var handled = false; + + + if (browseDirectoryButtonDown) { + var clickedOverlay = Overlays.getOverlayAtPoint({ + x: event.x, + y: event.y + }); + } + + newModelButtonDown = false; + browseDirectoryButtonDown = false; + + return handled; + } + + that.cleanup = function() { + toolBar.cleanup(); + }; + + initialize(); + return that; +}()); + +Controller.mousePressEvent.connect(toolBar.mousePressEvent) +Script.scriptEnding.connect(toolBar.cleanup); diff --git a/scripts/simplifiedUI/system/emote.js b/scripts/simplifiedUI/system/emote.js new file mode 100644 index 0000000000..6dfd1ae1ef --- /dev/null +++ b/scripts/simplifiedUI/system/emote.js @@ -0,0 +1,179 @@ +"use strict"; + +// +// emote.js +// scripts/system/ +// +// Created by Brad Hefta-Gaub on 7 Jan 2018 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +/* globals Script, Tablet */ +/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */ + +(function() { // BEGIN LOCAL_SCOPE + + +var EMOTE_ANIMATIONS = + ['Crying', 'Surprised', 'Dancing', 'Cheering', 'Waving', 'Fall', 'Pointing', 'Clapping', 'Sit1', 'Sit2', 'Sit3', 'Love']; +var ANIMATIONS = Array(); + +var eventMappingName = "io.highfidelity.away"; // restoreAnimation on hand controller button events, too +var eventMapping = Controller.newMapping(eventMappingName); + +EMOTE_ANIMATIONS.forEach(function (name) { + var animationURL = Script.resolvePath("assets/animations/" + name + ".fbx"); + var resource = AnimationCache.prefetch(animationURL); + var animation = AnimationCache.getAnimation(animationURL); + ANIMATIONS[name] = { url: animationURL, animation: animation, resource: resource}; +}); + + +var EMOTE_APP_BASE = "html/EmoteApp.html"; +var EMOTE_APP_URL = Script.resolvePath(EMOTE_APP_BASE); +var EMOTE_LABEL = "EMOTE"; +var EMOTE_APP_SORT_ORDER = 12; +var FPS = 60; +var MSEC_PER_SEC = 1000; +var FINISHED = 3; // see ScriptableResource::State + +var onEmoteScreen = false; +var button; +var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); +var activeTimer = false; // Used to cancel active timer if a user plays an animation while another animation is playing +var activeEmote = false; // To keep track of the currently playing emote + +button = tablet.addButton({ + icon: "icons/tablet-icons/emote-i.svg", + activeIcon: "icons/tablet-icons/emote-a.svg", + text: EMOTE_LABEL, + sortOrder: EMOTE_APP_SORT_ORDER +}); + +function onClicked() { + if (onEmoteScreen) { + tablet.gotoHomeScreen(); + } else { + onEmoteScreen = true; + tablet.gotoWebScreen(EMOTE_APP_URL); + } +} + +function onScreenChanged(type, url) { + onEmoteScreen = type === "Web" && (url.indexOf(EMOTE_APP_BASE) === url.length - EMOTE_APP_BASE.length); + button.editProperties({ isActive: onEmoteScreen }); +} + +// Handle the events we're receiving from the web UI +function onWebEventReceived(event) { + + // Converts the event to a JavasScript Object + if (typeof event === "string") { + event = JSON.parse(event); + } + + if (event.type === "click") { + + // Allow for a random sitting animation when a user selects sit + var randSit = Math.floor(Math.random() * 3) + 1; + + var emoteName = event.data; + + if (emoteName === "Sit"){ + emoteName = event.data + randSit; // Sit1, Sit2, Sit3 + } + + if (ANIMATIONS[emoteName].resource.state === FINISHED) { + + if (activeTimer !== false) { + Script.clearTimeout(activeTimer); + } + + // If the activeEmote is different from the chosen emote, then play the new emote + // This is a second click on the same emote as the activeEmote, and we will just stop it + if (activeEmote !== emoteName) { + activeEmote = emoteName; + + + // Sit is the only animation currently that plays and then ends at the last frame + if (emoteName.match(/^Sit.*$/)) { + + // If user provides input during a sit, the avatar animation state should be restored + Controller.keyPressEvent.connect(restoreAnimation); + Controller.enableMapping(eventMappingName); + MyAvatar.overrideAnimation(ANIMATIONS[emoteName].url, FPS, false, 0, frameCount); + + } else { + + activeEmote = emoteName; + var frameCount = ANIMATIONS[emoteName].animation.frames.length; + MyAvatar.overrideAnimation(ANIMATIONS[emoteName].url, FPS, false, 0, frameCount); + + var timeOut = MSEC_PER_SEC * frameCount / FPS; + activeTimer = Script.setTimeout(function () { + MyAvatar.restoreAnimation(); + activeTimer = false; + activeEmote = false; + }, timeOut); + + } + + } else { + activeEmote = false; + MyAvatar.restoreAnimation(); + } + } + } +} + +// Restore the navigation animation states (idle, walk, run) +function restoreAnimation() { + MyAvatar.restoreAnimation(); + + // Make sure the input is disconnected after animations are restored so it doesn't affect any emotes other than sit + Controller.keyPressEvent.disconnect(restoreAnimation); + Controller.disableMapping(eventMappingName); +} + +// Note peek() so as to not interfere with other mappings. +eventMapping.from(Controller.Standard.LeftPrimaryThumb).peek().to(restoreAnimation); +eventMapping.from(Controller.Standard.RightPrimaryThumb).peek().to(restoreAnimation); +eventMapping.from(Controller.Standard.LeftSecondaryThumb).peek().to(restoreAnimation); +eventMapping.from(Controller.Standard.RightSecondaryThumb).peek().to(restoreAnimation); +eventMapping.from(Controller.Standard.LB).peek().to(restoreAnimation); +eventMapping.from(Controller.Standard.LS).peek().to(restoreAnimation); +eventMapping.from(Controller.Standard.RY).peek().to(restoreAnimation); +eventMapping.from(Controller.Standard.RX).peek().to(restoreAnimation); +eventMapping.from(Controller.Standard.LY).peek().to(restoreAnimation); +eventMapping.from(Controller.Standard.LX).peek().to(restoreAnimation); +eventMapping.from(Controller.Standard.LeftGrip).peek().to(restoreAnimation); +eventMapping.from(Controller.Standard.RB).peek().to(restoreAnimation); +eventMapping.from(Controller.Standard.RS).peek().to(restoreAnimation); +eventMapping.from(Controller.Standard.RightGrip).peek().to(restoreAnimation); +eventMapping.from(Controller.Standard.Back).peek().to(restoreAnimation); +eventMapping.from(Controller.Standard.Start).peek().to(restoreAnimation); + + +button.clicked.connect(onClicked); +tablet.screenChanged.connect(onScreenChanged); +tablet.webEventReceived.connect(onWebEventReceived); + +Script.scriptEnding.connect(function () { + if (onEmoteScreen) { + tablet.gotoHomeScreen(); + } + button.clicked.disconnect(onClicked); + tablet.screenChanged.disconnect(onScreenChanged); + if (tablet) { + tablet.removeButton(button); + } + if (activeTimer !== false) { + Script.clearTimeout(activeTimer); + MyAvatar.restoreAnimation(); + } +}); + + +}()); // END LOCAL_SCOPE diff --git a/scripts/simplifiedUI/system/fingerPaint.js b/scripts/simplifiedUI/system/fingerPaint.js new file mode 100644 index 0000000000..88245503e8 --- /dev/null +++ b/scripts/simplifiedUI/system/fingerPaint.js @@ -0,0 +1,465 @@ +// +// fingerPaint.js +// +// Created by David Rowe on 15 Feb 2017 +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +(function () { + var tablet, + button, + BUTTON_NAME = "PAINT", + isFingerPainting = false, + shouldPointFingers = false, + leftHand = null, + rightHand = null, + leftBrush = null, + rightBrush = null, + CONTROLLER_MAPPING_NAME = "com.highfidelity.fingerPaint", + isTabletDisplayed = false, + HIFI_POINT_INDEX_MESSAGE_CHANNEL = "Hifi-Point-Index", + HIFI_GRAB_DISABLE_MESSAGE_CHANNEL = "Hifi-Grab-Disable", + HIFI_POINTER_DISABLE_MESSAGE_CHANNEL = "Hifi-Pointer-Disable"; + HOW_TO_EXIT_MESSAGE = "Press B on your controller to exit FingerPainting mode"; + + function paintBrush(name) { + // Paints in 3D. + var brushName = name, + STROKE_COLOR = { red: 250, green: 0, blue: 0 }, + ERASE_SEARCH_RADIUS = 0.1, // m + isDrawingLine = false, + entityID, + basePosition, + strokePoints, + strokeNormals, + strokeWidths, + timeOfLastPoint, + MIN_STROKE_LENGTH = 0.005, // m + MIN_STROKE_INTERVAL = 66, // ms + MAX_POINTS_PER_LINE = 70; // Hard-coded limit in PolyLineEntityItem.h. + + function strokeNormal() { + return Vec3.multiplyQbyV(Camera.getOrientation(), Vec3.UNIT_NEG_Z); + } + + function startLine(position, width) { + // Start drawing a polyline. + + if (isDrawingLine) { + print("ERROR: startLine() called when already drawing line"); + // Nevertheless, continue on and start a new line. + } + + basePosition = position; + + strokePoints = [Vec3.ZERO]; + strokeNormals = [strokeNormal()]; + strokeWidths = [width]; + timeOfLastPoint = Date.now(); + + entityID = Entities.addEntity({ + type: "PolyLine", + name: "fingerPainting", + color: STROKE_COLOR, + position: position, + linePoints: strokePoints, + normals: strokeNormals, + strokeWidths: strokeWidths, + dimensions: { x: 10, y: 10, z: 10 } + }); + + isDrawingLine = true; + } + + function drawLine(position, width) { + // Add a stroke to the polyline if stroke is a sufficient length. + var localPosition, + distanceToPrevious, + MAX_DISTANCE_TO_PREVIOUS = 1.0; + + if (!isDrawingLine) { + print("ERROR: drawLine() called when not drawing line"); + return; + } + + localPosition = Vec3.subtract(position, basePosition); + distanceToPrevious = Vec3.distance(localPosition, strokePoints[strokePoints.length - 1]); + + if (distanceToPrevious > MAX_DISTANCE_TO_PREVIOUS) { + // Ignore occasional spurious finger tip positions. + return; + } + + if (distanceToPrevious >= MIN_STROKE_LENGTH + && (Date.now() - timeOfLastPoint) >= MIN_STROKE_INTERVAL + && strokePoints.length < MAX_POINTS_PER_LINE) { + strokePoints.push(localPosition); + strokeNormals.push(strokeNormal()); + strokeWidths.push(width); + timeOfLastPoint = Date.now(); + + Entities.editEntity(entityID, { + linePoints: strokePoints, + normals: strokeNormals, + strokeWidths: strokeWidths + }); + } + } + + function finishLine(position, width) { + // Finish drawing polyline; delete if it has only 1 point. + + if (!isDrawingLine) { + print("ERROR: finishLine() called when not drawing line"); + return; + } + + if (strokePoints.length === 1) { + // Delete "empty" line. + Entities.deleteEntity(entityID); + } + + isDrawingLine = false; + } + + function cancelLine() { + // Cancel any line being drawn. + if (isDrawingLine) { + Entities.deleteEntity(entityID); + isDrawingLine = false; + } + } + + function eraseClosestLine(position) { + // Erase closest line that is within search radius of finger tip. + var entities, + entitiesLength, + properties, + i, + pointsLength, + j, + distance, + found = false, + foundID, + foundDistance = ERASE_SEARCH_RADIUS; + + // Find entities with bounding box within search radius. + entities = Entities.findEntities(position, ERASE_SEARCH_RADIUS); + + // Fine polyline entity with closest point within search radius. + for (i = 0, entitiesLength = entities.length; i < entitiesLength; i += 1) { + properties = Entities.getEntityProperties(entities[i], ["type", "position", "linePoints"]); + if (properties.type === "PolyLine") { + basePosition = properties.position; + for (j = 0, pointsLength = properties.linePoints.length; j < pointsLength; j += 1) { + distance = Vec3.distance(position, Vec3.sum(basePosition, properties.linePoints[j])); + if (distance <= foundDistance) { + found = true; + foundID = entities[i]; + foundDistance = distance; + } + } + } + } + + // Delete found entity. + if (found) { + Entities.deleteEntity(foundID); + } + } + + function tearDown() { + cancelLine(); + } + + return { + startLine: startLine, + drawLine: drawLine, + finishLine: finishLine, + cancelLine: cancelLine, + eraseClosestLine: eraseClosestLine, + tearDown: tearDown + }; + } + + function handController(name) { + // Translates controller data into application events. + var handName = name, + + triggerPressedCallback, + triggerPressingCallback, + triggerReleasedCallback, + gripPressedCallback, + + rawTriggerValue = 0.0, + triggerValue = 0.0, + isTriggerPressed = false, + TRIGGER_SMOOTH_RATIO = 0.1, + TRIGGER_OFF = 0.05, + TRIGGER_ON = 0.1, + TRIGGER_START_WIDTH_RAMP = 0.15, + TRIGGER_FINISH_WIDTH_RAMP = 1.0, + TRIGGER_RAMP_WIDTH = TRIGGER_FINISH_WIDTH_RAMP - TRIGGER_START_WIDTH_RAMP, + MIN_LINE_WIDTH = 0.005, + MAX_LINE_WIDTH = 0.03, + RAMP_LINE_WIDTH = MAX_LINE_WIDTH - MIN_LINE_WIDTH, + + rawGripValue = 0.0, + gripValue = 0.0, + isGripPressed = false, + GRIP_SMOOTH_RATIO = 0.1, + GRIP_OFF = 0.05, + GRIP_ON = 0.1; + + function onTriggerPress(value) { + // Controller values are only updated when they change so store latest for use in update. + rawTriggerValue = value; + } + + function updateTriggerPress(value) { + var wasTriggerPressed, + fingerTipPosition, + lineWidth; + + triggerValue = triggerValue * TRIGGER_SMOOTH_RATIO + rawTriggerValue * (1.0 - TRIGGER_SMOOTH_RATIO); + + wasTriggerPressed = isTriggerPressed; + if (isTriggerPressed) { + isTriggerPressed = triggerValue > TRIGGER_OFF; + } else { + isTriggerPressed = triggerValue > TRIGGER_ON; + } + + if (wasTriggerPressed || isTriggerPressed) { + fingerTipPosition = MyAvatar.getJointPosition(handName === "left" ? "LeftHandIndex4" : "RightHandIndex4"); + if (triggerValue < TRIGGER_START_WIDTH_RAMP) { + lineWidth = MIN_LINE_WIDTH; + } else { + lineWidth = MIN_LINE_WIDTH + + (triggerValue - TRIGGER_START_WIDTH_RAMP) / TRIGGER_RAMP_WIDTH * RAMP_LINE_WIDTH; + } + + if (!wasTriggerPressed && isTriggerPressed) { + triggerPressedCallback(fingerTipPosition, lineWidth); + } else if (wasTriggerPressed && isTriggerPressed) { + triggerPressingCallback(fingerTipPosition, lineWidth); + } else { + triggerReleasedCallback(fingerTipPosition, lineWidth); + } + } + } + + function onGripPress(value) { + // Controller values are only updated when they change so store latest for use in update. + rawGripValue = value; + } + + function updateGripPress() { + var fingerTipPosition; + + gripValue = gripValue * GRIP_SMOOTH_RATIO + rawGripValue * (1.0 - GRIP_SMOOTH_RATIO); + + if (isGripPressed) { + isGripPressed = gripValue > GRIP_OFF; + } else { + isGripPressed = gripValue > GRIP_ON; + if (isGripPressed) { + fingerTipPosition = MyAvatar.getJointPosition(handName === "left" ? "LeftHandIndex4" : "RightHandIndex4"); + gripPressedCallback(fingerTipPosition); + } + } + } + + function onUpdate() { + updateTriggerPress(); + updateGripPress(); + } + + function setUp(onTriggerPressed, onTriggerPressing, onTriggerReleased, onGripPressed) { + triggerPressedCallback = onTriggerPressed; + triggerPressingCallback = onTriggerPressing; + triggerReleasedCallback = onTriggerReleased; + gripPressedCallback = onGripPressed; + } + + function tearDown() { + // Nothing to do. + } + + return { + onTriggerPress: onTriggerPress, + onGripPress: onGripPress, + onUpdate: onUpdate, + setUp: setUp, + tearDown: tearDown + }; + } + + function updateHandFunctions() { + // Update other scripts' hand functions. + var enabled = !isFingerPainting || isTabletDisplayed; + + Messages.sendMessage(HIFI_GRAB_DISABLE_MESSAGE_CHANNEL, JSON.stringify({ + holdEnabled: enabled, + nearGrabEnabled: enabled, + farGrabEnabled: enabled + }), true); + Messages.sendMessage(HIFI_POINTER_DISABLE_MESSAGE_CHANNEL, JSON.stringify({ + pointerEnabled: enabled + }), true); + + var newShouldPointFingers = !enabled; + if (newShouldPointFingers !== shouldPointFingers) { + Messages.sendMessage(HIFI_POINT_INDEX_MESSAGE_CHANNEL, JSON.stringify({ + pointIndex: newShouldPointFingers + }), true); + shouldPointFingers = newShouldPointFingers; + } + } + + function howToExitTutorial() { + HMD.requestShowHandControllers(); + setControllerPartLayer('button_b', 'highlight'); + messageWindow = Window.alert(HOW_TO_EXIT_MESSAGE); + setControllerPartLayer('button_b', 'blank'); + HMD.requestHideHandControllers(); + Settings.setValue("FingerPaintTutorialComplete", true); + } + + function enableProcessing() { + // Connect controller API to handController objects. + leftHand = handController("left"); + rightHand = handController("right"); + var controllerMapping = Controller.newMapping(CONTROLLER_MAPPING_NAME); + controllerMapping.from(Controller.Standard.LT).to(leftHand.onTriggerPress); + controllerMapping.from(Controller.Standard.LeftGrip).to(leftHand.onGripPress); + controllerMapping.from(Controller.Standard.RT).to(rightHand.onTriggerPress); + controllerMapping.from(Controller.Standard.RightGrip).to(rightHand.onGripPress); + controllerMapping.from(Controller.Standard.B).to(onButtonClicked); + Controller.enableMapping(CONTROLLER_MAPPING_NAME); + + if (!Settings.getValue("FingerPaintTutorialComplete")) { + howToExitTutorial(); + } + + // Connect handController outputs to paintBrush objects. + leftBrush = paintBrush("left"); + leftHand.setUp(leftBrush.startLine, leftBrush.drawLine, leftBrush.finishLine, leftBrush.eraseClosestLine); + rightBrush = paintBrush("right"); + rightHand.setUp(rightBrush.startLine, rightBrush.drawLine, rightBrush.finishLine, rightBrush.eraseClosestLine); + + // Messages channels for enabling/disabling other scripts' functions. + Messages.subscribe(HIFI_POINT_INDEX_MESSAGE_CHANNEL); + Messages.subscribe(HIFI_GRAB_DISABLE_MESSAGE_CHANNEL); + Messages.subscribe(HIFI_POINTER_DISABLE_MESSAGE_CHANNEL); + + // Update hand controls. + Script.update.connect(leftHand.onUpdate); + Script.update.connect(rightHand.onUpdate); + } + + function disableProcessing() { + Script.update.disconnect(leftHand.onUpdate); + Script.update.disconnect(rightHand.onUpdate); + + Controller.disableMapping(CONTROLLER_MAPPING_NAME); + + leftBrush.tearDown(); + leftBrush = null; + leftHand.tearDown(); + leftHand = null; + + rightBrush.tearDown(); + rightBrush = null; + rightHand.tearDown(); + rightHand = null; + + Messages.unsubscribe(HIFI_POINT_INDEX_MESSAGE_CHANNEL); + Messages.unsubscribe(HIFI_GRAB_DISABLE_MESSAGE_CHANNEL); + Messages.unsubscribe(HIFI_POINTER_DISABLE_MESSAGE_CHANNEL); + } + + function onButtonClicked() { + var wasFingerPainting = isFingerPainting; + + isFingerPainting = !isFingerPainting; + button.editProperties({ isActive: isFingerPainting }); + + print("Finger painting: " + isFingerPainting ? "on" : "off"); + + if (wasFingerPainting) { + leftBrush.cancelLine(); + rightBrush.cancelLine(); + } + + if (isFingerPainting) { + enableProcessing(); + } + + updateHandFunctions(); + + if (!isFingerPainting) { + disableProcessing(); + } + } + + function onTabletScreenChanged(type, url) { + var TABLET_SCREEN_CLOSED = "Closed"; + + isTabletDisplayed = type !== TABLET_SCREEN_CLOSED; + updateHandFunctions(); + } + + function setUp() { + tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + if (!tablet) { + return; + } + + // Tablet button. + button = tablet.addButton({ + icon: "icons/tablet-icons/finger-paint-i.svg", + activeIcon: "icons/tablet-icons/finger-paint-a.svg", + text: BUTTON_NAME, + isActive: isFingerPainting + }); + button.clicked.connect(onButtonClicked); + + // Track whether tablet is displayed or not. + tablet.screenChanged.connect(onTabletScreenChanged); + } + + function tearDown() { + if (!tablet) { + return; + } + + if (isFingerPainting) { + isFingerPainting = false; + updateHandFunctions(); + disableProcessing(); + } + + tablet.screenChanged.disconnect(onTabletScreenChanged); + + button.clicked.disconnect(onButtonClicked); + tablet.removeButton(button); + } + + /** + * A controller is made up of parts, and each part can have multiple "layers," + * which are really just different texures. For example, the "trigger" part + * has "normal" and "highlight" layers. + */ + function setControllerPartLayer(part, layer) { + data = {}; + data[part] = layer; + Messages.sendLocalMessage('Controller-Set-Part-Layer', JSON.stringify(data)); + } + + setUp(); + Script.scriptEnding.connect(tearDown); +}()); diff --git a/scripts/simplifiedUI/system/firstPersonHMD.js b/scripts/simplifiedUI/system/firstPersonHMD.js new file mode 100644 index 0000000000..5fdee1b7b5 --- /dev/null +++ b/scripts/simplifiedUI/system/firstPersonHMD.js @@ -0,0 +1,23 @@ +"use strict"; + +// +// firstPersonHMD.js +// system +// +// Created by Zander Otavka on 6/24/16 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +(function() { // BEGIN LOCAL_SCOPE + +// Automatically enter first person mode when entering HMD mode +HMD.displayModeChanged.connect(function(isHMDMode) { + if (isHMDMode) { + Camera.setModeString("first person"); + } +}); + +}()); // END LOCAL_SCOPE diff --git a/scripts/simplifiedUI/system/generalSettings.js b/scripts/simplifiedUI/system/generalSettings.js new file mode 100644 index 0000000000..d3848da7d0 --- /dev/null +++ b/scripts/simplifiedUI/system/generalSettings.js @@ -0,0 +1,56 @@ +"use strict"; + +// +// generalSettings.js +// scripts/system/ +// +// Created by Dante Ruiz on 9 Feb 2017 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +/* globals Tablet, Toolbars, Script, HMD, DialogsManager */ + +(function() { // BEGIN LOCAL_SCOPE + + var button; + var buttonName = "Settings"; + var toolBar = null; + var tablet = null; + var settings = "hifi/tablet/TabletGeneralPreferences.qml" + function onClicked(){ + if (tablet) { + tablet.loadQMLSource(settings); + } + } + + if (Settings.getValue("HUDUIEnabled")) { + toolBar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system"); + button = toolBar.addButton({ + objectName: buttonName, + imageURL: Script.resolvePath("assets/images/tools/directory.svg"), + visible: true, + alpha: 0.9 + }); + } else { + tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + button = tablet.addButton({ + icon: "icons/tablet-icons/goto-i.svg", + text: buttonName + }); + } + + button.clicked.connect(onClicked); + + Script.scriptEnding.connect(function () { + button.clicked.disconnect(onClicked); + if (tablet) { + tablet.removeButton(button); + } + if (toolBar) { + toolBar.removeButton(buttonName); + } + }); + +}()); // END LOCAL_SCOPE diff --git a/scripts/simplifiedUI/system/goto.js b/scripts/simplifiedUI/system/goto.js new file mode 100644 index 0000000000..5cc5bad844 --- /dev/null +++ b/scripts/simplifiedUI/system/goto.js @@ -0,0 +1,65 @@ +"use strict"; + +// +// goto.js +// scripts/system/ +// +// Created by Howard Stearns on 2 Jun 2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +/* globals Tablet, Toolbars, Script, HMD, DialogsManager */ + +(function() { // BEGIN LOCAL_SCOPE + +var button; +var buttonName = "GOTO"; +var toolBar = null; +var tablet = null; +var onGotoScreen = false; +function onAddressBarShown(visible) { + button.editProperties({isActive: visible}); +} + +function onClicked(){ + DialogsManager.toggleAddressBar(); + onGotoScreen = !onGotoScreen; +} + +if (Settings.getValue("HUDUIEnabled")) { + toolBar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system"); + button = toolBar.addButton({ + objectName: buttonName, + imageURL: Script.resolvePath("assets/images/tools/directory.svg"), + visible: true, + alpha: 0.9 + }); +} else { + tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + button = tablet.addButton({ + icon: "icons/tablet-icons/goto-i.svg", + activeIcon: "icons/tablet-icons/goto-a.svg", + text: buttonName + }); +} + +button.clicked.connect(onClicked); +DialogsManager.addressBarShown.connect(onAddressBarShown); + +Script.scriptEnding.connect(function () { + if (onGotoScreen) { + DialogsManager.toggleAddressBar(); + } + button.clicked.disconnect(onClicked); + if (tablet) { + tablet.removeButton(button); + } + if (toolBar) { + toolBar.removeButton(buttonName); + } + DialogsManager.addressBarShown.disconnect(onAddressBarShown); +}); + +}()); // END LOCAL_SCOPE diff --git a/scripts/simplifiedUI/system/help.js b/scripts/simplifiedUI/system/help.js new file mode 100644 index 0000000000..40bbf6dbe2 --- /dev/null +++ b/scripts/simplifiedUI/system/help.js @@ -0,0 +1,29 @@ +"use strict"; +/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */ +// +// help.js +// scripts/system/ +// +// Created by Howard Stearns on 2 Nov 2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +/* globals Tablet, Script, HMD, Controller, Menu */ + +(function () { // BEGIN LOCAL_SCOPE +var AppUi = Script.require('appUi'); + +var HELP_URL = Script.resourcesPath() + "html/tabletHelp.html"; +var HELP_BUTTON_NAME = "HELP"; +var ui; +function startup() { + ui = new AppUi({ + buttonName: HELP_BUTTON_NAME, + sortOrder: 6, + home: HELP_URL + }); +} +startup(); +}()); // END LOCAL_SCOPE diff --git a/scripts/simplifiedUI/system/hmd.js b/scripts/simplifiedUI/system/hmd.js new file mode 100644 index 0000000000..858b93ef1e --- /dev/null +++ b/scripts/simplifiedUI/system/hmd.js @@ -0,0 +1,92 @@ +"use strict"; + +// +// hmd.js +// scripts/system/ +// +// Created by Howard Stearns on 2 Jun 2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +/* globals HMD, Script, Menu, Tablet, Camera */ +/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */ + +(function() { // BEGIN LOCAL_SCOPE + +var headset; // The preferred headset. Default to the first one found in the following list. +var displayMenuName = "Display"; +var desktopMenuItemName = "Desktop"; +['HTC Vive', 'Oculus Rift', 'WindowMS'].forEach(function (name) { + if (!headset && Menu.menuItemExists(displayMenuName, name)) { + headset = name; + } +}); + +var controllerDisplay = false; +function updateControllerDisplay() { + if (HMD.active && Menu.isOptionChecked("Third Person")) { + if (!controllerDisplay) { + HMD.requestShowHandControllers(); + controllerDisplay = true; + } + } else if (controllerDisplay) { + HMD.requestHideHandControllers(); + controllerDisplay = false; + } +} + +var button; +var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + +var switchToVR = "ENTER VR"; +var switchToDesktop = "EXIT VR"; + +function onHmdChanged(isHmd) { + HMD.closeTablet(); + if (isHmd) { + button.editProperties({ + icon: "icons/tablet-icons/switch-desk-i.svg", + text: switchToDesktop + }); + } else { + button.editProperties({ + icon: "icons/tablet-icons/switch-vr-i.svg", + text: switchToVR + }); + } + updateControllerDisplay(); +} + +function onClicked() { + var isDesktop = Menu.isOptionChecked(desktopMenuItemName); + Menu.setIsOptionChecked(isDesktop ? headset : desktopMenuItemName, true); + if (!isDesktop) { + UserActivityLogger.logAction("exit_vr"); + } +} + +if (headset) { + button = tablet.addButton({ + icon: HMD.active ? "icons/tablet-icons/switch-desk-i.svg" : "icons/tablet-icons/switch-vr-i.svg", + text: HMD.active ? switchToDesktop : switchToVR, + sortOrder: 2 + }); + onHmdChanged(HMD.active); + + button.clicked.connect(onClicked); + HMD.displayModeChanged.connect(onHmdChanged); + Camera.modeUpdated.connect(updateControllerDisplay); + + Script.scriptEnding.connect(function () { + button.clicked.disconnect(onClicked); + if (tablet) { + tablet.removeButton(button); + } + HMD.displayModeChanged.disconnect(onHmdChanged); + Camera.modeUpdated.disconnect(updateControllerDisplay); + }); +} + +}()); // END LOCAL_SCOPE diff --git a/scripts/simplifiedUI/system/html/ChatPage.html b/scripts/simplifiedUI/system/html/ChatPage.html new file mode 100644 index 0000000000..9606eeab3e --- /dev/null +++ b/scripts/simplifiedUI/system/html/ChatPage.html @@ -0,0 +1,511 @@ + + + + Chat + + + + + + + + +
+ +
+ Chat +
+ +
+ +
+ +
+ +
+ + + + + + diff --git a/scripts/simplifiedUI/system/html/EmoteApp.html b/scripts/simplifiedUI/system/html/EmoteApp.html new file mode 100644 index 0000000000..6b42fb8dc8 --- /dev/null +++ b/scripts/simplifiedUI/system/html/EmoteApp.html @@ -0,0 +1,138 @@ + + + + Emote App + + + + + + +
+

Emote App

+
+
+

Choose an emote:

+

+

+

+

+

+

+

+

+

+

+
+ + + + + \ No newline at end of file diff --git a/scripts/simplifiedUI/system/html/SnapshotReview.html b/scripts/simplifiedUI/system/html/SnapshotReview.html new file mode 100644 index 0000000000..f080cd204a --- /dev/null +++ b/scripts/simplifiedUI/system/html/SnapshotReview.html @@ -0,0 +1,38 @@ + + + Share + + + + + + + +
+ + + +
+
+
+
+
+
+
+
+
+
+ +
+ +
+
+ +
+ +
+
+ + diff --git a/scripts/simplifiedUI/system/html/css/SnapshotReview.css b/scripts/simplifiedUI/system/html/css/SnapshotReview.css new file mode 100644 index 0000000000..54d39aaad3 --- /dev/null +++ b/scripts/simplifiedUI/system/html/css/SnapshotReview.css @@ -0,0 +1,346 @@ +/* +// SnapshotReview.css +// +// Created by Howard Stearns for David Rowe 8/22/2016. +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +*/ + +/* +// START styling of top bar and its contents +*/ + +.title { + padding: 6px 10px; + text-align: left; + height: 26px; + line-height: 26px; + clear: both; +} + +.title label { + position: relative; + font-size: 18px; + float: left; +} + +#snapshotSettings { + position: relative; + float: right; +} +#settingsLabel { + position: relative; + float: right; + font-family: Raleway-SemiBold; + font-size: 14px; +} +.hifi-glyph { + font-size: 30px; + top: -4px; +} +input[type=button].naked { + color: #afafaf; + background: none; +} +input[type=button].naked:hover { + color: #ffffff; +} +input[type=button].naked:active { + color: #afafaf; +} +/* +// END styling of top bar and its contents +*/ + +/* +// START styling of snapshot instructions panel +*/ +.snapshotInstructions { + font-family: Raleway-Regular; + margin: 0 20px; + width: 100%; + height: 50%; +} +/* +// END styling of snapshot instructions panel +*/ + +/* +// START styling of snapshot pane and its contents +*/ +#snapshot-pane { + width: 100%; + height: 560px; + display: flex; + justify-content: center; + align-items: center; +} + +#snapshot-images { + width: 100%; + display: flex; + justify-content: center; + flex-direction: column; +} + +#snapshot-images img { + max-width: 100%; + max-height: 100%; +} + +.gifLabel { + position:absolute; + left: 15px; + top: 10px; + font-family: Raleway-SemiBold; + font-size: 18px; + color: white; + text-shadow: 2px 2px 3px #000000; +} +/* +// END styling of snapshot pane and its contents +*/ + +/* +// START styling of share overlay +*/ +.shareControls { + display: flex; + justify-content: space-between; + flex-direction: row; + align-items: center; + height: 65px; + line-height: 65px; + width: calc(100% - 8px); + position: absolute; + bottom: 4px; + left: 4px; + right: 4px; +} +.showShareButtonsButtonDiv { + display: inline-flex; + align-items: center; + font-family: Raleway-SemiBold; + font-size: 14px; + color: white; + width: 75px; + height: 100%; + margin-bottom: 0px; +} +.showShareButtonsButtonDiv.active:hover { + background-color: rgba(0, 0, 0, 0.45); + background-size: 2px; +} +.showShareButtonsButtonDiv > label { + text-shadow: 2px 2px 3px #000000; + margin-bottom: -14px; + margin-left: 12px; +} +.showShareButtonsButtonDiv:hover > label { + text-shadow: none; +} +.showShareButtonDots { + display: block; + width: 40px; + height: 40px; + font-family: HiFi-Glyphs; + font-size: 60px; + position: absolute; + left: 6px; + bottom: 32px; + color: white; + pointer-events: none; +} +.shareButtons { + display: flex; + align-items: flex-end; + height: 40px; + width: calc(100% - 60px); + margin-bottom: -24px; + margin-left: 0; +} +.shareButtons img { + width: 40px; + height: 40px; +} +.shareButton { + width: 40px; + height: 40px; + display: inline-block; +} +.shareButton.disabled { + background-color: #000000; + opacity: 0.5; +} +.shareControlsHelp { + height: 25px; + line-height: 25px; + position: absolute; + bottom: 40px; + left: 73px; + right: 0; + font-family: Raleway-Regular; + font-weight: 500; + font-size: 16px; + padding-left: 8px; + color: white; +} +.helpTextDiv { + width: 350px; + height: 65px; + margin-right: 15px; + line-height: 65px; + position: absolute; + bottom: 0; + right: 0; + font-family: Raleway-Regular; + font-weight: 500; + font-size: 16px; + color: white; +} +/* +// END styling of share overlay +*/ + +/* +// START styling of confirmation message +*/ +.confirmationMessageContainer { + width: 100%; + height: 100%; + position: absolute; + background-color: rgba(0, 0, 0, 0.45); + text-align: center; + left: 0; + top: 0; + pointer-events: none; + color: white; + font-weight: bold; + font-size: 16px; +} +.confirmationMessage { + width: 130px; + height: 130px; + margin: 50px auto 0 auto; +} +.confirmationMessage > img { + width: 72px; + height: 72px; + display: block; + margin: 0 auto; + padding: 10px 0 0 0; +} +/* +// END styling of uploading message +*/ + +/* +// START styling of snapshot controls (bottom panel) and its contents +*/ +#snapshot-controls { + width: 100%; + position: absolute; + left: 0; + overflow: hidden; + display: flex; + justify-content: center; +} +#snap-settings { + display: inline; + width: 150px; + margin: 2px auto 0 auto; +} +#snap-settings form input { + margin-bottom: 5px; +} + +#snap-button { + width: 72px; + height: 72px; + padding: 0; + border-radius: 50%; + background: #EA4C5F; + border: 3px solid white; + margin: 2px auto 0 auto; + box-sizing: content-box; + display: inline; + outline:none; +} +#snap-button:disabled { + background: gray; +} +#snap-button:hover:enabled { + background: #C62147; +} +#snap-button:active:enabled { + background: #EA4C5F; +} +#snap-settings-right { + display: inline; + width: 150px; + margin: auto; +} +/* +// END styling of snapshot controls (bottom panel) and its contents +*/ + + +/* +// START polaroid styling +*/ + +#print-button { + width: 72px; + height: 72px; + margin-left: 30px; + margin-top: -10px; + box-sizing: content-box; + display: inline; + outline:none; +} + +.print-icon { + margin: auto; +} + +.print-icon-default { + background: url(../img/button-snap-print.svg) no-repeat; + margin-right: -1px; + width: 64px; + height: 64px; +} + +.print-icon-loading { + background: url(../img/loader.gif) no-repeat; + width: 32px; + height: 32px; +} + +/* +// END polaroid styling +*/ + + +/* +// START misc styling +*/ +body { + padding: 0; + margin: 0; + overflow: hidden; +} +p { + margin: 2px 0; +} +h4 { + margin: 14px 0 0 0; +} +.centeredImage { + margin: 0 auto; + display: block; +} +/* +// END misc styling +*/ diff --git a/scripts/simplifiedUI/system/html/css/colpick.css b/scripts/simplifiedUI/system/html/css/colpick.css new file mode 100644 index 0000000000..fc50c4b3fb --- /dev/null +++ b/scripts/simplifiedUI/system/html/css/colpick.css @@ -0,0 +1,433 @@ +/* +colpick Color Picker / colpick.com +*/ + +/*Main container*/ +.colpick { + position: absolute; + width: 346px; + height: 170px; + overflow: hidden; + display: none; + font-family: Arial, Helvetica, sans-serif; + background:#ebebeb; + border: 1px solid #bbb; + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + border-radius: 5px; + + /*Prevents selecting text when dragging the selectors*/ + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + -o-user-select: none; + user-select: none; +} +/*Color selection box with gradients*/ +.colpick_color { + position: absolute; + touch-action: none; + left: 7px; + top: 7px; + width: 156px; + height: 156px; + overflow: hidden; + outline: 1px solid #aaa; + cursor: crosshair; +} +.colpick_color_overlay1 { + position: absolute; + left:0; + top:0; + width: 156px; + height: 156px; + -ms-filter: "progid:DXImageTransform.Microsoft.gradient(GradientType=1,startColorstr='#ffffff', endColorstr='#00ffffff')"; /* IE8 */ + background: -moz-linear-gradient(left, rgba(255,255,255,1) 0%, rgba(255,255,255,0) 100%); /* FF3.6+ */ + background: -webkit-gradient(linear, left top, right top, color-stop(0%,rgba(255,255,255,1)), color-stop(100%,rgba(255,255,255,0))); /* Chrome,Safari4+ */ + background: -webkit-linear-gradient(left, rgba(255,255,255,1) 0%,rgba(255,255,255,0) 100%); /* Chrome10+,Safari5.1+ */ + background: -o-linear-gradient(left, rgba(255,255,255,1) 0%,rgba(255,255,255,0) 100%); /* Opera 11.10+ */ + background: -ms-linear-gradient(left, rgba(255,255,255,1) 0%,rgba(255,255,255,0) 100%); /* IE10+ */ + background: linear-gradient(to right, rgba(255,255,255,1) 0%, rgba(255,255,255,0) 100%); + filter: progid:DXImageTransform.Microsoft.gradient(GradientType=1,startColorstr='#ffffff', endColorstr='#00ffffff'); /* IE6 & IE7 */ +} +.colpick_color_overlay2 { + position: absolute; + left:0; + top:0; + width: 156px; + height: 156px; + -ms-filter: "progid:DXImageTransform.Microsoft.gradient(GradientType=0,startColorstr='#00000000', endColorstr='#000000')"; /* IE8 */ + background: -moz-linear-gradient(top, rgba(0,0,0,0) 0%, rgba(0,0,0,1) 100%); /* FF3.6+ */ + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(0,0,0,0)), color-stop(100%,rgba(0,0,0,1))); /* Chrome,Safari4+ */ + background: -webkit-linear-gradient(top, rgba(0,0,0,0) 0%,rgba(0,0,0,1) 100%); /* Chrome10+,Safari5.1+ */ + background: -o-linear-gradient(top, rgba(0,0,0,0) 0%,rgba(0,0,0,1) 100%); /* Opera 11.10+ */ + background: -ms-linear-gradient(top, rgba(0,0,0,0) 0%,rgba(0,0,0,1) 100%); /* IE10+ */ + background: linear-gradient(to bottom, rgba(0,0,0,0) 0%,rgba(0,0,0,1) 100%); /* W3C */ + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#00000000', endColorstr='#000000',GradientType=0 ); /* IE6-9 */ +} +/*Circular color selector*/ +.colpick_selector_outer { + background:none; + position: absolute; + width: 11px; + height: 11px; + margin: -6px 0 0 -6px; + border: 1px solid black; + border-radius: 50%; +} +.colpick_selector_inner{ + position: absolute; + width: 9px; + height: 9px; + border: 1px solid white; + border-radius: 50%; +} +/*Vertical hue bar*/ +.colpick_hue { + position: absolute; + touch-action: none; + top: 6px; + left: 175px; + width: 19px; + height: 156px; + border: 1px solid #aaa; + cursor: n-resize; +} +/*Hue bar sliding indicator*/ +.colpick_hue_arrs { + position: absolute; + touch-action: none; + left: -8px; + width: 35px; + height: 7px; + margin: -7px 0 0 0; +} +.colpick_hue_larr { + position:absolute; + touch-action: none; + width: 0; + height: 0; + border-top: 6px solid transparent; + border-bottom: 6px solid transparent; + border-left: 7px solid #858585; +} +.colpick_hue_rarr { + position:absolute; + touch-action: none; + right:0; + width: 0; + height: 0; + border-top: 6px solid transparent; + border-bottom: 6px solid transparent; + border-right: 7px solid #858585; +} +/*New color box*/ +.colpick_new_color { + position: absolute; + touch-action: none; + left: 207px; + top: 6px; + width: 60px; + height: 27px; + background: #f00; + border: 1px solid #8f8f8f; +} +/*Current color box*/ +.colpick_current_color { + position: absolute; + touch-action: none; + left: 277px; + top: 6px; + width: 60px; + height: 27px; + background: #f00; + border: 1px solid #8f8f8f; +} +/*Input field containers*/ +.colpick_field, .colpick_hex_field { + position: absolute; + touch-action: none; + height: 20px; + width: 60px; + overflow:hidden; + background:#f3f3f3; + color:#b8b8b8; + font-size:12px; + border:1px solid #bdbdbd; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + border-radius: 3px; +} +.colpick_rgb_r { + top: 40px; + left: 207px; +} +.colpick_rgb_g { + top: 67px; + left: 207px; +} +.colpick_rgb_b { + top: 94px; + left: 207px; +} +.colpick_hsb_h { + top: 40px; + left: 277px; +} +.colpick_hsb_s { + top: 67px; + left: 277px; +} +.colpick_hsb_b { + top: 94px; + left: 277px; +} +.colpick_hex_field { + width: 68px; + left: 207px; + top: 121px; +} +/*Text field container on focus*/ +.colpick_focus { + border-color: #999; +} +/*Field label container*/ +.colpick_field_letter { + position: absolute; + width: 12px; + height: 20px; + line-height: 20px; + padding-left: 4px; + background: #efefef; + border-right: 1px solid #bdbdbd; + font-weight: bold; + color:#777; +} +/*Text inputs*/ +.colpick_field input, .colpick_hex_field input { + position: absolute; + touch-action: none; + right: 11px; + margin: 0; + padding: 0; + height: 20px; + line-height: 20px; + background: transparent; + border: none; + font-size: 12px; + font-family: Arial, Helvetica, sans-serif; + color: #555; + text-align: right; + outline: none; +} +.colpick_hex_field input { + right: 4px; +} +/*Field up/down arrows*/ +.colpick_field_arrs { + position: absolute; + touch-action: none; + top: 0; + right: 0; + width: 9px; + height: 21px; + cursor: n-resize; +} +.colpick_field_uarr { + position: absolute; + touch-action: none; + top: 5px; + width: 0; + height: 0; + border-left: 4px solid transparent; + border-right: 4px solid transparent; + border-bottom: 4px solid #959595; +} +.colpick_field_darr { + position: absolute; + touch-action: none; + bottom:5px; + width: 0; + height: 0; + border-left: 4px solid transparent; + border-right: 4px solid transparent; + border-top: 4px solid #959595; +} +/*Submit/Select button*/ +.colpick_submit { + position: absolute; + touch-action: none; + left: 207px; + top: 149px; + width: 130px; + height: 22px; + line-height:22px; + background: #efefef; + text-align: center; + color: #555; + font-size: 12px; + font-weight:bold; + border: 1px solid #bdbdbd; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + border-radius: 3px; +} +.colpick_submit:hover { + background:#f3f3f3; + border-color:#999; + cursor: pointer; +} + +/*full layout with no submit button*/ +.colpick_full_ns .colpick_submit { + display:none; +} +.colpick_full_ns .colpick_new_color { + width: 130px; + height: 25px; +} +.colpick_full_ns .colpick_rgb_r, .colpick_full_ns .colpick_hsb_h { + top: 42px; +} +.colpick_full_ns .colpick_rgb_g, .colpick_full_ns .colpick_hsb_s { + top: 73px; +} +.colpick_full_ns .colpick_rgb_b, .colpick_full_ns .colpick_hsb_b { + top: 104px; +} +.colpick_full_ns .colpick_hex_field { + top: 135px; +} + +/*rgbhex layout*/ +.colpick_rgbhex .colpick_hsb_h, .colpick_rgbhex .colpick_hsb_s, .colpick_rgbhex .colpick_hsb_b { + display:none; +} +.colpick_rgbhex { + width:282px; +} +.colpick_rgbhex .colpick_field, .colpick_rgbhex .colpick_submit { + width:68px; +} +.colpick_rgbhex .colpick_new_color { + width:34px; + border-right:none; +} +.colpick_rgbhex .colpick_current_color { + width:34px; + left:240px; + border-left:none; +} + +/*rgbhex layout, no submit button*/ +.colpick_rgbhex_ns .colpick_submit { + display:none; +} +.colpick_rgbhex_ns .colpick_new_color{ + width:34px; + border: 1px solid #8f8f8f; +} +.colpick_rgbhex_ns .colpick_rgb_r { + top: 42px; +} +.colpick_rgbhex_ns .colpick_rgb_g { + top: 73px; +} +.colpick_rgbhex_ns .colpick_rgb_b { + top: 104px; +} +.colpick_rgbhex_ns .colpick_hex_field { + top: 135px; +} + +/*hex layout*/ +.colpick_hex .colpick_hsb_h, .colpick_hex .colpick_hsb_s, .colpick_hex .colpick_hsb_b, .colpick_hex .colpick_rgb_r, .colpick_hex .colpick_rgb_g, .colpick_hex .colpick_rgb_b { + display:none; +} +.colpick_hex { + width:206px; + height:201px; +} +.colpick_hex .colpick_hex_field { + width:72px; + height:25px; + top:168px; + left:80px; +} +.colpick_hex .colpick_hex_field div, .colpick_hex .colpick_hex_field input { + height: 25px; + line-height: 25px; +} +.colpick_hex .colpick_new_color { + left:9px; + top:168px; + width:30px; + border-right:none; +} +.colpick_hex .colpick_current_color { + left:39px; + top:168px; + width:30px; + border-left:none; +} +.colpick_hex .colpick_submit { + left:164px; + top: 168px; + width:30px; + height:25px; + line-height: 25px; +} + +/*hex layout, no submit button*/ +.colpick_hex_ns .colpick_submit { + display:none; +} +.colpick_hex_ns .colpick_hex_field { + width:80px; +} +.colpick_hex_ns .colpick_new_color{ + width:60px; + border: 1px solid #8f8f8f; +} + +/*Dark color scheme*/ +.colpick_dark { + background: #161616; + border-color: #2a2a2a; +} +.colpick_dark .colpick_color { + outline-color: #333; +} +.colpick_dark .colpick_hue { + border-color: #555; +} +.colpick_dark .colpick_field, .colpick_dark .colpick_hex_field { + background: #101010; + border-color: #2d2d2d; +} +.colpick_dark .colpick_field_letter { + background: #131313; + border-color: #2d2d2d; + color: #696969; +} +.colpick_dark .colpick_field input, .colpick_dark .colpick_hex_field input { + color: #7a7a7a; +} +.colpick_dark .colpick_field_uarr { + border-bottom-color:#696969; +} +.colpick_dark .colpick_field_darr { + border-top-color:#696969; +} +.colpick_dark .colpick_focus { + border-color:#444; +} +.colpick_dark .colpick_submit { + background: #131313; + border-color:#2d2d2d; + color:#7a7a7a; +} +.colpick_dark .colpick_submit:hover { + background-color:#101010; + border-color:#444; +} \ No newline at end of file diff --git a/scripts/simplifiedUI/system/html/css/edit-style.css b/scripts/simplifiedUI/system/html/css/edit-style.css new file mode 100644 index 0000000000..470e57ad6d --- /dev/null +++ b/scripts/simplifiedUI/system/html/css/edit-style.css @@ -0,0 +1,1796 @@ +/* +// edit-style.css +// +// Created by Ryan Huffman on 13 Nov 2014 +// Copyright 2014 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 +*/ + +@font-face { + font-family: Raleway-Regular; + src: url(../../../../resources/fonts/Raleway-Regular.ttf), /* Windows production */ + url(../../../../fonts/Raleway-Regular.ttf), /* OSX production */ + url(../../../../interface/resources/fonts/Raleway-Regular.ttf), /* Development, running script in /HiFi/examples */ + url(../fonts/Raleway-Regular.ttf); /* Marketplace script */ +} + +@font-face { + font-family: Raleway-Light; + src: url(../../../../resources/fonts/Raleway-Light.ttf), + url(../../../../fonts/Raleway-Light.ttf), + url(../../../../interface/resources/fonts/Raleway-Light.ttf), + url(../fonts/Raleway-Light.ttf); +} + +@font-face { + font-family: Raleway-Bold; + src: url(../../../../resources/fonts/Raleway-Bold.ttf), + url(../../../../fonts/Raleway-Bold.ttf), + url(../../../../interface/resources/fonts/Raleway-Bold.ttf), + url(../fonts/Raleway-Bold.ttf); +} + +@font-face { + font-family: Raleway-SemiBold; + src: url(../../../../resources/fonts/Raleway-SemiBold.ttf), + url(../../../../fonts/Raleway-SemiBold.ttf), + url(../../../../interface/resources/fonts/Raleway-SemiBold.ttf), + url(../fonts/Raleway-SemiBold.ttf); +} + +@font-face { + font-family: FiraSans-SemiBold; + src: url(../../../../resources/fonts/FiraSans-SemiBold.ttf), + url(../../../../fonts/FiraSans-SemiBold.ttf), + url(../../../../interface/resources/fonts/FiraSans-SemiBold.ttf), + url(../fonts/FiraSans-SemiBold.ttf); +} + +@font-face { + font-family: AnonymousPro-Regular; + src: url(../../../../resources/fonts/AnonymousPro-Regular.ttf), + url(../../../../fonts/AnonymousPro-Regular.ttf), + url(../../../../interface/resources/fonts/AnonymousPro-Regular.ttf), + url(../fonts/AnonymousPro-Regular.ttf); +} + +@font-face { + font-family: HiFi-Glyphs; + src: url(../../../../resources/fonts/hifi-glyphs.ttf), + url(../../../../fonts/hifi-glyphs.ttf), + url(../../../../interface/resources/fonts/hifi-glyphs.ttf), + url(../fonts/hifi-glyphs.ttf); +} + +* { + margin: 0; + padding: 0; +} + +body { + + color: #afafaf; + background-color: #404040; + font-family: Raleway-Regular; + font-size: 12px; + + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + + overflow-x: hidden; + overflow-y: auto; +} + +table { + font-family: FiraSans-SemiBold; + font-size: 15px; + color: #afafaf; + border-collapse: collapse; + width: 100%; + border: 2px solid #575757; + border-radius: 7px; +} + +thead { + font-family: Raleway-Regular; + font-size: 12px; + text-transform: uppercase; + background-color: #1c1c1c; + padding: 1px 0; + border-bottom: 1px solid #575757; + width: 100%; +} + +tbody { + width: 100%; + display: block; +} + +tfoot { + font-family: Raleway-Light; + font-size: 13px; + background-color: #1c1c1c; + border-top: 1px solid #575757; + width: 100%; +} + +tfoot tr { + background-color: #1c1cff; +} + +thead tr { + height: 26px; /* 28px with thead padding */ +} + +thead th { + height: 26px; + background-color: #1c1c1c; + border-right: 1px solid #575757; +} + +thead th:last-child { + border: none; +} + +tbody td { + height: 26px; +} + +tfoot td { + height: 18px; + width: 100%; + background-color: #1c1c1c; + margin-left: 12px; +} + +tr { + width: 100%; + cursor: pointer; +} + +tr:nth-child(odd) { + background-color: #2e2e2e; +} + +tr:nth-child(even) { + background-color: #1c1c1c; +} + +tr:focus { + outline: none; +} + +tr.selected { + color: #000000; + background-color: #00b4ef; +} + +tr.selected + tr.selected { + border-top: 1px solid #2e2e2e; +} + +th { + text-align: center; + word-wrap: nowrap; + white-space: nowrap; + padding-left: 12px; + padding-right: 12px; +} + +td { + overflow: hidden; + text-overflow: clip; + white-space: nowrap; + word-wrap: nowrap; + padding-left: 12px; + padding-right: 12px; +} + +td.hidden { + padding-left: 0; + padding-right: 0; +} + +td.url { + white-space: nowrap; + overflow: hidden; +} + + +input[type="text"], input[type="search"], input[type="number"], textarea { + margin: 0; + padding: 0 12px; + color: #afafaf; + background-color: #252525; + border: none; + font-family: FiraSans-SemiBold; + font-size: 15px; +} + +textarea { + font-family: AnonymousPro-Regular; + font-size: 16px; + padding-top: 5px; + padding-bottom: 5px; + min-height: 64px; + width: 100%; + resize: vertical; +} + +input::-webkit-input-placeholder { + font-style: italic; +} + +input:focus, textarea:focus, button:focus { + color: #fff; + background-color: #000; + outline: 1px solid #00b4ef; + outline-offset: -1px; +} + +input::selection, textarea::selection { + color: #000000; + background-color: #00b4ef; +} + +input.search { + border-radius: 14px; +} + +input.search:focus { + outline: none; + box-sizing: border-box; + height: 26px; + margin-top: 1px; + margin-bottom: 1px; + box-shadow: 0 0 0 1px #00b4ef; +} + +input:disabled, textarea:disabled, .draggable-number.text[disabled="disabled"] { + background-color: #383838; + color: #afafaf; +} + +input[type="text"] { + height: 28px; + width: 100%; +} + +input.multi-diff:not(:focus) + span.multi-diff, +textarea.multi-diff:not(:focus) + span.multi-diff, +.draggable-number.multi-diff>input:not(:focus)+span.multi-diff, +dl>dt.multi-diff:not(:focus) + span.multi-diff { + visibility: visible; + position: absolute; + display: inline-block; + z-index: 2; + top: 7.5px; + left: 20px; + max-width: 50px; + min-width: 10px; + width: 50%; + height: 13px; + background-image: linear-gradient(transparent 0%, transparent 10%, #afafaf 10%, #afafaf 20%, transparent 20%, transparent 45%, #afafaf 45%, #afafaf 55%, transparent 55%, transparent 80%, #afafaf 80%, #afafaf 90%, transparent 90%, transparent 100%); + background-repeat: no-repeat; + pointer-events: none; +} + +input.multi-diff:not(:focus)::-webkit-input-placeholder, input.multi-diff:not(:focus) { + color: transparent; +} + +.draggable-number.multi-diff .text { + color: transparent; +} + +.dropdown > span.multi-diff { + top: 5px; + left: 10px; +} + +.text, .url, .texture, .textarea { + position: relative; +} + +input[type="search"] { + height: 28px; + width: 100%; +} +input[type="search"]::-webkit-search-cancel-button { + -webkit-appearance: none; + height: 20px; + width: 20px; + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAB3RJTUUH4goNAQIFbBwsbwAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAZfSURBVDgRAVQGq/kAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA9PT0YAwMDBgAAAAD8/Pz5+vr67MrKyv0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAA+Pj4KAgICQgAAAE3///9RAQEBFQAAAAD////pAQEBu/39/ab+/v7BxcXF9gAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAADs7OzMEBASIAQEBRAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8AAACm+/v7cMXFxewAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAPT09OwEBAagBAQEcAQEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8AAAAA2f///2XCwsLDAAAAAAAAAAABAAAAAAAAAAA9PT0KAwMDt////z4AAAAAAAAAAAEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAcIBAQFJvr6+9gAAAAACAAAAAAAAAAAAAABg////PgEBAQAAAAAAS0tLADg4OAAAAAAAAAAAAP///wADAwMAQEBAACEhIQD///8A////AP7+/j76+vpWAAAAAAAAAAACAAAAAD09PQ8CAgJkAQEBAP///wD///8ACgoKAFhYWAAyMjIAAAAAAAICAgBGRkYAT09PABEREQAAAAAAAAAAAAAAAAACAgJwOjo6EAAAAAAEAAAAAAICAg8BAQExAAAAAAEBAQABAQEAsrKyAAoKCgBaWloA9/f3ABsbGwBISEgAtra2AM7OzgACAgIA////AP///wABAQEuBQUFDgAAAPAEAAAAAPz8/BkEBAQAAQEBAAAAAAAAAAAA+vr6AKioqAALCwsAZWVlAAcHBwC/v78Au7u7AAEBAQD///8AAAAAAAAAAAAAAAABAAAAAAAAAAACAAAAAAQEBOgBAQEAAQEBAAEBAQABAQEAAQEBAPz8/ADT09MADg4OAP39/QDQ0NAA/v7+AP///wAAAAAAAAAAAAEBAQABAQEAAQEBAAAAAAACAAAAAAAAAP8AAAD/AAAAAAAAAAAAAAAAAAAAACkpKQBQUFAAx8fHAObm5gBfX18AFxcXAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAP39/fz+/v7z////AP///wD///8AJycnAGFhYQDc3NwApaWlAJaWlgD29vYAZmZmABQUFAACAgIAAQEBAAEBAQABAQH1AAAA/AAAAAACAAAAAPr6+ukBAQGkAAAAAAAAAAABAQEAQEBAAObm5gCmpqYA+fn5APPz8wCdnZ0A////ACwsLAD///8AAAAAAAAAAAD///+k9vb26QAAAAABAAAAAAAAAAA+Pj4uAgICxgAAAAsAAAAAEBAQAPr6+gD29vYAAAAAAAAAAAABAQEAAgICAP///wD+/v4AAAAAAAAAAPL8/Pw/xMTE0AAAAAACAAAAAAAAAAD5+fnV////nQICAgABAQEA8fHxAPX19QABAQEAAAAAAAAAAAD///8A/v7+AP7+/gAAAAAAAAAAAP7+/p36+vrSAAAAAAAAAAADAAAAAAAAAADl5eX/ICAgwQAAAA////8q////BgEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD1/f39mAEBAXrGxsb7AAAAAAAAAAADAAAAAAAAAAAAAAAA4eHh/BgYGLsBAQHDBAQEHAAAACP///8AAQEBAAAAAAAAAAAAAAAA+////7QBAQFu+fn5m8bGxvoAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPz8/Cv7+/iUBAQFMAgICEQICAgD8/PzdAwMDs/j4+OvHx8f5AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8TUnpZ7EwQgAAAABJRU5ErkJggg==') +} + +input[type="number"] { + position: relative; + height: 28px; + width: 124px; +} +input[type=number] { + padding-right: 3px; +} +input[type=number]::-webkit-inner-spin-button { + opacity: 1.0; + display: block; + position: relative; + width: 10px; + height: 90%; + overflow: hidden; + font-family: HiFi-Glyphs; + font-size: 32px; + color: #afafaf; + cursor: pointer; + background-color: #000000; +} +input[type=number]::-webkit-inner-spin-button:before, +input[type=number]::-webkit-inner-spin-button:after { + position:absolute; + left: -19px; + line-height: 8px; + text-align: center; +} +input[type=number]::-webkit-inner-spin-button:before { + content: "6"; + top: 4px; +} +input[type=number]::-webkit-inner-spin-button:after { + content: "5"; + bottom: 4px; +} + +input[type=number].hover-up::-webkit-inner-spin-button:before, +input[type=number].hover-down::-webkit-inner-spin-button:after { + color: #ffffff; +} + +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 { + outline: none; +} + +input.no-spin::-webkit-outer-spin-button, +input.no-spin::-webkit-inner-spin-button { + display: none; + -webkit-appearance: none; + margin: 0; /* <-- Apparently some margin are still there even though it's hidden */ + padding-right: 12px; +} + +input[type=button], button.hifi-edit-button { + font-family: Raleway-Bold; + font-size: 13px; + text-transform: uppercase; + vertical-align: top; + height: 28px; + min-width: 120px; + padding: 0 18px; + margin-right: 6px; + border-radius: 5px; + border: none; + color: #fff; + background-color: #000; + background: linear-gradient(#343434 20%, #000 100%); + cursor: pointer; +} + +input[type=button].glyph, button.hifi-edit-button.glyph { + font-family: HiFi-Glyphs; + font-size: 20px; + text-transform: none; + min-width: 32px; + padding: 0; +} + +input[type=button].red, button.hifi-edit-button.red { + color: #fff; + background-color: #94132e; + background: linear-gradient(#d42043 20%, #94132e 100%); +} +input[type=button].blue, button.hifi-edit-button.blue { + color: #fff; + background-color: #1080b8; + background: linear-gradient(#00b4ef 20%, #1080b8 100%); +} +input[type=button].white, button.hifi-edit-button.white { + color: #121212; + background-color: #afafaf; + background: linear-gradient(#fff 20%, #afafaf 100%); +} + +input[type=button]:enabled:hover, button.hifi-edit-button:enabled:hover { + background: linear-gradient(#000, #000); + border: none; +} +input[type=button].red:enabled:hover, button.hifi-edit-button.red:enabled:hover { + background: linear-gradient(#d42043, #d42043); + border: none; +} +input[type=button].blue:enabled:hover, button.hifi-edit-button.blue:enabled:hover { + background: linear-gradient(#00b4ef, #00b4ef); + border: none; +} +input[type=button].white:enabled:hover, button.hifi-edit-button.white:enabled:hover { + background: linear-gradient(#fff, #fff); + border: none; +} + +input[type=button]:active, button.hifi-edit-button:active { + background: linear-gradient(#343434, #343434); +} +input[type=button].red:active, button.hifi-edit-button.red:active { + background: linear-gradient(#94132e, #94132e); +} +input[type=button].blue:active, button.hifi-edit-button.blue:active { + background: linear-gradient(#1080b8, #1080b8); +} +input[type=button].white:active, button.hifi-edit-button.white:active { + background: linear-gradient(#afafaf, #afafaf); +} + +input[type=button]:disabled, button.hifi-edit-button:disabled { + color: #252525; + background: linear-gradient(#575757 20%, #252525 100%); +} + +input[type=button][pressed=pressed], button.hifi-edit-button[pressed=pressed] { + color: #00b4ef; +} + +input[type=checkbox] { + display: none; +} +input[type=checkbox] + label { + padding-left: 24px; + background-repeat: no-repeat; + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAACpSURBVDhPY2xoaGD68+dPMSMjY9L////VgTQjAw4AlH8PxLOPHj1azWxjY1MBVNsBFBfBpwkEgNKcQGwtJyfHyATkF0KEiQdAzYlMQEIUyicFyDD9+/ePgRxMvsb///4zkIOZ/v0HmkAGHginYjGNGAzS+BpdkAj8mun/3//92DyPD//993cG88nTJ4+Zm5p/BSZeJYb/DEJADEzNOPF7hn8Mk69cvVIPAHN5pyfo70F5AAAAAElFTkSuQmCC); + cursor: pointer; +} +input[type=checkbox]:enabled + label:hover { + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAClSURBVDhPY2hoaGD6//9/6b9//64D8T8gGycASr/7+/dv5/79+1kYgIxKqDjRAKiniRFIv2JgYBAFYlLAE0aQ66AckgDjjx8/yNP44cMH8jS+fPmSPI0PHz4kT+PNmzfJ03jp0iXyNJ46dYo8jYcPHyYnAbxm+vnzZz8wLhlIwd+/f5/BrKSkdExCQuLrnz9/lIBpUAiIQekXF34PTGmTT548WQ8AokXg+rhVtPYAAAAASUVORK5CYII=); +} +input[type=checkbox]:checked + label { + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAFLSURBVDhPjZK9SgNBFIXvbCaQRDQq7mIhQRPBRisJKcwLWOobaCsExEaxcEEQe0trGysfwg0EwWoDsbFIJUaIBJOwus547saNP3FlPzgzzJxzL5edFbZtG77v7wkhtrXWS9gFRQC/DZ07jnOYKJfL+8ie4n7mvyIGdhpay+VyQuK8y5dPZoHuVtbpZcLi4wjJ1x4t316R9dDgBlsSi8mGu7pJjyJFzVaH+r7iqyHSELSQzVADjS0UgjlDKUUsLzVO98+9kSLGV5qaHXhjU0GWNSxk3hCIwnsfeMNCjTArLmHeUBodoLiE+R+jxuHPUZP4elGE3teonx2S/Q7lJzOUlkYQ+A4/xzyegzNhXmJpwTMXry9IFjcoa84O0r+QXpcK1cugCLREZadyoA19Ergxwf96nKjd1KqlYqmLQ540TUNwItUmRWdu3T36AODjwgpY9xqqAAAAAElFTkSuQmCC); +} +input[type=checkbox]:checked + label:hover { + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAEySURBVDhPnZLPSsNAEMa/XVPBCE0RhNy0OarP4Av4AD6JB0GwVBA8efBBxHsgh4CQswcRoUIpiIpVAm3zZ5M4szFSbQPBH3xkJvNNZskOer2eLIriKM/ze1JOcS1UHmdZduF5ngEKjr/fN4Z6+oKerwA2gxC4HAFPEWVLsAzgZAvYt3Q6Enw6jg7uBAaTFMNwhpnKdbXCkAJdy8ROu4XrXW2HTJIErHcFDD6nC02Mom8PwymeE2gvS0ZRBBaTlsOXEmdlrfLLOI7Bakrl/zWxCT8T/904f9QW/b06qtrCUdtFCqdjYs2Q2jAPX8c2XQd7Kr/wfV8vwIPs4Ga1ixe5Xrr/YFLTYfKIvWzM6ZtwXZdX7lxXG0L+sxXHcW5t254opRzawQ0S72+dPmjTroIgOP0CQSMt5LDn1T8AAAAASUVORK5CYII=); +} +input.multi-diff[type=checkbox] + label { + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAFbSURBVDhPY2xoaGD68+dPMSMjY9L////VgTQjAw4AlH8PxLOPHj1azWxjY1MBVNsBFBfBpwkEgNKcQGwtJyfHyALkF4IE34gqM9zU9WT4wicG4mIA1l/fGIyOL2EQeP8EZEAiC5AQBUlcMQ5ieMXIwfDo9SeG73/+gRXDAAsTI4Pd9wdgTVAgw/Tv3z8GEP7Jwctw78M3DE0goPr6BoPludVgdTAM1wgCv//9B9PIQOPNDYaAGxtRNIEw03+gYhDGBtSBNgVc3wiWR8dM//4DTQBidKD++jqD//X1YDlsGMWpMKD26jqD79V1GM5DxihOZQWGntqrawy+V9ZiOA0dw21k/f6JwerzHQbvS2swTMeGGfPz8l8BLRP9KizDwP0WHk+EwGum/3//94M8y/nmEdZAwIb//vs7g/nk6ZPHzE3NvwITrxLDfwYhIAamZpz4PcM/hslXrl6pBwAmfz5iaAlAuAAAAABJRU5ErkJggg==); +} +input.multi-diff[type=checkbox] + label:hover { + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAFPSURBVDhPjZJBSwJBHMXfrG6rWEkl1MFDGOihDn2JIIrqc3QJunbyFhUkRieD+hYepWteuxctXiJ1Q5xmdmZ3bWZTUHezfvAu/3lv3n+HRblcTrbb7fN+v/8eBMFgFpxz13Gcu3q9bqHb7V4M5/9GhatE3cIsy0o99YBKC3jliCWbBK43gK0MoDI9otfTB/vPBC9Uwu4xMC8IzSOSBsFxIYNqMTGcAIYQAlodD3j5/IqENIc5gqt1P/SNZKhaXR0a5E/5BEcrwH1xEHrGZbiuC604DpZ81AoiPJ/WROM4e4sSt3kaaRopNrg7z1FZdSLmcU2saqrX20lTXC5/RFabFmk2m+GLnBnbWJMOThJv4SV/QRqNBjNNM9UiGeQHdDiejZSSG5TSG71zjnVivyVOKlNLlEqlx+xCds7zvU31G6Z938dvEq4QjLMH27ZPvwHFVYQr3h7uHwAAAABJRU5ErkJggg==); +} + +.rgb.fstuple .color-picker.multi-diff:after { + width: 20px; + height: 20px; + content: ' '; + background: darkgray; + display: flex; + clip-path: polygon(0 0, 0 100%, 100% 100%); +} + +.icon-input input { + position: relative; + padding-left: 36px; +} +.icon-input span { + position: absolute; + left: 6px; + top: -2px; + font-family: HiFi-Glyphs; + font-size: 30px; + color: #afafaf; +} +.icon-input input:focus + span { + color: #ffffff; +} + +.icon { + font-family: HiFi-Glyphs; + color: white; +} + +#property-type-icon { + font-size: 50px; +} + +.selectable { + -webkit-touch-callout: text; + -webkit-user-select: text; + -khtml-user-select: text; + -moz-user-select: text; + -ms-user-select: text; + user-select: text; + cursor: text; +} + +.color-box { + display: inline-block; + width: 15pt; + height: 15pt; + border: 0.75pt solid black; + margin: 1.5pt; + cursor: pointer; +} + +.color-box.highlight { + width: 13.5pt; + height: 13.5pt; + border: 1.5pt solid black; +} + +#properties-list { + display: flex; + flex-direction: column; + + margin-top: 16px; +} + +#properties-list .fieldset { + position: relative; + /* 0.1px on the top is to prevent margin collapsing between this and it's first child */ + margin: 0 -21px 21px -21px; + padding: 0.1px 21px 0 21px; + border: none; + border-top: 1px rgb(90,90,90) solid; + box-shadow: 0 -1px 0 rgb(37,37,37); +} + +#properties-list .fieldset.fstuple, #properties-list .fieldset.fsrow { + margin-top: 21px; + border: none; + box-shadow: none; +} + +#properties-list > .fieldset[data-collapsed="true"] + .fieldset { + margin-top: 0; +} + +#properties-list > .fieldset[data-collapsed="true"] > *:not(div.legend) { + display: none !important; +} + +.section-header { + padding: 0 16px; + border-top: 1px rgb(90,90,90) solid; + box-shadow: 1px -1px 0 rgb(37,37,37); + border-bottom: 1px solid rgb(37, 37, 37); +} + +div.section-header, hr { + display: flex; + flex-flow: row nowrap; + padding: 10px 16px; + font-family: Raleway-Regular; + font-size: 12px; + color: #afafaf; + height: 28px; + text-transform: uppercase; + outline: none; + margin-bottom: 10px; + align-items: center; +} + +.section.minor { + margin: 0 21px; + box-shadow: 1px -1px 0 rgb(37,37,37); + border-left: 1px solid #575757; +} + +.container.property { + padding: 0 16px; +} + +.stretch { + width: 100%; +} + +div.section-header .label { + width: 100%; +} + +.section.minor div.section-header { + border-right: 0; +} + +div.section[collapsed="true"] > .container { + display: none; +} + +div.section[collapsed="true"], div.section[collapsed="true"] > .section-header { + margin-bottom: 0; +} + +.section.major { + margin-bottom: 20px; +} + +.section.minor.last { + margin-bottom: 20px; + border-bottom: 1px solid rgb(37,37,37); +} + +.section-header { + background-color: #373737; +} + + +.section-header span { + font-size: 30px; + font-family: HiFi-Glyphs; +} + +.triple-label { + text-transform: uppercase; + text-align: center; + padding: 6px 0; + cursor: default; +} + +.triple-item { + margin-right: 10px; +} + +.triple-item.rgb.fstuple { + display: block !important; +} + +.section-header[collapsed="true"] { + margin-bottom: -21px; +} + +#properties-list .sub-section-header { + border-top: none; + box-shadow: none; + margin-top: 8px; +} + +.sub-section-header + .property { + margin-top: 0; +} + +hr { + border: none; + padding-top: 2px; +} + +.property { + min-height: 28px; +} + +.property.checkbox { + width: auto; +} + +span.indented { + padding-left: 16px; +} + +.property label, .number label { + display: table-cell; + vertical-align: middle; + font-family: Raleway-SemiBold; + font-size: 14px; +} +.property label .unit, .number label .unit { + margin-left: 8px; + font-family: Raleway-Light; + font-size: 13px; +} + +.property div.legend, .number div.legend { + display: table-cell; + vertical-align: middle; + font-family: Raleway-SemiBold; + font-size: 14px; +} +.property div.legend .unit, .number div.legend .unit { + margin-left: 8px; + font-family: Raleway-Light; + font-size: 13px; +} + +.value { + display: block; + min-height: 18px; +} +.value label { + display: inline-block; + vertical-align: top; +} +.value div.legend { + display: inline-block; + vertical-align: top; + width: 48px; +} +.value span { + font-size: 15px; + margin-right: 4px; +} + +#placeholder-property-type { + display: flex; + align-items: center; + width: auto; + margin-right: 20px; +} + +#placeholder-property-locked { + margin-left: 6px; +} + +.checkbox + .checkbox { + margin-top: 0; +} + +.checkbox-sub-props { + margin-top: 18px; +} + +.property .number { + float: left; +} +.property .number + .number { + margin-left: 10px; +} + +.property.range label{ + padding-bottom: 3px; +} +.property.range input[type=number]{ + margin-left: 0.8rem; + width: 5.4rem; + height: 1.8rem; +} + +.dropdown { + position: relative; + margin-bottom: -17px; +} + +.dropdown select { + clear: both; +} + +.dropdown dl { + clear: both; + cursor: pointer; + font-family: FiraSans-SemiBold; + font-size: 15px; + width: 292px; + height: 28px; + padding: 0 28px 0 12px; + color: #afafaf; + background: #575757; + position: relative; + display: flex; + align-items: center; +} + +.dropdown dl[dropped="true"] { + color: #404040; + background: linear-gradient(#afafaf, #afafaf); + z-index: 998; +} + +.dropdown dt { + height: 100%; + box-sizing: border-box; + border-right: 1px solid #121212; + width: 100%; +} +.dropdown dt:hover { + color: #404040; +} +.dropdown dt:focus { + outline: none; +} +.dropdown dt span:first-child { + display: inline-block; + position: relative; + top: 5px; +} +.dropdown dt span:last-child { + font-family: HiFi-Glyphs; + font-size: 42px; + float: right; + margin-right: -48px; + position: relative; + left: -12px; + top: -9px; +} + +.dropdown dd { + position: absolute; + top: 28px; + left: 3px; + display: none; +} +.dropdown dl[dropped="true"] dd { + display: block; +} + +.dropdown li { + list-style-type: none; + padding: 3px 0 1px 12px; + width: 320px; + height: auto; + font-family: FiraSans-SemiBold; + font-size: 15px; + color: #404040; + background-color: #afafaf; + z-index: 999; +} +.dropdown li:hover { + background-color: #00b4ef; +} + +.dropdown dl[disabled="disabled"], .dropdown dl[disabled="disabled"][dropped="true"] { + color: #252525; + background: linear-gradient(#575757 20%, #252525 100%); +} +.dropdown dl[disabled="disabled"] dd { + display: none; +} +.dropdown dl[disabled="disabled"] dt:hover { + color: #252525; +} + +.multiselect-box { + position: absolute; +} +.multiselect-box select { + font-family: FiraSans-SemiBold; + font-size: 15px; + color: #afafaf; + background-color: #252525; + border: none; + text-align-last: center; +} +.over-select { + position: absolute; + left: 0; + right: 0; + top: 0; + bottom: 0; +} +.multiselect-options { + position: absolute; + display: none; + border: none; +} +.multiselect-options span { + font-family: hifi-glyphs; + font-size: 13px; + color: #000000; +} +.multiselect-options label { + z-index: 2; + display: block; + font-family: FiraSans-SemiBold; + font-size: 11px; + color: #000000; + background-color: #afafaf; +} +.multiselect-options label:hover { + background-color: #1e90ff; +} +.multiselect-options input[type=checkbox] + label { + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACwAAAAsCAYAAAAehFoBAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAB3RJTUUH4goSADUOYnF4LQAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAIMSURBVFjD7ZmxkqowFIZ/7mwJPen1AezV3t6hFvrQhweAHvrQ8wL2xt4HwD7ppd+tvHOvu0gCYdEZTsmAfpNzzpcTcAB84o3iD94sZuCx4+Pxwvl8dl4JcL1ef84lMQPPwBZDSgkp5XsASylBKUUYhhBCvDbw7XYDpRRKKTRNA8YYOOevC5ymKZRS/13jnHdCTwLMOW8tAc45GGNomuY1gKuq6lxFIQQopdMDXy4X5HmudW8URdMCSynBGNOG3Ww20wHf9dVWl4+wbav7a8CMsW9G+Cm22+1T2F8BzvMc1+u18z5CCJIkseNhKSX2+z2qqjLWl84zhBAURQHXde0A31Oa57nWbqSrLwDwPA9FUcD3fTtb82NKu8QOAHVda+srSRJt2E7gtpQKIXA4HH6csmzpyxj4dDo9TalSCpRS1HX9TV86RujSlxGwlBJpmnY+rJRCGIZ/s2BTX9qnZgBwHAee52mJ/l7nx+PRqr6MVtj3fZRlaVRf/5aGDX0Z17DrusiyrHfqhuqrt9aiKEIcx4OBTfU1aOMIggBlWYIQ0utP+uhr8CyxXC5RFIUxdBAE1srKePgxbcbVamWlnAZNa7rNSAhBlmWv8yLlWTPa0Nco83BbM2ZZZsUIowzwj80YxzEWi8VoB4IPGz9yb0YhBHa73agnGGtHJNd1R4ed9FVV33Awf6ebgd8b+Av9A/rq6s3hjgAAAABJRU5ErkJggg=='); + background-size: 11px 11px; + background-position: top 5px left 14px; +} +.multiselect-options input[type=checkbox]:enabled + label:hover { + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACwAAAAsCAYAAAAehFoBAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAB3RJTUUH4goSADUOYnF4LQAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAIMSURBVFjD7ZmxkqowFIZ/7mwJPen1AezV3t6hFvrQhweAHvrQ8wL2xt4HwD7ppd+tvHOvu0gCYdEZTsmAfpNzzpcTcAB84o3iD94sZuCx4+Pxwvl8dl4JcL1ef84lMQPPwBZDSgkp5XsASylBKUUYhhBCvDbw7XYDpRRKKTRNA8YYOOevC5ymKZRS/13jnHdCTwLMOW8tAc45GGNomuY1gKuq6lxFIQQopdMDXy4X5HmudW8URdMCSynBGNOG3Ww20wHf9dVWl4+wbav7a8CMsW9G+Cm22+1T2F8BzvMc1+u18z5CCJIkseNhKSX2+z2qqjLWl84zhBAURQHXde0A31Oa57nWbqSrLwDwPA9FUcD3fTtb82NKu8QOAHVda+srSRJt2E7gtpQKIXA4HH6csmzpyxj4dDo9TalSCpRS1HX9TV86RujSlxGwlBJpmnY+rJRCGIZ/s2BTX9qnZgBwHAee52mJ/l7nx+PRqr6MVtj3fZRlaVRf/5aGDX0Z17DrusiyrHfqhuqrt9aiKEIcx4OBTfU1aOMIggBlWYIQ0utP+uhr8CyxXC5RFIUxdBAE1srKePgxbcbVamWlnAZNa7rNSAhBlmWv8yLlWTPa0Nco83BbM2ZZZsUIowzwj80YxzEWi8VoB4IPGz9yb0YhBHa73agnGGtHJNd1R4ed9FVV33Awf6ebgd8b+Av9A/rq6s3hjgAAAABJRU5ErkJggg=='); +} +.multiselect-options input[type=checkbox]:checked + label { + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACwAAAAsCAYAAAAehFoBAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAB3RJTUUH4goSADMveELP9QAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAIqSURBVFjD7ZmxkqowFIb/7GwJPfT6APZib+9QC33o4QGghz70vIC9sfcBsE966bPNWlxnlQTDRWc4JUT4hpPz5SQSAAofFF/4sJiBx47v+wun04m8E+B6vVbzlJiBZ2CLIYRQQgj1EcBCCEUpRRRF4Jyrtwa+Xq+glEJKia7rkKYpGGPqbYHzPFdSyn+uMcZ6oScBZowpzvmje0jTVHVd9x7ATdMoxtjTMZxzUErV5MDn81mVZak1No7jab+wEEKlaaoNGwQBmQz4pq9H8/IeNo5jMmnRpWmKeyP8FZvN5insfwEuy1JdLpfecb7vI8uy3tb2Szelu91ONU1jtP9jjKmmabRgq6qC4zh2VrpbSsuy1FqNdPUFAK7roqoqeJ6ntXH4Mk1pn9gBoG1bbX1lWaYN2wv8KKWcc+z3+z+7LFv6MgY+Ho9PUyqlBKUUbduqe33pGKFPX0bAQgiV53nvj6WUiKIIt2K0qS/tXTMAEELguq6W6H/nOQ6Hg1V9GX1hz/NIXdckCALtB7Vta1VfxnPYcRwURUEeNSGmYaqvwVqL45gkSfIysKm+Xlo4wjAkdV3D9/1BLxmir5d7ieVySaqqMoYOw3CwEV5ufkyLcbVaIUkSq2d1xt2abjH6vo+iKKwfLA5uL58Vow19jdIPPyrGoiisGGGUBv6+GJMkwWKxGO2M+dvGQ36LEZxztd1uRz0Qt7ZFchwHY8NOelQ1NAjm/+lm4M8G/gH2zx33BSr7jAAAAABJRU5ErkJggg=='); +} +.multiselect-options input[type=checkbox]:checked + label:hover { + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACwAAAAsCAYAAAAehFoBAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAB3RJTUUH4goSADMveELP9QAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAIqSURBVFjD7ZmxkqowFIb/7GwJPfT6APZib+9QC33o4QGghz70vIC9sfcBsE966bPNWlxnlQTDRWc4JUT4hpPz5SQSAAofFF/4sJiBx47v+wun04m8E+B6vVbzlJiBZ2CLIYRQQgj1EcBCCEUpRRRF4Jyrtwa+Xq+glEJKia7rkKYpGGPqbYHzPFdSyn+uMcZ6oScBZowpzvmje0jTVHVd9x7ATdMoxtjTMZxzUErV5MDn81mVZak1No7jab+wEEKlaaoNGwQBmQz4pq9H8/IeNo5jMmnRpWmKeyP8FZvN5insfwEuy1JdLpfecb7vI8uy3tb2Szelu91ONU1jtP9jjKmmabRgq6qC4zh2VrpbSsuy1FqNdPUFAK7roqoqeJ6ntXH4Mk1pn9gBoG1bbX1lWaYN2wv8KKWcc+z3+z+7LFv6MgY+Ho9PUyqlBKUUbduqe33pGKFPX0bAQgiV53nvj6WUiKIIt2K0qS/tXTMAEELguq6W6H/nOQ6Hg1V9GX1hz/NIXdckCALtB7Vta1VfxnPYcRwURUEeNSGmYaqvwVqL45gkSfIysKm+Xlo4wjAkdV3D9/1BLxmir5d7ieVySaqqMoYOw3CwEV5ufkyLcbVaIUkSq2d1xt2abjH6vo+iKKwfLA5uL58Vow19jdIPPyrGoiisGGGUBv6+GJMkwWKxGO2M+dvGQ36LEZxztd1uRz0Qt7ZFchwHY8NOelQ1NAjm/+lm4M8G/gH2zx33BSr7jAAAAABJRU5ErkJggg=='); +} + +.dynamic-multiselect { + position: relative; + top: 6px; + padding-bottom: 6px; +} + +div.refresh { + box-sizing: border-box; + padding-right: 44px; +} +div.refresh input[type="button"] { + float: right; + margin-right: -44px; + position: relative; + left: 10px; +} + +.color-picker { + box-sizing: border-box; + width: 26px; + height: 26px; + border: 3px solid #2B2B2B; + cursor: pointer; +} +.color-picker:focus { + outline: none; +} +.color-picker[active="true"] { + border-color: #000; +} + +.color-picker[disabled="disabled"] { + border-color: #afafaf; +} + +.colpick { + z-index: 3; +} +.colpick[disabled="disabled"] { + display: none !important; +} + +.rgb label { + float: left; + margin-top: 10px; + margin-left: 21px; +} +.rgb label + * { + clear: both; +} + +.rgb div.legend { + float: left; + margin-top: 10px; + margin-left: 21px; +} +.rgb div.legend + * { + clear: both; +} + +.draggable-number-container { + flex: 0 1 124px; +} +.draggable-number { + position: relative; + height: 28px; + flex: 0 1 124px; + display: flex; + align-items: center; +} + +.draggable-number .text { + position: absolute; + display: inline-block; + color: #afafaf; + background-color: #252525; + font-family: FiraSans-SemiBold; + font-size: 15px; + margin: 0; + padding: 0 16px; + height: 28px; + width: 100%; + line-height: 2; + box-sizing: border-box; + z-index: 1; +} +.draggable-number .text:hover { + cursor: ew-resize; +} +.draggable-number .left-arrow, .draggable-number .right-arrow { + position: absolute; + display: inline-block; + font-family: HiFi-Glyphs; + font-size: 20px; + z-index: 2; +} +.draggable-number span:hover { + cursor: default; +} +.draggable-number .left-arrow { + top: 3px; + left: 0; + transform: rotate(180deg); +} +.draggable-number .right-arrow { + top: 3px; + right: 0; +} +.draggable-number input[type=number] { + position: absolute; + right: 0; + width: 100%; +} +.draggable-number input[type=button] { + position: absolute; + top: 0; +} +.draggable-number input::-webkit-inner-spin-button { + -webkit-appearance: none; + visibility: hidden; +} +.draggable-number.fstuple { + height: 28px; + width: 124px; + left: 12px; +} +.draggable-number.fstuple + .draggable-number.fstuple { + margin-left: 28px; +} +.draggable-number.fstuple input { + right: -10px; +} +.draggable-number.fstuple .sublabel { + position: absolute; + top: 6px; + left: -16px; + font-family: FiraSans-SemiBold; + font-size: 15px; +} + +.rect .rect-row { + margin-bottom: 8px; +} + +.row .property { + width: auto; + display: inline-block; + margin-right: 6px; +} +.row .property:last-child { + margin-right: 0; +} +.row .property input { + clear: both; + float: left; +} + +.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,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAACXBIWXMAAAsTAAALEwEAmpwYAAAKT2lDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVNnVFPpFj333vRCS4iAlEtvUhUIIFJCi4AUkSYqIQkQSoghodkVUcERRUUEG8igiAOOjoCMFVEsDIoK2AfkIaKOg6OIisr74Xuja9a89+bN/rXXPues852zzwfACAyWSDNRNYAMqUIeEeCDx8TG4eQuQIEKJHAAEAizZCFz/SMBAPh+PDwrIsAHvgABeNMLCADATZvAMByH/w/qQplcAYCEAcB0kThLCIAUAEB6jkKmAEBGAYCdmCZTAKAEAGDLY2LjAFAtAGAnf+bTAICd+Jl7AQBblCEVAaCRACATZYhEAGg7AKzPVopFAFgwABRmS8Q5ANgtADBJV2ZIALC3AMDOEAuyAAgMADBRiIUpAAR7AGDIIyN4AISZABRG8lc88SuuEOcqAAB4mbI8uSQ5RYFbCC1xB1dXLh4ozkkXKxQ2YQJhmkAuwnmZGTKBNA/g88wAAKCRFRHgg/P9eM4Ors7ONo62Dl8t6r8G/yJiYuP+5c+rcEAAAOF0ftH+LC+zGoA7BoBt/qIl7gRoXgugdfeLZrIPQLUAoOnaV/Nw+H48PEWhkLnZ2eXk5NhKxEJbYcpXff5nwl/AV/1s+X48/Pf14L7iJIEyXYFHBPjgwsz0TKUcz5IJhGLc5o9H/LcL//wd0yLESWK5WCoU41EScY5EmozzMqUiiUKSKcUl0v9k4t8s+wM+3zUAsGo+AXuRLahdYwP2SycQWHTA4vcAAPK7b8HUKAgDgGiD4c93/+8//UegJQCAZkmScQAAXkQkLlTKsz/HCAAARKCBKrBBG/TBGCzABhzBBdzBC/xgNoRCJMTCQhBCCmSAHHJgKayCQiiGzbAdKmAv1EAdNMBRaIaTcA4uwlW4Dj1wD/phCJ7BKLyBCQRByAgTYSHaiAFiilgjjggXmYX4IcFIBBKLJCDJiBRRIkuRNUgxUopUIFVIHfI9cgI5h1xGupE7yAAygvyGvEcxlIGyUT3UDLVDuag3GoRGogvQZHQxmo8WoJvQcrQaPYw2oefQq2gP2o8+Q8cwwOgYBzPEbDAuxsNCsTgsCZNjy7EirAyrxhqwVqwDu4n1Y8+xdwQSgUXACTYEd0IgYR5BSFhMWE7YSKggHCQ0EdoJNwkDhFHCJyKTqEu0JroR+cQYYjIxh1hILCPWEo8TLxB7iEPENyQSiUMyJ7mQAkmxpFTSEtJG0m5SI+ksqZs0SBojk8naZGuyBzmULCAryIXkneTD5DPkG+Qh8lsKnWJAcaT4U+IoUspqShnlEOU05QZlmDJBVaOaUt2ooVQRNY9aQq2htlKvUYeoEzR1mjnNgxZJS6WtopXTGmgXaPdpr+h0uhHdlR5Ol9BX0svpR+iX6AP0dwwNhhWDx4hnKBmbGAcYZxl3GK+YTKYZ04sZx1QwNzHrmOeZD5lvVVgqtip8FZHKCpVKlSaVGyovVKmqpqreqgtV81XLVI+pXlN9rkZVM1PjqQnUlqtVqp1Q61MbU2epO6iHqmeob1Q/pH5Z/YkGWcNMw09DpFGgsV/jvMYgC2MZs3gsIWsNq4Z1gTXEJrHN2Xx2KruY/R27iz2qqaE5QzNKM1ezUvOUZj8H45hx+Jx0TgnnKKeX836K3hTvKeIpG6Y0TLkxZVxrqpaXllirSKtRq0frvTau7aedpr1Fu1n7gQ5Bx0onXCdHZ4/OBZ3nU9lT3acKpxZNPTr1ri6qa6UbobtEd79up+6Ynr5egJ5Mb6feeb3n+hx9L/1U/W36p/VHDFgGswwkBtsMzhg8xTVxbzwdL8fb8VFDXcNAQ6VhlWGX4YSRudE8o9VGjUYPjGnGXOMk423GbcajJgYmISZLTepN7ppSTbmmKaY7TDtMx83MzaLN1pk1mz0x1zLnm+eb15vft2BaeFostqi2uGVJsuRaplnutrxuhVo5WaVYVVpds0atna0l1rutu6cRp7lOk06rntZnw7Dxtsm2qbcZsOXYBtuutm22fWFnYhdnt8Wuw+6TvZN9un2N/T0HDYfZDqsdWh1+c7RyFDpWOt6azpzuP33F9JbpL2dYzxDP2DPjthPLKcRpnVOb00dnF2e5c4PziIuJS4LLLpc+Lpsbxt3IveRKdPVxXeF60vWdm7Obwu2o26/uNu5p7ofcn8w0nymeWTNz0MPIQ+BR5dE/C5+VMGvfrH5PQ0+BZ7XnIy9jL5FXrdewt6V3qvdh7xc+9j5yn+M+4zw33jLeWV/MN8C3yLfLT8Nvnl+F30N/I/9k/3r/0QCngCUBZwOJgUGBWwL7+Hp8Ib+OPzrbZfay2e1BjKC5QRVBj4KtguXBrSFoyOyQrSH355jOkc5pDoVQfujW0Adh5mGLw34MJ4WHhVeGP45wiFga0TGXNXfR3ENz30T6RJZE3ptnMU85ry1KNSo+qi5qPNo3ujS6P8YuZlnM1VidWElsSxw5LiquNm5svt/87fOH4p3iC+N7F5gvyF1weaHOwvSFpxapLhIsOpZATIhOOJTwQRAqqBaMJfITdyWOCnnCHcJnIi/RNtGI2ENcKh5O8kgqTXqS7JG8NXkkxTOlLOW5hCepkLxMDUzdmzqeFpp2IG0yPTq9MYOSkZBxQqohTZO2Z+pn5mZ2y6xlhbL+xW6Lty8elQfJa7OQrAVZLQq2QqboVFoo1yoHsmdlV2a/zYnKOZarnivN7cyzytuQN5zvn//tEsIS4ZK2pYZLVy0dWOa9rGo5sjxxedsK4xUFK4ZWBqw8uIq2Km3VT6vtV5eufr0mek1rgV7ByoLBtQFr6wtVCuWFfevc1+1dT1gvWd+1YfqGnRs+FYmKrhTbF5cVf9go3HjlG4dvyr+Z3JS0qavEuWTPZtJm6ebeLZ5bDpaql+aXDm4N2dq0Dd9WtO319kXbL5fNKNu7g7ZDuaO/PLi8ZafJzs07P1SkVPRU+lQ27tLdtWHX+G7R7ht7vPY07NXbW7z3/T7JvttVAVVN1WbVZftJ+7P3P66Jqun4lvttXa1ObXHtxwPSA/0HIw6217nU1R3SPVRSj9Yr60cOxx++/p3vdy0NNg1VjZzG4iNwRHnk6fcJ3/ceDTradox7rOEH0x92HWcdL2pCmvKaRptTmvtbYlu6T8w+0dbq3nr8R9sfD5w0PFl5SvNUyWna6YLTk2fyz4ydlZ19fi753GDborZ752PO32oPb++6EHTh0kX/i+c7vDvOXPK4dPKy2+UTV7hXmq86X23qdOo8/pPTT8e7nLuarrlca7nuer21e2b36RueN87d9L158Rb/1tWeOT3dvfN6b/fF9/XfFt1+cif9zsu72Xcn7q28T7xf9EDtQdlD3YfVP1v+3Njv3H9qwHeg89HcR/cGhYPP/pH1jw9DBY+Zj8uGDYbrnjg+OTniP3L96fynQ89kzyaeF/6i/suuFxYvfvjV69fO0ZjRoZfyl5O/bXyl/erA6xmv28bCxh6+yXgzMV70VvvtwXfcdx3vo98PT+R8IH8o/2j5sfVT0Kf7kxmTk/8EA5jz/GMzLdsAAAAgY0hSTQAAeiUAAICDAAD5/wAAgOkAAHUwAADqYAAAOpgAABdvkl/FRgAABhNJREFUeNrsnVFy4joQRVsSCwAqBMwqsrRsIavMEkICoeAf2+8j1R5ZGDBgpLzoUDVVmTT2dc8It/paOpi3t7faOSciImVZyn6/l6qqRETEWivj8VistYPFd7ud1HUtIiLGGBmPx5JKX0RkMplIzvmPnHNijBERaS7Ef1lrB40bY1oXgH5a/ZH+8P7+LlVVycfHR/MGa60URdGcYOi4MUaKomhGaGx9EZHlcplMP2X+Ly8vPwOgLEtxzklVVVJVVfOznqAsy9YFXhuvqqq5AF/Lj+srtr7+LpV+yvz1mNF+vxcRkdVqJdZaeXp6ap1ws9m0TjibzVoj6lJ8vV6fjJdlKev1ujViU+j7t8tc8p9Op1KWpYw06L9JL0Av0r9l+jXl3nhd11JV1VE8tn5YM3PI3xjzoxVOGvyDU7zQj6s/0tGmI++amtNV087F9Wf/FnVPzRtCXz8RdV1nlb/efUbaJy4Wi0FqzjU1yRgjs9ls0Jp3jb6IyPPzczL9lPkvFot/dwCtB/om/x9oyJoXxps65NW8mPpdNTeX/JtBEtYE/+AUL/Tj6g/qA3TVnD41a6g++Bp9rYOp9FPnH80HOBcvy1I2m81D++BL+o/2AX5r/vgA+AD4AOif8AH8EdpVcy71sX3jWp/8W2AKff/TkUv+Oufr9AF0YuKc66xJ18T7eNP3nP9WfZ0EzufzJPqp8y+KQuq67vYBdETqCDpVU/rEw5oUnr+rD46h73/qUuinzh8fAP22D6AjxznXcqq6akrf+KmaFB6vf4+t7/sAelfIJf/GB9jtdmKMkdVq1dQM3zg4VVNU/NY+1Bgjh8Oh6YM1+dj6X19fzXwgp/wbH0DFtS7oyf0RdKqmhPFr+1RdseKfP7a+Px/IKX98APTbPoDOJrv60L417d54TH3V8lfS5pT/yfUA6/X6qOZcqkm3xrUm6X9CTH3fB0ihnzr/Ix9A/3T1qbfWpGvjMfX9T0UK/dT54wOg/88H8EfGPTVr6D740frhLDmn/Hv5AH1qku9t31KTzh3/aP1LPsBfzr+XDxCO0K6ack/N6qp5MfUv+QB/Of/ePsCQfWmfc6EfV3/kjzZrrRwOh9YtKHSm/LjOH3yrMTzej4c1y//51PHoP0a/tR7AOSdFURw9rz5VU049zw7jl2qWrosP++BY+iI/+wJS6afMv9kXoA6gvimsieHzZr/m6MTp3PPuc3G9SP95OPpx9JtOgT4cHwA+QCJ9+ADwAeADsC+AfQHo/4b1APAB4APAB4APAB8APgB9OD4AfAD4AFFqEnwA+AD4APgA6P86HwA+AHyAZhIBHwA+AHwA+AD04X/eB4APAB8APgB8APgA8AHow/P0AeADwAeADwAfAD4AfAD68Px8APgA8AHgA8AHgA8AH0DO70/v6lHvjaOfVn8U/iLcXx5OUML96X49vRTX3/nPw9FPo9+sB5hMJuKck+VyeVRTrLWtdfNdcf95eldNCuOfn5+tSYy/Pz+2voi0fICc8p/P5z93gJAPEN4+wufN4evaePj99eH+ePTj6p/1Abp60kt9Ksf/v46HDwAfAD6A/6gUPgD7AtgXwPP4DNcDwAeADwAfAD4AfAD4ADyPz289AHyA+Pqp84cPIPAB8AHwAfAB8AHgA7Q+HfAB4APAB4APAB+APjw3HwA+AHwA+ADwAeADwAegD8/TB4APAB8APgB8APgA8AHow/PzAeADwAeADwAfAD4AfACJ//316KfVH/mjLeb31+vx/kWhH0+/tR7AOSdFUUT9/nq9oK4+OJa+iLT25+eUf7MvIOQDxPr+en2F++PRj6PfdAr04fgA8AES6cMHgA8AH4B9AewLQP83rAeADwAfAD4AfAD4APAB6MPxAeADwAeIUpPgA8AHwAfAB0D/1/kA8AHgAzSTCPgA8AHgA8AHoA//8z4AfAD4APAB4APAB4APQB+epw8AHwA+AHwA+ADwAeAD0Ifn5wPAB4APAB8APgB8gBz5AOb19bX2TYLpdNpqQ7bbbctJGjJeVZVst9vWLSu2/vf3t+Sc/yicFIRr0C7Fu76f/lw8XBePflr9/wYAqWwWUSLcO54AAAAASUVORK5CYII='); +} +.texture-image.no-texture { + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAACXBIWXMAAAsTAAALEwEAmpwYAAAKT2lDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVNnVFPpFj333vRCS4iAlEtvUhUIIFJCi4AUkSYqIQkQSoghodkVUcERRUUEG8igiAOOjoCMFVEsDIoK2AfkIaKOg6OIisr74Xuja9a89+bN/rXXPues852zzwfACAyWSDNRNYAMqUIeEeCDx8TG4eQuQIEKJHAAEAizZCFz/SMBAPh+PDwrIsAHvgABeNMLCADATZvAMByH/w/qQplcAYCEAcB0kThLCIAUAEB6jkKmAEBGAYCdmCZTAKAEAGDLY2LjAFAtAGAnf+bTAICd+Jl7AQBblCEVAaCRACATZYhEAGg7AKzPVopFAFgwABRmS8Q5ANgtADBJV2ZIALC3AMDOEAuyAAgMADBRiIUpAAR7AGDIIyN4AISZABRG8lc88SuuEOcqAAB4mbI8uSQ5RYFbCC1xB1dXLh4ozkkXKxQ2YQJhmkAuwnmZGTKBNA/g88wAAKCRFRHgg/P9eM4Ors7ONo62Dl8t6r8G/yJiYuP+5c+rcEAAAOF0ftH+LC+zGoA7BoBt/qIl7gRoXgugdfeLZrIPQLUAoOnaV/Nw+H48PEWhkLnZ2eXk5NhKxEJbYcpXff5nwl/AV/1s+X48/Pf14L7iJIEyXYFHBPjgwsz0TKUcz5IJhGLc5o9H/LcL//wd0yLESWK5WCoU41EScY5EmozzMqUiiUKSKcUl0v9k4t8s+wM+3zUAsGo+AXuRLahdYwP2SycQWHTA4vcAAPK7b8HUKAgDgGiD4c93/+8//UegJQCAZkmScQAAXkQkLlTKsz/HCAAARKCBKrBBG/TBGCzABhzBBdzBC/xgNoRCJMTCQhBCCmSAHHJgKayCQiiGzbAdKmAv1EAdNMBRaIaTcA4uwlW4Dj1wD/phCJ7BKLyBCQRByAgTYSHaiAFiilgjjggXmYX4IcFIBBKLJCDJiBRRIkuRNUgxUopUIFVIHfI9cgI5h1xGupE7yAAygvyGvEcxlIGyUT3UDLVDuag3GoRGogvQZHQxmo8WoJvQcrQaPYw2oefQq2gP2o8+Q8cwwOgYBzPEbDAuxsNCsTgsCZNjy7EirAyrxhqwVqwDu4n1Y8+xdwQSgUXACTYEd0IgYR5BSFhMWE7YSKggHCQ0EdoJNwkDhFHCJyKTqEu0JroR+cQYYjIxh1hILCPWEo8TLxB7iEPENyQSiUMyJ7mQAkmxpFTSEtJG0m5SI+ksqZs0SBojk8naZGuyBzmULCAryIXkneTD5DPkG+Qh8lsKnWJAcaT4U+IoUspqShnlEOU05QZlmDJBVaOaUt2ooVQRNY9aQq2htlKvUYeoEzR1mjnNgxZJS6WtopXTGmgXaPdpr+h0uhHdlR5Ol9BX0svpR+iX6AP0dwwNhhWDx4hnKBmbGAcYZxl3GK+YTKYZ04sZx1QwNzHrmOeZD5lvVVgqtip8FZHKCpVKlSaVGyovVKmqpqreqgtV81XLVI+pXlN9rkZVM1PjqQnUlqtVqp1Q61MbU2epO6iHqmeob1Q/pH5Z/YkGWcNMw09DpFGgsV/jvMYgC2MZs3gsIWsNq4Z1gTXEJrHN2Xx2KruY/R27iz2qqaE5QzNKM1ezUvOUZj8H45hx+Jx0TgnnKKeX836K3hTvKeIpG6Y0TLkxZVxrqpaXllirSKtRq0frvTau7aedpr1Fu1n7gQ5Bx0onXCdHZ4/OBZ3nU9lT3acKpxZNPTr1ri6qa6UbobtEd79up+6Ynr5egJ5Mb6feeb3n+hx9L/1U/W36p/VHDFgGswwkBtsMzhg8xTVxbzwdL8fb8VFDXcNAQ6VhlWGX4YSRudE8o9VGjUYPjGnGXOMk423GbcajJgYmISZLTepN7ppSTbmmKaY7TDtMx83MzaLN1pk1mz0x1zLnm+eb15vft2BaeFostqi2uGVJsuRaplnutrxuhVo5WaVYVVpds0atna0l1rutu6cRp7lOk06rntZnw7Dxtsm2qbcZsOXYBtuutm22fWFnYhdnt8Wuw+6TvZN9un2N/T0HDYfZDqsdWh1+c7RyFDpWOt6azpzuP33F9JbpL2dYzxDP2DPjthPLKcRpnVOb00dnF2e5c4PziIuJS4LLLpc+Lpsbxt3IveRKdPVxXeF60vWdm7Obwu2o26/uNu5p7ofcn8w0nymeWTNz0MPIQ+BR5dE/C5+VMGvfrH5PQ0+BZ7XnIy9jL5FXrdewt6V3qvdh7xc+9j5yn+M+4zw33jLeWV/MN8C3yLfLT8Nvnl+F30N/I/9k/3r/0QCngCUBZwOJgUGBWwL7+Hp8Ib+OPzrbZfay2e1BjKC5QRVBj4KtguXBrSFoyOyQrSH355jOkc5pDoVQfujW0Adh5mGLw34MJ4WHhVeGP45wiFga0TGXNXfR3ENz30T6RJZE3ptnMU85ry1KNSo+qi5qPNo3ujS6P8YuZlnM1VidWElsSxw5LiquNm5svt/87fOH4p3iC+N7F5gvyF1weaHOwvSFpxapLhIsOpZATIhOOJTwQRAqqBaMJfITdyWOCnnCHcJnIi/RNtGI2ENcKh5O8kgqTXqS7JG8NXkkxTOlLOW5hCepkLxMDUzdmzqeFpp2IG0yPTq9MYOSkZBxQqohTZO2Z+pn5mZ2y6xlhbL+xW6Lty8elQfJa7OQrAVZLQq2QqboVFoo1yoHsmdlV2a/zYnKOZarnivN7cyzytuQN5zvn//tEsIS4ZK2pYZLVy0dWOa9rGo5sjxxedsK4xUFK4ZWBqw8uIq2Km3VT6vtV5eufr0mek1rgV7ByoLBtQFr6wtVCuWFfevc1+1dT1gvWd+1YfqGnRs+FYmKrhTbF5cVf9go3HjlG4dvyr+Z3JS0qavEuWTPZtJm6ebeLZ5bDpaql+aXDm4N2dq0Dd9WtO319kXbL5fNKNu7g7ZDuaO/PLi8ZafJzs07P1SkVPRU+lQ27tLdtWHX+G7R7ht7vPY07NXbW7z3/T7JvttVAVVN1WbVZftJ+7P3P66Jqun4lvttXa1ObXHtxwPSA/0HIw6217nU1R3SPVRSj9Yr60cOxx++/p3vdy0NNg1VjZzG4iNwRHnk6fcJ3/ceDTradox7rOEH0x92HWcdL2pCmvKaRptTmvtbYlu6T8w+0dbq3nr8R9sfD5w0PFl5SvNUyWna6YLTk2fyz4ydlZ19fi753GDborZ752PO32oPb++6EHTh0kX/i+c7vDvOXPK4dPKy2+UTV7hXmq86X23qdOo8/pPTT8e7nLuarrlca7nuer21e2b36RueN87d9L158Rb/1tWeOT3dvfN6b/fF9/XfFt1+cif9zsu72Xcn7q28T7xf9EDtQdlD3YfVP1v+3Njv3H9qwHeg89HcR/cGhYPP/pH1jw9DBY+Zj8uGDYbrnjg+OTniP3L96fynQ89kzyaeF/6i/suuFxYvfvjV69fO0ZjRoZfyl5O/bXyl/erA6xmv28bCxh6+yXgzMV70VvvtwXfcdx3vo98PT+R8IH8o/2j5sfVT0Kf7kxmTk/8EA5jz/GMzLdsAAAAgY0hSTQAAeiUAAICDAAD5/wAAgOkAAHUwAADqYAAAOpgAABdvkl/FRgAAB81JREFUeNrsnTGPm0oXht97FWm2Ch2pTEeHpUihsyvTuXO67Ta/IPkr+Qfp3MWdO7Zad0SKZDo6XIWOrTzV9xVXZ8SygGHXG4/t96lW68GGw8vMmZlzDv98+/btfyBXy780wXXzTv74/fs3rXFFfPz4kT0AoQAoAJqAAiAUAKEACAVAKABCARAKgFAAhAIgFAChAAgFQCgAQgEQCoBQAIQCIBQAoQAIBUAoAHLmvDv3C7i7u4PjOMiyDOv1+mC75XKJoiga2wRBAN/34TgOHMdBWZYoigJpmiLPcwrARhzHAQD4vg/P81pvlLRrwvM8zGYz00ZrbY5xHAe+7yPPc9zf36MsSwrAVmazGX78+DHoGM/zsFgsAAB5nmOz2ZgeQimF8XiMMAxNu+VyaQRCH8Ai8jyH4zgIw7D3MUopzOdzAECaplitVk+GB601kiTBz58/obWG4ziIoohOoI38+vULABCGYWd3X2U6nUIphbIsEcdxa7uiKPDw8GCGGtd1KQDbKMsSWZZBKYXJZNLrGN/3zdN/iDRNTdcvx1EAFqGUwmazeeIQduG6LpRSAIAsy3r9hrRjD2BxL5AkiXEI+8wetNa9PXtp13eIoQBOQJIkxmHrcgjlJkov8JKpJwVgIVpr47CFYdh6g/f7/ZM5/9CehgKwmDRNURQFlFKYTqeNN/rx8dH0AH2faBn7KYAzQKZ1QRCYZd0qf/78MX+PRqNe3ymO5W63owBsR9bwZShoGirEq++zeBQEweBZAwVwYh4eHqC1RhAErQ6jOHVdK3yu65qhJE1TDgHn5BDKTW6auxdFYdYOgiDAYrF40k4phTAM8fnzZyilUBRF54rhOfIOF06SJMYPaPt8v99jOp3C8zx4nget9bPZQ5ZlF3fzL0IAZVke9OLv7+/Njl/brCHLMozHY4xGI3z48MH0EEVRIMuyi40H+EdqBbNS6HXBSqGEAiAUAAVAE1AAhAIgFAChAAgFQCgAQgGQq+Eom0GLxeJgGHYVSdCUhM02yrI0qV5hGGIymaAsy9b0LNd1cXt7CwDYbDa98wOA/zKLVquVSQGr/nYTbe2iKDIh53JtZVmiLEvsdjtst9tn5z7EDmfXA3QFXdaTMbvYbrdm568tgkdueJ7njbt3QwJA+8YJ1tsFQQDXdXFzc2N2E0Uwk8kEX758eXbMEDtY2QOsVqtn//v69SsAYL1eH9xK7dNGgjuiKMJ4PH4WmSN7+QBMFu/3798bn1oAzz47NvVrqmYgz2azRpv1scNV+wDVaN969y6JIEmSWBmyJenlIgZbcgvOzgmUqJxqkmY18ldCvGwkz/MntQcogBcgETrVMV98Aptvfh1JTKEAXsBms4HWGp7nYT6fw3Ec5Hlufbi253lQSkFr3VqmhgLoQVmW2G63ZigQx8/2my/FKCR17WLWAV7LfD5vzOFLkqS1W0/T1HT9RVFY5/jNZjMz3ouvorVGHMet9QheYoer7AGq478Y2LaiDTc3N3Bd90megSwG2YQVPcDQ+a/ccK01ttutWSWsetl/i7bfq16TzP1lGFgul0exw9X2AJLGJV3joRXCl3rnXbUDhmQKl2WJ9XoNrbV1vdXZCUCWWqvVQGR8HFIgqmuaKUiCSJcA+nrzWmvzdA/ZN6EAKlTz/eXmA3iSuXOoNEzfBRsA+PTpU+PnUjxSfnvo9/ZNR6cAakjFj2rqd3VtQJ6u1z5h1e+SdYbqdK5aWHLImC0OoFQgpRN4YPoD/LfRVC8C2TQlkhVC3/dfVDG0/l1xHCOKIvi+b572atJoURSdtYnbfAHxV0aj0TP/oY8dzqYH6OscHXK26tO+rqcujmNTIKqtJkDfc0vTFMvl8smu436/R57niOO4NSbh0HfLkFHtpYbY4dgwOfRKYXIooQAIBUAB0AQUAKEACAVAKABCARAKgFAA5Gp4s93AKIrw/v17ExsnFEWB/X6P3W6HLMtaN0+GJkwOad+W2FlPLq3GHFSRdq85h2PYyGoByG6cvJOnHiEryZJSg7e+s1ZNmOyzSza0ffWYJsIwbMzk7Tp+6Dm81kZWC0BoCnSU7dowDE2K12q1alT60EDJYwVWKqUQRdHgPf9jnfMQG52dDyA5fLKnLlGztiB5Bn1eP3fuNvr31IaWZM9jhHIdEwk5G1Jk4hxtdPJZQJZlJrLWlnBpx3FMmrnrup3RReduIyumgXJxtryRUxw4mQXIO4Yv0UZWCMDWN3I2vX7u0mxk1RtDmp6yoQmTbe27kjK7iOMYt7e3CIIA2+22VyLIWyZ5Hrsnsmol0Jac+fo51QtSXJKNrOgBuvLsTrUOUO8FxAP3ff/gTXiLc3irt5aevAdQSpmpja0vZqq+fm4ymfz18i5vaaOTC0DSvapv8rQRmRY6joPxeHwxNjqpAGSpUwx8ikKJQ5AyNFKb4BJsdBIfwPM8BEFgFjXSNG3debMJSUv7GyuWf8tGby6Aaq2c+qvaJce/a3p2ioTJQ73A3d3di6aBbef8WhtZKQDJ6K1fTJ7neHx8PFjWTcbbvvPePm8QbVtc6ft/+UwKUdfbDT3n19roGDA59EphciihAAgFQAHQBBQAoQAIBUAoAEIBEAqAUACEAiAUAKEACAVAKABCARAKgFAAhAIgFAChAAgFQC4CkxgiceKEPQC5Iv4/APgB2O7x8IXXAAAAAElFTkSuQmCC'); +} +.texture-image.no-preview { + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAIAAABMXPacAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAA8sSURBVHhe7Z3rbxXFG8d7B9SWthRabLmIYlHkIEXKJdXYBEXxHtEXprwxxsR3/jG+8PLCaDDGeAkmKsTEoCUVKoVCA6WNtLS2UEUKBSy0tKW/D+eZM9nu7tmz55z+mC2Zz4tl9tk5c2bnO/PMM2dnS+6nn36aYzFH7vvvv6+SFhMoAY4fPy7nljvG448/zjFPTiymsAIYxgpgGCuAYawAhrECGMYKYBgrgGGsAIaxAhjGCmAYK4BhrACGsQIYxgpgGCuAYawAhrECGMYKYBgrgGGsAIaxAhjmLhQgPz+/pKRk3rx56jzaRHFf0ObNmxctWkTi7Nmzp0+fFqNm+/btRUVFP/30kzp3UFtbu27duqVLl+bl3e5Y169f7+rqam1tvXnzpmSIFNHdF1RTU7M6TkNDQ0FBgbImWLVqFZfUSQKyvfzyy88991x1dfXU1NSFCxdGRkbuueeeurq6pqam0tJSlS96RNcFSQvSo9V5IC+88MIDDzwwOjr6448/fvTRR19++eVnn322Z8+ev//+u7i4+M0331ywYIHKGjGiK8Aff/zBMRaL5ebmiiUZjz322MqVK/Ez33333ZkzZxgBYh8eHt67d++lS5do/W3btokxakRXANxIf38/3mPNmjXKlARxpkeOHKGtxaIZHx9vaWkhwfTg9WZRILoCgIQG0r7JKC8vlxm7s7NTLC6YyW/cuFFYWIiPUqYoEWkB+vr6cOJLlizBwyiTB2l9vA0xj1hcTE9PDw4OkiA6EkukiLQAcOzYMY4bN26UUy8LFy7k+O+//8qpL1euXOF43333yWmkiLoATKqEQwSmlZWVyjQTIiWOwZG+npYjSNQFwIG0tbWRqK+vF4sL1r0qlZzJyUmOYXLeeaIuAHR3d+PfmQbE27hgguUY3LgS/0RzHMwBAei/R48ezcvL8x0EOCiOxEJy6osoJ1JFjTkgAHR0dExMTBDLexe0EvsTKQUMgsWLF3OUWChqzA0BGARoQBN7wyHWa6Ojo1x6+OGHlWkmaEOoeuvWrXPnzilTlJgbAgBeiEEQi8W8Pf3kyZMct27d6v0JGsf15JNPkmA5lmyhYJY5IwAenNmYBW1RUZEyJSBMYiYoLi7etWtXWVmZsubkkHPHjh2EsCjX3NysrBFjzggANDSeRJ04wEF9//33rLYqKip27979yiuvNDY2Pvvss2+//TZ+ieBn//79V69eVbkjRv6WLVv4hxW/nEcB+iyuo6ura3x8XJnicIqToV8zGpgSlDXO2NhYZ2cnV+WnIVZtTLxEn+fPn9+3b180p9+qqiqOd9ub8ihH67M8xuPT65mf1YXocXe+KY+PGhoa6unp4Rjl1tfcbQLMOawAhrECGMYKYBgrgGGsAIaxAhjGCmAYK4BhrACGyfy3oNdff72mpkadJLh27Vpvb29LS8vExIRYdu7c6dpLOz09ffPmTXLypadOnVLWnJzGxsZYLKZOPHR0dDQ3N7/33nv5+fkff/yx7/PFBQsWvPPOO5T/4YcfLly4sKmpaXBw8Ntvv5Wr7777bsAOUbINDw+Th5IpX1kTyGcPHz7c2tqqTHG4NW7wzz//9N2tHczs/BY0NjZ2PQFVLy4uXr9+/UsvvaQuJxgfH1eZ4tkKCwsrKiq2b9/u3XbozOkEzaamps6ePUueZHvcsOfl5ZFHtkH4oorzQOFU7MqVKzS0S6fy8nKxeDvckiVLOGbza2u22yW/+eYbOo46ie9Te/XVV5ctW7Z8+fK//vpLWXNyfvjhB2ctaaaGhoYNGzZs3bq1q6tLWeP88ssvdCh14oFLDz30EA3tuxFRhBGRkvHJJ5+olB8XLlxg6NCs/f39ypRo93/++Wfp0qWMP+fuCnna7N2TGp5ZngMQ48iRIyQefPBBsfhy69atgwcPjo6OlpSU+G42SQaicv80tPfBJBbslBwsQDBDQ0McpVk1CMBAx2HyFa79jUhFfeRTmTH7k7DsEky5DxBPffHiRRKytS0kNMTAwAAN4d0tigX7+fPnfaeHkEjlxbFoEIAvlTFRXV0tRhBnNTIy4hwT6TL7Asgz2zBvBUlO/K+chkQc1IoVK+RUI5YzZ87IaWZIX3buMpIJAP+Jroxv5zQgOmW52WL2BZDtyv/995+cJkMeHHJX6T42wcPgZ5gJ1HkCsWTjf4C+TCuXlpZqFyctLl6etpZpIH5F6eScAjNglgVg+n3iiSdIuHoiI/f2S19xamtrN23a9NprrzEVt7W1uSKWtWvXPu2HuhzfHkF/pFfef//9ypSTQxoLPi3lw3dV3Ez4UnU5/nicJpZuBAigvTzfyyU9DWQfAkG2UdCLL76oPeC99947f/58Et3d3cQMYhTk0b8TejGhfXt7uzpPgCfxuhf49ddfVSonp6enhyhr1apVeHyxkOYYxv8QJauUA9yaXpEQCKEH8zAJThGA1pd7lLamM0mCPNhl73vGZDsCGK10FgGffvnyZZYqP//8s7qcgCY7EUemMvz+F198ceDAAaZiyaA5duwYixov6nIcaWhpdEHSfIucBqCKm4m8hSDIBhHp3URoMgHEr9wefHoaYChw71qbjMlWgK+//pp1o/DBBx98/vnnLBfp3epyAmI4ujDs3bv3t99+I/J5/vnnfd++4/7pj17U5TjohzsuKysTL8yRNM5HwqpgVHEzce7KoYlpUynZO83qaYAOxzGbFYCQrQAsXOkXgrc7+4IYuA5WwgHvvaSEVuMoKy859vb23r6QNbQ+zof2Je2cAAQ9DYhCWU4AMPtRUBhko2B9fX1aiwAnEu3IakCOYfxPSFgN4HnwP7h7xHA6GT0NyFScZQgEZgRgimYyKCwsrKurU6Y0weHIbwO0FEfGX5bxuBPp8kR0jAPX22d8EY2Oa6qqqiJt3gVlzKFDhzjGYjFaUCzpgs/BGzQ2NnJkWg7pAMMg8Y/8Wul1Mn19fUiONtl3fzAmAP0XN8IgcM0EGzZs2JkElSOBTAMsLDiGnwBUWR74XpUjvuxiJS/TgK8AdBpUz34CAGMCgPy27hoEdC5Zr3lRORIQ8krYMzExMTAwIMaUqLI8iE/XyCCgj+NnxKLRoWf2/gcyfyBDGDNv3jw6csCP70C0QPvSUq6tzgKelK5EUxJZElazlFMX/PB6efkIJXsD0IKCgsrKSuclmpi1t6S9uBy6lJzMy1My5ae892DExdn/R8wYd+fu6DmHFcAwVgDDWAEMYwUwjBXAMFYAw1gBDGMFMIwVwDBp/xSxZs2aqqqqsbGxw4cPK1PiD2W0t7cne0K9ePHitWvXXr9+Xf4aKFRWVj7yyCMkKIfSxKgpLS1lpT4yMqIrxinGU6dOBf95OGH16tXV1dWuSmrkmbs6iTM5OXnjxo2enh7560Oap+O7MZz7AVzIF6kTPwI+m+FPEbT1+vXrN2/eXFJSokzxfXAYH330UXXuYd26dWRw/uoZi8WwgPPZukYKdO5vJI0FDdR5IL6V1KxYseL2FzvYuHFjQ0NDU1OTa7uRXFUnftTU1EieZKh8yUlPALott3T58mXSiC9GkJ/mA/aDyo1JNsjPz6fdr169OjU15SxnVqioqCgrK/NW0oXefrF///4DBw5QN2r1zDPPFBcXqxyhOXnypBTlReVITnoCyP20tLS4Gq6/v58hvGjRIudfi9HIrqnR0VG9jWfZsmXz58/nnoeGhiQt9llBVxIXFCCA3n7R3d3d0dFBY3EXRUVF4hjTAq8oRXlROZKTtgATExN9fX0DAwMyGsQ+PT0te3V8b1iMztqIpbe3l6JkNIh9VtCVpEGdlUyJPOjnI3J6Z0hDALkZbozuL63pbG6vReMSQFqcEcOACPhUZoj/kUrKPonwhcvTlTDbimeRNASQt1mkp9N5uUPn+y2Dg4M4Ge7f1eOQTR4taf+zcuVKfI6UI5sbli9f7pyfs0GaWwpnmLoqGYxswwr/dHNWSEMA7o37kfdecK+4b+luchUv5NudnS0iiEU/Rmfg5+XlBb/QEZ7gSjoh0CpPwOy1adMmQrVz58653tgJAz1MFTQT79+w8xJWACZSvobeoWN2r9MXAWSfmkb8u8v/UIjuaOk6igCkrYMrqXnqqad2JyAA3bZtG8N037593n2VKamvr1cFzaS2tlblSE5YAeQenLvPpJc57w0ng0thYaL3u0mLcGN6Bwf+p7CwkOmRfiqWixcv4rsIqLP3QmEqqRkeHqZWQK8njMH1U+233nor5FLDCcs3KcpFypckIOz2dLkHhiqrG7EAlZYmlqAb6Oksaoj65W+6iWOhG+pdU1IOGjjLQSGGF5nlD1BmTMhKCq2trXpcAkOT5RuV37Fjx1dffaWs4Whvb3f9DbvwhBoBdE8aiASr5y0O5B0j519MlVvSDt21/iooKBCPxFEVEYcGwhhmwAYgrUwiZSV9YUQeOnQI31VVVZXWe4NZEkoAqT3tyIrRibwQ6Ww4Qho6mvgTmoNG4ZZ0/EO70/cZ7+rzDojc+VTGe3VBur+3kvq/MInnCgINqD+JDLxQxqQWIDc3VzoyHYSB5uT333/HfUtDS2agCYhqWN8CpxKwyiVpI/XhmUhQJBkyQz7rrWRbWxvu3lXJZMhw0RW+A6QWQLoz9+DyoYI3hmFlzxHN+CAJp/+RAMk5SWqyjIXE/ySrJOsyjikLp+OzaiEKohxl+v+TWgCpt2+rgTfOu3TpEoENrQ/OcBP/w0RHyMGUKxYnrAbod84IyheCa/K4YH4KrqSvAK6i6urq3njjDcbu6dOnXTVUOWZCf1KX48opqweZOwNIEQVp/6PXTS7w77SyDHC9C5NeT0RBorOz0+V/5PcWL5OTk0hFkEq2EydOKKsHJlWVcoCjl8KTVVJUd1XStyjmp4MHD6qTBLt27VIpB3v27NEDZUMcSbugbrhBdeJHij9dTDyAvFQrWaMQXyLS+Pj4tWvX9PAn/kV5hgJhJXYxMgLIQDm+u3SBeZgOKJM2/YuhwJSoN+SWlJTQiJTphTZlzRlQSXBWkjUwsan6cBy+iLD9+PHjzc3Nzv22RLQqhwfEphBukx6mTH6wEEn2kOru/NPFc4gMn4hZZhcrgGGsAIaxAhjGCmAYK4BhrACGsQIYxgpgGCuAYawAhrECGMYKYBgrgGGsAIaxAhjGCmAYK4BhrACGsQIYxgpgGCuAYdS2FIsp7AgwSk7O/wCqCi/+JioQYgAAAABJRU5ErkJggg=='); +} + +.two-column { + display: table; + width: 100%; +} +.two-column > div { + display: table-cell; + width: 50%; +} + +#properties-list .fieldset .two-column { + padding-top: 10px; + display: flex; +} + +#properties-list .two-column .fieldset { + width: 50%; + margin: 0; + padding: 0; + border-top: none; + box-shadow: none; +} + +#properties-list .two-column .column { + position: relative; + top: -10px; +} + +#properties-list .two-column .fieldset div.legend { + width: 100%; + margin: 21px -21px 0 -21px; + padding: 16px 0 0 21px; + font-family: Raleway-Regular; + font-size: 12px; + color: #afafaf; + height: 10px; + text-transform: uppercase; + outline: none; +} + +#properties-list .two-column + .property { + margin-top: 6px; +} + +.fieldset .checkbox-sub-props { + margin-top: 0; +} + +.fieldset .checkbox-sub-props .property:first-child { + margin-top: 0; +} + +.column { + vertical-align: top; +} + +.indent { + margin-left: 24px; +} + +::-webkit-scrollbar { + width: 20px; + height: 10px; +} +::-webkit-scrollbar-track { + background-color: #2e2e2e; +} +#entity-table-scroll::-webkit-scrollbar-track { + border-bottom-right-radius: 7px; +} + +::-webkit-scrollbar-thumb { + background-color: #696969; + border: 2px solid #2e2e2e; + border-radius: 8px; +} + +/* FIXME: Revisit textarea resizer/corner when move to Qt 5.6 or later: see if can get resizer/corner to always be visible and +have correct background color with and without scrollbars. */ +textarea:enabled::-webkit-resizer { + background-size: 10px 10px; + background: #252525 url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAIAAAACUFjqAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAXSURBVChTY1RVVWXADZigNA4wMqUZGACS3gCD5UUtKAAAAABJRU5ErkJggg==) no-repeat bottom right; +} +textarea:focus::-webkit-resizer { + background-size: 10px 10px; + background: #000000 url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAACOSURBVChThdC5DQQhDAXQvyRI5LQxFdABARWQElAPogYkiqEWQhLYGe8xxzJaS5a/8AuQHwDG2n+Lvee0hBDQWlO+hRvy3mNZFjDG5vCDOOeIMaL3/guPKISAWiu9n+AVSSlhraXdF86Qcw6tNdoTvEOlFOScd6iUOv3JGEMopYQx9jNvaawnoHnNr8Z4AuRLPOq2gPgnAAAAAElFTkSuQmCC) no-repeat bottom right; +} +textarea:enabled[scrolling="true"]::-webkit-resizer { + background-size: 10px 10px; + background: #2e2e2e url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAACKSURBVChTjdAxDsMgDAXQT4UYuQIzCwsSKxsSJ4YDoByDY7AwUOG2aZMQqX+xhd9gzIwxA3/k8a7LCCFgraX+Fk4UY4RSCoyxNfwgzjlyzhhjXOEvSimhtUbvB3hGUkp472m2wxUKIaD3TnOCd6jWim3bvlBrfdjJOUeolEJoZj/4PMH83bl/BXgCWSs2Z09IjgoAAAAASUVORK5CYII=) no-repeat bottom right; +} + + +div#grid-section, body#entity-list-body { + padding-bottom: 0; + margin: 16px; +} + +#entity-list-header { + margin-bottom: 36px; +} + +#entity-list-header div { + display: inline-block; + width: 65px; + margin-right: 6px; +} + +#entity-list-header div input:first-child { + margin-right: 0; + float: left; + width: 33px; + border-right: 1px solid #808080; + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} +#entity-list-header div input:last-child { + margin-right: 0; + float: right; + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} + +#delete { + float: right; + margin-right: 0; + background-color: #ff0000; +} + +#entity-list { + position: relative; /* New positioning context. */ +} + +#filter-area { + padding-right: 168px; + padding-bottom: 24px; +} + +#filter-type-multiselect-box select { + border-radius: 14.5px; + width: 107px; + height: 28px; +} +#filter-type-options { + position: absolute; + top: 48px; +} +#filter-type-options div { + position: relative; + height: 22px; +} +#filter-type-options span { + position: relative; + top: 3px; + font-family: HiFi-Glyphs; + font-size: 13px; + color: #000000; + padding-left: 6px; + padding-right: 4px; +} +#filter-type-options label { + position: absolute; + top: -20px; + z-index: 2; + height: 22px; + width: 200px; + padding-top: 1px; +} +#filter-type-options-buttons { + top: -22px; + width: 224px; + z-index: 2; + background-color: #afafaf; + padding-bottom: 6px; +} +#filter-type-options input[type=button] { + position: relative; + left: 16px; + z-index: 3; + height: 23px; + min-width: 60px; + font-size: 10px; + color: #000; + background: linear-gradient(#afafaf 20%, #808080 100%); +} +#filter-type-options input[type=button]:enabled:hover { + background: linear-gradient(#afafaf 20%, #575757 100%); +} + +#filter-search-and-icon { + position: relative; + left: 118px; + width: calc(100% - 126px); +} + +#filter-in-view { + position: absolute; + top: 0; + right: 126px; +} + +#filter-radius-and-unit { + position: relative; + float: right; + margin-right: -168px; + top: -45px; +} +#filter-radius-and-unit label { + margin-left: 2px; +} +#filter-radius-and-unit span { + position: relative; + top: 25px; + right: 9px; + z-index: 2; + font-style: italic; +} +#filter-radius-and-unit input { + width: 120px; + border-radius: 14.5px; + font-style: italic; +} +#filter-radius-and-unit input[type=number]::-webkit-inner-spin-button { + display: none; +} + +#entity-list-footer { + padding-top: 9px; +} + +#footer-text { + float: right; + padding-top: 12px; + padding-right: 22px; +} + +input[type=button]#export { + height: 38px; + width: 180px; +} + +#no-entities { + display: none; + position: absolute; + top: 80px; + padding: 12px; + font-family: FiraSans-SemiBold; + font-size: 15px; + font-style: italic; + color: #afafaf; +} + +#entity-table-columns-multiselect { + position: absolute; + top: 51px; + right: 22px; +} +#entity-table-columns-multiselect-box select { + height: 28px; + width: 20px; + background-color: #1c1c1c; + border-top-right-radius: 7px; +} +#entity-table-columns-options { + position: absolute; + top: 50px; + right: 110px; +} +#entity-table-columns-options div { + position: relative; + height: 22px; +} +#entity-table-columns-options label { + position: absolute; + top: -22px; + height: 22px; + width: 100px; + padding-top: 4px; +} +#entity-table-columns-options input[type=checkbox] + label { + padding-left: 30px; +} + +#entity-table-scroll { + /* Height is set by JavaScript. */ + width: 100%; + overflow-x: hidden; + overflow-y: auto; + box-sizing: border-box; + padding-top: 28px; /* Space for header and footer outside of scroll region. */ + margin-top: 28px; + border-left: 2px solid #575757; + border-right: 2px solid #575757; + border-bottom: 2px solid #575757; + border-bottom-left-radius: 7px; + border-bottom-right-radius: 7px; + background-color: #1c1c1c; +} + +#entity-table-scroll .glyph { + font-family: HiFi-Glyphs; + font-size: 15px; +} + +#entity-table { + margin-top: -28px; + margin-bottom: -18px; + table-layout: fixed; + border: none; + background-color: #1c1c1c; +} + +#entity-table thead tr, #entity-table thead tr th { + background: none; +} + +#entity-table .glyph { + margin: 0 -2px 0 -2px; + vertical-align: middle; +} + +#entity-table thead { + box-sizing: border-box; + border: 2px solid #575757; + border-top-left-radius: 7px; + border-top-right-radius: 7px; + border-bottom: 1px solid #575757; + position: absolute; + top: 49px; + left: 0; + width: 100%; + word-wrap: nowrap; + white-space: nowrap; + overflow: hidden; +} + +#entity-table th { + display: inline-block; + box-sizing: border-box; + padding: 5px 0 0 0; + vertical-align: middle; + overflow: hidden; + text-overflow: ellipsis; +} + +#entity-table th:focus { + outline: none; +} + +#entity-table th .glyph { + position: relative; + left: 4px; +} +#entity-table th .glyph + .sort-order { + position: relative; + left: 4px; +} + +#entity-table thead .sort-order { + display: inline-block; + width: 8px; + margin: -5px 0 -3px 0; + vertical-align: middle; +} + +#entity-table thead .resizer { + position: absolute; + top: 1px; + height: 26px; + width: 10px; + cursor: col-resize; +} + +#entity-table .dragging { + background-color: #b3ecff; +} + +#entity-table td { + box-sizing: border-box; +} +#entity-table td.glyph { + text-align: center; + padding: 0; +} + +#properties-base { + border-top: none !important; + box-shadow: none !important; + margin-bottom: 5px !important; +} + +#properties-base #property-type-icon { + font-family: HiFi-Glyphs; + font-size: 31px; + color: #00b4ef; + margin: -4px 12px -4px -2px; + width: auto; + display: none; +} + +#properties-base #property-type { + padding: 5px 24px 5px 0; + border-right: 1px solid #808080; + width: auto; + display: inline-block; +} + +#properties-base .checkbox label span { + font-family: HiFi-Glyphs; + font-size: 20px; + padding-right: 6px; + vertical-align: top; + position: relative; + top: -4px; +} + +#properties-base input[type=checkbox]:checked + label span { + color: #ffffff; +} + +#id label { + width: 24px; +} +#property-id { + display: inline-block; +} +#property-id::selection { + color: #000000; + background-color: #00b4ef; +} + +input#property-scale-button-rescale { + min-width: 50px; + left: 152px; +} +input#property-scale-button-reset { + margin-right: 0; + left: 250px; +} + +#property-userData-static, +#property-materialData-static { + display: none; + z-index: 99; + position: absolute; + width: 96%; + padding-left: 1%; + margin-top: 5px; + margin-bottom: 10px; + background-color: #2e2e2e; +} + +#property-userData-saved, +#property-materialData-saved { + margin-top: 5px; + font-size: 16px; + display: none; +} + + +#div-property-collisionSoundURL[style*="display: none"] + .property { + margin-top: 0; +} + +.context-menu { + display: none; + position: fixed; + color: #000000; + background-color: #afafaf; + padding: 5px 0 5px 0; + cursor: default; +} +.context-menu li { + list-style-type: none; + padding: 4px 18px 4px 18px; + margin: 0; + white-space: nowrap; +} +.context-menu li:hover { + background-color: #e3e3e3; +} +.context-menu li.separator { + border-top: 1px solid #333333; + margin: 5px 5px; + padding: 0 0; +} +.context-menu li.disabled { + color: #333333; +} +.context-menu li.separator:hover, .context-menu li.disabled:hover { + background-color: #afafaf; +} + +input.rename-entity { + height: 100%; + width: 100%; + border: none; + font-family: FiraSans-SemiBold; + font-size: 15px; + /* need this to show the text cursor when the input field is empty */ + padding-left: 2px; +} + +.create-app-tooltip { + z-index: 100; + position: absolute; + background: #6a6a6a; + border: 1px solid black; + width: 258px; + min-height: 20px; + padding: 5px; + z-index: 100; +} + +.create-app-tooltip .create-app-tooltip-description { + font-size: 12px; + font-style: italic; + color: #ffffff; +} + +.create-app-tooltip .create-app-tooltip-js-attribute { + font-family: Raleway-SemiBold; + font-size: 11px; + color: #000000; + bottom: 0; + margin-top: 5px; +} + +#toggle-space-mode::before { + font-family: HiFi-Glyphs; + font-size: 20px; + text-transform: none; + min-width: 32px; + padding-right: 4px; + vertical-align: middle; +} + +#toggle-space-mode.space-mode-local::before { + content: "m"; +} + +#toggle-space-mode.space-mode-world::before { + content: "\e02c"; +} + +.container { + display: flex; + flex-flow: row nowrap; + margin-bottom: 8px; + min-height: 28px; +} + +.container > label { + margin-top: 6px; + width: 160px; + min-width: 160px; + max-width: 160px; +} + +.container > div.checkbox { + padding-top: 6px; +} + +.container > .value { + width: 100%; +} + +.container .row { + display: flex; + flex-flow: row nowrap; +} + +.container.shrink { + width: min-content; +} + +.fstuple { + display: flex; + flex-flow: row; +} +.fstuple input { + margin-left: 4px; + margin-right: 10px; +} +.fstuple label.red, .fstuple label.x, .fstuple label.w { + color: #C62147; +} +.fstuple label.green, .fstuple label.y, .fstuple label.h { + color: #359D85; +} +.fstuple label.blue, .fstuple label.z { + color: #0093C5; +} + +.xyz.fstuple, .pyr.fstuple { + position: relative; + left: -12px; + min-width: 50px; + width: 100px; +} + +.rgb.fstuple .tuple { + display: none; +} + +input.number-slider { + background: #575757; + border-radius: 4px; + color: white; +} + +.fstuple > div { + display: flex; + align-items: center; + justify-content: left; +} + +.flex-row { + display: flex; + flex-flow: row; +} + +.flex-column { + display: flex; + flex-flow: column; +} + +.flex-center { + align-items: center; +} + +.flex-evenly-spaced { + flex: 1; +} + +#property-serverScripts-status { + font-family: Raleway-Light; + font-size: 14px; + margin: 6px 0; + cursor: default; +} + +#property-name, #property-id { + display: flex; + width: 100%; +} + +.spacemode-hidden { + display: none; +} + +#placeholder-property-type { + min-width: 0; +} + +.collapse-icon { + cursor: pointer; +} + +#property-userData-editor.error { + border: 2px solid red; +} + +#property-userData-editorStatus { + color: white; + background-color: red; + padding: 5px; + display: none; + cursor: pointer; +} + +#property-materialData-editor.error { + border: 2px solid red; +} + +#property-materialData-editorStatus { + color: white; + background-color: red; + padding: 5px; + display: none; + cursor: pointer; +} + +input[type=number].hide-spinner::-webkit-inner-spin-button { + -webkit-appearance: none; + visibility: hidden; +} + +div.jsoneditor-menu a.jsoneditor-poweredBy { + display: none; +} diff --git a/scripts/simplifiedUI/system/html/css/hifi-style.css b/scripts/simplifiedUI/system/html/css/hifi-style.css new file mode 100644 index 0000000000..90a5b366c2 --- /dev/null +++ b/scripts/simplifiedUI/system/html/css/hifi-style.css @@ -0,0 +1,175 @@ +/* +// hifi-style.css +// +// Created by Zach Fox on 2017-04-18 +// 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 +*/ + +@font-face { + font-family: Raleway-Regular; + src: url(../../../../resources/fonts/Raleway-Regular.ttf), /* Windows production */ + url(../../../../fonts/Raleway-Regular.ttf), /* OSX production */ + url(../../../../interface/resources/fonts/Raleway-Regular.ttf); /* Development, running script in /HiFi/examples */ +} + +@font-face { + font-family: Raleway-Light; + src: url(../../../../resources/fonts/Raleway-Light.ttf), + url(../../../../fonts/Raleway-Light.ttf), + url(../../../../interface/resources/fonts/Raleway-Light.ttf); +} + +@font-face { + font-family: Raleway-Bold; + src: url(../../../../resources/fonts/Raleway-Bold.ttf), + url(../../../../fonts/Raleway-Bold.ttf), + url(../../../../interface/resources/fonts/Raleway-Bold.ttf); +} + +@font-face { + font-family: Raleway-SemiBold; + src: url(../../../../resources/fonts/Raleway-SemiBold.ttf), + url(../../../../fonts/Raleway-SemiBold.ttf), + url(../../../../interface/resources/fonts/Raleway-SemiBold.ttf); +} + +@font-face { + font-family: FiraSans-SemiBold; + src: url(../../../../resources/fonts/FiraSans-SemiBold.ttf), + url(../../../../fonts/FiraSans-SemiBold.ttf), + url(../../../../interface/resources/fonts/FiraSans-SemiBold.ttf); +} + +@font-face { + font-family: AnonymousPro-Regular; + src: url(../../../../resources/fonts/AnonymousPro-Regular.ttf), + url(../../../../fonts/AnonymousPro-Regular.ttf), + url(../../../../interface/resources/fonts/AnonymousPro-Regular.ttf); +} + +@font-face { + font-family: HiFi-Glyphs; + src: url(../../../../resources/fonts/hifi-glyphs.ttf), + url(../../../../fonts/hifi-glyphs.ttf), + url(../../../../interface/resources/fonts/hifi-glyphs.ttf); +} + +body { + color: #afafaf; + background-color: #404040; + font-family: Raleway-Regular; + font-size: 15px; + + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + + overflow-x: hidden; + overflow-y: auto; +} + +hr { + border: none; + background: #404040 url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAYAAACp8Z5+AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAAjSURBVBhXY1RVVf3PgARYjIyMoEwIYHRwcEBRwQSloYCBAQCwjgPMiI7W2QAAAABJRU5ErkJggg==) repeat-x top left; + padding: 1px; + -webkit-margin-before: 0; + -webkit-margin-after: 0; + -webkit-margin-start: 0; + -webkit-margin-end: 0; + width: 100%; + position: absolute; +} + +.hifi-glyph { + font-family: HiFi-Glyphs; + border: none; + //margin: -10px; + padding: 0; +} + +input[type=radio] { + width: 2em; + margin: 0; + padding: 0; + font-size: 1em; + opacity: 0; +} +input[type=radio] + label{ + display: inline-block; + margin-left: -2em; + line-height: 2em; + font-family: Raleway-SemiBold; + font-size: 14px; +} +input[type=radio] + label > span{ + display: inline-block; + width: 20px; + height: 20px; + margin: 5px; + border-radius: 50%; + background: #6B6A6B; + background-image: linear-gradient(#7D7D7D, #6B6A6B); + vertical-align: bottom; +} +input[type=radio]:checked + label > span{ + background-image: linear-gradient(#7D7D7D, #6B6A6B); +} +input[type=radio]:active + label > span, +input[type=radio]:hover + label > span{ + background-image: linear-gradient(#FFFFFF, #AFAFAF); +} +input[type=radio]:checked + label > span > span, +input[type=radio]:active + label > span > span{ + display: block; + width: 10px; + height: 10px; + margin: 3px; + border: 2px solid #36CDFF; + border-radius: 50%; + background: #00B4EF; +} + +.grayButton { + font-family: Raleway-Bold; + font-size: 13px; + color: black; + padding: 0 10px; + border-radius: 3px; + border-width: 0; + background-image: linear-gradient(#FFFFFF, #AFAFAF); + min-height: 30px; +} +.grayButton:hover { + background-image: linear-gradient(#FFFFFF, #FFFFFF); +} +.grayButton:active { + background-image: linear-gradient(#AFAFAF, #AFAFAF); +} +.grayButton:disabled { + background-image: linear-gradient(#FFFFFF, ##AFAFAF); +} +.blueButton { + font-family: Raleway-Bold; + font-size: 13px; + color: white; + padding: 0 10px; + border-radius: 3px; + border-width: 0; + background-image: linear-gradient(#00B4EF, #1080B8); + min-height: 30px; +} +.blueButton:hover { + background-image: linear-gradient(#00B4EF, #00B4EF); +} +.blueButton:active { + background-image: linear-gradient(#1080B8, #1080B8); +} +.blueButton:disabled { + background-image: linear-gradient(#FFFFFF, #AFAFAF); +} diff --git a/scripts/simplifiedUI/system/html/css/img/jsoneditor-icons.svg b/scripts/simplifiedUI/system/html/css/img/jsoneditor-icons.svg new file mode 100644 index 0000000000..1b40068aad --- /dev/null +++ b/scripts/simplifiedUI/system/html/css/img/jsoneditor-icons.svg @@ -0,0 +1,893 @@ + + + JSON Editor Icons + + + + image/svg+xml + + JSON Editor Icons + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/html/css/img/mt-expand-hover.svg b/scripts/simplifiedUI/system/html/css/img/mt-expand-hover.svg new file mode 100644 index 0000000000..a8e84c42ad --- /dev/null +++ b/scripts/simplifiedUI/system/html/css/img/mt-expand-hover.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/scripts/simplifiedUI/system/html/css/img/mt-expand-normal.svg b/scripts/simplifiedUI/system/html/css/img/mt-expand-normal.svg new file mode 100644 index 0000000000..aac349ebda --- /dev/null +++ b/scripts/simplifiedUI/system/html/css/img/mt-expand-normal.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/scripts/simplifiedUI/system/html/css/img/mt-goto-hover.svg b/scripts/simplifiedUI/system/html/css/img/mt-goto-hover.svg new file mode 100644 index 0000000000..4cad54331a --- /dev/null +++ b/scripts/simplifiedUI/system/html/css/img/mt-goto-hover.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/scripts/simplifiedUI/system/html/css/img/mt-goto-normal.svg b/scripts/simplifiedUI/system/html/css/img/mt-goto-normal.svg new file mode 100644 index 0000000000..ead63329fb --- /dev/null +++ b/scripts/simplifiedUI/system/html/css/img/mt-goto-normal.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/scripts/simplifiedUI/system/html/css/img/mt-mute-hover.svg b/scripts/simplifiedUI/system/html/css/img/mt-mute-hover.svg new file mode 100644 index 0000000000..9a18ccd933 --- /dev/null +++ b/scripts/simplifiedUI/system/html/css/img/mt-mute-hover.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/scripts/simplifiedUI/system/html/css/img/mt-mute-normal.svg b/scripts/simplifiedUI/system/html/css/img/mt-mute-normal.svg new file mode 100644 index 0000000000..472f03f138 --- /dev/null +++ b/scripts/simplifiedUI/system/html/css/img/mt-mute-normal.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/scripts/simplifiedUI/system/html/css/jsoneditor.css b/scripts/simplifiedUI/system/html/css/jsoneditor.css new file mode 100644 index 0000000000..eedef60a7f --- /dev/null +++ b/scripts/simplifiedUI/system/html/css/jsoneditor.css @@ -0,0 +1,930 @@ +/* reset styling (prevent conflicts with bootstrap, materialize.css, etc.) */ + +div.jsoneditor input { + height: auto; + border: inherit; +} + +div.jsoneditor input:focus { + border: none !important; + box-shadow: none !important; +} + +div.jsoneditor table { + border-collapse: collapse; + width: auto; +} + +div.jsoneditor td, +div.jsoneditor th { + padding: 0; + display: table-cell; + text-align: left; + vertical-align: inherit; + border-radius: inherit; +} + + +div.jsoneditor-field, +div.jsoneditor-value, +div.jsoneditor-readonly { + border: 1px solid transparent; + min-height: 16px; + min-width: 32px; + padding: 2px; + margin: 1px; + word-wrap: break-word; + float: left; +} + +/* adjust margin of p elements inside editable divs, needed for Opera, IE */ + +div.jsoneditor-field p, +div.jsoneditor-value p { + margin: 0; +} + +div.jsoneditor-value { + word-break: break-word; +} + +div.jsoneditor-readonly { + min-width: 16px; + color: red; +} + +div.jsoneditor-empty { + border-color: lightgray; + border-style: dashed; + border-radius: 2px; +} + +div.jsoneditor-field.jsoneditor-empty::after, +div.jsoneditor-value.jsoneditor-empty::after { + pointer-events: none; + color: lightgray; + font-size: 8pt; +} + +div.jsoneditor-field.jsoneditor-empty::after { + content: "field"; +} + +div.jsoneditor-value.jsoneditor-empty::after { + content: "value"; +} + +div.jsoneditor-value.jsoneditor-url, +a.jsoneditor-value.jsoneditor-url { + color: green; + text-decoration: underline; +} + +a.jsoneditor-value.jsoneditor-url { + display: inline-block; + padding: 2px; + margin: 2px; +} + +a.jsoneditor-value.jsoneditor-url:hover, +a.jsoneditor-value.jsoneditor-url:focus { + color: #ee422e; +} + +div.jsoneditor td.jsoneditor-separator { + padding: 3px 0; + vertical-align: top; + color: gray; +} + +div.jsoneditor-field[contenteditable=true]:focus, +div.jsoneditor-field[contenteditable=true]:hover, +div.jsoneditor-value[contenteditable=true]:focus, +div.jsoneditor-value[contenteditable=true]:hover, +div.jsoneditor-field.jsoneditor-highlight, +div.jsoneditor-value.jsoneditor-highlight { + background-color: #FFFFAB; + border: 1px solid yellow; + border-radius: 2px; +} + +div.jsoneditor-field.jsoneditor-highlight-active, +div.jsoneditor-field.jsoneditor-highlight-active:focus, +div.jsoneditor-field.jsoneditor-highlight-active:hover, +div.jsoneditor-value.jsoneditor-highlight-active, +div.jsoneditor-value.jsoneditor-highlight-active:focus, +div.jsoneditor-value.jsoneditor-highlight-active:hover { + background-color: #ffee00; + border: 1px solid #ffc700; + border-radius: 2px; +} + +div.jsoneditor-value.jsoneditor-string { + color: #008000; +} + +div.jsoneditor-value.jsoneditor-object, +div.jsoneditor-value.jsoneditor-array { + min-width: 16px; + color: #808080; +} + +div.jsoneditor-value.jsoneditor-number { + color: #ee422e; +} + +div.jsoneditor-value.jsoneditor-boolean { + color: #ff8c00; +} + +div.jsoneditor-value.jsoneditor-null { + color: #004ED0; +} + +div.jsoneditor-value.jsoneditor-invalid { + color: #000000; +} + +div.jsoneditor-tree button { + width: 24px; + height: 24px; + padding: 0; + margin: 0; + border: none; + cursor: pointer; + background: transparent url("img/jsoneditor-icons.svg"); +} + +div.jsoneditor-mode-view tr.jsoneditor-expandable td.jsoneditor-tree, +div.jsoneditor-mode-form tr.jsoneditor-expandable td.jsoneditor-tree { + cursor: pointer; +} + +div.jsoneditor-tree button.jsoneditor-collapsed { + background-position: 0 -48px; +} + +div.jsoneditor-tree button.jsoneditor-expanded { + background-position: 0 -72px; +} + +div.jsoneditor-tree button.jsoneditor-contextmenu { + background-position: -48px -72px; +} + +div.jsoneditor-tree button.jsoneditor-contextmenu:hover, +div.jsoneditor-tree button.jsoneditor-contextmenu:focus, +div.jsoneditor-tree button.jsoneditor-contextmenu.jsoneditor-selected, +tr.jsoneditor-selected.jsoneditor-first button.jsoneditor-contextmenu { + background-position: -48px -48px; +} + +div.jsoneditor-tree *:focus { + outline: none; +} + +div.jsoneditor-tree button:focus { + /* TODO: nice outline for buttons with focus + outline: #97B0F8 solid 2px; + box-shadow: 0 0 8px #97B0F8; + */ + background-color: #f5f5f5; + outline: #e5e5e5 solid 1px; +} + +div.jsoneditor-tree button.jsoneditor-invisible { + visibility: hidden; + background: none; +} + +#userdata-editor{ + height:100%; +} + +div.jsoneditor { + color: #1A1A1A; + border: 1px solid #2e2e2e; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; + width: 100%; + overflow: hidden; + position: relative; + padding: 0; + line-height: 100%; +} + +div.jsoneditor-tree table.jsoneditor-tree { + border-collapse: collapse; + border-spacing: 0; + width: 100%; + margin: 0; +} + +div.jsoneditor-outer { + width: 100%; + margin: -35px 0 0 0; + padding: 0 0 0 0; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; + overflow-y: auto; +} + +.ace-jsoneditor { + min-height: 150px; + height: auto !important; +} + +div.jsoneditor-tree { + width: 100%; + position: relative; +} + +textarea.jsoneditor-text { + width: 100%; + margin: 0; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; + outline-width: 0; + border: none; + background-color: white; + resize: none; +} + +tr.jsoneditor-highlight, +tr.jsoneditor-selected { + background-color: #e6e6e6; +} + +tr.jsoneditor-selected button.jsoneditor-dragarea, +tr.jsoneditor-selected button.jsoneditor-contextmenu { + visibility: hidden; +} + +tr.jsoneditor-selected.jsoneditor-first button.jsoneditor-dragarea, +tr.jsoneditor-selected.jsoneditor-first button.jsoneditor-contextmenu { + visibility: visible; +} + +div.jsoneditor-tree button.jsoneditor-dragarea { + background: url("img/jsoneditor-icons.svg") -72px -72px; + cursor: move; +} + +div.jsoneditor-tree button.jsoneditor-dragarea:hover, +div.jsoneditor-tree button.jsoneditor-dragarea:focus, +tr.jsoneditor-selected.jsoneditor-first button.jsoneditor-dragarea { + background-position: -72px -48px; +} + +div.jsoneditor tr, +div.jsoneditor th, +div.jsoneditor td { + padding: 0; + margin: 0; + overflow: visible; +} + +div.jsoneditor td { + vertical-align: top; +} + +div.jsoneditor td.jsoneditor-tree { + vertical-align: top; +} + +div.jsoneditor-field, +div.jsoneditor-value, +div.jsoneditor td, +div.jsoneditor th, +div.jsoneditor textarea, +.jsoneditor-schema-error { + font-family: droid sans mono, consolas, monospace, courier new, courier, sans-serif; + font-size: 10pt; + color: grey; +} + +/* popover */ + +.jsoneditor-schema-error { + cursor: default; + display: inline-block; + /*font-family: arial, sans-serif;*/ + height: 24px; + line-height: 24px; + position: relative; + text-align: center; + width: 24px; +} + +div.jsoneditor-tree .jsoneditor-schema-error { + width: 24px; + height: 24px; + padding: 0; + margin: 0 4px 0 0; + background: url("img/jsoneditor-icons.svg") -168px -48px; +} + +.jsoneditor-schema-error .jsoneditor-popover { + background-color: #4c4c4c; + border-radius: 3px; + box-shadow: 0 0 5px rgba(0,0,0,0.4); + color: #fff; + display: none; + padding: 7px 10px; + position: absolute; + width: 200px; + z-index: 4; +} + +.jsoneditor-schema-error .jsoneditor-popover.jsoneditor-above { + bottom: 32px; + left: -98px; +} + +.jsoneditor-schema-error .jsoneditor-popover.jsoneditor-below { + top: 32px; + left: -98px; +} + +.jsoneditor-schema-error .jsoneditor-popover.jsoneditor-left { + top: -7px; + right: 32px; +} + +.jsoneditor-schema-error .jsoneditor-popover.jsoneditor-right { + top: -7px; + left: 32px; +} + +.jsoneditor-schema-error .jsoneditor-popover:before { + border-right: 7px solid transparent; + border-left: 7px solid transparent; + content: ''; + display: block; + left: 50%; + margin-left: -7px; + position: absolute; +} + +.jsoneditor-schema-error .jsoneditor-popover.jsoneditor-above:before { + border-top: 7px solid #4c4c4c; + bottom: -7px; +} + +.jsoneditor-schema-error .jsoneditor-popover.jsoneditor-below:before { + border-bottom: 7px solid #4c4c4c; + top: -7px; +} + +.jsoneditor-schema-error .jsoneditor-popover.jsoneditor-left:before { + border-left: 7px solid #4c4c4c; + border-top: 7px solid transparent; + border-bottom: 7px solid transparent; + content: ''; + top: 19px; + right: -14px; + left: inherit; + margin-left: inherit; + margin-top: -7px; + position: absolute; +} + +.jsoneditor-schema-error .jsoneditor-popover.jsoneditor-right:before { + border-right: 7px solid #4c4c4c; + border-top: 7px solid transparent; + border-bottom: 7px solid transparent; + content: ''; + top: 19px; + left: -14px; + margin-left: inherit; + margin-top: -7px; + position: absolute; +} + +.jsoneditor-schema-error:hover .jsoneditor-popover, +.jsoneditor-schema-error:focus .jsoneditor-popover { + display: block; + -webkit-animation: fade-in .3s linear 1, move-up .3s linear 1; + -moz-animation: fade-in .3s linear 1, move-up .3s linear 1; + -ms-animation: fade-in .3s linear 1, move-up .3s linear 1; +} + +@-webkit-keyframes fade-in { + from { + opacity: 0; + } + + to { + opacity: 1; + } +} + +@-moz-keyframes fade-in { + from { + opacity: 0; + } + + to { + opacity: 1; + } +} + +@-ms-keyframes fade-in { + from { + opacity: 0; + } + + to { + opacity: 1; + } +} + +/*@-webkit-keyframes move-up {*/ + +/*from { bottom: 24px; }*/ + +/*to { bottom: 32px; }*/ + +/*}*/ + +/*@-moz-keyframes move-up {*/ + +/*from { bottom: 24px; }*/ + +/*to { bottom: 32px; }*/ + +/*}*/ + +/*@-ms-keyframes move-up {*/ + +/*from { bottom: 24px; }*/ + +/*to { bottom: 32px; }*/ + +/*}*/ + +/* JSON schema errors displayed at the bottom of the editor in mode text and code */ + +.jsoneditor .jsoneditor-text-errors { + width: 100%; + border-collapse: collapse; + background-color: #ffef8b; + border-top: 1px solid #ffd700; +} + +.jsoneditor .jsoneditor-text-errors td { + padding: 3px 6px; + vertical-align: middle; +} + +.jsoneditor-text-errors .jsoneditor-schema-error { + border: none; + width: 24px; + height: 24px; + padding: 0; + margin: 0 4px 0 0; + background: url("img/jsoneditor-icons.svg") -168px -48px; +} +/* ContextMenu - main menu */ + +div.jsoneditor-contextmenu-root { + position: relative; + width: 0; + height: 0; +} + +div.jsoneditor-contextmenu { + position: absolute; + box-sizing: content-box; + z-index: 998; +} + +div.jsoneditor-contextmenu ul, +div.jsoneditor-contextmenu li { + box-sizing: content-box; +} + +div.jsoneditor-contextmenu ul { + position: relative; + left: 0; + top: 0; + width: 124px; + background: white; + border: 1px solid #d3d3d3; + box-shadow: 2px 2px 12px rgba(128, 128, 128, 0.3); + list-style: none; + margin: 0; + padding: 0; +} + +div.jsoneditor-contextmenu ul li button { + padding: 0; + margin: 0; + width: 124px; + height: 24px; + border: none; + cursor: pointer; + color: #4d4d4d; + background: transparent; + font-size: 10pt; + font-family: arial, sans-serif; + box-sizing: border-box; + line-height: 26px; + text-align: left; +} + +/* Fix button padding in firefox */ + +div.jsoneditor-contextmenu ul li button::-moz-focus-inner { + padding: 0; + border: 0; +} + +div.jsoneditor-contextmenu ul li button:hover, +div.jsoneditor-contextmenu ul li button:focus { + color: #1a1a1a; + background-color: #f5f5f5; + outline: none; +} + +div.jsoneditor-contextmenu ul li button.jsoneditor-default { + width: 92px; +} + +div.jsoneditor-contextmenu ul li button.jsoneditor-expand { + float: right; + width: 32px; + height: 24px; + border-left: 1px solid #e5e5e5; +} + +div.jsoneditor-contextmenu div.jsoneditor-icon { + float: left; + width: 24px; + height: 24px; + border: none; + padding: 0; + margin: 0; + background-image: url("img/jsoneditor-icons.svg"); +} + +div.jsoneditor-contextmenu ul li button div.jsoneditor-expand { + float: right; + width: 24px; + height: 24px; + padding: 0; + margin: 0 4px 0 0; + background: url("img/jsoneditor-icons.svg") 0 -72px; + opacity: 0.4; +} + +div.jsoneditor-contextmenu ul li button:hover div.jsoneditor-expand, +div.jsoneditor-contextmenu ul li button:focus div.jsoneditor-expand, +div.jsoneditor-contextmenu ul li.jsoneditor-selected div.jsoneditor-expand, +div.jsoneditor-contextmenu ul li button.jsoneditor-expand:hover div.jsoneditor-expand, +div.jsoneditor-contextmenu ul li button.jsoneditor-expand:focus div.jsoneditor-expand { + opacity: 1; +} + +div.jsoneditor-contextmenu div.jsoneditor-separator { + height: 0; + border-top: 1px solid #e5e5e5; + padding-top: 5px; + margin-top: 5px; +} + +div.jsoneditor-contextmenu button.jsoneditor-remove > div.jsoneditor-icon { + background-position: -24px -24px; +} + +div.jsoneditor-contextmenu button.jsoneditor-remove:hover > div.jsoneditor-icon, +div.jsoneditor-contextmenu button.jsoneditor-remove:focus > div.jsoneditor-icon { + background-position: -24px 0; +} + +div.jsoneditor-contextmenu button.jsoneditor-append > div.jsoneditor-icon { + background-position: 0 -24px; +} + +div.jsoneditor-contextmenu button.jsoneditor-append:hover > div.jsoneditor-icon, +div.jsoneditor-contextmenu button.jsoneditor-append:focus > div.jsoneditor-icon { + background-position: 0 0; +} + +div.jsoneditor-contextmenu button.jsoneditor-insert > div.jsoneditor-icon { + background-position: 0 -24px; +} + +div.jsoneditor-contextmenu button.jsoneditor-insert:hover > div.jsoneditor-icon, +div.jsoneditor-contextmenu button.jsoneditor-insert:focus > div.jsoneditor-icon { + background-position: 0 0; +} + +div.jsoneditor-contextmenu button.jsoneditor-duplicate > div.jsoneditor-icon { + background-position: -48px -24px; +} + +div.jsoneditor-contextmenu button.jsoneditor-duplicate:hover > div.jsoneditor-icon, +div.jsoneditor-contextmenu button.jsoneditor-duplicate:focus > div.jsoneditor-icon { + background-position: -48px 0; +} + +div.jsoneditor-contextmenu button.jsoneditor-sort-asc > div.jsoneditor-icon { + background-position: -168px -24px; +} + +div.jsoneditor-contextmenu button.jsoneditor-sort-asc:hover > div.jsoneditor-icon, +div.jsoneditor-contextmenu button.jsoneditor-sort-asc:focus > div.jsoneditor-icon { + background-position: -168px 0; +} + +div.jsoneditor-contextmenu button.jsoneditor-sort-desc > div.jsoneditor-icon { + background-position: -192px -24px; +} + +div.jsoneditor-contextmenu button.jsoneditor-sort-desc:hover > div.jsoneditor-icon, +div.jsoneditor-contextmenu button.jsoneditor-sort-desc:focus > div.jsoneditor-icon { + background-position: -192px 0; +} + +/* ContextMenu - sub menu */ + +div.jsoneditor-contextmenu ul li button.jsoneditor-selected, +div.jsoneditor-contextmenu ul li button.jsoneditor-selected:hover, +div.jsoneditor-contextmenu ul li button.jsoneditor-selected:focus { + color: white; + background-color: #ee422e; +} + +div.jsoneditor-contextmenu ul li { + overflow: hidden; +} + +div.jsoneditor-contextmenu ul li ul { + display: none; + position: relative; + left: -10px; + top: 0; + border: none; + box-shadow: inset 0 0 10px rgba(128, 128, 128, 0.5); + padding: 0 10px; + /* TODO: transition is not supported on IE8-9 */ + -webkit-transition: all 0.3s ease-out; + -moz-transition: all 0.3s ease-out; + -o-transition: all 0.3s ease-out; + transition: all 0.3s ease-out; +} + + + +div.jsoneditor-contextmenu ul li ul li button { + padding-left: 24px; + animation: all ease-in-out 1s; +} + +div.jsoneditor-contextmenu ul li ul li button:hover, +div.jsoneditor-contextmenu ul li ul li button:focus { + background-color: #f5f5f5; +} + +div.jsoneditor-contextmenu button.jsoneditor-type-string > div.jsoneditor-icon { + background-position: -144px -24px; +} + +div.jsoneditor-contextmenu button.jsoneditor-type-string:hover > div.jsoneditor-icon, +div.jsoneditor-contextmenu button.jsoneditor-type-string:focus > div.jsoneditor-icon, +div.jsoneditor-contextmenu button.jsoneditor-type-string.jsoneditor-selected > div.jsoneditor-icon { + background-position: -144px 0; +} + +div.jsoneditor-contextmenu button.jsoneditor-type-auto > div.jsoneditor-icon { + background-position: -120px -24px; +} + +div.jsoneditor-contextmenu button.jsoneditor-type-auto:hover > div.jsoneditor-icon, +div.jsoneditor-contextmenu button.jsoneditor-type-auto:focus > div.jsoneditor-icon, +div.jsoneditor-contextmenu button.jsoneditor-type-auto.jsoneditor-selected > div.jsoneditor-icon { + background-position: -120px 0; +} + +div.jsoneditor-contextmenu button.jsoneditor-type-object > div.jsoneditor-icon { + background-position: -72px -24px; +} + +div.jsoneditor-contextmenu button.jsoneditor-type-object:hover > div.jsoneditor-icon, +div.jsoneditor-contextmenu button.jsoneditor-type-object:focus > div.jsoneditor-icon, +div.jsoneditor-contextmenu button.jsoneditor-type-object.jsoneditor-selected > div.jsoneditor-icon { + background-position: -72px 0; +} + +div.jsoneditor-contextmenu button.jsoneditor-type-array > div.jsoneditor-icon { + background-position: -96px -24px; +} + +div.jsoneditor-contextmenu button.jsoneditor-type-array:hover > div.jsoneditor-icon, +div.jsoneditor-contextmenu button.jsoneditor-type-array:focus > div.jsoneditor-icon, +div.jsoneditor-contextmenu button.jsoneditor-type-array.jsoneditor-selected > div.jsoneditor-icon { + background-position: -96px 0; +} + +div.jsoneditor-contextmenu button.jsoneditor-type-modes > div.jsoneditor-icon { + background-image: none; + width: 6px; +} +div.jsoneditor-menu { + width: 100%; + height: 35px; + padding: 2px; + margin: 0; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; + color: white; + background-color: #2e2e2e; + border-bottom: 1px solid #2e2e2e; +} + +div.jsoneditor-menu > button, +div.jsoneditor-menu > div.jsoneditor-modes > button { + width: 26px; + height: 26px; + margin: 2px; + padding: 0; + border-radius: 2px; + border: 1px solid transparent; + background: transparent url("img/jsoneditor-icons.svg"); + color: white; + opacity: 0.8; + font-family: arial, sans-serif; + font-size: 10pt; + float: left; +} + +div.jsoneditor-menu > button:hover, +div.jsoneditor-menu > div.jsoneditor-modes > button:hover { + background-color: rgba(255,255,255,0.2); + border: 1px solid rgba(255,255,255,0.4); +} + +div.jsoneditor-menu > button:focus, +div.jsoneditor-menu > button:active, +div.jsoneditor-menu > div.jsoneditor-modes > button:focus, +div.jsoneditor-menu > div.jsoneditor-modes > button:active { + background-color: rgba(255,255,255,0.3); +} + +div.jsoneditor-menu > button:disabled, +div.jsoneditor-menu > div.jsoneditor-modes > button:disabled { + opacity: 0.5; +} + +div.jsoneditor-menu > button.jsoneditor-collapse-all { + background-position: 0 -96px; +} + +div.jsoneditor-menu > button.jsoneditor-expand-all { + background-position: 0 -120px; +} + +div.jsoneditor-menu > button.jsoneditor-undo { + background-position: -24px -96px; +} + +div.jsoneditor-menu > button.jsoneditor-undo:disabled { + background-position: -24px -120px; +} + +div.jsoneditor-menu > button.jsoneditor-redo { + background-position: -48px -96px; +} + +div.jsoneditor-menu > button.jsoneditor-redo:disabled { + background-position: -48px -120px; +} + +div.jsoneditor-menu > button.jsoneditor-compact { + background-position: -72px -96px; +} + +div.jsoneditor-menu > button.jsoneditor-format { + background-position: -72px -120px; +} + +div.jsoneditor-menu > div.jsoneditor-modes { + display: inline-block; + float: left; +} + +div.jsoneditor-menu > div.jsoneditor-modes > button { + background-image: none; + width: auto; + padding-left: 6px; + padding-right: 6px; +} + +div.jsoneditor-menu > button.jsoneditor-separator, +div.jsoneditor-menu > div.jsoneditor-modes > button.jsoneditor-separator { + margin-left: 10px; +} + +div.jsoneditor-menu a { + font-family: arial, sans-serif; + font-size: 10pt; + color: white; + opacity: 0.8; + vertical-align: middle; +} + +div.jsoneditor-menu a:hover { + opacity: 1; +} + +div.jsoneditor-menu a.jsoneditor-poweredBy { + font-size: 8pt; + position: absolute; + right: 0; + top: 0; + padding: 10px; +} +table.jsoneditor-search input, +table.jsoneditor-search div.jsoneditor-results { + font-family: arial, sans-serif; + font-size: 10pt; + color: #1A1A1A; + background: transparent; + /* For Firefox */ +} + +table.jsoneditor-search div.jsoneditor-results { + color: white; + padding-right: 5px; + line-height: 24px; +} + +table.jsoneditor-search { + position: absolute; + right: 4px; + top: 4px; + border-collapse: collapse; + border-spacing: 0; +} + +table.jsoneditor-search div.jsoneditor-frame { + border: 1px solid transparent; + background-color: white; + padding: 0 2px; + margin: 0; +} + +table.jsoneditor-search div.jsoneditor-frame table { + border-collapse: collapse; +} + +table.jsoneditor-search input { + width: 120px; + border: none; + outline: none; + margin: 1px; + line-height: 20px; +} + +table.jsoneditor-search button { + width: 16px; + height: 24px; + padding: 0; + margin: 0; + border: none; + background: url("img/jsoneditor-icons.svg"); + vertical-align: top; +} + +table.jsoneditor-search button:hover { + background-color: transparent; +} + +table.jsoneditor-search button.jsoneditor-refresh { + width: 18px; + background-position: -99px -73px; +} + +table.jsoneditor-search button.jsoneditor-next { + cursor: pointer; + background-position: -124px -73px; +} + +table.jsoneditor-search button.jsoneditor-next:hover { + background-position: -124px -49px; +} + +table.jsoneditor-search button.jsoneditor-previous { + cursor: pointer; + background-position: -148px -73px; + margin-right: 2px; +} + +table.jsoneditor-search button.jsoneditor-previous:hover { + background-position: -148px -49px; +} diff --git a/scripts/simplifiedUI/system/html/css/marketplaces.css b/scripts/simplifiedUI/system/html/css/marketplaces.css new file mode 100644 index 0000000000..04c132eab1 --- /dev/null +++ b/scripts/simplifiedUI/system/html/css/marketplaces.css @@ -0,0 +1,224 @@ +/* +// +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +*/ + +/* + CSS rules copied from edit-style.css. + Edit-style.css is not used in its entirety because don't want custom scrollbars; default scrollbar styling is used in order + to match other marketplace pages. +*/ + +@font-face { + font-family: Raleway-Regular; + src: url(../../../../resources/fonts/Raleway-Regular.ttf), /* Windows production */ + url(../../../../fonts/Raleway-Regular.ttf), /* OSX production */ + url(../../../../interface/resources/fonts/Raleway-Regular.ttf); /* Development, running script in /HiFi/examples */ +} + +@font-face { + font-family: Raleway-Bold; + src: url(../../../../resources/fonts/Raleway-Bold.ttf), + url(../../../../fonts/Raleway-Bold.ttf), + url(../../../../interface/resources/fonts/Raleway-Bold.ttf); +} + +@font-face { + font-family: Raleway-SemiBold; + src: url(../../../../resources/fonts/Raleway-SemiBold.ttf), + url(../../../../fonts/Raleway-SemiBold.ttf), + url(../../../../interface/resources/fonts/Raleway-SemiBold.ttf); +} + +@font-face { + font-family: FiraSans-SemiBold; + src: url(../../../../resources/fonts/FiraSans-SemiBold.ttf), + url(../../../../fonts/FiraSans-SemiBold.ttf), + url(../../../../interface/resources/fonts/FiraSans-SemiBold.ttf); +} + +* { + margin: 0; + padding: 0; +} + +body { + padding: 21px 21px 21px 21px; + + color: #afafaf; + background-color: #404040; + font-family: Raleway-Regular; + font-size: 15px; + + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + + overflow-x: hidden; + overflow-y: auto; +} + +input[type=button] { + font-family: Raleway-Bold; + font-size: 13px; + text-transform: uppercase; + vertical-align: top; + height: 28px; + min-width: 120px; + padding: 0px 18px; + margin-right: 6px; + border-radius: 5px; + border: none; + color: #fff; + background-color: #000; + background: linear-gradient(#343434 20%, #000 100%); + cursor: pointer; +} + +input[type=button].blue { + color: #fff; + background-color: #1080b8; + background: linear-gradient(#00b4ef 20%, #1080b8 100%); +} + + +/* + Marketplaces-specific CSS. +*/ + +body { + background: white; + padding: 0 0 0 0; + font-family:Raleway-SemiBold; +} +.marketplaces-container { + display: inline-block; + color: black; + width: 94%; + margin-left: 3%; + height: 100%; +} +.marketplaces-title { + margin-top: 45px; + margin-bottom: 20px; +} +.marketplaces-intro-text { + margin-bottom: 30px; +} +.marketplace-tile { + float:left; + width: 100%; + margin-bottom: 25px; +} +.marketplace-tile-first-column { + text-align: center; + float: left; + width: 33%; +} +.marketplace-tile-second-column { + float: left; + margin-left:4%; + width: 62%; +} +.exploreButton { + font-size: 16px !important; + width: 200px !important; + height: 45px !important; + margin-top: 20px; + margin-bottom: 30px; +} +.tile-divider { + width: 100%; + margin-left: 0%; + display: block; + height: 1px; + border: 0; + border-top: 1px solid lightgrey; + margin: 1em 0; + padding: 0; + margin-bottom: 30px; +} +.marketplace-tile-description { + margin-top: 15px; + margin-bottom: 30px; +} +.marketplace-tile-image { + margin-top:15px; + max-width: 256px; + height: 128px; + margin-bottom:60px; + -webkit-box-shadow: -1px 4px 16px 0px rgba(0, 0, 0, 0.48); + -moz-box-shadow: -1px 4px 16px 0px rgba(0, 0, 0, 0.48); + box-shadow: -1px 4px 16px 0px rgba(0, 0, 0, 0.48); +} +.marketplace-clara-steps { + padding-left: 15px; +} +.marketplace-clara-steps > li { + margin-top: 5px; +} + +#marketplace-navigation { + width: 100%; + height: 50px; + background: #00b4ef; + position: fixed; + bottom: 0; +} +#marketplace-navigation .glyph { + /* + // Target look but can't use font in injected script. + font-family: HiFi-Glyphs; + font-size: 40px; + margin-left: 20px; + */ + font-family: sans-serif; + font-size: 24px; + margin-left: 20px; + margin-right: 3px; + color: #fff; + line-height: 50px; +} +#marketplace-navigation .text { + color: #fff; + font-size: 18px; + line-height: 50px; + vertical-align: top; + position: relative; + top: 1px; +} +#marketplace-navigation input { + position: absolute; + right: 20px; + margin-top: 12px; + padding-left: 15px; + padding-right: 15px; +} + +@media (max-width:768px) { + .marketplace-tile-first-column { + float: left; + width: 100%; + } + .marketplace-tile-second-column { + float: left; + width: 100%; + } + .exploreButton-holder { + width:100%; + text-align:center; + } + .tile-divider { + width: 100%; + margin-left: 0; + } + .marketplace-tile-image { + margin-bottom: 15px; + } +} diff --git a/scripts/simplifiedUI/system/html/css/miniTablet.css b/scripts/simplifiedUI/system/html/css/miniTablet.css new file mode 100644 index 0000000000..7598332d28 --- /dev/null +++ b/scripts/simplifiedUI/system/html/css/miniTablet.css @@ -0,0 +1,92 @@ +/* +miniTablet.css + +Created by David Rowe on 20 Aug 2018. +Copyright 2018 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 +*/ + +* { + box-sizing: border-box; + padding: 0; + margin: 0; + user-select: none; +} + +html { + background-color: #404040; +} + +body { + height: 100%; +} + +section { + background-color: #404040; + position: relative; + padding: 16px 16px; +} + +.button { + width: 116px; + height: 84px; + margin-top: 16px; + text-align: center; +} + + .button:first-child { + margin-top: 0; + } + +img { + width: 40px; +} + +#mute { + padding-top: 19px; + background-size: 100% 100%; + background-image: url("./img/mt-mute-normal.svg"); +} + + #mute:hover { + background-image: url("./img/mt-mute-hover.svg"); + } + +#goto { + padding-top: 19px; + background-size: 100% 100%; + background-image: url("./img/mt-goto-normal.svg"); +} + + #goto:hover { + background-image: url("./img/mt-goto-hover.svg"); + } + + #goto:hover.unhover { + background-image: url("./img/mt-goto-normal.svg"); + } + +#expand { + position: absolute; + right: 1px; + bottom: -1px; + width: 50px; + height: 50px; + background-size: 100% 100%; + background-image: url("./img/mt-expand-normal.svg"); +} + + #expand:hover { + background-image: url("./img/mt-expand-hover.svg"); + } + + #expand:hover.unhover { + background-image: url("./img/mt-expand-normal.svg"); + } + + #expand img { + width:34px; + margin-top: 7px; + } diff --git a/scripts/simplifiedUI/system/html/gridControls.html b/scripts/simplifiedUI/system/html/gridControls.html new file mode 100644 index 0000000000..8d6ee34bc0 --- /dev/null +++ b/scripts/simplifiedUI/system/html/gridControls.html @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + +
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+
+
+
+
+ +
+ + +
+
+
+ + diff --git a/scripts/simplifiedUI/system/html/img/blast_icon.svg b/scripts/simplifiedUI/system/html/img/blast_icon.svg new file mode 100644 index 0000000000..31df8e7f53 --- /dev/null +++ b/scripts/simplifiedUI/system/html/img/blast_icon.svg @@ -0,0 +1,20 @@ + + + + + + + + + diff --git a/scripts/simplifiedUI/system/html/img/blocks-tile.png b/scripts/simplifiedUI/system/html/img/blocks-tile.png new file mode 100644 index 0000000000..49de535c1c Binary files /dev/null and b/scripts/simplifiedUI/system/html/img/blocks-tile.png differ diff --git a/scripts/simplifiedUI/system/html/img/button-snap-print.svg b/scripts/simplifiedUI/system/html/img/button-snap-print.svg new file mode 100644 index 0000000000..d1570711d7 --- /dev/null +++ b/scripts/simplifiedUI/system/html/img/button-snap-print.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/html/img/clara-tile.png b/scripts/simplifiedUI/system/html/img/clara-tile.png new file mode 100644 index 0000000000..ae431dd510 Binary files /dev/null and b/scripts/simplifiedUI/system/html/img/clara-tile.png differ diff --git a/scripts/simplifiedUI/system/html/img/expand.svg b/scripts/simplifiedUI/system/html/img/expand.svg new file mode 100644 index 0000000000..f57e624374 --- /dev/null +++ b/scripts/simplifiedUI/system/html/img/expand.svg @@ -0,0 +1,85 @@ + + + +image/svg+xml + + + + + + + + + \ No newline at end of file diff --git a/scripts/simplifiedUI/system/html/img/fb_icon.svg b/scripts/simplifiedUI/system/html/img/fb_icon.svg new file mode 100644 index 0000000000..6d67d17bb2 --- /dev/null +++ b/scripts/simplifiedUI/system/html/img/fb_icon.svg @@ -0,0 +1,13 @@ + + + + + + diff --git a/scripts/simplifiedUI/system/html/img/hifi-marketplace-tile.png b/scripts/simplifiedUI/system/html/img/hifi-marketplace-tile.png new file mode 100644 index 0000000000..9a95c081a0 Binary files /dev/null and b/scripts/simplifiedUI/system/html/img/hifi-marketplace-tile.png differ diff --git a/scripts/simplifiedUI/system/html/img/hifi_icon.svg b/scripts/simplifiedUI/system/html/img/hifi_icon.svg new file mode 100644 index 0000000000..acbb98a3b3 --- /dev/null +++ b/scripts/simplifiedUI/system/html/img/hifi_icon.svg @@ -0,0 +1,19 @@ + + + + + + + diff --git a/scripts/simplifiedUI/system/html/img/loader.gif b/scripts/simplifiedUI/system/html/img/loader.gif new file mode 100644 index 0000000000..c464703c84 Binary files /dev/null and b/scripts/simplifiedUI/system/html/img/loader.gif differ diff --git a/scripts/simplifiedUI/system/html/img/snapshotIcon.png b/scripts/simplifiedUI/system/html/img/snapshotIcon.png new file mode 100644 index 0000000000..5cb2742a32 Binary files /dev/null and b/scripts/simplifiedUI/system/html/img/snapshotIcon.png differ diff --git a/scripts/simplifiedUI/system/html/img/twitter_icon.svg b/scripts/simplifiedUI/system/html/img/twitter_icon.svg new file mode 100644 index 0000000000..0393d963f2 --- /dev/null +++ b/scripts/simplifiedUI/system/html/img/twitter_icon.svg @@ -0,0 +1,12 @@ + + + + + + diff --git a/scripts/simplifiedUI/system/html/js/SnapshotReview.js b/scripts/simplifiedUI/system/html/js/SnapshotReview.js new file mode 100644 index 0000000000..1e8be9d644 --- /dev/null +++ b/scripts/simplifiedUI/system/html/js/SnapshotReview.js @@ -0,0 +1,814 @@ +/*jslint browser:true */ +/*jslint maxlen: 180*/ +"use strict"; +// +// SnapshotReview.js +// scripts/system/html/js/ +// +// Created by Howard Stearns 8/22/2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +var paths = []; +var idCounter = 0; +var imageCount = 0; +var blastShareText = "Blast to my Connections", + blastAlreadySharedText = "Already Blasted to Connections", + hifiShareText = "Share to Snaps Feed", + hifiAlreadySharedText = "Already Shared to Snaps Feed", + facebookShareText = "Share to Facebook", + twitterShareText = "Share to Twitter", + shareButtonLabelTextActive = "SHARE:", + shareButtonLabelTextInactive = "SHARE"; + +function fileExtensionMatches(filePath, extension) { + return filePath.split('.').pop().toLowerCase() === extension; +} + +function showSetupInstructions() { + var snapshotImagesDiv = document.getElementById("snapshot-images"); + snapshotImagesDiv.className = "snapshotInstructions"; + snapshotImagesDiv.innerHTML = 'Snapshot Instructions' + + '
' + + '

Take and share snaps and GIFs with people in High Fidelity, Facebook, and Twitter.

' + + "

Setup Instructions

" + + "

Before you can begin taking snaps, please choose where you'd like to save snaps on your computer:

" + + '
' + + '
' + + '' + + '
'; + document.getElementById("snap-button").disabled = true; +} +function showSetupComplete() { + var snapshotImagesDiv = document.getElementById("snapshot-images"); + snapshotImagesDiv.className = "snapshotInstructions"; + snapshotImagesDiv.innerHTML = 'Snapshot Instructions' + + '
' + + '
' + + '

Snapshot location set.

' + + '

Press the big red button to take a snap!

' + + '
'; + document.getElementById("snap-button").disabled = false; +} +function showSnapshotInstructions() { + var snapshotImagesDiv = document.getElementById("snapshot-images"); + snapshotImagesDiv.className = "snapshotInstructions"; + snapshotImagesDiv.innerHTML = 'Snapshot Instructions' + + '
' + + '

Take and share snaps and GIFs with people in High Fidelity, Facebook, and Twitter.

' + + '
' + + '
' + + '

Press the big red button to take a snap!

' + + '
'; +} +function chooseSnapshotLocation() { + EventBridge.emitWebEvent(JSON.stringify({ + type: "snapshot", + action: "chooseSnapshotLocation" + })); +} +function login() { + EventBridge.emitWebEvent(JSON.stringify({ + type: "snapshot", + action: "login" + })); +} +function clearImages() { + var snapshotImagesDiv = document.getElementById("snapshot-images"); + snapshotImagesDiv.classList.remove("snapshotInstructions"); + while (snapshotImagesDiv.hasChildNodes()) { + snapshotImagesDiv.removeChild(snapshotImagesDiv.lastChild); + } + paths = []; + imageCount = 0; + idCounter = 0; +} + +function selectImageWithHelpText(selectedID, isSelected) { + if (selectedID.id) { + selectedID = selectedID.id; // sometimes (?), `selectedID` is passed as an HTML object to these functions; we just want the ID + } + var imageContainer = document.getElementById(selectedID), + image = document.getElementById(selectedID + 'img'), + shareBar = document.getElementById(selectedID + "shareBar"), + helpTextDiv = document.getElementById(selectedID + "helpTextDiv"), + showShareButtonsButtonDiv = document.getElementById(selectedID + "showShareButtonsButtonDiv"), + itr, + containers = document.getElementsByClassName("shareControls"); + + if (isSelected) { + showShareButtonsButtonDiv.onclick = function () { selectImageWithHelpText(selectedID, false); }; + showShareButtonsButtonDiv.classList.remove("inactive"); + showShareButtonsButtonDiv.classList.add("active"); + + image.onclick = function () { selectImageWithHelpText(selectedID, false); }; + imageContainer.style.outline = "4px solid #00b4ef"; + imageContainer.style.outlineOffset = "-4px"; + + shareBar.style.backgroundColor = "rgba(0, 0, 0, 0.45)"; + shareBar.style.pointerEvents = "initial"; + + helpTextDiv.style.visibility = "visible"; + + for (itr = 0; itr < containers.length; itr += 1) { + var parentID = containers[itr].id.slice(0, 2); + if (parentID !== selectedID) { + selectImageWithHelpText(parentID, false); + } + } + } else { + showShareButtonsButtonDiv.onclick = function () { selectImageWithHelpText(selectedID, true); }; + showShareButtonsButtonDiv.classList.remove("active"); + showShareButtonsButtonDiv.classList.add("inactive"); + + image.onclick = function () { selectImageWithHelpText(selectedID, true); }; + imageContainer.style.outline = "none"; + + shareBar.style.backgroundColor = "rgba(0, 0, 0, 0.0)"; + shareBar.style.pointerEvents = "none"; + + helpTextDiv.style.visibility = "hidden"; + } +} +function selectImageToShare(selectedID, isSelected) { + if (selectedID.id) { + selectedID = selectedID.id; // sometimes (?), `selectedID` is passed as an HTML object to these functions; we just want the ID + } + var imageContainer = document.getElementById(selectedID), + image = document.getElementById(selectedID + 'img'), + shareBar = document.getElementById(selectedID + "shareBar"), + showShareButtonsDots = document.getElementById(selectedID + "showShareButtonsDots"), + showShareButtonsLabel = document.getElementById(selectedID + "showShareButtonsLabel"), + shareButtonsDiv = document.getElementById(selectedID + "shareButtonsDiv"), + shareBarHelp = document.getElementById(selectedID + "shareBarHelp"), + showShareButtonsButtonDiv = document.getElementById(selectedID + "showShareButtonsButtonDiv"), + itr, + containers = document.getElementsByClassName("shareControls"); + + if (isSelected) { + showShareButtonsButtonDiv.onclick = function () { selectImageToShare(selectedID, false); }; + showShareButtonsButtonDiv.classList.remove("inactive"); + showShareButtonsButtonDiv.classList.add("active"); + + image.onclick = function () { selectImageToShare(selectedID, false); }; + imageContainer.style.outline = "4px solid #00b4ef"; + imageContainer.style.outlineOffset = "-4px"; + + shareBar.style.backgroundColor = "rgba(0, 0, 0, 0.45)"; + shareBar.style.pointerEvents = "initial"; + + showShareButtonsDots.style.visibility = "hidden"; + showShareButtonsLabel.innerHTML = shareButtonLabelTextActive; + + shareButtonsDiv.style.visibility = "visible"; + shareBarHelp.style.visibility = "visible"; + + for (itr = 0; itr < containers.length; itr += 1) { + var parentID = containers[itr].id.slice(0, 2); + if (parentID !== selectedID) { + selectImageToShare(parentID, false); + } + } + } else { + showShareButtonsButtonDiv.onclick = function () { selectImageToShare(selectedID, true); }; + showShareButtonsButtonDiv.classList.remove("active"); + showShareButtonsButtonDiv.classList.add("inactive"); + + image.onclick = function () { selectImageToShare(selectedID, true); }; + imageContainer.style.outline = "none"; + + shareBar.style.backgroundColor = "rgba(0, 0, 0, 0.0)"; + shareBar.style.pointerEvents = "none"; + + showShareButtonsDots.style.visibility = "visible"; + showShareButtonsLabel.innerHTML = shareButtonLabelTextInactive; + + shareButtonsDiv.style.visibility = "hidden"; + shareBarHelp.style.visibility = "hidden"; + } +} +function createShareBar(parentID, isLoggedIn, canShare, isGif, blastButtonDisabled, hifiButtonDisabled, canBlast) { + var shareBar = document.createElement("div"), + shareBarHelpID = parentID + "shareBarHelp", + shareButtonsDivID = parentID + "shareButtonsDiv", + showShareButtonsButtonDivID = parentID + "showShareButtonsButtonDiv", + showShareButtonsDotsID = parentID + "showShareButtonsDots", + showShareButtonsLabelID = parentID + "showShareButtonsLabel", + blastToConnectionsButtonID = parentID + "blastToConnectionsButton", + shareWithEveryoneButtonID = parentID + "shareWithEveryoneButton", + facebookButtonID = parentID + "facebookButton", + twitterButtonID = parentID + "twitterButton", + shareBarInnerHTML = ''; + + shareBar.id = parentID + "shareBar"; + shareBar.className = "shareControls"; + + if (isLoggedIn) { + if (canShare) { + shareBarInnerHTML = '' + + '
' + + '' + + '' + + '' + + '
' + + '' + + ''; + + // Add onclick handler to parent DIV's img to toggle share buttons + document.getElementById(parentID + 'img').onclick = function () { selectImageToShare(parentID, true); }; + } else { + shareBarInnerHTML = '
' + + '' + + '' + + '' + + '
' + + '' + + ''; + // Add onclick handler to parent DIV's img to toggle share buttons + document.getElementById(parentID + 'img').onclick = function () { selectImageWithHelpText(parentID, true); }; + } + } else { + shareBarInnerHTML = '
' + + '' + + '' + + '' + + '
' + + '' + + ''; + // Add onclick handler to parent DIV's img to toggle share buttons + document.getElementById(parentID + 'img').onclick = function () { selectImageWithHelpText(parentID, true); }; + } + + shareBar.innerHTML = shareBarInnerHTML; + + return shareBar; +} +function appendShareBar(divID, isLoggedIn, canShare, isGif, blastButtonDisabled, hifiButtonDisabled, canBlast) { + if (divID.id) { + divID = divID.id; // sometimes (?), `containerID` is passed as an HTML object to these functions; we just want the ID + } + document.getElementById(divID).appendChild(createShareBar(divID, isLoggedIn, canShare, isGif, blastButtonDisabled, hifiButtonDisabled, canBlast)); + if (divID === "p0") { + if (isLoggedIn) { + if (canShare) { + selectImageToShare(divID, true); + } else { + selectImageWithHelpText(divID, true); + } + } else { + selectImageWithHelpText(divID, true); + } + } + if (isLoggedIn && canShare) { + if (canBlast) { + shareButtonHovered('blast', divID, false); + } else { + shareButtonHovered('hifi', divID, false); + } + } +} +function shareForUrl(selectedID) { + EventBridge.emitWebEvent(JSON.stringify({ + type: "snapshot", + action: "shareSnapshotForUrl", + data: paths[parseInt(selectedID.substring(1), 10)] + })); +} +function addImage(image_data, isLoggedIn, canShare, isGifLoading, isShowingPreviousImages, blastButtonDisabled, hifiButtonDisabled, canBlast) { + if (!image_data.localPath) { + return; + } + var imageContainer = document.createElement("DIV"), + img = document.createElement("IMG"), + isGif = fileExtensionMatches(image_data.localPath, "gif"), + id = "p" + (isGif ? "1" : "0"); + imageContainer.id = id; + imageContainer.style.width = "95%"; + imageContainer.style.height = "240px"; + imageContainer.style.margin = "5px auto"; + imageContainer.style.display = "flex"; + imageContainer.style.justifyContent = "center"; + imageContainer.style.alignItems = "center"; + imageContainer.style.position = "relative"; + img.id = id + "img"; + img.src = image_data.localPath; + imageContainer.appendChild(img); + document.getElementById("snapshot-images").appendChild(imageContainer); + paths.push(image_data.localPath); + img.onload = function () { + if (isGif) { + imageContainer.innerHTML += 'GIF'; + } + if (!isGifLoading) { + appendShareBar(id, isLoggedIn, canShare, isGif, blastButtonDisabled, hifiButtonDisabled, canBlast); + } + if ((!isShowingPreviousImages && ((isGif && !isGifLoading) || !isGif)) || (isShowingPreviousImages && !image_data.story_id)) { + shareForUrl(id); + } + if (isShowingPreviousImages && isLoggedIn && image_data.story_id) { + updateShareInfo(id, image_data.story_id); + } + if (isShowingPreviousImages) { + requestPrintButtonUpdate(); + } + }; + img.onerror = function () { + img.onload = null; + img.src = image_data.errorPath; + EventBridge.emitWebEvent(JSON.stringify({ + type: "snapshot", + action: "alertSnapshotLoadFailed" + })); + }; +} +function showConfirmationMessage(selectedID, destination) { + if (selectedID.id) { + selectedID = selectedID.id; // sometimes (?), `containerID` is passed as an HTML object to these functions; we just want the ID + } + + var opacity = 2.0, + fadeRate = 0.05, + timeBetweenFadesMS = 50, + confirmationMessageContainer = document.createElement("div"), + confirmationMessage = document.createElement("div"); + confirmationMessageContainer.className = "confirmationMessageContainer"; + + confirmationMessage.className = "confirmationMessage"; + + var socialIcon = document.createElement("img"); + switch (destination) { + case 'blast': + socialIcon.src = "img/blast_icon.svg"; + confirmationMessage.appendChild(socialIcon); + confirmationMessage.innerHTML += 'Blast Sent!'; + confirmationMessage.style.backgroundColor = "#EA4C5F"; + break; + case 'hifi': + socialIcon.src = "img/hifi_icon.svg"; + confirmationMessage.appendChild(socialIcon); + confirmationMessage.innerHTML += 'Snap Shared!'; + confirmationMessage.style.backgroundColor = "#1FC6A6"; + break; + } + + confirmationMessageContainer.appendChild(confirmationMessage); + document.getElementById(selectedID).appendChild(confirmationMessageContainer); + + setInterval(function () { + if (opacity <= fadeRate) { + confirmationMessageContainer.remove(); + } + opacity -= fadeRate; + confirmationMessageContainer.style.opacity = opacity; + }, timeBetweenFadesMS); +} +function showUploadingMessage(selectedID, destination) { + if (selectedID.id) { + selectedID = selectedID.id; // sometimes (?), `containerID` is passed as an HTML object to these functions; we just want the ID + } + + var shareBarHelp = document.getElementById(selectedID + "shareBarHelp"); + + shareBarHelp.innerHTML = 'Preparing to Share'; + shareBarHelp.classList.add("uploading"); + shareBarHelp.setAttribute("data-destination", destination); +} +function hideUploadingMessageAndMaybeShare(selectedID, storyID) { + if (selectedID.id) { + selectedID = selectedID.id; // sometimes (?), `containerID` is passed as an HTML object to these functions; we just want the ID + } + + var shareBarHelp = document.getElementById(selectedID + "shareBarHelp"), + shareBarHelpDestination = shareBarHelp.getAttribute("data-destination"); + + shareBarHelp.classList.remove("uploading"); + if (shareBarHelpDestination) { + switch (shareBarHelpDestination) { + case 'blast': + blastToConnections(selectedID, selectedID === "p1"); + shareBarHelp.innerHTML = blastAlreadySharedText; + break; + case 'hifi': + shareWithEveryone(selectedID, selectedID === "p1"); + shareBarHelp.innerHTML = hifiAlreadySharedText; + break; + case 'facebook': + var facebookButton = document.getElementById(selectedID + "facebookButton"); + window.open(facebookButton.getAttribute("href"), "_blank"); + shareBarHelp.innerHTML = facebookShareText; + // This emitWebEvent() call isn't necessary in the "hifi" and "blast" cases + // because the "removeFromStoryIDsToMaybeDelete()" call happens + // in snapshot.js when sharing with that method. + EventBridge.emitWebEvent(JSON.stringify({ + type: "snapshot", + action: "removeFromStoryIDsToMaybeDelete", + story_id: storyID + })); + break; + case 'twitter': + var twitterButton = document.getElementById(selectedID + "twitterButton"); + window.open(twitterButton.getAttribute("href"), "_blank"); + shareBarHelp.innerHTML = twitterShareText; + EventBridge.emitWebEvent(JSON.stringify({ + type: "snapshot", + action: "removeFromStoryIDsToMaybeDelete", + story_id: storyID + })); + break; + } + + shareBarHelp.setAttribute("data-destination", ""); + } +} +function updateShareInfo(containerID, storyID) { + if (containerID.id) { + containerID = containerID.id; // sometimes (?), `containerID` is passed as an HTML object to these functions; we just want the ID + } + var shareBar = document.getElementById(containerID + "shareBar"), + parentDiv = document.getElementById(containerID), + shareURL = "https://highfidelity.com/user_stories/" + storyID, + facebookButton = document.getElementById(containerID + "facebookButton"), + twitterButton = document.getElementById(containerID + "twitterButton"); + + parentDiv.setAttribute('data-story-id', storyID); + + facebookButton.setAttribute("target", "_blank"); + facebookButton.setAttribute("href", 'https://www.facebook.com/dialog/feed?app_id=1585088821786423&link=' + shareURL); + + twitterButton.setAttribute("target", "_blank"); + twitterButton.setAttribute("href", 'https://twitter.com/intent/tweet?text=I%20just%20took%20a%20snapshot!&url=' + shareURL + '&via=highfidelityVR&hashtags=VR,HiFi'); + + hideUploadingMessageAndMaybeShare(containerID, storyID); +} +function blastToConnections(selectedID, isGif) { + if (selectedID.id) { + selectedID = selectedID.id; // sometimes (?), `selectedID` is passed as an HTML object to these functions; we just want the ID + } + + var blastToConnectionsButton = document.getElementById(selectedID + "blastToConnectionsButton"), + shareBar = document.getElementById(selectedID + "shareBar"), + shareBarHelp = document.getElementById(selectedID + "shareBarHelp"); + blastToConnectionsButton.onclick = function () { }; + + var storyID = document.getElementById(selectedID).getAttribute("data-story-id"); + + if (storyID) { + EventBridge.emitWebEvent(JSON.stringify({ + type: "snapshot", + action: "blastToConnections", + story_id: storyID, + isGif: isGif + })); + showConfirmationMessage(selectedID, 'blast'); + blastToConnectionsButton.classList.add("disabled"); + blastToConnectionsButton.style.backgroundColor = "#000000"; + blastToConnectionsButton.style.opacity = "0.5"; + shareBarHelp.style.backgroundColor = "#000000"; + shareBarHelp.style.opacity = "0.5"; + } else { + showUploadingMessage(selectedID, 'blast'); + } +} +function shareWithEveryone(selectedID, isGif) { + if (selectedID.id) { + selectedID = selectedID.id; // sometimes (?), `selectedID` is passed as an HTML object to these functions; we just want the ID + } + + var shareWithEveryoneButton = document.getElementById(selectedID + "shareWithEveryoneButton"), + shareBar = document.getElementById(selectedID + "shareBar"), + shareBarHelp = document.getElementById(selectedID + "shareBarHelp"); + shareWithEveryoneButton.onclick = function () { }; + + var storyID = document.getElementById(selectedID).getAttribute("data-story-id"); + + if (storyID) { + EventBridge.emitWebEvent(JSON.stringify({ + type: "snapshot", + action: "shareSnapshotWithEveryone", + story_id: storyID, + isGif: isGif + })); + showConfirmationMessage(selectedID, 'hifi'); + shareWithEveryoneButton.classList.add("disabled"); + shareWithEveryoneButton.style.backgroundColor = "#000000"; + shareWithEveryoneButton.style.opacity = "0.5"; + shareBarHelp.style.backgroundColor = "#000000"; + shareBarHelp.style.opacity = "0.5"; + } else { + showUploadingMessage(selectedID, 'hifi'); + } +} +function shareButtonHovered(destination, selectedID, shouldAlsoModifyOther) { + if (selectedID.id) { + selectedID = selectedID.id; // sometimes (?), `selectedID` is passed as an HTML object to these functions; we just want the ID + } + var shareBarHelp = document.getElementById(selectedID + "shareBarHelp"), + shareButtonsDiv = document.getElementById(selectedID + "shareButtonsDiv").childNodes, + itr; + + if (!shareBarHelp.classList.contains("uploading")) { + for (itr = 0; itr < shareButtonsDiv.length; itr += 1) { + shareButtonsDiv[itr].style.backgroundColor = "rgba(0, 0, 0, 0)"; + } + shareBarHelp.style.opacity = "1.0"; + switch (destination) { + case 'blast': + var blastToConnectionsButton = document.getElementById(selectedID + "blastToConnectionsButton"); + if (!blastToConnectionsButton.classList.contains("disabled")) { + shareBarHelp.style.backgroundColor = "#EA4C5F"; + shareBarHelp.style.opacity = "1.0"; + blastToConnectionsButton.style.backgroundColor = "#EA4C5F"; + blastToConnectionsButton.style.opacity = "1.0"; + shareBarHelp.innerHTML = blastShareText; + } else { + shareBarHelp.style.backgroundColor = "#000000"; + shareBarHelp.style.opacity = "0.5"; + blastToConnectionsButton.style.backgroundColor = "#000000"; + blastToConnectionsButton.style.opacity = "0.5"; + shareBarHelp.innerHTML = blastAlreadySharedText; + } + break; + case 'hifi': + var shareWithEveryoneButton = document.getElementById(selectedID + "shareWithEveryoneButton"); + if (!shareWithEveryoneButton.classList.contains("disabled")) { + shareBarHelp.style.backgroundColor = "#1FC6A6"; + shareBarHelp.style.opacity = "1.0"; + shareWithEveryoneButton.style.backgroundColor = "#1FC6A6"; + shareWithEveryoneButton.style.opacity = "1.0"; + shareBarHelp.innerHTML = hifiShareText; + } else { + shareBarHelp.style.backgroundColor = "#000000"; + shareBarHelp.style.opacity = "0.5"; + shareWithEveryoneButton.style.backgroundColor = "#000000"; + shareWithEveryoneButton.style.opacity = "0.5"; + shareBarHelp.innerHTML = hifiAlreadySharedText; + } + break; + case 'facebook': + shareBarHelp.style.backgroundColor = "#3C58A0"; + shareBarHelp.innerHTML = facebookShareText; + document.getElementById(selectedID + "facebookButton").style.backgroundColor = "#3C58A0"; + break; + case 'twitter': + shareBarHelp.style.backgroundColor = "#00B4EE"; + shareBarHelp.innerHTML = twitterShareText; + document.getElementById(selectedID + "twitterButton").style.backgroundColor = "#00B4EE"; + break; + } + } + + if (shouldAlsoModifyOther && imageCount > 1) { + if (selectedID === "p0" && !document.getElementById("p1").classList.contains("processingGif")) { + shareButtonHovered(destination, "p1", false); + } else if (selectedID === "p1") { + shareButtonHovered(destination, "p0", false); + } + } +} +function shareButtonClicked(destination, selectedID) { + if (selectedID.id) { + selectedID = selectedID.id; // sometimes (?), `selectedID` is passed as an HTML object to these functions; we just want the ID + } + var storyID = document.getElementById(selectedID).getAttribute("data-story-id"); + + if (!storyID) { + showUploadingMessage(selectedID, destination); + } else { + EventBridge.emitWebEvent(JSON.stringify({ + type: "snapshot", + action: "removeFromStoryIDsToMaybeDelete", + story_id: storyID + })); + } +} + +function handleCaptureSetting(setting) { + var stillAndGif = document.getElementById('stillAndGif'), + stillOnly = document.getElementById('stillOnly'); + + stillAndGif.checked = setting; + stillOnly.checked = !setting; + + stillAndGif.onclick = function () { + EventBridge.emitWebEvent(JSON.stringify({ + type: "snapshot", + action: "captureStillAndGif" + })); + }; + + stillOnly.onclick = function () { + EventBridge.emitWebEvent(JSON.stringify({ + type: "snapshot", + action: "captureStillOnly" + })); + }; + +} +window.onload = function () { + // Uncomment the line below to test functionality in a browser. + // See definition of "testInBrowser()" to modify tests. + //testInBrowser(4); + openEventBridge(function () { + // Set up a handler for receiving the data, and tell the .js we are ready to receive it. + EventBridge.scriptEventReceived.connect(function (message) { + + message = JSON.parse(message); + + if (message.type !== "snapshot") { + return; + } + + var messageOptions = message.options; + + switch (message.action) { + case 'showSetupInstructions': + showSetupInstructions(); + break; + case 'snapshotLocationChosen': + clearImages(); + showSetupComplete(); + break; + case 'clearPreviousImages': + clearImages(); + break; + case 'showPreviousImages': + clearImages(); + imageCount = message.image_data.length; + if (imageCount > 0) { + message.image_data.forEach(function (element, idx) { + addImage(element, messageOptions.isLoggedIn, message.canShare, false, true, message.image_data[idx].blastButtonDisabled, message.image_data[idx].hifiButtonDisabled, messageOptions.canBlast); + }); + } else { + showSnapshotInstructions(); + } + break; + case 'addImages': + // The last element of the message contents list contains a bunch of options, + // including whether or not we can share stuff + // The other elements of the list contain image paths. + if (messageOptions.containsGif === true) { + if (messageOptions.processingGif === true) { + imageCount = message.image_data.length + 1; // "+1" for the GIF that'll finish processing soon + message.image_data.push({ localPath: messageOptions.loadingGifPath }); + message.image_data.forEach(function (element, idx) { + addImage(element, messageOptions.isLoggedIn, idx === 0 && messageOptions.canShare, idx === 1, false, false, false, true); + }); + document.getElementById("p1").classList.add("processingGif"); + document.getElementById("snap-button").disabled = true; + } else { + var gifPath = message.image_data[0].localPath, + p1img = document.getElementById('p1img'); + p1img.src = gifPath; + + paths[1] = gifPath; + shareForUrl("p1"); + appendShareBar("p1", messageOptions.isLoggedIn, messageOptions.canShare, true, false, false, messageOptions.canBlast); + document.getElementById("p1").classList.remove("processingGif"); + document.getElementById("snap-button").disabled = false; + } + } else { + imageCount = message.image_data.length; + message.image_data.forEach(function (element) { + addImage(element, messageOptions.isLoggedIn, messageOptions.canShare, false, false, false, false, true); + }); + document.getElementById("snap-button").disabled = false; + } + break; + case 'captureSettings': + handleCaptureSetting(message.setting); + break; + case 'setPrintButtonEnabled': + setPrintButtonEnabled(); + break; + case 'setPrintButtonLoading': + setPrintButtonLoading(); + break; + case 'setPrintButtonDisabled': + setPrintButtonDisabled(); + break; + case 'snapshotUploadComplete': + var isGif = fileExtensionMatches(message.image_url, "gif"); + updateShareInfo(isGif ? "p1" : "p0", message.story_id); + if (isPrintProcessing()) { + setPrintButtonEnabled(); + } + break; + default: + console.log("Unknown message action received in SnapshotReview.js."); + break; + } + }); + + EventBridge.emitWebEvent(JSON.stringify({ + type: "snapshot", + action: "ready" + })); + });; +}; +function snapshotSettings() { + EventBridge.emitWebEvent(JSON.stringify({ + type: "snapshot", + action: "openSettings" + })); +} +function takeSnapshot() { + document.getElementById("snap-button").disabled = true; + EventBridge.emitWebEvent(JSON.stringify({ + type: "snapshot", + action: "takeSnapshot" + })); +} + +function isPrintDisabled() { + var printElement = document.getElementById('print-icon'); + + return printElement.classList.contains("print-icon") && + printElement.classList.contains("print-icon-default") && + document.getElementById('print-button').disabled; +} +function isPrintProcessing() { + var printElement = document.getElementById('print-icon'); + + return printElement.classList.contains("print-icon") && + printElement.classList.contains("print-icon-loading") && + document.getElementById('print-button').disabled; +} +function isPrintEnabled() { + var printElement = document.getElementById('print-icon'); + + return printElement.classList.contains("print-icon") && + printElement.classList.contains("print-icon-default") && + !document.getElementById('print-button').disabled; +} + +function setPrintButtonLoading() { + document.getElementById('print-icon').className = "print-icon print-icon-loading"; + document.getElementById('print-button').disabled = true; +} +function setPrintButtonDisabled() { + document.getElementById('print-icon').className = "print-icon print-icon-default"; + document.getElementById('print-button').disabled = true; +} +function setPrintButtonEnabled() { + document.getElementById('print-button').disabled = false; + document.getElementById('print-icon').className = "print-icon print-icon-default"; +} + +function requestPrintButtonUpdate() { + EventBridge.emitWebEvent(JSON.stringify({ + type: "snapshot", + action: "requestPrintButtonUpdate" + })); +} + +function printToPolaroid() { + if (isPrintEnabled()) { + EventBridge.emitWebEvent(JSON.stringify({ + type: "snapshot", + action: "printToPolaroid" + })); + } else { + setPrintButtonLoading(); + } +} + +function testInBrowser(test) { + if (test === 0) { + showSetupInstructions(); + } else if (test === 1) { + imageCount = 2; + //addImage({ localPath: 'http://lorempixel.com/553/255' }); + addImage({ localPath: 'D:/Dropbox/Screenshots/High Fidelity Snapshots/hifi-snap-by-zfox-on-2017-05-01_13-28-58.jpg', story_id: 1338 }, true, true, false, true, false, false, true); + addImage({ localPath: 'D:/Dropbox/Screenshots/High Fidelity Snapshots/hifi-snap-by-zfox-on-2017-05-01_13-28-58.gif', story_id: 1337 }, true, true, false, true, false, false, true); + } else if (test === 2) { + addImage({ localPath: 'D:/Dropbox/Screenshots/High Fidelity Snapshots/hifi-snap-by-zfox-on-2017-05-01_13-28-58.jpg', story_id: 1338 }, true, true, false, true, false, false, true); + addImage({ localPath: 'D:/Dropbox/Screenshots/High Fidelity Snapshots/hifi-snap-by-zfox-on-2017-05-01_13-28-58.gif', story_id: 1337 }, true, true, false, true, false, false, true); + showConfirmationMessage("p0", 'blast'); + showConfirmationMessage("p1", 'hifi'); + } else if (test === 3) { + imageCount = 2; + //addImage({ localPath: 'http://lorempixel.com/553/255' }); + addImage({ localPath: 'D:/Dropbox/Screenshots/High Fidelity Snapshots/hifi-snap-by-zfox-on-2017-05-01_13-28-58.jpg', story_id: 1338 }, true, true, false, true, false, false, true); + addImage({ localPath: 'D:/Dropbox/Screenshots/High Fidelity Snapshots/hifi-snap-by-zfox-on-2017-05-01_13-28-58.gif', story_id: 1337 }, true, true, false, true, false, false, true); + showUploadingMessage("p0", 'hifi'); + } else if (test === 4) { + imageCount = 2; + //addImage({ localPath: 'http://lorempixel.com/553/255' }); + addImage({ localPath: 'D:/Dropbox/Screenshots/High Fidelity Snapshots/hifi-snap-by-zfox-on-2017-05-01_13-28-58.jpg', story_id: 1338 }, false, true, false, true, false, false, true); + addImage({ localPath: 'D:/Dropbox/Screenshots/High Fidelity Snapshots/hifi-snap-by-zfox-on-2017-05-01_13-28-58.gif', story_id: 1337 }, false, true, false, true, false, false, true); +} +} diff --git a/scripts/simplifiedUI/system/html/js/colpick.js b/scripts/simplifiedUI/system/html/js/colpick.js new file mode 100644 index 0000000000..e4ad65dfb6 --- /dev/null +++ b/scripts/simplifiedUI/system/html/js/colpick.js @@ -0,0 +1,616 @@ +/* +colpick Color Picker +Copyright 2013 Jose Vargas. Licensed under GPL license. Based on Stefan Petre's Color Picker www.eyecon.ro, dual licensed +under the MIT and GPL licenses + +For usage and examples: colpick.com/plugin + */ + +/* global console, document, Element, EventBridge, jQuery, navigator, window, _ $ */ + +(function ($) { + var colpick = function () { + var + tpl = '
' + + '
' + + '
' + + '
' + + '
' + + '
#
' + + '
' + + '
R
' + + '
' + + '
' + + '
G
' + + '
' + + '
B
' + + '
' + + '
' + + '
H
' + + '
' + + '
' + + '
S
' + + '
' + + '
' + + '
B
' + + '
' + + '
' + + '
', + defaults = { + showEvent: 'click', + onShow: function () {}, + onBeforeShow: function(){}, + onHide: function () {}, + onChange: function () {}, + onSubmit: function () {}, + colorScheme: 'light', + color: '3289c7', + livePreview: true, + flat: false, + layout: 'full', + submit: 1, + submitText: 'OK', + height: 156 + }, + // Fill the inputs of the plugin + fillRGBFields = function (hsb, cal) { + var rgb = hsbToRgb(hsb); + $(cal).data('colpick').fields + .eq(1).val(rgb.r).end() + .eq(2).val(rgb.g).end() + .eq(3).val(rgb.b).end(); + }, + fillHSBFields = function (hsb, cal) { + $(cal).data('colpick').fields + .eq(4).val(Math.round(hsb.h)).end() + .eq(5).val(Math.round(hsb.s)).end() + .eq(6).val(Math.round(hsb.b)).end(); + }, + fillHexFields = function (hsb, cal) { + $(cal).data('colpick').fields.eq(0).val(hsbToHex(hsb)); + }, + // Set the round selector position + setSelector = function (hsb, cal) { + $(cal).data('colpick').selector.css('backgroundColor', '#' + hsbToHex({h: hsb.h, s: 100, b: 100})); + $(cal).data('colpick').selectorIndic.css({ + left: parseInt($(cal).data('colpick').height * hsb.s/100, 10), + top: parseInt($(cal).data('colpick').height * (100-hsb.b)/100, 10) + }); + }, + // Set the hue selector position + setHue = function (hsb, cal) { + $(cal).data('colpick').hue.css('top', + parseInt($(cal).data('colpick').height - $(cal).data('colpick').height * hsb.h / 360, 10)); + }, + // Set current and new colors + setCurrentColor = function (hsb, cal) { + $(cal).data('colpick').currentColor.css('backgroundColor', '#' + hsbToHex(hsb)); + }, + setNewColor = function (hsb, cal) { + $(cal).data('colpick').newColor.css('backgroundColor', '#' + hsbToHex(hsb)); + }, + // Called when the new color is changed + change = function (ev) { + var cal = $(this).parent().parent(), col; + if (this.parentNode.className.indexOf('_hex') > 0) { + cal.data('colpick').color = col = hexToHsb(fixHex(this.value)); + fillRGBFields(col, cal.get(0)); + fillHSBFields(col, cal.get(0)); + fillHexFields(col, cal.get(0)); + } else if (this.parentNode.className.indexOf('_hsb') > 0) { + cal.data('colpick').color = col = fixHSB({ + h: parseInt(cal.data('colpick').fields.eq(4).val(), 10), + s: parseInt(cal.data('colpick').fields.eq(5).val(), 10), + b: parseInt(cal.data('colpick').fields.eq(6).val(), 10) + }); + fillRGBFields(col, cal.get(0)); + fillHexFields(col, cal.get(0)); + fillHSBFields(col, cal.get(0)); + } else { + cal.data('colpick').color = col = rgbToHsb(fixRGB({ + r: parseInt(cal.data('colpick').fields.eq(1).val(), 10), + g: parseInt(cal.data('colpick').fields.eq(2).val(), 10), + b: parseInt(cal.data('colpick').fields.eq(3).val(), 10) + })); + fillHexFields(col, cal.get(0)); + fillHSBFields(col, cal.get(0)); + fillRGBFields(col, cal.get(0)); + } + setSelector(col, cal.get(0)); + setHue(col, cal.get(0)); + setNewColor(col, cal.get(0)); + cal.data('colpick').onChange.apply(cal.parent(), + [col, hsbToHex(col), hsbToRgb(col), cal.data('colpick').el, 0]); + }, + // Change style on blur and on focus of inputs + blur = function (ev) { + $(this).parent().removeClass('colpick_focus'); + }, + focus = function () { + $(this).parent().parent().data('colpick').fields.parent().removeClass('colpick_focus'); + $(this).parent().addClass('colpick_focus'); + }, + // Increment/decrement arrows functions + downIncrement = function (ev) { + ev.preventDefault ? ev.preventDefault() : ev.returnValue = false; + var field = $(this).parent().find('input').focus(); + var current = { + el: $(this).parent().addClass('colpick_slider'), + max: this.parentNode.className.indexOf('_hsb_h') > 0 ? 360 : + (this.parentNode.className.indexOf('_hsb') > 0 ? 100 : 255), + y: ev.pageY, + field: field, + val: parseInt(field.val(), 10), + preview: $(this).parent().parent().data('colpick').livePreview + }; + $(document).mouseup(current, upIncrement); + $(document).mousemove(current, moveIncrement); + }, + moveIncrement = function (ev) { + ev.data.field.val(Math.max(0, Math.min(ev.data.max, parseInt(ev.data.val - ev.pageY + ev.data.y, 10)))); + if (ev.data.preview) { + change.apply(ev.data.field.get(0), [true]); + } + return false; + }, + upIncrement = function (ev) { + change.apply(ev.data.field.get(0), [true]); + ev.data.el.removeClass('colpick_slider').find('input').focus(); + $(document).off('mouseup', upIncrement); + $(document).off('mousemove', moveIncrement); + return false; + }, + // Hue slider functions + downHue = function (ev) { + ev.preventDefault ? ev.preventDefault() : ev.returnValue = false; + var current = { + cal: $(this).parent(), + y: $(this).offset().top + }; + $(document).on('mouseup touchend',current,upHue); + $(document).on('mousemove touchmove',current,moveHue); + + var pageY = ((ev.type === 'touchstart') ? ev.originalEvent.changedTouches[0].pageY : ev.pageY ); + change.apply( + current.cal.data('colpick') + .fields.eq(4).val(parseInt(360 * (current.cal.data('colpick').height - + (pageY - current.y)) / current.cal.data('colpick').height, 10)) + .get(0), + [current.cal.data('colpick').livePreview] + ); + return false; + }, + moveHue = function (ev) { + var pageY = ((ev.type === 'touchmove') ? ev.originalEvent.changedTouches[0].pageY : ev.pageY ); + change.apply( + ev.data.cal.data('colpick') + .fields.eq(4).val(parseInt(360 * (ev.data.cal.data('colpick').height - + Math.max(0, Math.min(ev.data.cal.data('colpick').height, (pageY - ev.data.y)))) / + ev.data.cal.data('colpick').height, 10)) + .get(0), + [ev.data.preview] + ); + return false; + }, + upHue = function (ev) { + fillRGBFields(ev.data.cal.data('colpick').color, ev.data.cal.get(0)); + fillHexFields(ev.data.cal.data('colpick').color, ev.data.cal.get(0)); + $(document).off('mouseup touchend',upHue); + $(document).off('mousemove touchmove',moveHue); + return false; + }, + // Color selector functions + downSelector = function (ev) { + ev.preventDefault ? ev.preventDefault() : ev.returnValue = false; + var current = { + cal: $(this).parent(), + pos: $(this).offset() + }; + current.preview = current.cal.data('colpick').livePreview; + + $(document).on('mouseup touchend',current,upSelector); + $(document).on('mousemove touchmove',current,moveSelector); + + var pageX,pageY; + if (ev.type === 'touchstart') { + pageX = ev.originalEvent.changedTouches[0].pageX, + pageY = ev.originalEvent.changedTouches[0].pageY; + } else { + pageX = ev.pageX; + pageY = ev.pageY; + } + + change.apply( + current.cal.data('colpick').fields + .eq(6).val(parseInt(100 * (current.cal.data('colpick').height - (pageY - current.pos.top)) / + current.cal.data('colpick').height, 10)).end() + .eq(5).val(parseInt(100*(pageX - current.pos.left)/current.cal.data('colpick').height, 10)) + .get(0), + [current.preview] + ); + return false; + }, + moveSelector = function (ev) { + var pageX,pageY; + if (ev.type === 'touchmove') { + pageX = ev.originalEvent.changedTouches[0].pageX, + pageY = ev.originalEvent.changedTouches[0].pageY; + } else { + pageX = ev.pageX; + pageY = ev.pageY; + } + + change.apply( + ev.data.cal.data('colpick').fields + .eq(6).val(parseInt(100 * (ev.data.cal.data('colpick').height - + Math.max(0, Math.min(ev.data.cal.data('colpick').height, (pageY - ev.data.pos.top)))) / + ev.data.cal.data('colpick').height, 10)).end() + .eq(5).val(parseInt(100 * (Math.max(0, Math.min(ev.data.cal.data('colpick').height, + (pageX - ev.data.pos.left)))) / ev.data.cal.data('colpick').height, 10)) + .get(0), + [ev.data.preview] + ); + return false; + }, + upSelector = function (ev) { + fillRGBFields(ev.data.cal.data('colpick').color, ev.data.cal.get(0)); + fillHexFields(ev.data.cal.data('colpick').color, ev.data.cal.get(0)); + $(document).off('mouseup touchend',upSelector); + $(document).off('mousemove touchmove',moveSelector); + return false; + }, + // Submit button + clickSubmit = function (ev) { + var cal = $(this).parent(); + var col = cal.data('colpick').color; + cal.data('colpick').origColor = col; + setCurrentColor(col, cal.get(0)); + cal.data('colpick').onSubmit(col, hsbToHex(col), hsbToRgb(col), cal.data('colpick').el); + }, + // Show/hide the color picker + show = function (ev) { + if ($(this).attr('disabled')) { + return; + } + // Prevent the trigger of any direct parent + ev.stopPropagation(); + var cal = $('#' + $(this).data('colpickId')); + cal.data('colpick').onBeforeShow.apply(this, [cal.get(0)]); + var pos = $(this).offset(); + var top = pos.top + this.offsetHeight; + var left = pos.left; + var viewPort = getViewport(); + var calW = cal.width(); + if (left + calW > viewPort.l + viewPort.w) { + left -= calW; + } + cal.css({left: left + 'px', top: top + 'px'}); + if (cal.data('colpick').onShow.apply(this, [cal.get(0)]) !== false) { + cal.show(); + } + // Hide when user clicks outside + $('html').mousedown({cal:cal}, hide); + cal.mousedown(function(ev){ + ev.stopPropagation(); + }); + }, + hide = function (ev) { + if (ev.data.cal.data('colpick').onHide.apply(this, [ev.data.cal.get(0)]) !== false) { + ev.data.cal.hide(); + } + $('html').off('mousedown', hide); + }, + getViewport = function () { + var m = document.compatMode === 'CSS1Compat'; + return { + l : window.pageXOffset || (m ? document.documentElement.scrollLeft : document.body.scrollLeft), + w : window.innerWidth || (m ? document.documentElement.clientWidth : document.body.clientWidth) + }; + }, + // Fix the values if the user enters a negative or high value + fixHSB = function (hsb) { + hsb.h = isNaN(hsb.h) ? 0 : hsb.h; + hsb.s = isNaN(hsb.s) ? 0 : hsb.s; + hsb.b = isNaN(hsb.b) ? 0 : hsb.b; + return { + h: Math.min(360, Math.max(0, hsb.h)), + s: Math.min(100, Math.max(0, hsb.s)), + b: Math.min(100, Math.max(0, hsb.b)) + }; + }, + fixRGB = function (rgb) { + rgb.r = isNaN(rgb.r) ? 0 : rgb.r; + rgb.g = isNaN(rgb.g) ? 0 : rgb.g; + rgb.b = isNaN(rgb.b) ? 0 : rgb.b; + return { + r: Math.min(255, Math.max(0, rgb.r)), + g: Math.min(255, Math.max(0, rgb.g)), + b: Math.min(255, Math.max(0, rgb.b)) + }; + }, + fixHex = function (hex) { + var len = 6 - hex.length; + if (len > 0) { + var o = []; + for (var i=0; i').attr('style', + 'height:8.333333%; filter:progid:' + + 'DXImageTransform.Microsoft.gradient(GradientType=0,startColorstr=' + stops[i] + + ', endColorstr=' + stops[i + 1] + + '); -ms-filter: "progid:DXImageTransform.Microsoft.gradient(GradientType=0,startColorstr=' + + stops[i] + ', endColorstr=' + stops[i + 1] + ')";'); + huebar.append(div); + } + } else { + var stopList = stops.join(','); + huebar.attr('style', 'background:-webkit-linear-gradient(top,' + stopList + + '); background: -o-linear-gradient(top,' + stopList + + '); background: -ms-linear-gradient(top,' + stopList + + '); background:-moz-linear-gradient(top,' + stopList + + '); -webkit-linear-gradient(top,' + stopList + + '); background:linear-gradient(to bottom,' + stopList + '); '); + } + cal.find('div.colpick_hue').on('mousedown touchstart',downHue); + options.newColor = cal.find('div.colpick_new_color'); + options.currentColor = cal.find('div.colpick_current_color'); + // Store options and fill with default color + cal.data('colpick', options); + fillRGBFields(options.color, cal.get(0)); + fillHSBFields(options.color, cal.get(0)); + fillHexFields(options.color, cal.get(0)); + setHue(options.color, cal.get(0)); + setSelector(options.color, cal.get(0)); + setCurrentColor(options.color, cal.get(0)); + setNewColor(options.color, cal.get(0)); + // Append to body if flat=false, else show in place + if (options.flat) { + cal.appendTo(this).show(); + cal.css({ + position: 'relative', + display: 'block' + }); + } else { + cal.appendTo(document.body); + $(this).on(options.showEvent, show); + cal.css({ + position:'absolute' + }); + } + } + }); + }, + // Shows the picker + showPicker: function() { + return this.each( function () { + if ($(this).data('colpickId')) { + show.apply(this); + } + }); + }, + // Hides the picker + hidePicker: function() { + return this.each( function () { + if ($(this).data('colpickId')) { + $('#' + $(this).data('colpickId')).hide(); + } + }); + }, + // Sets a color as new and current (default) + setColor: function(col, setCurrent) { + setCurrent = (typeof setCurrent === "undefined") ? 1 : setCurrent; + if (typeof col === 'string') { + col = hexToHsb(col); + } else if (col.r !== undefined && col.g !== undefined && col.b !== undefined) { + col = rgbToHsb(col); + } else if (col.h !== undefined && col.s !== undefined && col.b !== undefined) { + col = fixHSB(col); + } else { + return this; + } + return this.each(function(){ + if ($(this).data('colpickId')) { + var cal = $('#' + $(this).data('colpickId')); + cal.data('colpick').color = col; + cal.data('colpick').origColor = col; + fillRGBFields(col, cal.get(0)); + fillHSBFields(col, cal.get(0)); + fillHexFields(col, cal.get(0)); + setHue(col, cal.get(0)); + setSelector(col, cal.get(0)); + + setNewColor(col, cal.get(0)); + cal.data('colpick').onChange.apply(cal.parent(), + [col, hsbToHex(col), hsbToRgb(col), cal.data('colpick').el, 1]); + if (setCurrent) { + setCurrentColor(col, cal.get(0)); + } + } + }); + } + }; + }(); + // Color space convertions + var hexToRgb = function (hexString) { + if (typeof hexString !== "string") { + print("Error - ColPick.js::hexToRgb expects string object."); + return; + } + + var hexNumber = parseInt(((hexString.indexOf('#') > -1) ? hexString.substring(1) : hexString), 16); + return { r: hexNumber >> 16, g: (hexNumber & 0x00FF00) >> 8, b: (hexNumber & 0x0000FF)}; + }; + var hexToHsb = function (hexString) { + if (typeof hexString !== "string") { + print("Error - ColPick.js::hexToHsb expects string object."); + return; + } + + return rgbToHsb(hexToRgb(hexString)); + }; + var rgbToHsb = function (rgb) { + var hsb = {h: 0, s: 0, b: 0}; + var min = Math.min(rgb.r, rgb.g, rgb.b); + var max = Math.max(rgb.r, rgb.g, rgb.b); + var delta = max - min; + hsb.b = max; + hsb.s = max != 0 ? 255 * delta / max : 0; // eslint-disable-line eqeqeq + if (hsb.s != 0) { // eslint-disable-line eqeqeq + if (rgb.r == max) { // eslint-disable-line eqeqeq + hsb.h = (rgb.g - rgb.b) / delta; + } else if (rgb.g == max) { // eslint-disable-line eqeqeq + hsb.h = 2 + (rgb.b - rgb.r) / delta; + } else { + hsb.h = 4 + (rgb.r - rgb.g) / delta; + } + } else { + hsb.h = -1; + } + hsb.h *= 60; + if (hsb.h < 0) { + hsb.h += 360; + } + hsb.s *= 100/255; + hsb.b *= 100/255; + return hsb; + }; + var hsbToRgb = function (hsb) { + var rgb = {}; + var h = hsb.h; + var s = hsb.s*255/100; + var v = hsb.b*255/100; + if (s == 0) { // eslint-disable-line eqeqeq + rgb.r = rgb.g = rgb.b = v; + } else { + var t1 = v; + var t2 = (255-s)*v/255; + var t3 = (t1-t2)*(h%60)/60; + if (h==360) { // eslint-disable-line eqeqeq + h = 0; + } + if (h<60) { + rgb.r=t1; rgb.b=t2; rgb.g=t2+t3; + } else if (h<120) { + rgb.g=t1; rgb.b=t2; rgb.r=t1-t3; + } else if (h<180) { + rgb.g=t1; rgb.r=t2; rgb.b=t2+t3; + } else if (h<240) { + rgb.b=t1; rgb.r=t2; rgb.g=t1-t3; + } else if (h<300) { + rgb.b=t1; rgb.g=t2; rgb.r=t2+t3; + } else if (h<360) { + rgb.r=t1; rgb.g=t2; rgb.b=t1-t3; + } else { + rgb.r=0; rgb.g=0; rgb.b=0; + } + } + return {r:Math.round(rgb.r), g:Math.round(rgb.g), b:Math.round(rgb.b)}; + }; + var rgbToHex = function (rgb) { + var hex = [ + rgb.r.toString(16), + rgb.g.toString(16), + rgb.b.toString(16) + ]; + $.each(hex, function (nr, val) { + if (val.length == 1) { // eslint-disable-line eqeqeq + hex[nr] = '0' + val; + } + }); + return hex.join(''); + }; + var hsbToHex = function (hsb) { + return rgbToHex(hsbToRgb(hsb)); + }; + $.fn.extend({ + colpick: colpick.init, + colpickHide: colpick.hidePicker, + colpickShow: colpick.showPicker, + colpickSetColor: colpick.setColor + }); + $.extend({ + colpick:{ + rgbToHex: rgbToHex, + rgbToHsb: rgbToHsb, + hsbToHex: hsbToHex, + hsbToRgb: hsbToRgb, + hexToHsb: hexToHsb, + hexToRgb: hexToRgb + } + }); +})(jQuery); + diff --git a/scripts/system/html/js/draggableNumber.js b/scripts/simplifiedUI/system/html/js/draggableNumber.js similarity index 100% rename from scripts/system/html/js/draggableNumber.js rename to scripts/simplifiedUI/system/html/js/draggableNumber.js diff --git a/scripts/system/html/js/entityList.js b/scripts/simplifiedUI/system/html/js/entityList.js similarity index 100% rename from scripts/system/html/js/entityList.js rename to scripts/simplifiedUI/system/html/js/entityList.js diff --git a/scripts/system/html/js/entityListContextMenu.js b/scripts/simplifiedUI/system/html/js/entityListContextMenu.js similarity index 100% rename from scripts/system/html/js/entityListContextMenu.js rename to scripts/simplifiedUI/system/html/js/entityListContextMenu.js diff --git a/scripts/system/html/js/entityProperties.js b/scripts/simplifiedUI/system/html/js/entityProperties.js similarity index 100% rename from scripts/system/html/js/entityProperties.js rename to scripts/simplifiedUI/system/html/js/entityProperties.js diff --git a/scripts/simplifiedUI/system/html/js/eventBridgeLoader.js b/scripts/simplifiedUI/system/html/js/eventBridgeLoader.js new file mode 100644 index 0000000000..411780853b --- /dev/null +++ b/scripts/simplifiedUI/system/html/js/eventBridgeLoader.js @@ -0,0 +1,19 @@ + +//public slots: +// void emitWebEvent(const QString& data); +// void emitScriptEvent(const QString& data); +// +//signals: +// void webEventReceived(const QString& data); +// void scriptEventReceived(const QString& data); +// + +var EventBridge; +var WebChannel; + +openEventBridge = function(callback) { + WebChannel = new QWebChannel(qt.webChannelTransport, function (channel) { + EventBridge = WebChannel.objects.eventBridge; + callback(EventBridge); + }); +} diff --git a/scripts/simplifiedUI/system/html/js/gridControls.js b/scripts/simplifiedUI/system/html/js/gridControls.js new file mode 100644 index 0000000000..e27dac522b --- /dev/null +++ b/scripts/simplifiedUI/system/html/js/gridControls.js @@ -0,0 +1,161 @@ +// gridControls.js +// +// Created by Ryan Huffman on 6 Nov 2014 +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + +function loaded() { + openEventBridge(function() { + elPosY = document.getElementById("horiz-y"); + elMinorSpacing = document.getElementById("minor-spacing"); + elMajorSpacing = document.getElementById("major-spacing"); + elSnapToGrid = document.getElementById("snap-to-grid"); + elHorizontalGridVisible = document.getElementById("horiz-grid-visible"); + elMoveToSelection = document.getElementById("move-to-selection"); + elMoveToAvatar = document.getElementById("move-to-avatar"); + + if (window.EventBridge !== undefined) { + EventBridge.scriptEventReceived.connect(function(data) { + data = JSON.parse(data); + + if (data.origin) { + var origin = data.origin; + elPosY.value = origin.y; + } + + if (data.minorGridEvery !== undefined) { + elMinorSpacing.value = data.minorGridEvery; + } + + if (data.majorGridEvery !== undefined) { + elMajorSpacing.value = data.majorGridEvery; + } + + if (data.gridColor) { + gridColor = data.gridColor; + } + + if (data.snapToGrid !== undefined) { + elSnapToGrid.checked = data.snapToGrid == true; + } + + if (data.visible !== undefined) { + elHorizontalGridVisible.checked = data.visible == true; + } + }); + + function emitUpdate() { + EventBridge.emitWebEvent(JSON.stringify({ + type: "update", + origin: { + y: elPosY.value, + }, + minorGridEvery: elMinorSpacing.value, + majorGridEvery: elMajorSpacing.value, + gridColor: gridColor, + snapToGrid: elSnapToGrid.checked, + visible: elHorizontalGridVisible.checked, + })); + } + + } + + elPosY.addEventListener("change", emitUpdate); + elMinorSpacing.addEventListener("change", emitUpdate); + elMajorSpacing.addEventListener("change", emitUpdate); + elSnapToGrid.addEventListener("change", emitUpdate); + elHorizontalGridVisible.addEventListener("change", emitUpdate); + + elMoveToAvatar.addEventListener("click", function() { + EventBridge.emitWebEvent(JSON.stringify({ + type: "action", + action: "moveToAvatar", + })); + }); + elMoveToSelection.addEventListener("click", function() { + EventBridge.emitWebEvent(JSON.stringify({ + type: "action", + action: "moveToSelection", + })); + }); + + var gridColor = { red: 255, green: 255, blue: 255 }; + var elColor = document.getElementById("grid-color"); + elColor.style.backgroundColor = "rgb(" + gridColor.red + "," + gridColor.green + "," + gridColor.blue + ")"; + + var colorPickFunction = function (red, green, blue) { + gridColor = { red: red, green: green, blue: blue }; + emitUpdate(); + }; + + $('#grid-color').colpick({ + colorScheme: 'dark', + layout: 'rgbhex', + color: { r: gridColor.red, g: gridColor.green, b: gridColor.blue }, + submit: false, + onShow: function (colpick) { + $('#grid-color').attr('active', 'true'); + }, + onHide: function (colpick) { + $('#grid-color').attr('active', 'false'); + }, + onChange: function (hsb, hex, rgb, el) { + $(el).css('background-color', '#' + hex); + colorPickFunction(rgb.r, rgb.g, rgb.b); + } + }); + + augmentSpinButtons(); + disableDragDrop(); + + EventBridge.emitWebEvent(JSON.stringify({ type: 'init' })); + }); + + const KEY_CODES = { + BACKSPACE: 8, + DELETE: 46 + }; + + document.addEventListener("keyup", function (keyUpEvent) { + const FILTERED_NODE_NAMES = ["INPUT", "TEXTAREA"]; + if (FILTERED_NODE_NAMES.includes(keyUpEvent.target.nodeName)) { + return; + } + let {code, key, keyCode, altKey, ctrlKey, metaKey, shiftKey} = keyUpEvent; + + let controlKey = window.navigator.platform.startsWith("Mac") ? metaKey : ctrlKey; + + let keyCodeString; + switch (keyCode) { + case KEY_CODES.DELETE: + keyCodeString = "Delete"; + break; + case KEY_CODES.BACKSPACE: + keyCodeString = "Backspace"; + break; + default: + keyCodeString = String.fromCharCode(keyUpEvent.keyCode); + break; + } + + EventBridge.emitWebEvent(JSON.stringify({ + type: 'keyUpEvent', + keyUpEvent: { + code, + key, + keyCode, + keyCodeString, + altKey, + controlKey, + shiftKey, + } + })); + }, false); + + // Disable right-click context menu which is not visible in the HMD and makes it seem like the app has locked + document.addEventListener("contextmenu", function (event) { + event.preventDefault(); + }, false); +} diff --git a/scripts/simplifiedUI/system/html/js/includes.js b/scripts/simplifiedUI/system/html/js/includes.js new file mode 100644 index 0000000000..c604115f91 --- /dev/null +++ b/scripts/simplifiedUI/system/html/js/includes.js @@ -0,0 +1,27 @@ +// +// includes.js +// +// Created by David Back on 3 Jan 2019 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +const ENTITY_TYPE_ICON = { + Box: "m", + Grid: "", + Image: "", + Light: "p", + Material: "", + Model: "", + ParticleEffect: "", + PolyVox: "", + PolyLine: "", + Shape: "n", + Sphere: "n", + Text: "l", + Web: "q", + Zone: "o", + Multiple: "", +}; diff --git a/scripts/simplifiedUI/system/html/js/jquery-2.1.4.min.js b/scripts/simplifiedUI/system/html/js/jquery-2.1.4.min.js new file mode 100644 index 0000000000..49990d6e14 --- /dev/null +++ b/scripts/simplifiedUI/system/html/js/jquery-2.1.4.min.js @@ -0,0 +1,4 @@ +/*! jQuery v2.1.4 | (c) 2005, 2015 jQuery Foundation, Inc. | jquery.org/license */ +!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=c.slice,e=c.concat,f=c.push,g=c.indexOf,h={},i=h.toString,j=h.hasOwnProperty,k={},l=a.document,m="2.1.4",n=function(a,b){return new n.fn.init(a,b)},o=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,p=/^-ms-/,q=/-([\da-z])/gi,r=function(a,b){return b.toUpperCase()};n.fn=n.prototype={jquery:m,constructor:n,selector:"",length:0,toArray:function(){return d.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:d.call(this)},pushStack:function(a){var b=n.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a,b){return n.each(this,a,b)},map:function(a){return this.pushStack(n.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:c.sort,splice:c.splice},n.extend=n.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||n.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(a=arguments[h]))for(b in a)c=g[b],d=a[b],g!==d&&(j&&d&&(n.isPlainObject(d)||(e=n.isArray(d)))?(e?(e=!1,f=c&&n.isArray(c)?c:[]):f=c&&n.isPlainObject(c)?c:{},g[b]=n.extend(j,f,d)):void 0!==d&&(g[b]=d));return g},n.extend({expando:"jQuery"+(m+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===n.type(a)},isArray:Array.isArray,isWindow:function(a){return null!=a&&a===a.window},isNumeric:function(a){return!n.isArray(a)&&a-parseFloat(a)+1>=0},isPlainObject:function(a){return"object"!==n.type(a)||a.nodeType||n.isWindow(a)?!1:a.constructor&&!j.call(a.constructor.prototype,"isPrototypeOf")?!1:!0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?h[i.call(a)]||"object":typeof a},globalEval:function(a){var b,c=eval;a=n.trim(a),a&&(1===a.indexOf("use strict")?(b=l.createElement("script"),b.text=a,l.head.appendChild(b).parentNode.removeChild(b)):c(a))},camelCase:function(a){return a.replace(p,"ms-").replace(q,r)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b,c){var d,e=0,f=a.length,g=s(a);if(c){if(g){for(;f>e;e++)if(d=b.apply(a[e],c),d===!1)break}else for(e in a)if(d=b.apply(a[e],c),d===!1)break}else if(g){for(;f>e;e++)if(d=b.call(a[e],e,a[e]),d===!1)break}else for(e in a)if(d=b.call(a[e],e,a[e]),d===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(o,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(s(Object(a))?n.merge(c,"string"==typeof a?[a]:a):f.call(c,a)),c},inArray:function(a,b,c){return null==b?-1:g.call(b,a,c)},merge:function(a,b){for(var c=+b.length,d=0,e=a.length;c>d;d++)a[e++]=b[d];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,f=0,g=a.length,h=s(a),i=[];if(h)for(;g>f;f++)d=b(a[f],f,c),null!=d&&i.push(d);else for(f in a)d=b(a[f],f,c),null!=d&&i.push(d);return e.apply([],i)},guid:1,proxy:function(a,b){var c,e,f;return"string"==typeof b&&(c=a[b],b=a,a=c),n.isFunction(a)?(e=d.call(arguments,2),f=function(){return a.apply(b||this,e.concat(d.call(arguments)))},f.guid=a.guid=a.guid||n.guid++,f):void 0},now:Date.now,support:k}),n.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(a,b){h["[object "+b+"]"]=b.toLowerCase()});function s(a){var b="length"in a&&a.length,c=n.type(a);return"function"===c||n.isWindow(a)?!1:1===a.nodeType&&b?!0:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var t=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ha(),z=ha(),A=ha(),B=function(a,b){return a===b&&(l=!0),0},C=1<<31,D={}.hasOwnProperty,E=[],F=E.pop,G=E.push,H=E.push,I=E.slice,J=function(a,b){for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return c;return-1},K="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",L="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",N=M.replace("w","w#"),O="\\["+L+"*("+M+")(?:"+L+"*([*^$|!~]?=)"+L+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+N+"))|)"+L+"*\\]",P=":("+M+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+O+")*)|.*)\\)|)",Q=new RegExp(L+"+","g"),R=new RegExp("^"+L+"+|((?:^|[^\\\\])(?:\\\\.)*)"+L+"+$","g"),S=new RegExp("^"+L+"*,"+L+"*"),T=new RegExp("^"+L+"*([>+~]|"+L+")"+L+"*"),U=new RegExp("="+L+"*([^\\]'\"]*?)"+L+"*\\]","g"),V=new RegExp(P),W=new RegExp("^"+N+"$"),X={ID:new RegExp("^#("+M+")"),CLASS:new RegExp("^\\.("+M+")"),TAG:new RegExp("^("+M.replace("w","w*")+")"),ATTR:new RegExp("^"+O),PSEUDO:new RegExp("^"+P),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+L+"*(even|odd|(([+-]|)(\\d*)n|)"+L+"*(?:([+-]|)"+L+"*(\\d+)|))"+L+"*\\)|)","i"),bool:new RegExp("^(?:"+K+")$","i"),needsContext:new RegExp("^"+L+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+L+"*((?:-\\d)?\\d*)"+L+"*\\)|)(?=[^-]|$)","i")},Y=/^(?:input|select|textarea|button)$/i,Z=/^h\d$/i,$=/^[^{]+\{\s*\[native \w/,_=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,aa=/[+~]/,ba=/'|\\/g,ca=new RegExp("\\\\([\\da-f]{1,6}"+L+"?|("+L+")|.)","ig"),da=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},ea=function(){m()};try{H.apply(E=I.call(v.childNodes),v.childNodes),E[v.childNodes.length].nodeType}catch(fa){H={apply:E.length?function(a,b){G.apply(a,I.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function ga(a,b,d,e){var f,h,j,k,l,o,r,s,w,x;if((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,d=d||[],k=b.nodeType,"string"!=typeof a||!a||1!==k&&9!==k&&11!==k)return d;if(!e&&p){if(11!==k&&(f=_.exec(a)))if(j=f[1]){if(9===k){if(h=b.getElementById(j),!h||!h.parentNode)return d;if(h.id===j)return d.push(h),d}else if(b.ownerDocument&&(h=b.ownerDocument.getElementById(j))&&t(b,h)&&h.id===j)return d.push(h),d}else{if(f[2])return H.apply(d,b.getElementsByTagName(a)),d;if((j=f[3])&&c.getElementsByClassName)return H.apply(d,b.getElementsByClassName(j)),d}if(c.qsa&&(!q||!q.test(a))){if(s=r=u,w=b,x=1!==k&&a,1===k&&"object"!==b.nodeName.toLowerCase()){o=g(a),(r=b.getAttribute("id"))?s=r.replace(ba,"\\$&"):b.setAttribute("id",s),s="[id='"+s+"'] ",l=o.length;while(l--)o[l]=s+ra(o[l]);w=aa.test(a)&&pa(b.parentNode)||b,x=o.join(",")}if(x)try{return H.apply(d,w.querySelectorAll(x)),d}catch(y){}finally{r||b.removeAttribute("id")}}}return i(a.replace(R,"$1"),b,d,e)}function ha(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ia(a){return a[u]=!0,a}function ja(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ka(a,b){var c=a.split("|"),e=a.length;while(e--)d.attrHandle[c[e]]=b}function la(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||C)-(~a.sourceIndex||C);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function na(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function oa(a){return ia(function(b){return b=+b,ia(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function pa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=ga.support={},f=ga.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},m=ga.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=g.documentElement,e=g.defaultView,e&&e!==e.top&&(e.addEventListener?e.addEventListener("unload",ea,!1):e.attachEvent&&e.attachEvent("onunload",ea)),p=!f(g),c.attributes=ja(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ja(function(a){return a.appendChild(g.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=$.test(g.getElementsByClassName),c.getById=ja(function(a){return o.appendChild(a).id=u,!g.getElementsByName||!g.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c&&c.parentNode?[c]:[]}},d.filter.ID=function(a){var b=a.replace(ca,da);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(ca,da);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=$.test(g.querySelectorAll))&&(ja(function(a){o.appendChild(a).innerHTML="",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+L+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+L+"*(?:value|"+K+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ja(function(a){var b=g.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+L+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=$.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ja(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",P)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=$.test(o.compareDocumentPosition),t=b||$.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===g||a.ownerDocument===v&&t(v,a)?-1:b===g||b.ownerDocument===v&&t(v,b)?1:k?J(k,a)-J(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,h=[a],i=[b];if(!e||!f)return a===g?-1:b===g?1:e?-1:f?1:k?J(k,a)-J(k,b):0;if(e===f)return la(a,b);c=a;while(c=c.parentNode)h.unshift(c);c=b;while(c=c.parentNode)i.unshift(c);while(h[d]===i[d])d++;return d?la(h[d],i[d]):h[d]===v?-1:i[d]===v?1:0},g):n},ga.matches=function(a,b){return ga(a,null,null,b)},ga.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(U,"='$1']"),!(!c.matchesSelector||!p||r&&r.test(b)||q&&q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return ga(b,n,null,[a]).length>0},ga.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},ga.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&D.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},ga.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},ga.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=ga.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=ga.selectors={cacheLength:50,createPseudo:ia,match:X,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(ca,da),a[3]=(a[3]||a[4]||a[5]||"").replace(ca,da),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||ga.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&ga.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return X.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&V.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(ca,da).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+L+")"+a+"("+L+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=ga.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(Q," ")+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h;if(q){if(f){while(p){l=b;while(l=l[p])if(h?l.nodeName.toLowerCase()===r:1===l.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){k=q[u]||(q[u]={}),j=k[a]||[],n=j[0]===w&&j[1],m=j[0]===w&&j[2],l=n&&q.childNodes[n];while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if(1===l.nodeType&&++m&&l===b){k[a]=[w,n,m];break}}else if(s&&(j=(b[u]||(b[u]={}))[a])&&j[0]===w)m=j[1];else while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if((h?l.nodeName.toLowerCase()===r:1===l.nodeType)&&++m&&(s&&((l[u]||(l[u]={}))[a]=[w,m]),l===b))break;return m-=e,m===d||m%d===0&&m/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||ga.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ia(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=J(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ia(function(a){var b=[],c=[],d=h(a.replace(R,"$1"));return d[u]?ia(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ia(function(a){return function(b){return ga(a,b).length>0}}),contains:ia(function(a){return a=a.replace(ca,da),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ia(function(a){return W.test(a||"")||ga.error("unsupported lang: "+a),a=a.replace(ca,da).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Z.test(a.nodeName)},input:function(a){return Y.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:oa(function(){return[0]}),last:oa(function(a,b){return[b-1]}),eq:oa(function(a,b,c){return[0>c?c+b:c]}),even:oa(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:oa(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:oa(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:oa(function(a,b,c){for(var d=0>c?c+b:c;++db;b++)d+=a[b].value;return d}function sa(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(i=b[u]||(b[u]={}),(h=i[d])&&h[0]===w&&h[1]===f)return j[2]=h[2];if(i[d]=j,j[2]=a(b,c,g))return!0}}}function ta(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function ua(a,b,c){for(var d=0,e=b.length;e>d;d++)ga(a,b[d],c);return c}function va(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(!c||c(f,d,e))&&(g.push(f),j&&b.push(h));return g}function wa(a,b,c,d,e,f){return d&&!d[u]&&(d=wa(d)),e&&!e[u]&&(e=wa(e,f)),ia(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||ua(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:va(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=va(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?J(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=va(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):H.apply(g,r)})}function xa(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=sa(function(a){return a===b},h,!0),l=sa(function(a){return J(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];f>i;i++)if(c=d.relative[a[i].type])m=[sa(ta(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return wa(i>1&&ta(m),i>1&&ra(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(R,"$1"),c,e>i&&xa(a.slice(i,e)),f>e&&xa(a=a.slice(e)),f>e&&ra(a))}m.push(c)}return ta(m)}function ya(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,m,o,p=0,q="0",r=f&&[],s=[],t=j,u=f||e&&d.find.TAG("*",k),v=w+=null==t?1:Math.random()||.1,x=u.length;for(k&&(j=g!==n&&g);q!==x&&null!=(l=u[q]);q++){if(e&&l){m=0;while(o=a[m++])if(o(l,g,h)){i.push(l);break}k&&(w=v)}c&&((l=!o&&l)&&p--,f&&r.push(l))}if(p+=q,c&&q!==p){m=0;while(o=b[m++])o(r,s,g,h);if(f){if(p>0)while(q--)r[q]||s[q]||(s[q]=F.call(i));s=va(s)}H.apply(i,s),k&&!f&&s.length>0&&p+b.length>1&&ga.uniqueSort(i)}return k&&(w=v,j=t),r};return c?ia(f):f}return h=ga.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=xa(b[c]),f[u]?d.push(f):e.push(f);f=A(a,ya(e,d)),f.selector=a}return f},i=ga.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(ca,da),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=X.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(ca,da),aa.test(j[0].type)&&pa(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&ra(j),!a)return H.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,aa.test(a)&&pa(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ja(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),ja(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||ka("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ja(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ka("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),ja(function(a){return null==a.getAttribute("disabled")})||ka(K,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),ga}(a);n.find=t,n.expr=t.selectors,n.expr[":"]=n.expr.pseudos,n.unique=t.uniqueSort,n.text=t.getText,n.isXMLDoc=t.isXML,n.contains=t.contains;var u=n.expr.match.needsContext,v=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,w=/^.[^:#\[\.,]*$/;function x(a,b,c){if(n.isFunction(b))return n.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return n.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(w.test(b))return n.filter(b,a,c);b=n.filter(b,a)}return n.grep(a,function(a){return g.call(b,a)>=0!==c})}n.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?n.find.matchesSelector(d,a)?[d]:[]:n.find.matches(a,n.grep(b,function(a){return 1===a.nodeType}))},n.fn.extend({find:function(a){var b,c=this.length,d=[],e=this;if("string"!=typeof a)return this.pushStack(n(a).filter(function(){for(b=0;c>b;b++)if(n.contains(e[b],this))return!0}));for(b=0;c>b;b++)n.find(a,e[b],d);return d=this.pushStack(c>1?n.unique(d):d),d.selector=this.selector?this.selector+" "+a:a,d},filter:function(a){return this.pushStack(x(this,a||[],!1))},not:function(a){return this.pushStack(x(this,a||[],!0))},is:function(a){return!!x(this,"string"==typeof a&&u.test(a)?n(a):a||[],!1).length}});var y,z=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,A=n.fn.init=function(a,b){var c,d;if(!a)return this;if("string"==typeof a){if(c="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:z.exec(a),!c||!c[1]&&b)return!b||b.jquery?(b||y).find(a):this.constructor(b).find(a);if(c[1]){if(b=b instanceof n?b[0]:b,n.merge(this,n.parseHTML(c[1],b&&b.nodeType?b.ownerDocument||b:l,!0)),v.test(c[1])&&n.isPlainObject(b))for(c in b)n.isFunction(this[c])?this[c](b[c]):this.attr(c,b[c]);return this}return d=l.getElementById(c[2]),d&&d.parentNode&&(this.length=1,this[0]=d),this.context=l,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):n.isFunction(a)?"undefined"!=typeof y.ready?y.ready(a):a(n):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),n.makeArray(a,this))};A.prototype=n.fn,y=n(l);var B=/^(?:parents|prev(?:Until|All))/,C={children:!0,contents:!0,next:!0,prev:!0};n.extend({dir:function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&n(a).is(c))break;d.push(a)}return d},sibling:function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c}}),n.fn.extend({has:function(a){var b=n(a,this),c=b.length;return this.filter(function(){for(var a=0;c>a;a++)if(n.contains(this,b[a]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=u.test(a)||"string"!=typeof a?n(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&n.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?n.unique(f):f)},index:function(a){return a?"string"==typeof a?g.call(n(a),this[0]):g.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(n.unique(n.merge(this.get(),n(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function D(a,b){while((a=a[b])&&1!==a.nodeType);return a}n.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return n.dir(a,"parentNode")},parentsUntil:function(a,b,c){return n.dir(a,"parentNode",c)},next:function(a){return D(a,"nextSibling")},prev:function(a){return D(a,"previousSibling")},nextAll:function(a){return n.dir(a,"nextSibling")},prevAll:function(a){return n.dir(a,"previousSibling")},nextUntil:function(a,b,c){return n.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return n.dir(a,"previousSibling",c)},siblings:function(a){return n.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return n.sibling(a.firstChild)},contents:function(a){return a.contentDocument||n.merge([],a.childNodes)}},function(a,b){n.fn[a]=function(c,d){var e=n.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=n.filter(d,e)),this.length>1&&(C[a]||n.unique(e),B.test(a)&&e.reverse()),this.pushStack(e)}});var E=/\S+/g,F={};function G(a){var b=F[a]={};return n.each(a.match(E)||[],function(a,c){b[c]=!0}),b}n.Callbacks=function(a){a="string"==typeof a?F[a]||G(a):n.extend({},a);var b,c,d,e,f,g,h=[],i=!a.once&&[],j=function(l){for(b=a.memory&&l,c=!0,g=e||0,e=0,f=h.length,d=!0;h&&f>g;g++)if(h[g].apply(l[0],l[1])===!1&&a.stopOnFalse){b=!1;break}d=!1,h&&(i?i.length&&j(i.shift()):b?h=[]:k.disable())},k={add:function(){if(h){var c=h.length;!function g(b){n.each(b,function(b,c){var d=n.type(c);"function"===d?a.unique&&k.has(c)||h.push(c):c&&c.length&&"string"!==d&&g(c)})}(arguments),d?f=h.length:b&&(e=c,j(b))}return this},remove:function(){return h&&n.each(arguments,function(a,b){var c;while((c=n.inArray(b,h,c))>-1)h.splice(c,1),d&&(f>=c&&f--,g>=c&&g--)}),this},has:function(a){return a?n.inArray(a,h)>-1:!(!h||!h.length)},empty:function(){return h=[],f=0,this},disable:function(){return h=i=b=void 0,this},disabled:function(){return!h},lock:function(){return i=void 0,b||k.disable(),this},locked:function(){return!i},fireWith:function(a,b){return!h||c&&!i||(b=b||[],b=[a,b.slice?b.slice():b],d?i.push(b):j(b)),this},fire:function(){return k.fireWith(this,arguments),this},fired:function(){return!!c}};return k},n.extend({Deferred:function(a){var b=[["resolve","done",n.Callbacks("once memory"),"resolved"],["reject","fail",n.Callbacks("once memory"),"rejected"],["notify","progress",n.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return n.Deferred(function(c){n.each(b,function(b,f){var g=n.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&n.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?n.extend(a,d):d}},e={};return d.pipe=d.then,n.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=d.call(arguments),e=c.length,f=1!==e||a&&n.isFunction(a.promise)?e:0,g=1===f?a:n.Deferred(),h=function(a,b,c){return function(e){b[a]=this,c[a]=arguments.length>1?d.call(arguments):e,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(e>1)for(i=new Array(e),j=new Array(e),k=new Array(e);e>b;b++)c[b]&&n.isFunction(c[b].promise)?c[b].promise().done(h(b,k,c)).fail(g.reject).progress(h(b,j,i)):--f;return f||g.resolveWith(k,c),g.promise()}});var H;n.fn.ready=function(a){return n.ready.promise().done(a),this},n.extend({isReady:!1,readyWait:1,holdReady:function(a){a?n.readyWait++:n.ready(!0)},ready:function(a){(a===!0?--n.readyWait:n.isReady)||(n.isReady=!0,a!==!0&&--n.readyWait>0||(H.resolveWith(l,[n]),n.fn.triggerHandler&&(n(l).triggerHandler("ready"),n(l).off("ready"))))}});function I(){l.removeEventListener("DOMContentLoaded",I,!1),a.removeEventListener("load",I,!1),n.ready()}n.ready.promise=function(b){return H||(H=n.Deferred(),"complete"===l.readyState?setTimeout(n.ready):(l.addEventListener("DOMContentLoaded",I,!1),a.addEventListener("load",I,!1))),H.promise(b)},n.ready.promise();var J=n.access=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===n.type(c)){e=!0;for(h in c)n.access(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,n.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(n(a),c)})),b))for(;i>h;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f};n.acceptData=function(a){return 1===a.nodeType||9===a.nodeType||!+a.nodeType};function K(){Object.defineProperty(this.cache={},0,{get:function(){return{}}}),this.expando=n.expando+K.uid++}K.uid=1,K.accepts=n.acceptData,K.prototype={key:function(a){if(!K.accepts(a))return 0;var b={},c=a[this.expando];if(!c){c=K.uid++;try{b[this.expando]={value:c},Object.defineProperties(a,b)}catch(d){b[this.expando]=c,n.extend(a,b)}}return this.cache[c]||(this.cache[c]={}),c},set:function(a,b,c){var d,e=this.key(a),f=this.cache[e];if("string"==typeof b)f[b]=c;else if(n.isEmptyObject(f))n.extend(this.cache[e],b);else for(d in b)f[d]=b[d];return f},get:function(a,b){var c=this.cache[this.key(a)];return void 0===b?c:c[b]},access:function(a,b,c){var d;return void 0===b||b&&"string"==typeof b&&void 0===c?(d=this.get(a,b),void 0!==d?d:this.get(a,n.camelCase(b))):(this.set(a,b,c),void 0!==c?c:b)},remove:function(a,b){var c,d,e,f=this.key(a),g=this.cache[f];if(void 0===b)this.cache[f]={};else{n.isArray(b)?d=b.concat(b.map(n.camelCase)):(e=n.camelCase(b),b in g?d=[b,e]:(d=e,d=d in g?[d]:d.match(E)||[])),c=d.length;while(c--)delete g[d[c]]}},hasData:function(a){return!n.isEmptyObject(this.cache[a[this.expando]]||{})},discard:function(a){a[this.expando]&&delete this.cache[a[this.expando]]}};var L=new K,M=new K,N=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,O=/([A-Z])/g;function P(a,b,c){var d;if(void 0===c&&1===a.nodeType)if(d="data-"+b.replace(O,"-$1").toLowerCase(),c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:N.test(c)?n.parseJSON(c):c}catch(e){}M.set(a,b,c)}else c=void 0;return c}n.extend({hasData:function(a){return M.hasData(a)||L.hasData(a)},data:function(a,b,c){ +return M.access(a,b,c)},removeData:function(a,b){M.remove(a,b)},_data:function(a,b,c){return L.access(a,b,c)},_removeData:function(a,b){L.remove(a,b)}}),n.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=M.get(f),1===f.nodeType&&!L.get(f,"hasDataAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=n.camelCase(d.slice(5)),P(f,d,e[d])));L.set(f,"hasDataAttrs",!0)}return e}return"object"==typeof a?this.each(function(){M.set(this,a)}):J(this,function(b){var c,d=n.camelCase(a);if(f&&void 0===b){if(c=M.get(f,a),void 0!==c)return c;if(c=M.get(f,d),void 0!==c)return c;if(c=P(f,d,void 0),void 0!==c)return c}else this.each(function(){var c=M.get(this,d);M.set(this,d,b),-1!==a.indexOf("-")&&void 0!==c&&M.set(this,a,b)})},null,b,arguments.length>1,null,!0)},removeData:function(a){return this.each(function(){M.remove(this,a)})}}),n.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=L.get(a,b),c&&(!d||n.isArray(c)?d=L.access(a,b,n.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=n.queue(a,b),d=c.length,e=c.shift(),f=n._queueHooks(a,b),g=function(){n.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return L.get(a,c)||L.access(a,c,{empty:n.Callbacks("once memory").add(function(){L.remove(a,[b+"queue",c])})})}}),n.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.lengthx",k.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var U="undefined";k.focusinBubbles="onfocusin"in a;var V=/^key/,W=/^(?:mouse|pointer|contextmenu)|click/,X=/^(?:focusinfocus|focusoutblur)$/,Y=/^([^.]*)(?:\.(.+)|)$/;function Z(){return!0}function $(){return!1}function _(){try{return l.activeElement}catch(a){}}n.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=L.get(a);if(r){c.handler&&(f=c,c=f.handler,e=f.selector),c.guid||(c.guid=n.guid++),(i=r.events)||(i=r.events={}),(g=r.handle)||(g=r.handle=function(b){return typeof n!==U&&n.event.triggered!==b.type?n.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(E)||[""],j=b.length;while(j--)h=Y.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o&&(l=n.event.special[o]||{},o=(e?l.delegateType:l.bindType)||o,l=n.event.special[o]||{},k=n.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&n.expr.match.needsContext.test(e),namespace:p.join(".")},f),(m=i[o])||(m=i[o]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,p,g)!==!1||a.addEventListener&&a.addEventListener(o,g,!1)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),n.event.global[o]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=L.hasData(a)&&L.get(a);if(r&&(i=r.events)){b=(b||"").match(E)||[""],j=b.length;while(j--)if(h=Y.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=n.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,m=i[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&q!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||n.removeEvent(a,o,r.handle),delete i[o])}else for(o in i)n.event.remove(a,o+b[j],c,d,!0);n.isEmptyObject(i)&&(delete r.handle,L.remove(a,"events"))}},trigger:function(b,c,d,e){var f,g,h,i,k,m,o,p=[d||l],q=j.call(b,"type")?b.type:b,r=j.call(b,"namespace")?b.namespace.split("."):[];if(g=h=d=d||l,3!==d.nodeType&&8!==d.nodeType&&!X.test(q+n.event.triggered)&&(q.indexOf(".")>=0&&(r=q.split("."),q=r.shift(),r.sort()),k=q.indexOf(":")<0&&"on"+q,b=b[n.expando]?b:new n.Event(q,"object"==typeof b&&b),b.isTrigger=e?2:3,b.namespace=r.join("."),b.namespace_re=b.namespace?new RegExp("(^|\\.)"+r.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=d),c=null==c?[b]:n.makeArray(c,[b]),o=n.event.special[q]||{},e||!o.trigger||o.trigger.apply(d,c)!==!1)){if(!e&&!o.noBubble&&!n.isWindow(d)){for(i=o.delegateType||q,X.test(i+q)||(g=g.parentNode);g;g=g.parentNode)p.push(g),h=g;h===(d.ownerDocument||l)&&p.push(h.defaultView||h.parentWindow||a)}f=0;while((g=p[f++])&&!b.isPropagationStopped())b.type=f>1?i:o.bindType||q,m=(L.get(g,"events")||{})[b.type]&&L.get(g,"handle"),m&&m.apply(g,c),m=k&&g[k],m&&m.apply&&n.acceptData(g)&&(b.result=m.apply(g,c),b.result===!1&&b.preventDefault());return b.type=q,e||b.isDefaultPrevented()||o._default&&o._default.apply(p.pop(),c)!==!1||!n.acceptData(d)||k&&n.isFunction(d[q])&&!n.isWindow(d)&&(h=d[k],h&&(d[k]=null),n.event.triggered=q,d[q](),n.event.triggered=void 0,h&&(d[k]=h)),b.result}},dispatch:function(a){a=n.event.fix(a);var b,c,e,f,g,h=[],i=d.call(arguments),j=(L.get(this,"events")||{})[a.type]||[],k=n.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=n.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,c=0;while((g=f.handlers[c++])&&!a.isImmediatePropagationStopped())(!a.namespace_re||a.namespace_re.test(g.namespace))&&(a.handleObj=g,a.data=g.data,e=((n.event.special[g.origType]||{}).handle||g.handler).apply(f.elem,i),void 0!==e&&(a.result=e)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&(!a.button||"click"!==a.type))for(;i!==this;i=i.parentNode||this)if(i.disabled!==!0||"click"!==a.type){for(d=[],c=0;h>c;c++)f=b[c],e=f.selector+" ",void 0===d[e]&&(d[e]=f.needsContext?n(e,this).index(i)>=0:n.find(e,this,null,[i]).length),d[e]&&d.push(f);d.length&&g.push({elem:i,handlers:d})}return h]*)\/>/gi,ba=/<([\w:]+)/,ca=/<|&#?\w+;/,da=/<(?:script|style|link)/i,ea=/checked\s*(?:[^=]|=\s*.checked.)/i,fa=/^$|\/(?:java|ecma)script/i,ga=/^true\/(.*)/,ha=/^\s*\s*$/g,ia={option:[1,""],thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};ia.optgroup=ia.option,ia.tbody=ia.tfoot=ia.colgroup=ia.caption=ia.thead,ia.th=ia.td;function ja(a,b){return n.nodeName(a,"table")&&n.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function ka(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function la(a){var b=ga.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function ma(a,b){for(var c=0,d=a.length;d>c;c++)L.set(a[c],"globalEval",!b||L.get(b[c],"globalEval"))}function na(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(L.hasData(a)&&(f=L.access(a),g=L.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;d>c;c++)n.event.add(b,e,j[e][c])}M.hasData(a)&&(h=M.access(a),i=n.extend({},h),M.set(b,i))}}function oa(a,b){var c=a.getElementsByTagName?a.getElementsByTagName(b||"*"):a.querySelectorAll?a.querySelectorAll(b||"*"):[];return void 0===b||b&&n.nodeName(a,b)?n.merge([a],c):c}function pa(a,b){var c=b.nodeName.toLowerCase();"input"===c&&T.test(a.type)?b.checked=a.checked:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}n.extend({clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=n.contains(a.ownerDocument,a);if(!(k.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||n.isXMLDoc(a)))for(g=oa(h),f=oa(a),d=0,e=f.length;e>d;d++)pa(f[d],g[d]);if(b)if(c)for(f=f||oa(a),g=g||oa(h),d=0,e=f.length;e>d;d++)na(f[d],g[d]);else na(a,h);return g=oa(h,"script"),g.length>0&&ma(g,!i&&oa(a,"script")),h},buildFragment:function(a,b,c,d){for(var e,f,g,h,i,j,k=b.createDocumentFragment(),l=[],m=0,o=a.length;o>m;m++)if(e=a[m],e||0===e)if("object"===n.type(e))n.merge(l,e.nodeType?[e]:e);else if(ca.test(e)){f=f||k.appendChild(b.createElement("div")),g=(ba.exec(e)||["",""])[1].toLowerCase(),h=ia[g]||ia._default,f.innerHTML=h[1]+e.replace(aa,"<$1>")+h[2],j=h[0];while(j--)f=f.lastChild;n.merge(l,f.childNodes),f=k.firstChild,f.textContent=""}else l.push(b.createTextNode(e));k.textContent="",m=0;while(e=l[m++])if((!d||-1===n.inArray(e,d))&&(i=n.contains(e.ownerDocument,e),f=oa(k.appendChild(e),"script"),i&&ma(f),c)){j=0;while(e=f[j++])fa.test(e.type||"")&&c.push(e)}return k},cleanData:function(a){for(var b,c,d,e,f=n.event.special,g=0;void 0!==(c=a[g]);g++){if(n.acceptData(c)&&(e=c[L.expando],e&&(b=L.cache[e]))){if(b.events)for(d in b.events)f[d]?n.event.remove(c,d):n.removeEvent(c,d,b.handle);L.cache[e]&&delete L.cache[e]}delete M.cache[c[M.expando]]}}}),n.fn.extend({text:function(a){return J(this,function(a){return void 0===a?n.text(this):this.empty().each(function(){(1===this.nodeType||11===this.nodeType||9===this.nodeType)&&(this.textContent=a)})},null,a,arguments.length)},append:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=ja(this,a);b.appendChild(a)}})},prepend:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=ja(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},remove:function(a,b){for(var c,d=a?n.filter(a,this):this,e=0;null!=(c=d[e]);e++)b||1!==c.nodeType||n.cleanData(oa(c)),c.parentNode&&(b&&n.contains(c.ownerDocument,c)&&ma(oa(c,"script")),c.parentNode.removeChild(c));return this},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(n.cleanData(oa(a,!1)),a.textContent="");return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return n.clone(this,a,b)})},html:function(a){return J(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a&&1===b.nodeType)return b.innerHTML;if("string"==typeof a&&!da.test(a)&&!ia[(ba.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(aa,"<$1>");try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(n.cleanData(oa(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=arguments[0];return this.domManip(arguments,function(b){a=this.parentNode,n.cleanData(oa(this)),a&&a.replaceChild(b,this)}),a&&(a.length||a.nodeType)?this:this.remove()},detach:function(a){return this.remove(a,!0)},domManip:function(a,b){a=e.apply([],a);var c,d,f,g,h,i,j=0,l=this.length,m=this,o=l-1,p=a[0],q=n.isFunction(p);if(q||l>1&&"string"==typeof p&&!k.checkClone&&ea.test(p))return this.each(function(c){var d=m.eq(c);q&&(a[0]=p.call(this,c,d.html())),d.domManip(a,b)});if(l&&(c=n.buildFragment(a,this[0].ownerDocument,!1,this),d=c.firstChild,1===c.childNodes.length&&(c=d),d)){for(f=n.map(oa(c,"script"),ka),g=f.length;l>j;j++)h=c,j!==o&&(h=n.clone(h,!0,!0),g&&n.merge(f,oa(h,"script"))),b.call(this[j],h,j);if(g)for(i=f[f.length-1].ownerDocument,n.map(f,la),j=0;g>j;j++)h=f[j],fa.test(h.type||"")&&!L.access(h,"globalEval")&&n.contains(i,h)&&(h.src?n._evalUrl&&n._evalUrl(h.src):n.globalEval(h.textContent.replace(ha,"")))}return this}}),n.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){n.fn[a]=function(a){for(var c,d=[],e=n(a),g=e.length-1,h=0;g>=h;h++)c=h===g?this:this.clone(!0),n(e[h])[b](c),f.apply(d,c.get());return this.pushStack(d)}});var qa,ra={};function sa(b,c){var d,e=n(c.createElement(b)).appendTo(c.body),f=a.getDefaultComputedStyle&&(d=a.getDefaultComputedStyle(e[0]))?d.display:n.css(e[0],"display");return e.detach(),f}function ta(a){var b=l,c=ra[a];return c||(c=sa(a,b),"none"!==c&&c||(qa=(qa||n("