3
0
Fork 0
mirror of https://github.com/lubosz/overte.git synced 2025-04-27 01:15:36 +02:00

Merge branch 'master' of https://github.com/highfidelity/hifi into QmlMarketplace

This commit is contained in:
Roxanne Skelly 2019-01-28 11:11:28 -08:00
commit d3cffa50d6
99 changed files with 3202 additions and 3194 deletions
.gitignoreINSTALL.md
domain-server/src
interface
libraries
plugins
scripts
server-console
tools/unity-avatar-exporter

4
.gitignore vendored
View file

@ -104,4 +104,6 @@ tools/unity-avatar-exporter/Logs
tools/unity-avatar-exporter/Packages
tools/unity-avatar-exporter/ProjectSettings
tools/unity-avatar-exporter/Temp
server-console/package-lock.json
vcpkg/
/tools/nitpick/compiledResources

View file

@ -4,7 +4,8 @@ During generation, CMake should produce an `install` target and a `package` targ
### Install
The `install` target will copy the High Fidelity targets and their dependencies to your `CMAKE_INSTALL_PREFIX`.
The `install` target will copy the High Fidelity targets and their dependencies to your `CMAKE_INSTALL_PREFIX`.
This variable is set by the `project(hifi)` command in `CMakeLists.txt` to `C:/Program Files/hifi` and stored in `build/CMakeCache.txt`
### Packaging
@ -14,17 +15,67 @@ To produce an installer, run the `package` target.
To produce an executable installer on Windows, the following are required:
- [Nullsoft Scriptable Install System](http://nsis.sourceforge.net/Download) - 3.0b3
- [UAC Plug-in for Nullsoft](http://nsis.sourceforge.net/UAC_plug-in) - 0.2.4c
- [nsProcess Plug-in for Nullsoft](http://nsis.sourceforge.net/NsProcess_plugin) - 1.6
- [Inetc Plug-in for Nullsoft](http://nsis.sourceforge.net/Inetc_plug-in) - 1.0
- [NSISpcre Plug-in for Nullsoft](http://nsis.sourceforge.net/NSISpcre_plug-in) - 1.0
- [nsisSlideshow Plug-in for Nullsoft](http://nsis.sourceforge.net/NsisSlideshow_plug-in) - 1.7
- [Nsisunz plug-in for Nullsoft](http://nsis.sourceforge.net/Nsisunz_plug-in)
- [ApplicationID plug-in for Nullsoft](http://nsis.sourceforge.net/ApplicationID_plug-in) - 1.0
1. [7-zip](<https://www.7-zip.org/download.html>)
Run the `package` target to create an executable installer using the Nullsoft Scriptable Install System.
1. [Nullsoft Scriptable Install System](http://nsis.sourceforge.net/Download) - 3.04
Install using defaults (will install to `C:\Program Files (x86)\NSIS`)
1. [UAC Plug-in for Nullsoft](http://nsis.sourceforge.net/UAC_plug-in) - 0.2.4c
1. Extract Zip
1. Copy `UAC.nsh` to `C:\Program Files (x86)\NSIS\Include\`
1. Copy `Plugins\x86-ansi\UAC.dll` to `C:\Program Files (x86)\NSIS\Plugins\x86-ansi\`
1. Copy `Plugins\x86-unicode\UAC.dll` to `C:\Program Files (x86)\NSIS\Plugins\x86-unicode\`
1. [nsProcess Plug-in for Nullsoft](http://nsis.sourceforge.net/NsProcess_plugin) - 1.6 (use the link marked **nsProcess_1_6.7z**)
1. Extract Zip
1. Copy `Include\nsProcess.nsh` to `C:\Program Files (x86)\NSIS\Include\`
1. Copy `Plugins\nsProcess.dll` to `C:\Program Files (x86)\NSIS\Plugins\x86-ansi\`
1. Copy `Plugins\nsProcessW.dll` to `C:\Program Files (x86)\NSIS\Plugins\x86-unicode\`
1. [InetC Plug-in for Nullsoft](http://nsis.sourceforge.net/Inetc_plug-in) - 1.0
1. Extract Zip
1. Copy `Plugin\x86-ansi\InetC.dll` to `C:\Program Files (x86)\NSIS\Plugins\x86-ansi\`
1. Copy `Plugin\x86-unicode\InetC.dll` to `C:\Program Files (x86)\NSIS\Plugins\x86-unicode\`
1. [NSISpcre Plug-in for Nullsoft](http://nsis.sourceforge.net/NSISpcre_plug-in) - 1.0
1. Extract Zip
1. Copy `NSISpre.nsh` to `C:\Program Files (x86)\NSIS\Include\`
1. Copy `NSISpre.dll` to `C:\Program Files (x86)\NSIS\Plugins\x86-ansi\`
1. [nsisSlideshow Plug-in for Nullsoft](<http://wiz0u.free.fr/prog/nsisSlideshow/>) - 1.7
1. Extract Zip
1. Copy `bin\nsisSlideshow.dll` to `C:\Program Files (x86)\NSIS\Plugins\x86-ansi\`
1. Copy `bin\nsisSlideshowW.dll` to `C:\Program Files (x86)\NSIS\Plugins\x86-unicode\`
1. [Nsisunz plug-in for Nullsoft](http://nsis.sourceforge.net/Nsisunz_plug-in)
1. Download both Zips and unzip
1. Copy `nsisunz\Release\nsisunz.dll` to `C:\Program Files (x86)\NSIS\Plugins\x86-ansi\`
1. Copy `NSISunzU\Plugin unicode\nsisunz.dll` to `C:\Program Files (x86)\NSIS\Plugins\x86-unicode\`
1. [ApplicationID plug-in for Nullsoft]() - 1.0
1. Download [`Pre-built DLLs`](<https://github.com/connectiblutz/NSIS-ApplicationID/releases/download/1.1/NSIS-ApplicationID.zip>)
1. Extract Zip
1. Copy `Release\ApplicationID.dll` to `C:\Program Files (x86)\NSIS\Plugins\x86-ansi\`
1. Copy `ReleaseUnicode\ApplicationID.dll` to `C:\Program Files (x86)\NSIS\Plugins\x86-unicode\`
1. [npm](<https://www.npmjs.com/get-npm>)
1. Install version 10.15.0 LTS
1. Perform a clean cmake from a new terminal.
1. Open the `hifi.sln` Solution and select the Release configuration.
1. Build the Solution.
1. Build `packaged-server-console` (found under **Server Console**)
This will add 2 folders to `build\server-console\` -
`server-console-win32-x64` and `x64`
1. Build CMakeTargets->PACKAGE
Installer is now available in `build\_CPack_Packages\win64\NSIS`
#### OS X
Run the `package` target to create an Apple Disk Image (.dmg).
1. [npm](<https://www.npmjs.com/get-npm>)
Install version 10.15.0 LTS
1. Perform a clean cmake.
1. Perform a Release build of ALL_BUILD
1. Perform a Release build of `packaged-server-console`
This will add a folder to `build\server-console\` -
Sandbox-darwin-x64
1. Perform a Release build of `package`
Installer is now available in `build/_CPack_Packages/Darwin/DragNDrop

View file

@ -246,6 +246,7 @@ void AssetsBackupHandler::createBackup(const QString& backupName, QuaZip& zip) {
if (_assetServerEnabled && _lastMappingsRefresh.time_since_epoch().count() == 0) {
qCWarning(asset_backup) << "Current mappings not yet loaded.";
_backups.emplace_back(backupName, AssetUtils::Mappings(), true);
return;
}

View file

@ -120,6 +120,6 @@ FocusScope {
}
Component.onCompleted: {
bodyLoader.setSource("LoginDialog/LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "linkSteam": false });
bodyLoader.setSource("LoginDialog/LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "linkSteam": false, "linkOculus": false });
}
}

View file

@ -22,11 +22,16 @@ Item {
width: root.width
height: root.height
readonly property string termsContainerText: qsTr("By creating this user profile, you agree to High Fidelity's Terms of Service")
readonly property string termsContainerOculusText: qsTr("By signing up, you agree to High Fidelity's Terms of Service")
readonly property int textFieldHeight: 31
readonly property string fontFamily: "Raleway"
readonly property int fontSize: 15
readonly property bool fontBold: true
readonly property int textFieldFontSize: 18
readonly property var passwordImageRatio: 16 / 23
readonly property bool withSteam: withSteam
property bool withOculus: withOculus
property bool withSteam: withSteam
property string errorString: errorString
readonly property bool loginDialogPoppedUp: loginDialog.getLoginDialogPoppedUp()
@ -61,15 +66,20 @@ Item {
Item {
id: contentItem
anchors.fill: parent
width: parent.width
height: errorContainer.height + fields.height + buttons.height + additionalTextContainer.height +
termsContainer.height
anchors.top: parent.top
anchors.topMargin: root.bannerHeight + 0.25 * parent.height
anchors.left: parent.left
Item {
id: errorContainer
width: parent.width
width: root.bannerWidth
height: loginErrorMessageTextMetrics.height
anchors {
bottom: buttons.top;
bottomMargin: hifi.dimensions.contentSpacing.y;
bottom: completeProfileBody.withOculus ? fields.top : buttons.top;
bottomMargin: 1.5 * hifi.dimensions.contentSpacing.y;
left: buttons.left;
}
TextMetrics {
@ -79,8 +89,8 @@ Item {
}
Text {
id: loginErrorMessage;
width: root.bannerWidth
color: "red";
width: root.bannerWidth;
font.family: completeProfileBody.fontFamily
font.pixelSize: 18
font.bold: completeProfileBody.fontBold
@ -88,13 +98,196 @@ Item {
horizontalAlignment: Text.AlignHCenter
text: completeProfileBody.errorString
visible: true
onTextChanged: {
mainContainer.recalculateErrorMessage();
}
Component.onCompleted: {
mainContainer.recalculateErrorMessage();
}
}
Component.onCompleted: {
if (loginErrorMessageTextMetrics.width > root.bannerWidth && root.isTablet) {
loginErrorMessage.wrapMode = Text.WordWrap;
loginErrorMessage.verticalAlignment = Text.AlignLeft;
loginErrorMessage.horizontalAlignment = Text.AlignLeft;
errorContainer.height = 3 * loginErrorMessageTextMetrics.height;
}
Item {
id: fields
width: root.bannerWidth
height: 3 * completeProfileBody.textFieldHeight + 2 * hifi.dimensions.contentSpacing.y
visible: completeProfileBody.withOculus
anchors {
left: parent.left
leftMargin: (parent.width - root.bannerWidth) / 2
bottom: buttons.top
bottomMargin: hifi.dimensions.contentSpacing.y
}
HifiControlsUit.TextField {
id: usernameField
width: root.bannerWidth
height: completeProfileBody.textFieldHeight
placeholderText: "Username"
font.pixelSize: completeProfileBody.textFieldFontSize
styleRenderType: Text.QtRendering
anchors {
top: parent.top
}
Keys.onPressed: {
if (!usernameField.visible) {
return;
}
switch (event.key) {
case Qt.Key_Tab:
event.accepted = true;
if (event.modifiers === Qt.ShiftModifier) {
passwordField.focus = true;
} else {
emailField.focus = true;
}
break;
case Qt.Key_Backtab:
event.accepted = true;
passwordField.focus = true;
break;
case Qt.Key_Enter:
case Qt.Key_Return:
event.accepted = true;
loginDialog.createAccountFromOculus(emailField.text, usernameField.text, passwordField.text);
bodyLoader.setSource("LoggingInBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": completeProfileBody.withSteam,
"linkSteam": false, "withOculus": completeProfileBody.withOculus, "linkOculus": false, "createOculus": true });
break;
}
}
onFocusChanged: {
root.text = "";
if (focus) {
root.isPassword = false;
}
}
Component.onCompleted: {
var userID = "";
if (completeProfileBody.withOculus) {
userID = loginDialog.oculusUserID();
}
usernameField.text = userID;
}
}
HifiControlsUit.TextField {
id: emailField
width: root.bannerWidth
height: completeProfileBody.textFieldHeight
anchors {
top: usernameField.bottom
topMargin: hifi.dimensions.contentSpacing.y
}
placeholderText: "Email"
font.pixelSize: completeProfileBody.textFieldFontSize
styleRenderType: Text.QtRendering
activeFocusOnPress: true
Keys.onPressed: {
switch (event.key) {
case Qt.Key_Tab:
event.accepted = true;
if (event.modifiers === Qt.ShiftModifier) {
usernameField.focus = true;
} else {
passwordField.focus = true;
}
break;
case Qt.Key_Backtab:
event.accepted = true;
usernameField.focus = true;
break;
case Qt.Key_Enter:
case Qt.Key_Return:
event.accepted = true;
loginDialog.createAccountFromOculus(emailField.text, usernameField.text, passwordField.text);
bodyLoader.setSource("LoggingInBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": completeProfileBody.withSteam,
"linkSteam": false, "withOculus": completeProfileBody.withOculus, "linkOculus": false, "createOculus": true });
break;
}
}
onFocusChanged: {
root.text = "";
if (focus) {
root.isPassword = false;
}
}
}
HifiControlsUit.TextField {
id: passwordField
width: root.bannerWidth
height: completeProfileBody.textFieldHeight
placeholderText: "Password (optional)"
font.pixelSize: completeProfileBody.textFieldFontSize
styleRenderType: Text.QtRendering
activeFocusOnPress: true
echoMode: passwordFieldMouseArea.showPassword ? TextInput.Normal : TextInput.Password
anchors {
top: emailField.bottom
topMargin: hifi.dimensions.contentSpacing.y
}
onFocusChanged: {
root.text = "";
root.isPassword = focus;
}
Item {
id: showPasswordContainer
z: 10
// width + image's rightMargin
width: showPasswordImage.width + 8
height: parent.height
anchors {
right: parent.right
}
Image {
id: showPasswordImage
width: passwordField.height * passwordImageRatio
height: passwordField.height * passwordImageRatio
anchors {
right: parent.right
rightMargin: 8
top: parent.top
topMargin: passwordFieldMouseArea.showPassword ? 6 : 8
bottom: parent.bottom
bottomMargin: passwordFieldMouseArea.showPassword ? 5 : 8
}
source: passwordFieldMouseArea.showPassword ? "../../images/eyeClosed.svg" : "../../images/eyeOpen.svg"
MouseArea {
id: passwordFieldMouseArea
anchors.fill: parent
acceptedButtons: Qt.LeftButton
property bool showPassword: false
onClicked: {
showPassword = !showPassword;
}
}
}
}
Keys.onPressed: {
switch (event.key) {
case Qt.Key_Tab:
event.accepted = true;
if (event.modifiers === Qt.ShiftModifier) {
emailField.focus = true;
} else if (usernameField.visible) {
usernameField.focus = true;
} else {
emailField.focus = true;
}
break;
case Qt.Key_Backtab:
event.accepted = true;
emailField.focus = true;
break;
case Qt.Key_Enter:
case Qt.Key_Return:
event.accepted = true;
loginDialog.createAccountFromOculus(emailField.text, usernameField.text, passwordField.text);
bodyLoader.setSource("LoggingInBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": completeProfileBody.withSteam,
"linkSteam": false, "withOculus": completeProfileBody.withOculus, "linkOculus": false, "createOculus": true });
break;
}
}
}
}
@ -105,7 +298,7 @@ Item {
height: d.minHeightButton
anchors {
top: parent.top
topMargin: (parent.height - additionalTextContainer.height) / 2 - hifi.dimensions.contentSpacing.y
topMargin: (parent.height - additionalTextContainer.height + fields.height) / 2 - hifi.dimensions.contentSpacing.y
left: parent.left
leftMargin: (parent.width - root.bannerWidth) / 2
}
@ -144,7 +337,7 @@ Item {
width: (parent.width - hifi.dimensions.contentSpacing.x) / 2
height: d.minHeightButton
text: qsTr("Create your profile")
text: completeProfileBody.withOculus ? qsTr("Sign Up") : qsTr("Create your profile")
color: hifi.buttons.blue
fontFamily: completeProfileBody.fontFamily
@ -158,55 +351,12 @@ Item {
UserActivityLogger.logAction("encourageLoginDialog", data);
}
loginErrorMessage.visible = false;
loginDialog.createAccountFromSteam();
}
}
}
Item {
id: additionalTextContainer
width: parent.width
height: additionalTextMetrics.height
anchors {
top: buttons.bottom
horizontalCenter: parent.horizontalCenter
topMargin: hifi.dimensions.contentSpacing.y
left: parent.left
}
TextMetrics {
id: additionalTextMetrics
font: additionalText.font
text: "Already have a High Fidelity profile? Link to an existing profile here."
}
HifiStylesUit.ShortcutText {
id: additionalText
text: "<a href='https://fake.link'>Already have a High Fidelity profile? Link to an existing profile here.</a>"
font.family: completeProfileBody.fontFamily
font.pixelSize: completeProfileBody.fontSize
font.bold: completeProfileBody.fontBold
wrapMode: Text.NoWrap
lineHeight: 1
lineHeightMode: Text.ProportionalHeight
horizontalAlignment: Text.AlignHCenter
linkColor: hifi.colors.blueAccent
onLinkActivated: {
loginDialog.isLogIn = true;
bodyLoader.setSource("LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "errorString": "", "withSteam": true, "linkSteam": true });
}
Component.onCompleted: {
if (additionalTextMetrics.width > root.bannerWidth && root.isTablet) {
additionalText.width = root.bannerWidth;
additionalText.wrapMode = Text.WordWrap;
additionalText.verticalAlignment = Text.AlignLeft;
additionalText.horizontalAlignment = Text.AlignLeft;
additionalTextContainer.height = (additionalTextMetrics.width / root.bannerWidth) * additionalTextMetrics.height;
additionalTextContainer.anchors.left = buttons.left;
} else {
additionalText.anchors.centerIn = additionalTextContainer;
if (completeProfileBody.withOculus) {
loginDialog.createAccountFromOculus(emailField.text, usernameField.text, passwordField.text);
bodyLoader.setSource("LoggingInBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": completeProfileBody.withSteam,
"linkSteam": false, "withOculus": completeProfileBody.withOculus, "linkOculus": false, "createOculus": true });
} else if (completeProfileBody.withSteam) {
loginDialog.createAccountFromSteam();
}
}
}
@ -217,29 +367,33 @@ Item {
width: parent.width
height: termsTextMetrics.height
anchors {
top: additionalTextContainer.bottom
top: buttons.bottom
horizontalCenter: parent.horizontalCenter
topMargin: 2 * hifi.dimensions.contentSpacing.y
topMargin: hifi.dimensions.contentSpacing.y
left: parent.left
}
TextMetrics {
id: termsTextMetrics
font: termsText.font
text: completeProfileBody.termsContainerText
text: completeProfileBody.withOculus ? completeProfileBody.termsContainerOculusText : completeProfileBody.termsContainerText
Component.onCompleted: {
// with the link.
termsText.text = qsTr("By creating this user profile, you agree to <a href='https://highfidelity.com/terms'>High Fidelity's Terms of Service</a>")
if (completeProfileBody.withOculus) {
termsText.text = qsTr("By signing up, you agree to <a href='https://highfidelity.com/terms'>High Fidelity's Terms of Service</a>")
} else {
termsText.text = qsTr("By creating this user profile, you agree to <a href='https://highfidelity.com/terms'>High Fidelity's Terms of Service</a>")
}
}
}
HifiStylesUit.InfoItem {
id: termsText
text: completeProfileBody.termsContainerText
text: completeProfileBody.withOculus ? completeProfileBody.termsContainerOculusText : completeProfileBody.termsContainerText
font.family: completeProfileBody.fontFamily
font.pixelSize: completeProfileBody.fontSize
font.bold: completeProfileBody.fontBold
wrapMode: Text.WordWrap
color: hifi.colors.lightGray
color: hifi.colors.white
linkColor: hifi.colors.blueAccent
lineHeight: 1
lineHeightMode: Text.ProportionalHeight
@ -247,7 +401,7 @@ Item {
onLinkActivated: loginDialog.openUrl(link);
Component.onCompleted: {
if (termsTextMetrics.width > root.bannerWidth && root.isTablet) {
if (termsTextMetrics.width > root.bannerWidth) {
termsText.width = root.bannerWidth;
termsText.wrapMode = Text.WordWrap;
additionalText.verticalAlignment = Text.AlignLeft;
@ -260,14 +414,86 @@ Item {
}
}
}
Item {
id: additionalTextContainer
width: parent.width
height: additionalTextMetrics.height
anchors {
top: termsContainer.bottom
horizontalCenter: parent.horizontalCenter
topMargin: 2 * hifi.dimensions.contentSpacing.y
left: parent.left
}
TextMetrics {
id: additionalTextMetrics
font: additionalText.font
text: "Already have a High Fidelity profile? Link to an existing profile here."
}
HifiStylesUit.ShortcutText {
id: additionalText
text: "<a href='https://fake.link'>Already have a High Fidelity profile? Link to an existing profile here.</a>"
width: root.bannerWidth;
font.family: completeProfileBody.fontFamily
font.pixelSize: completeProfileBody.fontSize
font.bold: completeProfileBody.fontBold
wrapMode: Text.NoWrap
lineHeight: 1
lineHeightMode: Text.ProportionalHeight
horizontalAlignment: Text.AlignHCenter
linkColor: hifi.colors.blueAccent
onLinkActivated: {
bodyLoader.setSource("LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "errorString": "",
"withSteam": completeProfileBody.withSteam, "linkSteam": completeProfileBody.withSteam, "withOculus": completeProfileBody.withOculus,
"linkOculus": completeProfileBody.withOculus });
}
Component.onCompleted: {
if (additionalTextMetrics.width > root.bannerWidth) {
additionalText.wrapMode = Text.WordWrap;
additionalText.verticalAlignment = Text.AlignLeft;
additionalText.horizontalAlignment = Text.AlignLeft;
additionalTextContainer.height = (additionalTextMetrics.width / root.bannerWidth) * additionalTextMetrics.height;
additionalTextContainer.anchors.left = buttons.left;
} else {
additionalText.anchors.centerIn = additionalTextContainer;
}
}
}
}
}
function recalculateErrorMessage() {
if (completeProfileBody.errorString !== "") {
loginErrorMessage.visible = true;
var errorLength = completeProfileBody.errorString.split(/\r\n|\r|\n/).length;
var errorStringEdited = completeProfileBody.errorString.replace(/[\n\r]+/g, "\n");
loginErrorMessage.text = errorStringEdited;
if (errorLength > 1.0) {
loginErrorMessage.wrapMode = Text.WordWrap;
loginErrorMessage.verticalAlignment = Text.AlignLeft;
loginErrorMessage.horizontalAlignment = Text.AlignLeft;
errorContainer.height = errorLength * loginErrorMessageTextMetrics.height;
} else if (loginErrorMessageTextMetrics.width > root.bannerWidth) {
loginErrorMessage.wrapMode = Text.WordWrap;
loginErrorMessage.verticalAlignment = Text.AlignLeft;
loginErrorMessage.horizontalAlignment = Text.AlignLeft;
errorContainer.height = (loginErrorMessageTextMetrics.width / root.bannerWidth) * loginErrorMessageTextMetrics.height;
} else {
loginErrorMessage.wrapMode = Text.NoWrap;
loginErrorMessage.verticalAlignment = Text.AlignVCenter;
loginErrorMessage.horizontalAlignment = Text.AlignHCenter;
errorContainer.height = loginErrorMessageTextMetrics.height;
}
}
}
}
Connections {
target: loginDialog
onHandleCreateCompleted: {
console.log("Create Succeeded")
console.log("Create Succeeded");
if (completeProfileBody.withSteam) {
if (completeProfileBody.loginDialogPoppedUp) {
var data = {
@ -277,20 +503,24 @@ Item {
}
loginDialog.loginThroughSteam();
}
bodyLoader.setSource("LoggingInBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": completeProfileBody.withSteam, "linkSteam": false });
bodyLoader.setSource("LoggingInBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": completeProfileBody.withSteam, "linkSteam": false,
"withOculus": completeProfileBody.withOculus, "linkOculus": false });
}
onHandleCreateFailed: {
console.log("Create Failed: " + error);
if (completeProfileBody.withSteam) {
if (completeProfileBody.withSteam || completeProfileBody.withOculus) {
if (completeProfileBody.loginDialogPoppedUp) {
action = completeProfileBody.withSteam ? "Steam" : "Oculus";
var data = {
"action": "user failed to create a profile with Steam from the complete profile screen"
"action": "user failed to create a profile with " + action + " from the complete profile screen"
}
UserActivityLogger.logAction("encourageLoginDialog", data);
}
}
bodyLoader.setSource("UsernameCollisionBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": completeProfileBody.withSteam });
if (!completeProfileBody.withOculus) {
bodyLoader.setSource("UsernameCollisionBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": completeProfileBody.withSteam,
"withOculus": completeProfileBody.withOculus });
}
}
}
@ -302,5 +532,6 @@ Item {
}
d.resize();
root.text = "";
usernameField.forceActiveFocus();
}
}

View file

@ -36,9 +36,10 @@ Item {
property bool keyboardRaised: false
property bool punctuationMode: false
property bool withSteam: false
property bool withSteam: withSteam
property bool linkSteam: linkSteam
property bool withOculus: false
property bool withOculus: withOculus
property bool linkOculus: linkOculus
property string errorString: errorString
property bool lostFocus: false
@ -83,23 +84,24 @@ Item {
}
UserActivityLogger.logAction("encourageLoginDialog", data);
}
bodyLoader.setSource("LoggingInBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": linkAccountBody.withSteam, "withOculus": linkAccountBody.withOculus, "linkSteam": linkAccountBody.linkSteam });
bodyLoader.setSource("LoggingInBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": linkAccountBody.withSteam,
"withOculus": linkAccountBody.withOculus, "linkSteam": linkAccountBody.linkSteam, "linkOculus": linkAccountBody.linkOculus });
}
function init() {
// going to/from sign in/up dialog.
loginDialog.isLogIn = true;
loginErrorMessage.text = linkAccountBody.errorString;
loginErrorMessage.visible = (linkAccountBody.errorString !== "");
loginButton.text = !linkAccountBody.linkSteam ? "Log In" : "Link Account";
if (loginErrorMessageTextMetrics.width > emailField.width) {
loginErrorMessage.wrapMode = Text.WordWrap;
errorContainer.height = (loginErrorMessageTextMetrics.width / emailField.width) * loginErrorMessageTextMetrics.height;
}
loginButton.text = (!linkAccountBody.linkSteam && !linkAccountBody.linkOculus) ? "Log In" : "Link Account";
loginButton.color = hifi.buttons.blue;
emailField.placeholderText = "Username or Email";
var savedUsername = Settings.getValue("keepMeLoggedIn/savedUsername", "");
emailField.text = keepMeLoggedInCheckbox.checked ? savedUsername === "Unknown user" ? "" : savedUsername : "";
if (linkAccountBody.linkSteam) {
steamInfoText.anchors.top = passwordField.bottom;
keepMeLoggedInCheckbox.anchors.top = steamInfoText.bottom;
if (linkAccountBody.linkSteam || linkAccountBody.linkOculus) {
loginButton.width = (passwordField.width - hifi.dimensions.contentSpacing.x) / 2;
loginButton.anchors.right = emailField.right;
} else {
@ -125,7 +127,7 @@ Item {
id: loginContainer
width: emailField.width
height: errorContainer.height + emailField.height + passwordField.height + 5.5 * hifi.dimensions.contentSpacing.y +
keepMeLoggedInCheckbox.height + loginButton.height + cantAccessTextMetrics.height + continueButton.height + steamInfoTextMetrics.height
keepMeLoggedInCheckbox.height + loginButton.height + cantAccessTextMetrics.height + continueButton.height
anchors {
top: parent.top
topMargin: root.bannerHeight + 0.25 * parent.height
@ -135,7 +137,7 @@ Item {
Item {
id: errorContainer
width: loginErrorMessageTextMetrics.width
width: parent.width
height: loginErrorMessageTextMetrics.height
anchors {
bottom: emailField.top;
@ -304,7 +306,7 @@ Item {
fontSize: linkAccountBody.fontSize
fontBold: linkAccountBody.fontBold
color: hifi.buttons.noneBorderlessWhite;
visible: linkAccountBody.linkSteam
visible: linkAccountBody.linkSteam || linkAccountBody.linkOculus
anchors {
top: keepMeLoggedInCheckbox.bottom
topMargin: hifi.dimensions.contentSpacing.y
@ -315,10 +317,9 @@ Item {
"action": "user clicked cancel at link account screen"
};
UserActivityLogger.logAction("encourageLoginDialog", data);
loginDialog.dismissLoginDialog();
}
bodyLoader.setSource("CompleteProfileBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": linkAccountBody.withSteam, "errorString": "" });
bodyLoader.setSource("CompleteProfileBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": linkAccountBody.withSteam,
"withOculus": linkAccountBody.withOculus, "errorString": "" });
}
}
HifiControlsUit.Button {
@ -337,33 +338,6 @@ Item {
linkAccountBody.login();
}
}
TextMetrics {
id: steamInfoTextMetrics
font: steamInfoText.font
text: steamInfoText.text
}
Text {
id: steamInfoText
width: root.bannerWidth
visible: linkAccountBody.linkSteam
anchors {
top: loginButton.bottom
topMargin: hifi.dimensions.contentSpacing.y
left: emailField.left
}
font.family: linkAccountBody.fontFamily
font.pixelSize: linkAccountBody.textFieldFontSize
color: "white"
text: qsTr("Your Steam account information will not be exposed to others.");
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
Component.onCompleted: {
if (steamInfoTextMetrics.width > root.bannerWidth) {
steamInfoText.wrapMode = Text.WordWrap;
}
}
}
TextMetrics {
id: cantAccessTextMetrics
font: cantAccessText.font
@ -372,7 +346,7 @@ Item {
HifiStylesUit.ShortcutText {
id: cantAccessText
z: 10
visible: !linkAccountBody.linkSteam
visible: !linkAccountBody.linkSteam && !linkAccountBody.linkOculus
anchors {
top: loginButton.bottom
topMargin: hifi.dimensions.contentSpacing.y
@ -423,10 +397,10 @@ Item {
buttonGlyphSize: 24
buttonGlyphRightMargin: 10
onClicked: {
// if (loginDialog.isOculusStoreRunning()) {
// linkAccountBody.withOculus = true;
// loginDialog.loginThroughSteam();
// } else
if (loginDialog.isOculusRunning()) {
linkAccountBody.withOculus = true;
loginDialog.loginThroughOculus();
} else
if (loginDialog.isSteamRunning()) {
linkAccountBody.withSteam = true;
loginDialog.loginThroughSteam();
@ -446,18 +420,17 @@ Item {
}
bodyLoader.setSource("LoggingInBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader,
"withSteam": linkAccountBody.withSteam, "withOculus": linkAccountBody.withOculus, "linkSteam": linkAccountBody.linkSteam });
"withSteam": linkAccountBody.withSteam, "withOculus": linkAccountBody.withOculus, "linkSteam": linkAccountBody.linkSteam, "linkOculus": linkAccountBody.linkOculus });
}
Component.onCompleted: {
if (linkAccountBody.linkSteam) {
if (linkAccountBody.linkSteam || linkAccountBody.linkOculus) {
continueButton.visible = false;
return;
}
// if (loginDialog.isOculusStoreRunning()) {
// continueButton.text = qsTr("CONTINUE WITH OCULUS");
// continueButton.buttonGlyph = hifi.glyphs.oculus;
// } else
if (loginDialog.isSteamRunning()) {
if (loginDialog.isOculusRunning()) {
continueButton.text = qsTr("CONTINUE WITH OCULUS");
continueButton.buttonGlyph = hifi.glyphs.oculus;
} else if (loginDialog.isSteamRunning()) {
continueButton.text = qsTr("CONTINUE WITH STEAM");
continueButton.buttonGlyph = hifi.glyphs.steamSquare;
} else {
@ -470,7 +443,7 @@ Item {
id: signUpContainer
width: loginContainer.width
height: signUpTextMetrics.height
visible: !linkAccountBody.linkSteam
visible: !linkAccountBody.linkSteam && !linkAccountBody.linkOculus
anchors {
left: loginContainer.left
top: loginContainer.bottom
@ -519,7 +492,7 @@ Item {
UserActivityLogger.logAction("encourageLoginDialog", data);
}
bodyLoader.setSource("SignUpBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader,
"errorString": "", "linkSteam": linkAccountBody.linkSteam });
"errorString": "" });
}
}
}
@ -543,7 +516,7 @@ Item {
fontFamily: linkAccountBody.fontFamily
fontSize: linkAccountBody.fontSize
fontBold: linkAccountBody.fontBold
visible: linkAccountBody.loginDialogPoppedUp && !linkAccountBody.linkSteam;
visible: loginDialog.getLoginDialogPoppedUp() && !linkAccountBody.linkSteam && !linkAccountBody.linkOculus;
onClicked: {
if (linkAccountBody.loginDialogPoppedUp) {
var data = {

View file

@ -29,6 +29,8 @@ Item {
property bool withSteam: withSteam
property bool withOculus: withOculus
property bool linkSteam: linkSteam
property bool linkOculus: linkOculus
property bool createOculus: createOculus
readonly property bool loginDialogPoppedUp: loginDialog.getLoginDialogPoppedUp()
@ -75,15 +77,25 @@ Item {
}
}
Timer {
id: oculusSuccessTimer
interval: 500;
running: false;
repeat: false;
onTriggered: {
loginDialog.loginThroughOculus();
init();
}
}
function init() {
// For the process of logging in.
loggingInText.wrapMode = Text.NoWrap;
if (loggingInBody.linkSteam) {
if (loggingInBody.createOculus) {
loggingInGlyph.text = hifi.glyphs.oculus;
loggingInGlyph.visible = true;
loggingInText.text = "Linking to Steam";
loggingInText.text = "Creating account with Oculus";
loggingInText.x = loggingInHeader.width/2 - loggingInTextMetrics.width/2 + loggingInGlyphTextMetrics.width/2;
loginDialog.linkSteam();
} else if (loggingInBody.withSteam) {
loggingInGlyph.visible = true;
loggingInText.text = "Logging in to Steam";
@ -100,12 +112,18 @@ Item {
loggingInSpinner.visible = true;
}
function loadingSuccess() {
loggingInSpinner.visible = false;
if (loggingInBody.linkSteam) {
loggingInText.text = "Linking to Steam";
loggingInText.x = loggingInHeader.width/2 - loggingInTextMetrics.width/2 + loggingInGlyphTextMetrics.width/2;
loginDialog.linkSteam();
return;
} else if (loggingInBody.linkOculus) {
loggingInText.text = "Linking to Oculus";
loggingInText.x = loggingInHeader.width/2 - loggingInTextMetrics.width/2 + loggingInGlyphTextMetrics.width/2;
loginDialog.linkOculus();
return;
}
loggingInSpinner.visible = false;
if (loggingInBody.withSteam) {
// reset the flag.
loggingInGlyph.visible = false;
@ -246,6 +264,26 @@ Item {
verticalAlignment: Text.AlignVCenter;
visible: false;
}
HifiControlsUit.Button {
id: okButton;
width: d.minWidthButton
height: d.minHeightButton
text: qsTr("OK")
color: hifi.buttons.white
anchors {
top: loggedInGlyph.bottom
topMargin: 3 * hifi.dimensions.contentSpacing.y
left: parent.left
leftMargin: (parent.width - width) / 2;
}
onClicked: {
root.tryDestroy();
if (loginDialog.getLoginDialogPoppedUp()) {
loginDialog.dismissLoginDialog();
}
}
visible: false
}
}
}
}
@ -257,6 +295,34 @@ Item {
Connections {
target: loginDialog
onHandleCreateCompleted: {
console.log("Create Succeeded")
if (loggingInBody.withOculus) {
if (loggingInBody.loginDialogPoppedUp) {
var data = {
"action": "user created Oculus account successfully"
};
UserActivityLogger.logAction("encourageLoginDialog", data);
}
loggingInBody.createOculus = false;
loggingInText.text = "Account created!";
loggingInText.x = loggingInHeader.width/2 - loggingInTextMetrics.width/2 + loggingInGlyphTextMetrics.width/2;
oculusSuccessTimer.start();
}
}
onHandleCreateFailed: {
console.log("Create Failed: " + error);
if (loggingInBody.withOculus) {
if (loggingInBody.loginDialogPoppedUp) {
var data = {
"action": "user created Oculus account unsuccessfully"
};
UserActivityLogger.logAction("encourageLoginDialog", data);
}
bodyLoader.setSource("CompleteProfileBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": loggingInBody.withSteam,
"withOculus": loggingInBody.withOculus, "errorString": error });
}
}
onHandleLinkCompleted: {
console.log("Link Succeeded");
if (loggingInBody.linkSteam) {
@ -267,21 +333,40 @@ Item {
};
UserActivityLogger.logAction("encourageLoginDialog", data);
}
loggingInBody.loadingSuccess();
} else if (loggingInBody.linkOculus) {
loggingInBody.linkOculus = false;
if (loggingInBody.loginDialogPoppedUp) {
var data = {
"action": "user linked Oculus with their hifi account credentials successfully"
};
UserActivityLogger.logAction("encourageLoginDialog", data);
}
}
loggingInBody.loadingSuccess();
}
onHandleLinkFailed: {
console.log("Link Failed: " + error);
if (loggingInBody.linkSteam) {
loggingInSpinner.visible = false;
if (loggingInBody.linkOculus) {
loggingInText.text = "Oculus failed to link";
if (loggingInBody.loginDialogPoppedUp) {
var data = {
"action": "user linked Oculus unsuccessfully"
};
UserActivityLogger.logAction("encourageLoginDialog", data);
}
okButton.visible = true;
} else if (loggingInBody.linkSteam){
if (loggingInBody.loginDialogPoppedUp) {
var data = {
"action": "user linked Steam unsuccessfully"
};
UserActivityLogger.logAction("encourageLoginDialog", data);
}
} else {
bodyLoader.setSource("LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "linkSteam": loggingInBody.linkSteam,
"linkOculus": loggingInBody.linkOculus, "errorString": error });
}
bodyLoader.setSource("LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "linkSteam": true, "errorString": error });
}
onHandleLoginCompleted: {
@ -292,8 +377,19 @@ Item {
onHandleLoginFailed: {
console.log("Login Failed")
loggingInSpinner.visible = false;
loggingInGlyph.visible = false;
var errorString = "";
if (loggingInBody.linkSteam && loggingInBody.withSteam) {
if (loggingInBody.linkOculus && loggingInBody.withOculus) {
errorString = "Username or password is incorrect.";
if (loggingInBody.loginDialogPoppedUp) {
var data = {
"action": "user failed to link Oculus with their hifi account credentials"
};
UserActivityLogger.logAction("encourageLoginDialog", data);
}
bodyLoader.setSource("LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": loggingInBody.withSteam,
"withOculus": loggingInBody.withOculus, "linkSteam": loggingInBody.linkSteam, "linkOculus": loggingInBody.linkOculus, "errorString": errorString });
} else if (loggingInBody.linkSteam && loggingInBody.withSteam) {
errorString = "Username or password is incorrect.";
if (loggingInBody.loginDialogPoppedUp) {
var data = {
@ -301,9 +397,9 @@ Item {
};
UserActivityLogger.logAction("encourageLoginDialog", data);
}
bodyLoader.setSource("LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": loggingInBody.withSteam, "linkSteam": loggingInBody.linkSteam, "errorString": errorString });
bodyLoader.setSource("LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": loggingInBody.withSteam,
"withOculus": loggingInBody.withOculus, "linkSteam": loggingInBody.linkSteam, "linkOculus": loggingInBody.linkOculus, "errorString": errorString });
} else if (loggingInBody.withSteam) {
loggingInGlyph.visible = false;
errorString = "Your Steam authentication has failed. Please make sure you are logged into Steam and try again.";
if (loggingInBody.loginDialogPoppedUp) {
var data = {
@ -311,19 +407,19 @@ Item {
};
UserActivityLogger.logAction("encourageLoginDialog", data);
}
bodyLoader.setSource("CompleteProfileBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": loggingInBody.withSteam, "errorString": errorString });
bodyLoader.setSource("CompleteProfileBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": loggingInBody.withSteam,
"withOculus": loggingInBody.withOculus, "linkSteam": loggingInBody.linkSteam, "linkOculus": loggingInBody.linkOculus, "errorString": errorString });
} else if (loggingInBody.withOculus) {
loggingInGlyph.visible = false;
errorString = "Your Oculus authentication has failed. Please make sure you are logged into Oculus and try again."
errorString = "Your Oculus account is not connected to an existing High Fidelity account. Please create a new one."
if (loggingInBody.loginDialogPoppedUp) {
var data = {
"action": "user failed to authenticate with Oculus to log in"
};
UserActivityLogger.logAction("encourageLoginDialog", data);
}
bodyLoader.setSource("LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "errorString": errorString });
}
else {
bodyLoader.setSource("CompleteProfileBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": loggingInBody.withSteam,
"withOculus": loggingInBody.withOculus, "linkSteam": loggingInBody.linkSteam, "linkOculus": loggingInBody.linkOculus, "errorString": errorString });
} else {
errorString = "Username or password is incorrect.";
if (loggingInBody.loginDialogPoppedUp) {
var data = {

View file

@ -23,6 +23,7 @@ Item {
clip: true
height: root.height
width: root.width
readonly property string termsContainerText: qsTr("By signing up, you agree to High Fidelity's Terms of Service")
property int textFieldHeight: 31
property string fontFamily: "Raleway"
property int fontSize: 15
@ -37,7 +38,6 @@ Item {
onKeyboardRaisedChanged: d.resize();
property string errorString: errorString
property bool linkSteam: linkSteam
property bool lostFocus: false
readonly property bool loginDialogPoppedUp: loginDialog.getLoginDialogPoppedUp()
@ -73,7 +73,6 @@ Item {
function init() {
// going to/from sign in/up dialog.
loginDialog.isLogIn = false;
emailField.placeholderText = "Email";
emailField.text = "";
emailField.anchors.top = usernameField.bottom;
@ -353,7 +352,7 @@ Item {
}
UserActivityLogger.logAction("encourageLoginDialog", data);
}
bodyLoader.setSource("LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "linkSteam": signUpBody.linkSteam });
bodyLoader.setSource("LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "linkSteam": false });
}
}
HifiControlsUit.Button {
@ -380,6 +379,54 @@ Item {
signUpBody.signup();
}
}
Item {
id: termsContainer
width: parent.width
height: termsTextMetrics.height
anchors {
top: signUpButton.bottom
horizontalCenter: parent.horizontalCenter
topMargin: 2 * hifi.dimensions.contentSpacing.y
left: parent.left
}
TextMetrics {
id: termsTextMetrics
font: termsText.font
text: signUpBody.termsContainerText
Component.onCompleted: {
// with the link.
termsText.text = qsTr("By signing up, you agree to <a href='https://highfidelity.com/terms'>High Fidelity's Terms of Service</a>")
}
}
HifiStylesUit.InfoItem {
id: termsText
text: signUpBody.termsContainerText
font.family: signUpBody.fontFamily
font.pixelSize: signUpBody.fontSize
font.bold: signUpBody.fontBold
wrapMode: Text.WordWrap
color: hifi.colors.white
linkColor: hifi.colors.blueAccent
lineHeight: 1
lineHeightMode: Text.ProportionalHeight
onLinkActivated: loginDialog.openUrl(link);
Component.onCompleted: {
if (termsTextMetrics.width > root.bannerWidth) {
termsText.width = root.bannerWidth;
termsText.wrapMode = Text.WordWrap;
additionalText.verticalAlignment = Text.AlignLeft;
additionalText.horizontalAlignment = Text.AlignLeft;
termsContainer.height = (termsTextMetrics.width / root.bannerWidth) * termsTextMetrics.height;
termsContainer.anchors.left = buttons.left;
} else {
termsText.anchors.centerIn = termsContainer;
}
}
}
}
}
}
@ -433,14 +480,15 @@ Item {
if (errorString !== "") {
loginErrorMessage.visible = true;
var errorLength = errorString.split(/\r\n|\r|\n/).length;
var errorStringEdited = errorString.replace(/[\n\r]+/g, "\n");
loginErrorMessage.text = errorStringEdited;
loginErrorMessageTextMetrics.text = errorString;
if (loginErrorMessageTextMetrics.width > usernameField.width) {
if (errorLength > 1.0) {
loginErrorMessage.width = root.bannerWidth;
loginErrorMessage.wrapMode = Text.WordWrap;
loginErrorMessage.verticalAlignment = Text.AlignLeft;
loginErrorMessage.horizontalAlignment = Text.AlignLeft;
errorContainer.height = (loginErrorMessageTextMetrics.width / usernameField.width) * loginErrorMessageTextMetrics.height;
errorContainer.height = errorLength * loginErrorMessageTextMetrics.height;
}
errorContainer.anchors.bottom = usernameField.top;
errorContainer.anchors.bottomMargin = hifi.dimensions.contentSpacing.y;

View file

@ -19,6 +19,7 @@ import TabletScriptingInterface 1.0
Item {
id: usernameCollisionBody
clip: true
readonly property string termsContainerText: qsTr("By creating this user profile, you agree to High Fidelity's Terms of Service")
width: root.width
height: root.height
readonly property string fontFamily: "Raleway"
@ -26,13 +27,18 @@ Item {
readonly property int textFieldFontSize: 18
readonly property bool fontBold: true
readonly property bool withSteam: withSteam
property bool withSteam: withSteam
property bool withOculus: withOculus
readonly property bool loginDialogPoppedUp: loginDialog.getLoginDialogPoppedUp()
function create() {
mainTextContainer.visible = false
loginDialog.createAccountFromSteam(textField.text);
if (usernameCollisionBody.withOculus) {
loginDialog.createAccountFromOculus(textField.text);
} else if (usernameCollisionBody.withSteam) {
loginDialog.createAccountFromSteam(textField.text);
}
}
property bool keyboardEnabled: false
@ -90,12 +96,19 @@ Item {
font.family: usernameCollisionBody.fontFamily
font.pixelSize: usernameCollisionBody.fontSize
font.bold: usernameCollisionBody.fontBold
text: qsTr("Your Steam username is not available.");
text: qsTr("");
wrapMode: Text.WordWrap
color: hifi.colors.redAccent
lineHeight: 1
lineHeightMode: Text.ProportionalHeight
horizontalAlignment: Text.AlignHCenter
Component.onCompleted: {
if (usernameCollisionBody.withOculus) {
text = qsTr("Your Oculus username is not available.");
} else if (usernameCollisionBody.withSteam) {
text = qsTr("Your Steam username is not available.");
}
}
}
@ -164,7 +177,8 @@ Item {
fontSize: usernameCollisionBody.fontSize
fontBold: usernameCollisionBody.fontBold
onClicked: {
bodyLoader.setSource("CompleteProfileBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "errorString": "" });
bodyLoader.setSource("CompleteProfileBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": usernameCollisionBody.withSteam,
"withOculus": usernameCollisionBody.withOculus, "errorString": "" });
}
}
HifiControlsUit.Button {
@ -187,6 +201,55 @@ Item {
}
}
}
Item {
id: termsContainer
width: parent.width
height: termsTextMetrics.height
anchors {
top: buttons.bottom
horizontalCenter: parent.horizontalCenter
topMargin: 2 * hifi.dimensions.contentSpacing.y
left: parent.left
leftMargin: (parent.width - buttons.width) / 2
}
TextMetrics {
id: termsTextMetrics
font: termsText.font
text: usernameCollisionBody.termsContainerText
Component.onCompleted: {
// with the link.
termsText.text = qsTr("By creating this user profile, you agree to <a href='https://highfidelity.com/terms'>High Fidelity's Terms of Service</a>")
}
}
HifiStylesUit.InfoItem {
id: termsText
text: usernameCollisionBody.termsContainerText
font.family: usernameCollisionBody.fontFamily
font.pixelSize: usernameCollisionBody.fontSize
font.bold: usernameCollisionBody.fontBold
wrapMode: Text.WordWrap
color: hifi.colors.white
linkColor: hifi.colors.blueAccent
lineHeight: 1
lineHeightMode: Text.ProportionalHeight
onLinkActivated: loginDialog.openUrl(link);
Component.onCompleted: {
if (termsTextMetrics.width > root.bannerWidth) {
termsText.width = root.bannerWidth;
termsText.wrapMode = Text.WordWrap;
additionalText.verticalAlignment = Text.AlignLeft;
additionalText.horizontalAlignment = Text.AlignLeft;
termsContainer.height = (termsTextMetrics.width / root.bannerWidth) * termsTextMetrics.height;
termsContainer.anchors.left = buttons.left;
} else {
termsText.anchors.centerIn = termsContainer;
}
}
}
}
}
Component.onCompleted: {
@ -201,18 +264,25 @@ Item {
target: loginDialog
onHandleCreateCompleted: {
console.log("Create Succeeded");
if (usernameCollisionBody.withSteam) {
if (usernameCollisionBody.withOculus) {
if (usernameCollisionBody.loginDialogPoppedUp) {
var data = {
"action": "user created a profile with Oculus successfully in the username collision screen"
}
UserActivityLogger.logAction("encourageLoginDialog", data);
}
loginDialog.loginThroughOculus();
} else if (usernameCollisionBody.withSteam) {
if (usernameCollisionBody.loginDialogPoppedUp) {
var data = {
"action": "user created a profile with Steam successfully in the username collision screen"
}
UserActivityLogger.logAction("encourageLoginDialog", data);
}
loginDialog.loginThroughSteam();
}
bodyLoader.setSource("LoggingInBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": usernameCollisionBody.withSteam, "linkSteam": false })
bodyLoader.setSource("LoggingInBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": usernameCollisionBody.withSteam,
"withOculus": usernameCollisionBody.withOculus, "linkSteam": false, "linkOculus": false })
}
onHandleCreateFailed: {
console.log("Create Failed: " + error)

View file

@ -150,6 +150,6 @@ FocusScope {
Component.onCompleted: {
keyboardTimer.start();
bodyLoader.setSource("LoginDialog/LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "linkSteam": false });
bodyLoader.setSource("LoginDialog/LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "linkSteam": false, "linkOculus": false });
}
}

View file

@ -57,16 +57,23 @@ SpinBox {
locale: Qt.locale("en_US")
onValueModified: realValue = value/factor
onValueChanged: realValue = value/factor
onValueModified: {
realValue = value / factor
}
onValueChanged: {
realValue = value / factor
spinBox.editingFinished();
}
onRealValueChanged: {
var newValue = Math.round(realValue*factor);
var newValue = Math.round(realValue * factor);
if(value != newValue) {
value = newValue;
}
}
stepSize: realStepSize*factor
stepSize: realStepSize * factor
to : realTo*factor
from : realFrom*factor
@ -90,11 +97,11 @@ SpinBox {
}
textFromValue: function(value, locale) {
return parseFloat(value/factor).toFixed(decimals);
return parseFloat(value / factor).toFixed(decimals);
}
valueFromText: function(text, locale) {
return Number.fromLocaleString(locale, text)*factor;
return Number.fromLocaleString(locale, text) * factor;
}
@ -102,7 +109,7 @@ SpinBox {
id: spinboxText
z: 2
color: isLightColorScheme
? (spinBox.activeFocus ? hifi.colors.black : hifi.colors.lightGray)
? (spinBox.activeFocus ? hifi.colors.black : hifi.colors.faintGray)
: (spinBox.activeFocus ? hifi.colors.white : hifi.colors.lightGrayText)
selectedTextColor: hifi.colors.black
selectionColor: hifi.colors.primaryHighlight
@ -112,8 +119,6 @@ SpinBox {
verticalAlignment: Qt.AlignVCenter
leftPadding: spinBoxLabelInside.visible ? 30 : hifi.dimensions.textPadding
width: spinBox.width - hifi.dimensions.spinnerSize
onEditingFinished: spinBox.editingFinished()
Text {
id: suffixText
x: metrics.advanceWidth(spinboxText.text + '*')
@ -125,7 +130,7 @@ SpinBox {
}
color: isLightColorScheme
? (spinBox.activeFocus ? hifi.colors.black : hifi.colors.lightGray)
? (spinBox.activeFocus ? hifi.colors.black : hifi.colors.faintGray)
: (spinBox.activeFocus ? hifi.colors.white : hifi.colors.lightGrayText)
text: suffix
verticalAlignment: Qt.AlignVCenter
@ -170,6 +175,22 @@ SpinBox {
}
}
Keys.onPressed: {
if (event.key === Qt.Key_Return) {
if (!spinboxText.acceptableInput) {
var number = spinBox.valueFromText(spinboxText.text, spinBox.locale) / spinBox.factor
if (number < spinBox.minimumValue) {
number = spinBox.minimumValue;
} else if (number > maximumValue) {
number = spinBox.maximumValue;
}
spinboxText.text = spinBox.textFromValue(Math.round(number * factor), spinBox.locale)
}
}
}
HifiControls.Label {
id: spinBoxLabel
text: spinBox.label

View file

@ -14,6 +14,8 @@ import QtQuick 2.5
import controlsUit 1.0 as HifiControlsUit
import stylesUit 1.0 as HifiStylesUit
import TabletScriptingInterface 1.0
import "../LoginDialog"
FocusScope {
@ -25,10 +27,9 @@ FocusScope {
width: parent.width
height: parent.height
signal sendToScript(var message);
signal canceled();
property var tabletProxy: Tablet.getTablet("com.highfidelity.interface.tablet.system");
property bool isHMD: false
property bool isHMD: HMD.active
property bool gotoPreviousApp: false;
property bool keyboardEnabled: false
@ -52,6 +53,7 @@ FocusScope {
}
function tryDestroy() {
tabletProxy.gotoHomeScreen();
}
MouseArea {
@ -76,7 +78,7 @@ FocusScope {
interval: 200
onTriggered: {
if (MenuInterface.isOptionChecked("Use 3D Keyboard")) {
if (MenuInterface.isOptionChecked("Use 3D Keyboard") && root.isHMD) {
KeyboardScriptingInterface.raised = true;
}
}
@ -169,11 +171,13 @@ FocusScope {
Component.onDestruction: {
loginKeyboard.raised = false;
KeyboardScriptingInterface.raised = false;
if (root.isHMD) {
KeyboardScriptingInterface.raised = false;
}
}
Component.onCompleted: {
keyboardTimer.start();
bodyLoader.setSource("../LoginDialog/LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "linkSteam": false });
bodyLoader.setSource("../LoginDialog/LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "linkSteam": false, "linkOculus": false });
}
}

View file

@ -254,6 +254,7 @@ Rectangle {
onSaveClicked: function() {
var avatarSettings = {
dominantHand : settings.dominantHandIsLeft ? 'left' : 'right',
hmdAvatarAlignmentType : settings.hmdAvatarAlignmentTypeIsEyes ? 'eyes' : 'head',
collisionsEnabled : settings.environmentCollisionsOn,
otherAvatarsCollisionsEnabled : settings.otherAvatarsCollisionsOn,
animGraphOverrideUrl : settings.avatarAnimationOverrideJSON,

View file

@ -37,6 +37,7 @@ Rectangle {
property alias dominantHandIsLeft: leftHandRadioButton.checked
property alias otherAvatarsCollisionsOn: otherAvatarsCollisionsEnabledRadiobutton.checked
property alias environmentCollisionsOn: environmentCollisionsEnabledRadiobutton.checked
property alias hmdAvatarAlignmentTypeIsEyes: eyesRadioButton.checked
property alias avatarAnimationOverrideJSON: avatarAnimationUrlInputText.text
property alias avatarAnimationJSON: avatarAnimationUrlInputText.placeholderText
property alias avatarCollisionSoundUrl: avatarCollisionSoundUrlInputText.text
@ -65,6 +66,11 @@ Rectangle {
} else {
environmentCollisionsDisabledRadiobutton.checked = true;
}
if (settings.hmdAvatarAlignmentType === 'eyes') {
eyesRadioButton.checked = true;
} else {
headRadioButton.checked = true;
}
avatarAnimationJSON = settings.animGraphUrl;
avatarAnimationOverrideJSON = settings.animGraphOverrideUrl;
@ -210,7 +216,7 @@ Rectangle {
anchors.left: parent.left
anchors.right: parent.right
rows: 2
rows: 4
rowSpacing: 25
columns: 3
@ -233,7 +239,7 @@ Rectangle {
Layout.row: 0
Layout.column: 1
Layout.leftMargin: -20
Layout.leftMargin: -15
ButtonGroup.group: leftRight
checked: true
@ -249,7 +255,7 @@ Rectangle {
id: rightHandRadioButton
Layout.row: 0
Layout.column: 3
Layout.column: 2
Layout.rightMargin: -15
ButtonGroup.group: leftRight
@ -260,7 +266,7 @@ Rectangle {
text: "Right"
boxSize: 20
}
HifiConstants {
id: hifi
}
@ -272,17 +278,17 @@ Rectangle {
Layout.column: 0
text: "Avatar to avatar collision"
}
ButtonGroup {
id: otherAvatarsOnOff
}
HifiControlsUit.RadioButton {
id: otherAvatarsCollisionsEnabledRadiobutton
Layout.row: 1
Layout.column: 1
Layout.leftMargin: -20
Layout.leftMargin: -15
ButtonGroup.group: otherAvatarsOnOff
@ -297,7 +303,7 @@ Rectangle {
id: otherAvatarsCollisionsDisabledRadiobutton
Layout.row: 1
Layout.column: 3
Layout.column: 2
Layout.rightMargin: -15
ButtonGroup.group: otherAvatarsOnOff
@ -320,13 +326,13 @@ Rectangle {
ButtonGroup {
id: worldOnOff
}
HifiControlsUit.RadioButton {
id: environmentCollisionsEnabledRadiobutton
Layout.row: 2
Layout.column: 1
Layout.leftMargin: -20
Layout.leftMargin: -15
ButtonGroup.group: worldOnOff
@ -341,7 +347,7 @@ Rectangle {
id: environmentCollisionsDisabledRadiobutton
Layout.row: 2
Layout.column: 3
Layout.column: 2
Layout.rightMargin: -15
ButtonGroup.group: worldOnOff
@ -352,6 +358,52 @@ Rectangle {
text: "Off"
boxSize: 20
}
// TextStyle9
RalewaySemiBold {
size: 17;
Layout.row: 3
Layout.column: 0
text: "HMD Alignment"
}
ButtonGroup {
id: headEyes
}
HifiControlsUit.RadioButton {
id: headRadioButton
Layout.row: 3
Layout.column: 1
Layout.leftMargin: -15
ButtonGroup.group: headEyes
checked: true
colorScheme: hifi.colorSchemes.light
fontSize: 17
letterSpacing: 1.4
text: "Head"
boxSize: 20
}
HifiControlsUit.RadioButton {
id: eyesRadioButton
Layout.row: 3
Layout.column: 2
Layout.rightMargin: -15
ButtonGroup.group: headEyes
colorScheme: hifi.colorSchemes.light
fontSize: 17
letterSpacing: 1.4
text: "Eyes"
boxSize: 20
}
}
ColumnLayout {

View file

@ -70,7 +70,7 @@ Flickable {
readonly property bool hmdDesktop: hmdInDesktop.checked
property int state: buttonState.disabled
property var lastConfiguration: null
property var lastConfiguration: null
HifiConstants { id: hifi }
@ -90,7 +90,6 @@ Flickable {
anchors.fill: parent
propagateComposedEvents: true
onPressed: {
parent.forceActiveFocus()
mouse.accepted = false;
}
}
@ -169,9 +168,7 @@ Flickable {
boxRadius: 7
visible: viveInDesktop.checked
anchors.top: viveInDesktop.bottom
anchors.topMargin: 5
anchors.left: openVrConfiguration.left
anchors.leftMargin: leftMargin + 10
onClicked: {
@ -214,13 +211,13 @@ Flickable {
onRealValueChanged: {
sendConfigurationSettings();
openVrConfiguration.forceActiveFocus();
}
}
HifiControls.SpinBox {
id: headZOffset
z: 10
width: 112
label: "Z Offset"
minimumValue: -50
@ -232,7 +229,6 @@ Flickable {
onRealValueChanged: {
sendConfigurationSettings();
openVrConfiguration.forceActiveFocus();
}
}
}
@ -326,7 +322,6 @@ Flickable {
onRealValueChanged: {
sendConfigurationSettings();
openVrConfiguration.forceActiveFocus();
}
}
@ -344,7 +339,6 @@ Flickable {
onRealValueChanged: {
sendConfigurationSettings();
openVrConfiguration.forceActiveFocus();
}
}
}
@ -578,7 +572,6 @@ Flickable {
onRealValueChanged: {
sendConfigurationSettings();
openVrConfiguration.forceActiveFocus();
}
}
@ -596,7 +589,6 @@ Flickable {
onRealValueChanged: {
sendConfigurationSettings();
openVrConfiguration.forceActiveFocus();
}
}
}
@ -747,8 +739,8 @@ Flickable {
}
Component.onCompleted: {
InputConfiguration.calibrationStatus.connect(calibrationStatusInfo);
lastConfiguration = composeConfigurationSettings();
InputConfiguration.calibrationStatus.connect(calibrationStatusInfo);
}
Component.onDestruction: {
@ -777,7 +769,6 @@ Flickable {
calibrationTimer.interval = realValue * 1000;
openVrConfiguration.countDown = realValue;
numberAnimation.duration = calibrationTimer.interval;
openVrConfiguration.forceActiveFocus();
}
}
@ -1048,6 +1039,9 @@ Flickable {
}
function updateButtonState() {
if (lastConfiguration === null) {
lastConfiguration = composeConfigurationSettings();
}
var settings = composeConfigurationSettings();
var bodySetting = settings["bodyConfiguration"];
var headSetting = settings["headConfiguration"];

View file

@ -120,6 +120,7 @@
#include <plugins/PluginManager.h>
#include <plugins/PluginUtils.h>
#include <plugins/SteamClientPlugin.h>
#include <plugins/OculusPlatformPlugin.h>
#include <plugins/InputConfiguration.h>
#include <RecordingScriptingInterface.h>
#include <render/EngineStats.h>
@ -801,7 +802,6 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) {
if (auto steamClient = pluginManager->getSteamClientPlugin()) {
steamClient->init();
}
PROFILE_SET_THREAD_NAME("Main Thread");
#if defined(Q_OS_WIN)
@ -2418,7 +2418,6 @@ void Application::updateVerboseLogging() {
bool enable = menu->isOptionChecked(MenuOption::VerboseLogging);
QString rules =
"hifi.*.debug=%1\n"
"hifi.*.info=%1\n"
"hifi.audio-stream.debug=false\n"
"hifi.audio-stream.info=false";
@ -2738,6 +2737,7 @@ Application::~Application() {
if (auto steamClient = PluginManager::getInstance()->getSteamClientPlugin()) {
steamClient->shutdown();
}
DependencyManager::destroy<PluginManager>();
DependencyManager::destroy<CompositorHelper>(); // must be destroyed before the FramebufferCache
@ -4883,6 +4883,10 @@ void Application::idle() {
steamClient->runCallbacks();
}
if (auto oculusPlugin = PluginManager::getInstance()->getOculusPlatformPlugin()) {
oculusPlugin->handleOVREvents();
}
float secondsSinceLastUpdate = (float)_lastTimeUpdated.nsecsElapsed() / NSECS_PER_MSEC / MSECS_PER_SECOND;
_lastTimeUpdated.start();
@ -6067,6 +6071,13 @@ void Application::update(float deltaTime) {
auto userInputMapper = DependencyManager::get<UserInputMapper>();
controller::HmdAvatarAlignmentType hmdAvatarAlignmentType;
if (myAvatar->getHmdAvatarAlignmentType() == "eyes") {
hmdAvatarAlignmentType = controller::HmdAvatarAlignmentType::Eyes;
} else {
hmdAvatarAlignmentType = controller::HmdAvatarAlignmentType::Head;
}
controller::InputCalibrationData calibrationData = {
myAvatar->getSensorToWorldMatrix(),
createMatFromQuatAndPos(myAvatar->getWorldOrientation(), myAvatar->getWorldPosition()),
@ -6080,7 +6091,8 @@ void Application::update(float deltaTime) {
myAvatar->getRightArmCalibrationMat(),
myAvatar->getLeftArmCalibrationMat(),
myAvatar->getRightHandCalibrationMat(),
myAvatar->getLeftHandCalibrationMat()
myAvatar->getLeftHandCalibrationMat(),
hmdAvatarAlignmentType
};
InputPluginPointer keyboardMousePlugin;
@ -8243,7 +8255,18 @@ void Application::toggleLogDialog() {
return;
}
if (! _logDialog) {
bool keepOnTop =_keepLogWindowOnTop.get();
#ifdef Q_OS_WIN
_logDialog = new LogDialog(keepOnTop ? qApp->getWindow() : nullptr, getLogger());
#else
_logDialog = new LogDialog(nullptr, getLogger());
if (keepOnTop) {
Qt::WindowFlags flags = _logDialog->windowFlags() | Qt::Tool;
_logDialog->setWindowFlags(flags);
}
#endif
}
if (_logDialog->isVisible()) {
@ -8253,6 +8276,19 @@ void Application::toggleLogDialog() {
}
}
void Application::recreateLogWindow(int keepOnTop) {
_keepLogWindowOnTop.set(keepOnTop != 0);
if (_logDialog) {
bool toggle = _logDialog->isVisible();
_logDialog->close();
_logDialog = nullptr;
if (toggle) {
toggleLogDialog();
}
}
}
void Application::toggleEntityScriptServerLogDialog() {
if (! _entityScriptServerLogDialog) {
_entityScriptServerLogDialog = new EntityScriptServerLogDialog(nullptr);

View file

@ -217,6 +217,8 @@ public:
void setDesktopTabletScale(float desktopTabletScale);
bool getDesktopTabletBecomesToolbarSetting() { return _desktopTabletBecomesToolbarSetting.get(); }
bool getLogWindowOnTopSetting() { return _keepLogWindowOnTop.get(); }
void setLogWindowOnTopSetting(bool keepOnTop) { _keepLogWindowOnTop.set(keepOnTop); }
void setDesktopTabletBecomesToolbarSetting(bool value);
bool getHmdTabletBecomesToolbarSetting() { return _hmdTabletBecomesToolbarSetting.get(); }
void setHmdTabletBecomesToolbarSetting(bool value);
@ -365,6 +367,7 @@ public slots:
Q_INVOKABLE void loadDialog();
Q_INVOKABLE void loadScriptURLDialog() const;
void toggleLogDialog();
void recreateLogWindow(int);
void toggleEntityScriptServerLogDialog();
Q_INVOKABLE void showAssetServerWidget(QString filePath = "");
Q_INVOKABLE void loadAddAvatarBookmarkDialog() const;
@ -656,6 +659,7 @@ private:
Setting::Handle<bool> _constrainToolbarPosition;
Setting::Handle<QString> _preferredCursor;
Setting::Handle<bool> _miniTabletEnabledSetting;
Setting::Handle<bool> _keepLogWindowOnTop { "keepLogWindowOnTop", false };
float _scaleMirror;
float _mirrorYawOffset;

View file

@ -95,6 +95,37 @@ const float CENTIMETERS_PER_METER = 100.0f;
const QString AVATAR_SETTINGS_GROUP_NAME { "Avatar" };
static const QString USER_RECENTER_MODEL_FORCE_SIT = QStringLiteral("ForceSit");
static const QString USER_RECENTER_MODEL_FORCE_STAND = QStringLiteral("ForceStand");
static const QString USER_RECENTER_MODEL_AUTO = QStringLiteral("Auto");
static const QString USER_RECENTER_MODEL_DISABLE_HMD_LEAN = QStringLiteral("DisableHMDLean");
MyAvatar::SitStandModelType stringToUserRecenterModel(const QString& str) {
if (str == USER_RECENTER_MODEL_FORCE_SIT) {
return MyAvatar::ForceSit;
} else if (str == USER_RECENTER_MODEL_FORCE_STAND) {
return MyAvatar::ForceStand;
} else if (str == USER_RECENTER_MODEL_DISABLE_HMD_LEAN) {
return MyAvatar::DisableHMDLean;
} else {
return MyAvatar::Auto;
}
}
QString userRecenterModelToString(MyAvatar::SitStandModelType model) {
switch (model) {
case MyAvatar::ForceSit:
return USER_RECENTER_MODEL_FORCE_SIT;
case MyAvatar::ForceStand:
return USER_RECENTER_MODEL_FORCE_STAND;
case MyAvatar::DisableHMDLean:
return USER_RECENTER_MODEL_DISABLE_HMD_LEAN;
case MyAvatar::Auto:
default:
return USER_RECENTER_MODEL_AUTO;
}
}
MyAvatar::MyAvatar(QThread* thread) :
Avatar(thread),
_yawSpeed(YAW_SPEED_DEFAULT),
@ -125,6 +156,7 @@ MyAvatar::MyAvatar(QThread* thread) :
_prevShouldDrawHead(true),
_audioListenerMode(FROM_HEAD),
_dominantHandSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "dominantHand", DOMINANT_RIGHT_HAND),
_hmdAvatarAlignmentTypeSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "hmdAvatarAlignmentType", DEFAULT_HMD_AVATAR_ALIGNMENT_TYPE),
_headPitchSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "", 0.0f),
_scaleSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "scale", _targetScale),
_yawSpeedSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "yawSpeed", _yawSpeed),
@ -138,7 +170,8 @@ MyAvatar::MyAvatar(QThread* thread) :
_useSnapTurnSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "useSnapTurn", _useSnapTurn),
_userHeightSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "userHeight", DEFAULT_AVATAR_HEIGHT),
_flyingHMDSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "flyingHMD", _flyingPrefHMD),
_avatarEntityCountSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "avatarEntityData" << "size", 0)
_avatarEntityCountSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "avatarEntityData" << "size", 0),
_userRecenterModelSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "userRecenterModel", USER_RECENTER_MODEL_AUTO)
{
_clientTraitsHandler.reset(new ClientTraitsHandler(this));
@ -286,10 +319,25 @@ MyAvatar::~MyAvatar() {
_myScriptEngine = nullptr;
}
QString MyAvatar::getDominantHand() const {
return _dominantHand.get();
}
void MyAvatar::setDominantHand(const QString& hand) {
if (hand == DOMINANT_LEFT_HAND || hand == DOMINANT_RIGHT_HAND) {
_dominantHand = hand;
emit dominantHandChanged(_dominantHand);
_dominantHand.set(hand);
emit dominantHandChanged(hand);
}
}
QString MyAvatar::getHmdAvatarAlignmentType() const {
return _hmdAvatarAlignmentType.get();
}
void MyAvatar::setHmdAvatarAlignmentType(const QString& type) {
if (type != _hmdAvatarAlignmentType.get()) {
_hmdAvatarAlignmentType.set(type);
emit hmdAvatarAlignmentTypeChanged(type);
}
}
@ -377,6 +425,7 @@ void MyAvatar::resetSensorsAndBody() {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "resetSensorsAndBody");
return;
}
qApp->getActiveDisplayPlugin()->resetSensors();
@ -1277,7 +1326,8 @@ void MyAvatar::resizeAvatarEntitySettingHandles(uint32_t maxIndex) {
}
void MyAvatar::saveData() {
_dominantHandSetting.set(_dominantHand);
_dominantHandSetting.set(getDominantHand());
_hmdAvatarAlignmentTypeSetting.set(getHmdAvatarAlignmentType());
_headPitchSetting.set(getHead()->getBasePitch());
_scaleSetting.set(_targetScale);
_yawSpeedSetting.set(_yawSpeed);
@ -1300,6 +1350,7 @@ void MyAvatar::saveData() {
_useSnapTurnSetting.set(_useSnapTurn);
_userHeightSetting.set(getUserHeight());
_flyingHMDSetting.set(getFlyingHMDPref());
_userRecenterModelSetting.set(userRecenterModelToString(getUserRecenterModel()));
auto hmdInterface = DependencyManager::get<HMDScriptingInterface>();
saveAvatarEntityDataToSettings();
@ -1882,9 +1933,12 @@ void MyAvatar::loadData() {
setCollisionSoundURL(_collisionSoundURLSetting.get(QUrl(DEFAULT_AVATAR_COLLISION_SOUND_URL)).toString());
setSnapTurn(_useSnapTurnSetting.get());
setDominantHand(_dominantHandSetting.get(DOMINANT_RIGHT_HAND).toLower());
setHmdAvatarAlignmentType(_hmdAvatarAlignmentTypeSetting.get(DEFAULT_HMD_AVATAR_ALIGNMENT_TYPE).toLower());
setUserHeight(_userHeightSetting.get(DEFAULT_AVATAR_HEIGHT));
setTargetScale(_scaleSetting.get());
setUserRecenterModel(stringToUserRecenterModel(_userRecenterModelSetting.get(USER_RECENTER_MODEL_AUTO)));
setEnableMeshVisible(Menu::getInstance()->isOptionChecked(MenuOption::MeshVisible));
_follow.setToggleHipsFollowing (Menu::getInstance()->isOptionChecked(MenuOption::ToggleHipsFollowing));
setEnableDebugDrawBaseOfSupport(Menu::getInstance()->isOptionChecked(MenuOption::AnimDebugDrawBaseOfSupport));
@ -4761,7 +4815,7 @@ bool MyAvatar::FollowHelper::shouldActivateHorizontalCG(MyAvatar& myAvatar) cons
}
bool MyAvatar::FollowHelper::shouldActivateVertical(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const {
const float CYLINDER_TOP = 0.1f;
const float CYLINDER_TOP = 2.0f;
const float CYLINDER_BOTTOM = -1.5f;
const float SITTING_BOTTOM = -0.02f;

View file

@ -252,6 +252,7 @@ class MyAvatar : public Avatar {
const QString DOMINANT_LEFT_HAND = "left";
const QString DOMINANT_RIGHT_HAND = "right";
const QString DEFAULT_HMD_AVATAR_ALIGNMENT_TYPE = "head";
using Clock = std::chrono::system_clock;
using TimePoint = Clock::time_point;
@ -519,7 +520,18 @@ public:
* @function MyAvatar.getDominantHand
* @returns {string}
*/
Q_INVOKABLE QString getDominantHand() const { return _dominantHand; }
Q_INVOKABLE QString getDominantHand() const;
/**jsdoc
* @function MyAvatar.setHmdAvatarAlignmentType
* @param {string} hand
*/
Q_INVOKABLE void setHmdAvatarAlignmentType(const QString& hand);
/**jsdoc
* @function MyAvatar.setHmdAvatarAlignmentType
* @returns {string}
*/
Q_INVOKABLE QString getHmdAvatarAlignmentType() const;
/**jsdoc
* @function MyAvatar.setCenterOfGravityModelEnabled
@ -1585,6 +1597,13 @@ signals:
*/
void dominantHandChanged(const QString& hand);
/**jsdoc
* @function MyAvatar.hmdAvatarAlignmentTypeChanged
* @param {string} type
* @returns {Signal}
*/
void hmdAvatarAlignmentTypeChanged(const QString& type);
/**jsdoc
* @function MyAvatar.sensorToWorldScaleChanged
* @param {number} scale
@ -1773,7 +1792,8 @@ private:
ThreadSafeValueCache<QUrl> _prefOverrideAnimGraphUrl;
QUrl _fstAnimGraphOverrideUrl;
bool _useSnapTurn { true };
QString _dominantHand { DOMINANT_RIGHT_HAND };
ThreadSafeValueCache<QString> _dominantHand { DOMINANT_RIGHT_HAND };
ThreadSafeValueCache<QString> _hmdAvatarAlignmentType { DEFAULT_HMD_AVATAR_ALIGNMENT_TYPE };
const float ROLL_CONTROL_DEAD_ZONE_DEFAULT = 8.0f; // degrees
const float ROLL_CONTROL_RATE_DEFAULT = 114.0f; // degrees / sec
@ -1946,6 +1966,7 @@ private:
TimePoint _nextTraitsSendWindow;
Setting::Handle<QString> _dominantHandSetting;
Setting::Handle<QString> _hmdAvatarAlignmentTypeSetting;
Setting::Handle<float> _headPitchSetting;
Setting::Handle<float> _scaleSetting;
Setting::Handle<float> _yawSpeedSetting;
@ -1962,6 +1983,7 @@ private:
Setting::Handle<bool> _allowTeleportingSetting { "allowTeleporting", true };
std::vector<Setting::Handle<QUuid>> _avatarEntityIDSettings;
std::vector<Setting::Handle<QByteArray>> _avatarEntityDataSettings;
Setting::Handle<QString> _userRecenterModelSetting;
// AvatarEntities stuff:
// We cache the "map of unfortunately-formatted-binary-blobs" because they are expensive to compute

View file

@ -20,9 +20,9 @@
#include <PathUtils.h>
const int TOP_BAR_HEIGHT = 124;
const int INITIAL_WIDTH = 720;
const int INITIAL_WIDTH = 800;
const int INITIAL_HEIGHT = 480;
const int MINIMAL_WIDTH = 700;
const int MINIMAL_WIDTH = 780;
const int SEARCH_BUTTON_LEFT = 25;
const int SEARCH_BUTTON_WIDTH = 20;
const int SEARCH_TOGGLE_BUTTON_WIDTH = 50;

View file

@ -19,6 +19,9 @@
#include <shared/AbstractLoggerInterface.h>
#include "Application.h"
#include "MainWindow.h"
const int REVEAL_BUTTON_WIDTH = 122;
const int ALL_LOGS_BUTTON_WIDTH = 90;
const int MARGIN_LEFT = 25;
@ -148,6 +151,16 @@ LogDialog::LogDialog(QWidget* parent, AbstractLoggerInterface* logger) : BaseLog
_messageCount->setObjectName("messageCount");
_messageCount->show();
_keepOnTopBox = new QCheckBox(" Keep window on top", this);
bool isOnTop = qApp-> getLogWindowOnTopSetting();
_keepOnTopBox->setCheckState(isOnTop ? Qt::Checked : Qt::Unchecked);
#ifdef Q_OS_WIN
connect(_keepOnTopBox, &QCheckBox::stateChanged, qApp, &Application::recreateLogWindow);
#else
connect(_keepOnTopBox, &QCheckBox::stateChanged, this, &LogDialog::handleKeepWindowOnTop);
#endif
_keepOnTopBox->show();
_extraDebuggingBox = new QCheckBox("Extra debugging", this);
if (_logger->extraDebugging()) {
_extraDebuggingBox->setCheckState(Qt::Checked);
@ -183,6 +196,11 @@ void LogDialog::resizeEvent(QResizeEvent* event) {
THIRD_ROW,
COMBOBOX_WIDTH,
ELEMENT_HEIGHT);
_keepOnTopBox->setGeometry(width() - ELEMENT_MARGIN - COMBOBOX_WIDTH - ELEMENT_MARGIN - ALL_LOGS_BUTTON_WIDTH - ELEMENT_MARGIN - COMBOBOX_WIDTH - ELEMENT_MARGIN,
THIRD_ROW,
COMBOBOX_WIDTH,
ELEMENT_HEIGHT);
_messageCount->setGeometry(_leftPad,
THIRD_ROW,
COMBOBOX_WIDTH,
@ -234,6 +252,23 @@ void LogDialog::handleInfoPrintBox(int state) {
printLogFile();
}
void LogDialog::handleKeepWindowOnTop(int state) {
bool keepOnTop = (state != 0);
Qt::WindowFlags flags = windowFlags();
if (keepOnTop) {
flags |= Qt::Tool;
} else {
flags &= ~Qt::Tool;
}
setWindowFlags(flags);
qApp->setLogWindowOnTopSetting(keepOnTop);
show();
}
void LogDialog::handleCriticalPrintBox(int state) {
_logger->setCriticalPrint(state != 0);
printLogFile();

View file

@ -34,6 +34,7 @@ public slots:
private slots:
void handleRevealButton();
void handleExtraDebuggingCheckbox(int);
void handleKeepWindowOnTop(int);
void handleDebugPrintBox(int);
void handleInfoPrintBox(int);
void handleCriticalPrintBox(int);
@ -55,6 +56,7 @@ protected:
private:
QCheckBox* _extraDebuggingBox;
QCheckBox* _keepOnTopBox;
QPushButton* _revealLogButton;
QPushButton* _allLogsButton;
QCheckBox* _debugPrintBox;

View file

@ -18,6 +18,7 @@
#include <plugins/PluginManager.h>
#include <plugins/SteamClientPlugin.h>
#include <plugins/OculusPlatformPlugin.h>
#include <shared/GlobalAppProperties.h>
#include <ui/TabletScriptingInterface.h>
#include <UserActivityLogger.h>
@ -109,8 +110,16 @@ bool LoginDialog::isSteamRunning() const {
return steamClient && steamClient->isRunning();
}
bool LoginDialog::isOculusStoreRunning() const {
return qApp->property(hifi::properties::OCULUS_STORE).toBool();
bool LoginDialog::isOculusRunning() const {
auto oculusPlatformPlugin = PluginManager::getInstance()->getOculusPlatformPlugin();
return (oculusPlatformPlugin && oculusPlatformPlugin->isRunning());
}
QString LoginDialog::oculusUserID() const {
if (auto oculusPlatformPlugin = PluginManager::getInstance()->getOculusPlatformPlugin()) {
return oculusPlatformPlugin->getOculusUserID();
}
return "";
}
void LoginDialog::dismissLoginDialog() {
@ -126,6 +135,79 @@ void LoginDialog::login(const QString& username, const QString& password) const
DependencyManager::get<AccountManager>()->requestAccessToken(username, password);
}
void LoginDialog::loginThroughOculus() {
qDebug() << "Attempting to login through Oculus";
if (auto oculusPlatformPlugin = PluginManager::getInstance()->getOculusPlatformPlugin()) {
oculusPlatformPlugin->requestNonceAndUserID([this] (QString nonce, QString oculusID) {
DependencyManager::get<AccountManager>()->requestAccessTokenWithOculus(nonce, oculusID);
});
}
}
void LoginDialog::linkOculus() {
qDebug() << "Attempting to link Oculus account";
if (auto oculusPlatformPlugin = PluginManager::getInstance()->getOculusPlatformPlugin()) {
oculusPlatformPlugin->requestNonceAndUserID([this] (QString nonce, QString oculusID) {
if (nonce.isEmpty() || oculusID.isEmpty()) {
emit handleLoginFailed();
return;
}
JSONCallbackParameters callbackParams;
callbackParams.callbackReceiver = this;
callbackParams.jsonCallbackMethod = "linkCompleted";
callbackParams.errorCallbackMethod = "linkFailed";
const QString LINK_OCULUS_PATH = "api/v1/user/oculus/link";
QJsonObject payload;
payload["oculus_nonce"] = nonce;
payload["oculus_id"] = oculusID;
auto accountManager = DependencyManager::get<AccountManager>();
accountManager->sendRequest(LINK_OCULUS_PATH, AccountManagerAuth::Required,
QNetworkAccessManager::PostOperation, callbackParams,
QJsonDocument(payload).toJson());
});
}
}
void LoginDialog::createAccountFromOculus(QString email, QString username, QString password) {
qDebug() << "Attempting to create account from Oculus info";
if (auto oculusPlatformPlugin = PluginManager::getInstance()->getOculusPlatformPlugin()) {
oculusPlatformPlugin->requestNonceAndUserID([this, email, username, password] (QString nonce, QString oculusID) {
if (nonce.isEmpty() || oculusID.isEmpty()) {
emit handleLoginFailed();
return;
}
JSONCallbackParameters callbackParams;
callbackParams.callbackReceiver = this;
callbackParams.jsonCallbackMethod = "createCompleted";
callbackParams.errorCallbackMethod = "createFailed";
const QString CREATE_ACCOUNT_FROM_OCULUS_PATH = "api/v1/user/oculus/create";
QJsonObject payload;
payload["oculus_nonce"] = nonce;
payload["oculus_id"] = oculusID;
if (!email.isEmpty()) {
payload["email"] = email;
}
if (!username.isEmpty()) {
payload["username"] = username;
}
if (!password.isEmpty()) {
payload["password"] = password;
}
auto accountManager = DependencyManager::get<AccountManager>();
accountManager->sendRequest(CREATE_ACCOUNT_FROM_OCULUS_PATH, AccountManagerAuth::None,
QNetworkAccessManager::PostOperation, callbackParams,
QJsonDocument(payload).toJson());
});
}
}
void LoginDialog::loginThroughSteam() {
qDebug() << "Attempting to login through Steam";
if (auto steamClient = PluginManager::getInstance()->getSteamClientPlugin()) {
@ -157,7 +239,7 @@ void LoginDialog::linkSteam() {
const QString LINK_STEAM_PATH = "api/v1/user/steam/link";
QJsonObject payload;
payload.insert("steam_auth_ticket", QJsonValue::fromVariant(QVariant(ticket)));
payload["steam_auth_ticket"] = QJsonValue::fromVariant(QVariant(ticket));
auto accountManager = DependencyManager::get<AccountManager>();
accountManager->sendRequest(LINK_STEAM_PATH, AccountManagerAuth::Required,
@ -184,9 +266,9 @@ void LoginDialog::createAccountFromSteam(QString username) {
const QString CREATE_ACCOUNT_FROM_STEAM_PATH = "api/v1/user/steam/create";
QJsonObject payload;
payload.insert("steam_auth_ticket", QJsonValue::fromVariant(QVariant(ticket)));
payload["steam_auth_ticket"] = QJsonValue::fromVariant(QVariant(ticket));
if (!username.isEmpty()) {
payload.insert("username", QJsonValue::fromVariant(QVariant(username)));
payload["username"] = username;
}
auto accountManager = DependencyManager::get<AccountManager>();
@ -214,6 +296,45 @@ void LoginDialog::createCompleted(QNetworkReply* reply) {
}
void LoginDialog::createFailed(QNetworkReply* reply) {
if (isOculusRunning()) {
auto replyData = reply->readAll();
QJsonParseError parseError;
auto doc = QJsonDocument::fromJson(replyData, &parseError);
if (parseError.error != QJsonParseError::NoError) {
emit handleCreateFailed(reply->errorString());
return;
}
auto data = doc["data"];
auto error = data["error"];
auto oculusError = data["oculus"];
auto user = error["username"].toArray();
auto uid = error["uid"].toArray();
auto email = error["email"].toArray();
auto password = error["password"].toArray();
QString reply;
if (uid[0].isString()) {
emit handleCreateFailed("Oculus ID " + uid[0].toString() + ".");
return;
}
if (user[0].isString()) {
reply = "Username " + user[0].toString() + ".";
}
if (email[0].isString()) {
reply.append((!reply.isEmpty()) ? "\n" : "");
reply.append("Email " + email[0].toString() + ".");
}
if (password[0].isString()) {
reply.append((!reply.isEmpty()) ? "\n" : "");
reply.append("Password " + password[0].toString() + ".");
}
if (!oculusError.isNull() && !oculusError.isUndefined()) {
emit handleCreateFailed("Could not verify token with Oculus. Please try again.");
return;
} else {
emit handleCreateFailed(reply);
return;
}
}
emit handleCreateFailed(reply->errorString());
}

View file

@ -22,7 +22,6 @@ extern const QUrl OVERLAY_LOGIN_DIALOG;
class LoginDialog : public OffscreenQmlDialog {
Q_OBJECT
Q_PROPERTY(bool isLogIn READ getIsLogIn WRITE setIsLogIn)
HIFI_QML_DECL
public:
@ -67,24 +66,23 @@ protected slots:
Q_INVOKABLE void dismissLoginDialog();
Q_INVOKABLE bool isSteamRunning() const;
Q_INVOKABLE bool isOculusStoreRunning() const;
Q_INVOKABLE bool isOculusRunning() const;
Q_INVOKABLE QString oculusUserID() const;
Q_INVOKABLE void login(const QString& username, const QString& password) const;
Q_INVOKABLE void loginThroughSteam();
Q_INVOKABLE void linkSteam();
Q_INVOKABLE void createAccountFromSteam(QString username = QString());
Q_INVOKABLE void loginThroughOculus();
Q_INVOKABLE void linkOculus();
Q_INVOKABLE void createAccountFromOculus(QString email = QString(), QString username = QString(), QString password = QString());
Q_INVOKABLE void signup(const QString& email, const QString& username, const QString& password);
Q_INVOKABLE void openUrl(const QString& url) const;
Q_INVOKABLE bool getLoginDialogPoppedUp() const;
private:
bool getIsLogIn() const { return _isLogIn; }
void setIsLogIn(const bool isLogIn) { _isLogIn = isLogIn; }
bool _isLogIn{ false };
};
#endif // hifi_LoginDialog_h

View file

@ -15,6 +15,11 @@
namespace controller {
enum class HmdAvatarAlignmentType {
Eyes = 0, // align the user's eyes with the avatars eyes
Head // align the user's head with the avatars head
};
struct InputCalibrationData {
glm::mat4 sensorToWorldMat; // sensor to world
glm::mat4 avatarMat; // avatar to world
@ -29,6 +34,7 @@ struct InputCalibrationData {
glm::mat4 defaultLeftArm; // default pose for leftArm joint in sensor space
glm::mat4 defaultRightHand; // default pose for rightHand joint in sensor space
glm::mat4 defaultLeftHand; // default pose for leftHand joint in sensor space
HmdAvatarAlignmentType hmdAvatarAlignmentType;
};
enum class ChannelType {

View file

@ -31,6 +31,8 @@ public:
virtual void compositeExtra() override;
virtual void pluginUpdate() override {};
protected:
mutable bool _isThrottled = false;

View file

@ -20,6 +20,7 @@ public:
QImage getScreenshot(float aspectRatio = 0.0f) const override;
QImage getSecondaryCameraScreenshot() const override;
void copyTextureToQuickFramebuffer(NetworkTexturePointer source, QOpenGLFramebufferObject* target, GLsync* fenceSync) override {};
void pluginUpdate() override {};
private:
static const QString NAME;
};

View file

@ -46,6 +46,8 @@ public:
virtual bool onDisplayTextureReset() override { _clearPreviewFlag = true; return true; };
void pluginUpdate() override {};
signals:
void hmdMountedChanged();
void hmdVisibleChanged(bool visible);

View file

@ -28,6 +28,8 @@ public:
// to the HMD plugins.
//virtual glm::mat4 getEyeToHeadTransform(Eye eye) const override;
virtual void pluginUpdate() override {};
protected:
virtual bool internalActivate() override;
virtual void internalDeactivate() override;

View file

@ -141,7 +141,7 @@ std::shared_ptr<T> make_renderer(const EntityItemPointer& entity) {
return std::shared_ptr<T>(new T(entity), [](T* ptr) { ptr->deleteLater(); });
}
EntityRenderer::EntityRenderer(const EntityItemPointer& entity) : _entity(entity) {
EntityRenderer::EntityRenderer(const EntityItemPointer& entity) : _created(entity->getCreated()), _entity(entity) {
connect(entity.get(), &EntityItem::requestRenderUpdate, this, [&] {
_needsRenderUpdate = true;
emit requestRenderUpdate();
@ -468,3 +468,32 @@ void EntityRenderer::removeMaterial(graphics::MaterialPointer material, const st
std::lock_guard<std::mutex> lock(_materialsLock);
_materials[parentMaterialName].remove(material);
}
glm::vec4 EntityRenderer::calculatePulseColor(const glm::vec4& color, const PulsePropertyGroup& pulseProperties, quint64 start) {
if (pulseProperties.getPeriod() == 0.0f || (pulseProperties.getColorMode() == PulseMode::NONE && pulseProperties.getAlphaMode() == PulseMode::NONE)) {
return color;
}
float t = ((float)(usecTimestampNow() - start)) / ((float)USECS_PER_SECOND);
float pulse = 0.5f * (cosf(t * (2.0f * (float)M_PI) / pulseProperties.getPeriod()) + 1.0f) * (pulseProperties.getMax() - pulseProperties.getMin()) + pulseProperties.getMin();
float outPulse = (1.0f - pulse);
glm::vec4 result = color;
if (pulseProperties.getColorMode() == PulseMode::IN_PHASE) {
result.r *= pulse;
result.g *= pulse;
result.b *= pulse;
} else if (pulseProperties.getColorMode() == PulseMode::OUT_PHASE) {
result.r *= outPulse;
result.g *= outPulse;
result.b *= outPulse;
}
if (pulseProperties.getAlphaMode() == PulseMode::IN_PHASE) {
result.a *= pulse;
} else if (pulseProperties.getAlphaMode() == PulseMode::OUT_PHASE) {
result.a *= outPulse;
}
return result;
}

View file

@ -62,6 +62,8 @@ public:
virtual scriptable::ScriptableModelBase getScriptableModel() override { return scriptable::ScriptableModelBase(); }
static glm::vec4 calculatePulseColor(const glm::vec4& color, const PulsePropertyGroup& pulseProperties, quint64 start);
protected:
virtual bool needsRenderUpdateFromEntity() const final { return needsRenderUpdateFromEntity(_entity); }
virtual void onAddToScene(const EntityItemPointer& entity);
@ -151,6 +153,8 @@ protected:
std::unordered_map<std::string, graphics::MultiMaterial> _materials;
std::mutex _materialsLock;
quint64 _created;
private:
// The base class relies on comparing the model transform to the entity transform in order
// to trigger an update, so the member must not be visible to derived classes as a modifiable

View file

@ -26,7 +26,7 @@ GridEntityRenderer::~GridEntityRenderer() {
}
bool GridEntityRenderer::isTransparent() const {
return Parent::isTransparent() || _alpha < 1.0f;
return Parent::isTransparent() || _alpha < 1.0f || _pulseProperties.getAlphaMode() != PulseMode::NONE;
}
bool GridEntityRenderer::needsRenderUpdate() const {
@ -55,6 +55,10 @@ bool GridEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPoint
return true;
}
if (_pulseProperties != entity->getPulseProperties()) {
return true;
}
return false;
});
@ -65,6 +69,7 @@ void GridEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scen
withWriteLock([&] {
_color = entity->getColor();
_alpha = entity->getAlpha();
_pulseProperties = entity->getPulseProperties();
_followCamera = entity->getFollowCamera();
_majorGridEvery = entity->getMajorGridEvery();
@ -105,11 +110,12 @@ ShapeKey GridEntityRenderer::getShapeKey() {
}
void GridEntityRenderer::doRender(RenderArgs* args) {
glm::u8vec3 color;
glm::vec4 color;
glm::vec3 dimensions;
Transform renderTransform;
withReadLock([&] {
color = _color;
color = glm::vec4(toGlm(_color), _alpha);
color = EntityRenderer::calculatePulseColor(color, _pulseProperties, _created);
dimensions = _dimensions;
renderTransform = _renderTransform;
});
@ -141,12 +147,11 @@ void GridEntityRenderer::doRender(RenderArgs* args) {
float majorGridColDivisions = dimensions.y / _majorGridEvery;
float minorGridRowDivisions = dimensions.x / _minorGridEvery;
float minorGridColDivisions = dimensions.y / _minorGridEvery;
glm::vec4 gridColor(toGlm(color), _alpha);
const float MINOR_GRID_EDGE = 0.0025f;
const float MAJOR_GRID_EDGE = 0.005f;
DependencyManager::get<GeometryCache>()->renderGrid(*batch, minCorner, maxCorner,
minorGridRowDivisions, minorGridColDivisions, MINOR_GRID_EDGE,
majorGridRowDivisions, majorGridColDivisions, MAJOR_GRID_EDGE,
gridColor, _geometryId);
color, _geometryId);
}

View file

@ -36,6 +36,7 @@ private:
glm::u8vec3 _color;
float _alpha;
PulsePropertyGroup _pulseProperties;
bool _followCamera;
uint32_t _majorGridEvery;

View file

@ -26,7 +26,7 @@ ImageEntityRenderer::~ImageEntityRenderer() {
}
bool ImageEntityRenderer::isTransparent() const {
return Parent::isTransparent() || (_textureIsLoaded && _texture->getGPUTexture() && _texture->getGPUTexture()->getUsage().isAlpha()) || _alpha < 1.0f;
return Parent::isTransparent() || (_textureIsLoaded && _texture->getGPUTexture() && _texture->getGPUTexture()->getUsage().isAlpha()) || _alpha < 1.0f || _pulseProperties.getAlphaMode() != PulseMode::NONE;
}
bool ImageEntityRenderer::needsRenderUpdate() const {
@ -71,6 +71,10 @@ bool ImageEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPoin
return true;
}
if (_pulseProperties != entity->getPulseProperties()) {
return true;
}
return false;
});
@ -97,6 +101,7 @@ void ImageEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce
_color = entity->getColor();
_alpha = entity->getAlpha();
_pulseProperties = entity->getPulseProperties();
if (!_textureIsLoaded && _texture && _texture->isLoaded()) {
_textureIsLoaded = true;
@ -135,13 +140,14 @@ ShapeKey ImageEntityRenderer::getShapeKey() {
void ImageEntityRenderer::doRender(RenderArgs* args) {
NetworkTexturePointer texture;
QRect subImage;
glm::u8vec3 color;
glm::vec4 color;
glm::vec3 dimensions;
Transform transform;
withReadLock([&] {
texture = _texture;
subImage = _subImage;
color = _color;
color = glm::vec4(toGlm(_color), _alpha);
color = EntityRenderer::calculatePulseColor(color, _pulseProperties, _created);
dimensions = _dimensions;
transform = _renderTransform;
});
@ -211,11 +217,9 @@ void ImageEntityRenderer::doRender(RenderArgs* args) {
glm::vec2 texCoordBottomRight((fromImage.x() + fromImage.width() - 0.5f) / imageWidth,
(fromImage.y() + fromImage.height() - 0.5f) / imageHeight);
glm::vec4 imageColor(toGlm(color), _alpha);
DependencyManager::get<GeometryCache>()->renderQuad(
*batch, topLeft, bottomRight, texCoordTopLeft, texCoordBottomRight,
imageColor, _geometryId
color, _geometryId
);
batch->setResourceTexture(0, nullptr);

View file

@ -44,6 +44,7 @@ private:
glm::u8vec3 _color;
float _alpha;
PulsePropertyGroup _pulseProperties;
glm::vec3 _dimensions;

View file

@ -71,8 +71,11 @@ bool ParticleEffectEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedE
return true;
}
auto particleProperties = entity->getParticleProperties();
if (particleProperties != _particleProperties) {
if (_particleProperties != entity->getParticleProperties()) {
return true;
}
if (_pulseProperties != entity->getPulseProperties()) {
return true;
}
@ -95,6 +98,10 @@ void ParticleEffectEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePoi
}
});
}
withWriteLock([&] {
_pulseProperties = entity->getPulseProperties();
});
_emitting = entity->getIsEmitting();
bool hasTexture = resultWithReadLock<bool>([&]{ return _particleProperties.textures.isEmpty(); });
@ -142,10 +149,6 @@ void ParticleEffectEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEn
particleUniforms.radius.middle = _particleProperties.radius.gradient.target;
particleUniforms.radius.finish = _particleProperties.radius.range.finish;
particleUniforms.radius.spread = _particleProperties.radius.gradient.spread;
particleUniforms.color.start = _particleProperties.getColorStart();
particleUniforms.color.middle = _particleProperties.getColorMiddle();
particleUniforms.color.finish = _particleProperties.getColorFinish();
particleUniforms.color.spread = _particleProperties.getColorSpread();
particleUniforms.spin.start = _particleProperties.spin.range.start;
particleUniforms.spin.middle = _particleProperties.spin.gradient.target;
particleUniforms.spin.finish = _particleProperties.spin.range.finish;
@ -158,6 +161,7 @@ void ParticleEffectEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEn
}
ItemKey ParticleEffectEntityRenderer::getKey() {
// FIXME: implement isTransparent() for particles and an opaque pipeline
if (_visible) {
return ItemKey::Builder::transparentShape().withTagBits(getTagMask()).withLayer(getHifiRenderLayer());
} else {
@ -334,12 +338,18 @@ void ParticleEffectEntityRenderer::doRender(RenderArgs* args) {
gpu::Batch& batch = *args->_batch;
batch.setResourceTexture(0, _networkTexture->getGPUTexture());
Transform transform;
Transform transform;
// The particles are in world space, so the transform is unused, except for the rotation, which we use
// if the particles are marked rotateWithEntity
withReadLock([&] {
transform.setRotation(_renderTransform.getRotation());
auto& color = _uniformBuffer.edit<ParticleUniforms>().color;
color.start = EntityRenderer::calculatePulseColor(_particleProperties.getColorStart(), _pulseProperties, _created);
color.middle = EntityRenderer::calculatePulseColor(_particleProperties.getColorMiddle(), _pulseProperties, _created);
color.finish = EntityRenderer::calculatePulseColor(_particleProperties.getColorFinish(), _pulseProperties, _created);
color.spread = EntityRenderer::calculatePulseColor(_particleProperties.getColorSpread(), _pulseProperties, _created);
});
batch.setModelTransform(transform);
batch.setUniformBuffer(0, _uniformBuffer);
batch.setInputFormat(_vertexFormat);

View file

@ -94,6 +94,8 @@ private:
BufferView _uniformBuffer;
quint64 _lastSimulated { 0 };
PulsePropertyGroup _pulseProperties;
NetworkTexturePointer _networkTexture;
ScenePointer _scene;
};

View file

@ -85,6 +85,10 @@ bool ShapeEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPoin
return true;
}
if (_pulseProperties != entity->getPulseProperties()) {
return true;
}
return false;
}
@ -97,6 +101,7 @@ void ShapeEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce
}
_shape = entity->getShape();
_pulseProperties = entity->getPulseProperties();
});
void* key = (void*)this;
@ -141,6 +146,10 @@ void ShapeEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPoint
}
bool ShapeEntityRenderer::isTransparent() const {
if (_pulseProperties.getAlphaMode() != PulseMode::NONE) {
return true;
}
if (_procedural.isEnabled() && _procedural.isFading()) {
return Interpolate::calculateFadeRatio(_procedural.getFadeStartTime()) < 1.0f;
}
@ -248,6 +257,7 @@ void ShapeEntityRenderer::doRender(RenderArgs* args) {
materials = _materials["0"];
auto& schema = materials.getSchemaBuffer().get<graphics::MultiMaterial::Schema>();
outColor = glm::vec4(schema._albedo, schema._opacity);
outColor = EntityRenderer::calculatePulseColor(outColor, _pulseProperties, _created);
if (_procedural.isReady()) {
outColor = _procedural.getColor(outColor);
outColor.a *= _procedural.isFading() ? Interpolate::calculateFadeRatio(_procedural.getFadeStartTime()) : 1.0f;

View file

@ -40,9 +40,12 @@ private:
Procedural _procedural;
QString _lastUserData;
entity::Shape _shape { entity::Sphere };
PulsePropertyGroup _pulseProperties;
std::shared_ptr<graphics::Material> _material { std::make_shared<graphics::Material>() };
glm::u8vec3 _color;
float _alpha;
glm::vec3 _position;
glm::vec3 _dimensions;
glm::quat _orientation;

View file

@ -41,7 +41,7 @@ TextEntityRenderer::~TextEntityRenderer() {
}
bool TextEntityRenderer::isTransparent() const {
return Parent::isTransparent() || _textAlpha < 1.0f || _backgroundAlpha < 1.0f;
return Parent::isTransparent() || _textAlpha < 1.0f || _backgroundAlpha < 1.0f || _pulseProperties.getAlphaMode() != PulseMode::NONE;
}
ShapeKey TextEntityRenderer::getShapeKey() {
@ -104,6 +104,10 @@ bool TextEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPoint
return true;
}
if (_pulseProperties != entity->getPulseProperties()) {
return true;
}
return false;
}
@ -119,32 +123,39 @@ void TextEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scen
}
void TextEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPointer& entity) {
_text = entity->getText();
_lineHeight = entity->getLineHeight();
_textColor = toGlm(entity->getTextColor());
_textAlpha = entity->getTextAlpha();
_backgroundColor = toGlm(entity->getBackgroundColor());
_backgroundAlpha = entity->getBackgroundAlpha();
_billboardMode = entity->getBillboardMode();
_leftMargin = entity->getLeftMargin();
_rightMargin = entity->getRightMargin();
_topMargin = entity->getTopMargin();
_bottomMargin = entity->getBottomMargin();
withWriteLock([&] {
_pulseProperties = entity->getPulseProperties();
_text = entity->getText();
_lineHeight = entity->getLineHeight();
_textColor = toGlm(entity->getTextColor());
_textAlpha = entity->getTextAlpha();
_backgroundColor = toGlm(entity->getBackgroundColor());
_backgroundAlpha = entity->getBackgroundAlpha();
_billboardMode = entity->getBillboardMode();
_leftMargin = entity->getLeftMargin();
_rightMargin = entity->getRightMargin();
_topMargin = entity->getTopMargin();
_bottomMargin = entity->getBottomMargin();
});
}
void TextEntityRenderer::doRender(RenderArgs* args) {
PerformanceTimer perfTimer("RenderableTextEntityItem::render");
glm::vec4 textColor;
glm::vec4 backgroundColor;
Transform modelTransform;
glm::vec3 dimensions;
withReadLock([&] {
modelTransform = _renderTransform;
dimensions = _dimensions;
});
float fadeRatio = _isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) : 1.0f;
glm::vec4 textColor = glm::vec4(_textColor, fadeRatio * _textAlpha);
glm::vec4 backgroundColor = glm::vec4(_backgroundColor, fadeRatio * _backgroundAlpha);
float fadeRatio = _isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) : 1.0f;
textColor = glm::vec4(_textColor, fadeRatio * _textAlpha);
textColor = EntityRenderer::calculatePulseColor(textColor, _pulseProperties, _created);
backgroundColor = glm::vec4(_backgroundColor, fadeRatio * _backgroundAlpha);
backgroundColor = EntityRenderer::calculatePulseColor(backgroundColor, _pulseProperties, _created);
});
// Render background
static const float SLIGHTLY_BEHIND = -0.005f;

View file

@ -38,6 +38,8 @@ private:
int _geometryID{ 0 };
std::shared_ptr<TextRenderer3D> _textRenderer;
PulsePropertyGroup _pulseProperties;
QString _text;
float _lineHeight;
glm::vec3 _textColor;

View file

@ -97,7 +97,7 @@ WebEntityRenderer::~WebEntityRenderer() {
bool WebEntityRenderer::isTransparent() const {
float fadeRatio = _isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) : 1.0f;
return fadeRatio < OPAQUE_ALPHA_THRESHOLD || _alpha < 1.0f;
return fadeRatio < OPAQUE_ALPHA_THRESHOLD || _alpha < 1.0f || _pulseProperties.getAlphaMode() != PulseMode::NONE;
}
bool WebEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const {
@ -143,6 +143,10 @@ bool WebEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPointe
return true;
}
if (_pulseProperties != entity->getPulseProperties()) {
return true;
}
return false;
}
@ -201,6 +205,7 @@ void WebEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene
_dpi = entity->getDPI();
_color = entity->getColor();
_alpha = entity->getAlpha();
_pulseProperties = entity->getPulseProperties();
if (_contentType == ContentType::NoContent) {
return;
@ -293,6 +298,7 @@ void WebEntityRenderer::doRender(RenderArgs* args) {
withReadLock([&] {
float fadeRatio = _isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) : 1.0f;
color = glm::vec4(toGlm(_color), _alpha * fadeRatio);
color = EntityRenderer::calculatePulseColor(color, _pulseProperties, _created);
batch.setModelTransform(_renderTransform);
});
batch.setResourceTexture(0, _texture);

View file

@ -84,6 +84,7 @@ private:
glm::u8vec3 _color;
float _alpha { 1.0f };
PulsePropertyGroup _pulseProperties;
QString _sourceURL;
uint16_t _dpi;

View file

@ -98,9 +98,7 @@ EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& param
requestedProperties += PROP_RENDER_LAYER;
requestedProperties += PROP_PRIMITIVE_MODE;
requestedProperties += PROP_IGNORE_PICK_INTERSECTION;
withReadLock([&] {
requestedProperties += _grabProperties.getEntityProperties(params);
});
requestedProperties += _grabProperties.getEntityProperties(params);
// Physics
requestedProperties += PROP_DENSITY;

View file

@ -42,6 +42,7 @@ BloomPropertyGroup EntityItemProperties::_staticBloom;
KeyLightPropertyGroup EntityItemProperties::_staticKeyLight;
AmbientLightPropertyGroup EntityItemProperties::_staticAmbientLight;
GrabPropertyGroup EntityItemProperties::_staticGrab;
PulsePropertyGroup EntityItemProperties::_staticPulse;
EntityPropertyList PROP_LAST_ITEM = (EntityPropertyList)(PROP_AFTER_LAST_ITEM - 1);
@ -514,6 +515,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
CHECK_PROPERTY_CHANGE(PROP_COMPOUND_SHAPE_URL, compoundShapeURL);
CHECK_PROPERTY_CHANGE(PROP_COLOR, color);
CHECK_PROPERTY_CHANGE(PROP_ALPHA, alpha);
changedProperties += _pulse.getChangedProperties();
CHECK_PROPERTY_CHANGE(PROP_TEXTURES, textures);
// Particles
@ -1115,6 +1117,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
* and <code>spinSpread == PI/2</code>, each particle will have a spin in the range <code>PI/2</code> &ndash; <code>3*PI/2</code>.
* @property {boolean} rotateWithEntity=false - Whether or not the particles' spin will rotate with the entity. If false, when <code>particleSpin == 0</code>, 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="none" - <em>Currently not used.</em> <em>Read-only.</em>
*
@ -1242,6 +1245,8 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
* @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.
* @example <caption>Create a cylinder.</caption>
* var shape = Entities.addEntity({
* type: "Shape",
@ -1281,6 +1286,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
* @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.
* @example <caption>Create a text entity.</caption>
* var text = Entities.addEntity({
* type: "Text",
@ -1310,6 +1316,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
* @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.
* @example <caption>Create a Web entity displaying at 1920 x 1080 resolution.</caption>
* var METERS_TO_INCHES = 39.3701;
* var entity = Entities.addEntity({
@ -1419,6 +1426,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
* 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 <caption>Create a image entity.</caption>
* var image = Entities.addEntity({
* type: "Image",
@ -1442,6 +1450,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
* line. Minimum value = <code>1</code>.
* @property {number} minorGridEvery=1 - Real number of meters at which to draw thin grid lines. Minimum value =
* <code>0.001</code>.
* @property {Entities.Pulse} pulse - The pulse-related properties. Deprecated.
* @example <caption>Create a grid entity.</caption>
* var grid = Entities.addEntity({
* type: "Grid",
@ -1579,6 +1588,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_SHAPE_TYPE, shapeType, getShapeTypeAsString());
COPY_PROPERTY_TO_QSCRIPTVALUE_TYPED(PROP_COLOR, color, u8vec3Color);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ALPHA, alpha);
_pulse.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_TEXTURES, textures);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_MAX_PARTICLES, maxParticles);
@ -1652,6 +1662,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool
if (_type == EntityTypes::Box || _type == EntityTypes::Sphere || _type == EntityTypes::Shape) {
COPY_PROPERTY_TO_QSCRIPTVALUE_TYPED(PROP_COLOR, color, u8vec3Color);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ALPHA, alpha);
_pulse.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_SHAPE, shape);
}
@ -1667,6 +1678,8 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool
// Text only
if (_type == EntityTypes::Text) {
_pulse.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_TEXT, text);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LINE_HEIGHT, lineHeight);
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER_TYPED(PROP_TEXT_COLOR, textColor, getTextColor(), u8vec3Color);
@ -1708,6 +1721,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool
if (_type == EntityTypes::Web) {
COPY_PROPERTY_TO_QSCRIPTVALUE_TYPED(PROP_COLOR, color, u8vec3Color);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ALPHA, alpha);
_pulse.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_SOURCE_URL, sourceUrl);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_DPI, dpi);
@ -1772,6 +1786,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool
if (_type == EntityTypes::Image) {
COPY_PROPERTY_TO_QSCRIPTVALUE_TYPED(PROP_COLOR, color, u8vec3Color);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ALPHA, alpha);
_pulse.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_IMAGE_URL, imageURL);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_EMISSIVE, emissive);
@ -1792,6 +1807,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool
if (_type == EntityTypes::Grid) {
COPY_PROPERTY_TO_QSCRIPTVALUE_TYPED(PROP_COLOR, color, u8vec3Color);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ALPHA, alpha);
_pulse.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_GRID_FOLLOW_CAMERA, followCamera);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_MAJOR_GRID_EVERY, majorGridEvery);
@ -1972,6 +1988,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool
COPY_PROPERTY_FROM_QSCRIPTVALUE(compoundShapeURL, QString, setCompoundShapeURL);
COPY_PROPERTY_FROM_QSCRIPTVALUE(color, u8vec3Color, setColor);
COPY_PROPERTY_FROM_QSCRIPTVALUE(alpha, float, setAlpha);
_pulse.copyFromScriptValue(object, _defaultSettings);
COPY_PROPERTY_FROM_QSCRIPTVALUE(textures, QString, setTextures);
// Particles
@ -2245,6 +2262,7 @@ void EntityItemProperties::merge(const EntityItemProperties& other) {
COPY_PROPERTY_IF_CHANGED(compoundShapeURL);
COPY_PROPERTY_IF_CHANGED(color);
COPY_PROPERTY_IF_CHANGED(alpha);
_pulse.merge(other._pulse);
COPY_PROPERTY_IF_CHANGED(textures);
// Particles
@ -2552,6 +2570,13 @@ bool EntityItemProperties::getPropertyInfo(const QString& propertyName, EntityPr
ADD_PROPERTY_TO_MAP(PROP_COMPOUND_SHAPE_URL, CompoundShapeURL, compoundShapeURL, QString);
ADD_PROPERTY_TO_MAP(PROP_COLOR, Color, color, u8vec3Color);
ADD_PROPERTY_TO_MAP_WITH_RANGE(PROP_ALPHA, Alpha, alpha, float, particle::MINIMUM_ALPHA, particle::MAXIMUM_ALPHA);
{ // Pulse
ADD_GROUP_PROPERTY_TO_MAP(PROP_PULSE_MIN, Pulse, pulse, Min, min);
ADD_GROUP_PROPERTY_TO_MAP(PROP_PULSE_MAX, Pulse, pulse, Max, max);
ADD_GROUP_PROPERTY_TO_MAP(PROP_PULSE_PERIOD, Pulse, pulse, Period, period);
ADD_GROUP_PROPERTY_TO_MAP(PROP_PULSE_COLOR_MODE, Pulse, pulse, ColorMode, colorMode);
ADD_GROUP_PROPERTY_TO_MAP(PROP_PULSE_ALPHA_MODE, Pulse, pulse, AlphaMode, alphaMode);
}
ADD_PROPERTY_TO_MAP(PROP_TEXTURES, Textures, textures, QString);
// Particles
@ -2952,6 +2977,9 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy
APPEND_ENTITY_PROPERTY(PROP_SHAPE_TYPE, (uint32_t)(properties.getShapeType()));
APPEND_ENTITY_PROPERTY(PROP_COLOR, properties.getColor());
APPEND_ENTITY_PROPERTY(PROP_ALPHA, properties.getAlpha());
_staticPulse.setProperties(properties);
_staticPulse.appendToEditPacket(packetData, requestedProperties, propertyFlags,
propertiesDidntFit, propertyCount, appendState);
APPEND_ENTITY_PROPERTY(PROP_TEXTURES, properties.getTextures());
APPEND_ENTITY_PROPERTY(PROP_MAX_PARTICLES, properties.getMaxParticles());
@ -3023,6 +3051,10 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy
}
if (properties.getType() == EntityTypes::Text) {
_staticPulse.setProperties(properties);
_staticPulse.appendToEditPacket(packetData, requestedProperties, propertyFlags,
propertiesDidntFit, propertyCount, appendState);
APPEND_ENTITY_PROPERTY(PROP_TEXT, properties.getText());
APPEND_ENTITY_PROPERTY(PROP_LINE_HEIGHT, properties.getLineHeight());
APPEND_ENTITY_PROPERTY(PROP_TEXT_COLOR, properties.getTextColor());
@ -3084,6 +3116,9 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy
if (properties.getType() == EntityTypes::Web) {
APPEND_ENTITY_PROPERTY(PROP_COLOR, properties.getColor());
APPEND_ENTITY_PROPERTY(PROP_ALPHA, properties.getAlpha());
_staticPulse.setProperties(properties);
_staticPulse.appendToEditPacket(packetData, requestedProperties, propertyFlags,
propertiesDidntFit, propertyCount, appendState);
APPEND_ENTITY_PROPERTY(PROP_SOURCE_URL, properties.getSourceUrl());
APPEND_ENTITY_PROPERTY(PROP_DPI, properties.getDPI());
@ -3118,6 +3153,9 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy
properties.getType() == EntityTypes::Sphere) {
APPEND_ENTITY_PROPERTY(PROP_COLOR, properties.getColor());
APPEND_ENTITY_PROPERTY(PROP_ALPHA, properties.getAlpha());
_staticPulse.setProperties(properties);
_staticPulse.appendToEditPacket(packetData, requestedProperties, propertyFlags,
propertiesDidntFit, propertyCount, appendState);
APPEND_ENTITY_PROPERTY(PROP_SHAPE, properties.getShape());
}
@ -3138,6 +3176,9 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy
if (properties.getType() == EntityTypes::Image) {
APPEND_ENTITY_PROPERTY(PROP_COLOR, properties.getColor());
APPEND_ENTITY_PROPERTY(PROP_ALPHA, properties.getAlpha());
_staticPulse.setProperties(properties);
_staticPulse.appendToEditPacket(packetData, requestedProperties, propertyFlags,
propertiesDidntFit, propertyCount, appendState);
APPEND_ENTITY_PROPERTY(PROP_IMAGE_URL, properties.getImageURL());
APPEND_ENTITY_PROPERTY(PROP_EMISSIVE, properties.getEmissive());
@ -3150,6 +3191,9 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy
if (properties.getType() == EntityTypes::Grid) {
APPEND_ENTITY_PROPERTY(PROP_COLOR, properties.getColor());
APPEND_ENTITY_PROPERTY(PROP_ALPHA, properties.getAlpha());
_staticPulse.setProperties(properties);
_staticPulse.appendToEditPacket(packetData, requestedProperties, propertyFlags,
propertiesDidntFit, propertyCount, appendState);
APPEND_ENTITY_PROPERTY(PROP_GRID_FOLLOW_CAMERA, properties.getFollowCamera());
APPEND_ENTITY_PROPERTY(PROP_MAJOR_GRID_EVERY, properties.getMajorGridEvery());
@ -3401,6 +3445,7 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SHAPE_TYPE, ShapeType, setShapeType);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COLOR, u8vec3Color, setColor);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ALPHA, float, setAlpha);
properties.getPulse().decodeFromEditPacket(propertyFlags, dataAt, processedBytes);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_TEXTURES, QString, setTextures);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MAX_PARTICLES, quint32, setMaxParticles);
@ -3472,6 +3517,8 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int
}
if (properties.getType() == EntityTypes::Text) {
properties.getPulse().decodeFromEditPacket(propertyFlags, dataAt, processedBytes);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_TEXT, QString, setText);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_LINE_HEIGHT, float, setLineHeight);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_TEXT_COLOR, u8vec3Color, setTextColor);
@ -3524,6 +3571,7 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int
if (properties.getType() == EntityTypes::Web) {
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COLOR, u8vec3Color, setColor);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ALPHA, float, setAlpha);
properties.getPulse().decodeFromEditPacket(propertyFlags, dataAt, processedBytes);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SOURCE_URL, QString, setSourceUrl);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_DPI, uint16_t, setDPI);
@ -3558,6 +3606,8 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int
properties.getType() == EntityTypes::Sphere) {
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COLOR, u8vec3Color, setColor);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ALPHA, float, setAlpha);
properties.getPulse().decodeFromEditPacket(propertyFlags, dataAt, processedBytes);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SHAPE, QString, setShape);
}
@ -3578,6 +3628,7 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int
if (properties.getType() == EntityTypes::Image) {
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COLOR, u8vec3Color, setColor);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ALPHA, float, setAlpha);
properties.getPulse().decodeFromEditPacket(propertyFlags, dataAt, processedBytes);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_IMAGE_URL, QString, setImageURL);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_EMISSIVE, bool, setEmissive);
@ -3590,6 +3641,7 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int
if (properties.getType() == EntityTypes::Grid) {
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COLOR, u8vec3Color, setColor);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ALPHA, float, setAlpha);
properties.getPulse().decodeFromEditPacket(propertyFlags, dataAt, processedBytes);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_GRID_FOLLOW_CAMERA, bool, setFollowCamera);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MAJOR_GRID_EVERY, uint32_t, setMajorGridEvery);
@ -3793,6 +3845,7 @@ void EntityItemProperties::markAllChanged() {
_shapeTypeChanged = true;
_colorChanged = true;
_alphaChanged = true;
_pulse.markAllChanged();
_texturesChanged = true;
_compoundShapeURLChanged = true;
@ -4257,6 +4310,7 @@ QList<QString> EntityItemProperties::listChangedProperties() {
if (alphaChanged()) {
out += "alpha";
}
getPulse().listChangedProperties(out);
if (texturesChanged()) {
out += "textures";
}

View file

@ -51,6 +51,7 @@
#include "SkyboxPropertyGroup.h"
#include "HazePropertyGroup.h"
#include "BloomPropertyGroup.h"
#include "PulsePropertyGroup.h"
#include "MaterialMappingMode.h"
#include "BillboardMode.h"
@ -235,6 +236,7 @@ public:
DEFINE_PROPERTY_REF(PROP_COMPOUND_SHAPE_URL, CompoundShapeURL, compoundShapeURL, QString, "");
DEFINE_PROPERTY_REF(PROP_COLOR, Color, color, u8vec3Color, particle::DEFAULT_COLOR);
DEFINE_PROPERTY(PROP_ALPHA, Alpha, alpha, float, particle::DEFAULT_ALPHA);
DEFINE_PROPERTY_GROUP(Pulse, pulse, PulsePropertyGroup);
DEFINE_PROPERTY_REF(PROP_TEXTURES, Textures, textures, QString, "");
// Particles

View file

@ -383,13 +383,29 @@ inline QRect QRect_convertFromScriptValue(const QScriptValue& v, bool& isValid)
} \
}
#define COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(P, S) \
QScriptValue P = object.property(#P); \
if (P.isValid()) { \
QString newValue = P.toVariant().toString(); \
if (_defaultSettings || newValue != get##S##AsString()) { \
set##S##FromString(newValue); \
} \
#define COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(P, S) \
{ \
QScriptValue P = object.property(#P); \
if (P.isValid()) { \
QString newValue = P.toVariant().toString(); \
if (_defaultSettings || newValue != get##S##AsString()) { \
set##S##FromString(newValue); \
} \
} \
}
#define COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE_ENUM(G, P, S) \
{ \
QScriptValue G = object.property(#G); \
if (G.isValid()) { \
QScriptValue P = G.property(#P); \
if (P.isValid()) { \
QString newValue = P.toVariant().toString(); \
if (_defaultSettings || newValue != get##S##AsString()) { \
set##S##FromString(newValue); \
} \
} \
} \
}
#define DEFINE_PROPERTY_GROUP(N, n, T) \

View file

@ -106,12 +106,17 @@ enum EntityPropertyList {
PROP_LOCAL_VELOCITY,
PROP_LOCAL_ANGULAR_VELOCITY,
PROP_LOCAL_DIMENSIONS,
// These properties are used by multiple subtypes but aren't in the base EntityItem
PROP_SHAPE_TYPE,
PROP_COMPOUND_SHAPE_URL,
PROP_COLOR,
PROP_ALPHA,
PROP_PULSE_MIN,
PROP_PULSE_MAX,
PROP_PULSE_PERIOD,
PROP_PULSE_COLOR_MODE,
PROP_PULSE_ALPHA_MODE,
PROP_TEXTURES,
////////////////////////////////////////////////////////////////////////////////////////////////////

View file

@ -35,6 +35,9 @@ EntityItemProperties GridEntityItem::getProperties(const EntityPropertyFlags& de
COPY_ENTITY_PROPERTY_TO_PROPERTIES(color, getColor);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(alpha, getAlpha);
withReadLock([&] {
_pulseProperties.getProperties(properties);
});
COPY_ENTITY_PROPERTY_TO_PROPERTIES(followCamera, getFollowCamera);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(majorGridEvery, getMajorGridEvery);
@ -48,6 +51,10 @@ bool GridEntityItem::setProperties(const EntityItemProperties& properties) {
SET_ENTITY_PROPERTY_FROM_PROPERTIES(color, setColor);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(alpha, setAlpha);
withWriteLock([&] {
bool pulsePropertiesChanged = _pulseProperties.setProperties(properties);
somethingChanged |= pulsePropertiesChanged;
});
SET_ENTITY_PROPERTY_FROM_PROPERTIES(followCamera, setFollowCamera);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(majorGridEvery, setMajorGridEvery);
@ -76,6 +83,13 @@ int GridEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data,
READ_ENTITY_PROPERTY(PROP_COLOR, u8vec3Color, setColor);
READ_ENTITY_PROPERTY(PROP_ALPHA, float, setAlpha);
withWriteLock([&] {
int bytesFromPulse = _pulseProperties.readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args,
propertyFlags, overwriteLocalData,
somethingChanged);
bytesRead += bytesFromPulse;
dataAt += bytesFromPulse;
});
READ_ENTITY_PROPERTY(PROP_GRID_FOLLOW_CAMERA, bool, setFollowCamera);
READ_ENTITY_PROPERTY(PROP_MAJOR_GRID_EVERY, uint32_t, setMajorGridEvery);
@ -89,6 +103,7 @@ EntityPropertyFlags GridEntityItem::getEntityProperties(EncodeBitstreamParams& p
requestedProperties += PROP_COLOR;
requestedProperties += PROP_ALPHA;
requestedProperties += _pulseProperties.getEntityProperties(params);
requestedProperties += PROP_GRID_FOLLOW_CAMERA;
requestedProperties += PROP_MAJOR_GRID_EVERY;
@ -98,7 +113,7 @@ EntityPropertyFlags GridEntityItem::getEntityProperties(EncodeBitstreamParams& p
}
void GridEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params,
EntityTreeElementExtraEncodeDataPointer modelTreeElementExtraEncodeData,
EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData,
EntityPropertyFlags& requestedProperties,
EntityPropertyFlags& propertyFlags,
EntityPropertyFlags& propertiesDidntFit,
@ -109,6 +124,10 @@ void GridEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBits
APPEND_ENTITY_PROPERTY(PROP_COLOR, getColor());
APPEND_ENTITY_PROPERTY(PROP_ALPHA, getAlpha());
withReadLock([&] {
_pulseProperties.appendSubclassData(packetData, params, entityTreeElementExtraEncodeData, requestedProperties,
propertyFlags, propertiesDidntFit, propertyCount, appendState);
});
APPEND_ENTITY_PROPERTY(PROP_GRID_FOLLOW_CAMERA, getFollowCamera());
APPEND_ENTITY_PROPERTY(PROP_MAJOR_GRID_EVERY, getMajorGridEvery());
@ -175,4 +194,10 @@ float GridEntityItem::getMinorGridEvery() const {
return resultWithReadLock<float>([&] {
return _minorGridEvery;
});
}
PulsePropertyGroup GridEntityItem::getPulseProperties() const {
return resultWithReadLock<PulsePropertyGroup>([&] {
return _pulseProperties;
});
}

View file

@ -11,6 +11,8 @@
#include "EntityItem.h"
#include "PulsePropertyGroup.h"
class GridEntityItem : public EntityItem {
using Pointer = std::shared_ptr<GridEntityItem>;
public:
@ -29,7 +31,7 @@ public:
EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override;
void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params,
EntityTreeElementExtraEncodeDataPointer modelTreeElementExtraEncodeData,
EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData,
EntityPropertyFlags& requestedProperties,
EntityPropertyFlags& propertyFlags,
EntityPropertyFlags& propertiesDidntFit,
@ -59,9 +61,13 @@ public:
void setMinorGridEvery(float minorGridEvery);
float getMinorGridEvery() const;
PulsePropertyGroup getPulseProperties() const;
protected:
glm::u8vec3 _color;
float _alpha;
PulsePropertyGroup _pulseProperties;
bool _followCamera { true };
uint32_t _majorGridEvery { DEFAULT_MAJOR_GRID_EVERY };
float _minorGridEvery { DEFAULT_MINOR_GRID_EVERY };

View file

@ -32,6 +32,9 @@ EntityItemProperties ImageEntityItem::getProperties(const EntityPropertyFlags& d
COPY_ENTITY_PROPERTY_TO_PROPERTIES(color, getColor);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(alpha, getAlpha);
withReadLock([&] {
_pulseProperties.getProperties(properties);
});
COPY_ENTITY_PROPERTY_TO_PROPERTIES(imageURL, getImageURL);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(emissive, getEmissive);
@ -47,6 +50,10 @@ bool ImageEntityItem::setProperties(const EntityItemProperties& properties) {
SET_ENTITY_PROPERTY_FROM_PROPERTIES(color, setColor);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(alpha, setAlpha);
withWriteLock([&] {
bool pulsePropertiesChanged = _pulseProperties.setProperties(properties);
somethingChanged |= pulsePropertiesChanged;
});
SET_ENTITY_PROPERTY_FROM_PROPERTIES(imageURL, setImageURL);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(emissive, setEmissive);
@ -77,6 +84,13 @@ int ImageEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data,
READ_ENTITY_PROPERTY(PROP_COLOR, u8vec3Color, setColor);
READ_ENTITY_PROPERTY(PROP_ALPHA, float, setAlpha);
withWriteLock([&] {
int bytesFromPulse = _pulseProperties.readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args,
propertyFlags, overwriteLocalData,
somethingChanged);
bytesRead += bytesFromPulse;
dataAt += bytesFromPulse;
});
READ_ENTITY_PROPERTY(PROP_IMAGE_URL, QString, setImageURL);
READ_ENTITY_PROPERTY(PROP_EMISSIVE, bool, setEmissive);
@ -92,6 +106,7 @@ EntityPropertyFlags ImageEntityItem::getEntityProperties(EncodeBitstreamParams&
requestedProperties += PROP_COLOR;
requestedProperties += PROP_ALPHA;
requestedProperties += _pulseProperties.getEntityProperties(params);
requestedProperties += PROP_IMAGE_URL;
requestedProperties += PROP_EMISSIVE;
@ -103,7 +118,7 @@ EntityPropertyFlags ImageEntityItem::getEntityProperties(EncodeBitstreamParams&
}
void ImageEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params,
EntityTreeElementExtraEncodeDataPointer modelTreeElementExtraEncodeData,
EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData,
EntityPropertyFlags& requestedProperties,
EntityPropertyFlags& propertyFlags,
EntityPropertyFlags& propertiesDidntFit,
@ -114,6 +129,10 @@ void ImageEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBit
APPEND_ENTITY_PROPERTY(PROP_COLOR, getColor());
APPEND_ENTITY_PROPERTY(PROP_ALPHA, getAlpha());
withReadLock([&] {
_pulseProperties.appendSubclassData(packetData, params, entityTreeElementExtraEncodeData, requestedProperties,
propertyFlags, propertiesDidntFit, propertyCount, appendState);
});
APPEND_ENTITY_PROPERTY(PROP_IMAGE_URL, getImageURL());
APPEND_ENTITY_PROPERTY(PROP_EMISSIVE, getEmissive());
@ -266,4 +285,10 @@ float ImageEntityItem::getAlpha() const {
return resultWithReadLock<float>([&] {
return _alpha;
});
}
PulsePropertyGroup ImageEntityItem::getPulseProperties() const {
return resultWithReadLock<PulsePropertyGroup>([&] {
return _pulseProperties;
});
}

View file

@ -11,6 +11,8 @@
#include "EntityItem.h"
#include "PulsePropertyGroup.h"
class ImageEntityItem : public EntityItem {
using Pointer = std::shared_ptr<ImageEntityItem>;
public:
@ -29,7 +31,7 @@ public:
EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override;
void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params,
EntityTreeElementExtraEncodeDataPointer modelTreeElementExtraEncodeData,
EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData,
EntityPropertyFlags& requestedProperties,
EntityPropertyFlags& propertyFlags,
EntityPropertyFlags& propertiesDidntFit,
@ -72,6 +74,8 @@ public:
void setAlpha(float alpha);
float getAlpha() const;
PulsePropertyGroup getPulseProperties() const;
protected:
QString _imageURL;
bool _emissive { false };
@ -81,6 +85,7 @@ protected:
glm::u8vec3 _color;
float _alpha;
PulsePropertyGroup _pulseProperties;
};
#endif // hifi_ImageEntityItem_h

View file

@ -412,6 +412,9 @@ EntityItemProperties ParticleEffectEntityItem::getProperties(const EntityPropert
COPY_ENTITY_PROPERTY_TO_PROPERTIES(shapeType, getShapeType);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(color, getColor);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(alpha, getAlpha);
withReadLock([&] {
_pulseProperties.getProperties(properties);
});
COPY_ENTITY_PROPERTY_TO_PROPERTIES(textures, getTextures);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(maxParticles, getMaxParticles);
@ -463,6 +466,10 @@ bool ParticleEffectEntityItem::setProperties(const EntityItemProperties& propert
SET_ENTITY_PROPERTY_FROM_PROPERTIES(shapeType, setShapeType);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(color, setColor);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(alpha, setAlpha);
withWriteLock([&] {
bool pulsePropertiesChanged = _pulseProperties.setProperties(properties);
somethingChanged |= pulsePropertiesChanged;
});
SET_ENTITY_PROPERTY_FROM_PROPERTIES(textures, setTextures);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(maxParticles, setMaxParticles);
@ -535,6 +542,13 @@ int ParticleEffectEntityItem::readEntitySubclassDataFromBuffer(const unsigned ch
READ_ENTITY_PROPERTY(PROP_SHAPE_TYPE, ShapeType, setShapeType);
READ_ENTITY_PROPERTY(PROP_COLOR, u8vec3Color, setColor);
READ_ENTITY_PROPERTY(PROP_ALPHA, float, setAlpha);
withWriteLock([&] {
int bytesFromPulse = _pulseProperties.readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args,
propertyFlags, overwriteLocalData,
somethingChanged);
bytesRead += bytesFromPulse;
dataAt += bytesFromPulse;
});
READ_ENTITY_PROPERTY(PROP_TEXTURES, QString, setTextures);
READ_ENTITY_PROPERTY(PROP_MAX_PARTICLES, quint32, setMaxParticles);
@ -586,6 +600,7 @@ EntityPropertyFlags ParticleEffectEntityItem::getEntityProperties(EncodeBitstrea
requestedProperties += PROP_SHAPE_TYPE;
requestedProperties += PROP_COLOR;
requestedProperties += PROP_ALPHA;
requestedProperties += _pulseProperties.getEntityProperties(params);
requestedProperties += PROP_TEXTURES;
requestedProperties += PROP_MAX_PARTICLES;
@ -643,6 +658,10 @@ void ParticleEffectEntityItem::appendSubclassData(OctreePacketData* packetData,
APPEND_ENTITY_PROPERTY(PROP_SHAPE_TYPE, (uint32_t)getShapeType());
APPEND_ENTITY_PROPERTY(PROP_COLOR, getColor());
APPEND_ENTITY_PROPERTY(PROP_ALPHA, getAlpha());
withReadLock([&] {
_pulseProperties.appendSubclassData(packetData, params, entityTreeElementExtraEncodeData, requestedProperties,
propertyFlags, propertiesDidntFit, propertyCount, appendState);
});
APPEND_ENTITY_PROPERTY(PROP_TEXTURES, getTextures());
APPEND_ENTITY_PROPERTY(PROP_MAX_PARTICLES, getMaxParticles());
@ -786,4 +805,10 @@ particle::Properties ParticleEffectEntityItem::getParticleProperties() const {
}
return result;
}
PulsePropertyGroup ParticleEffectEntityItem::getPulseProperties() const {
return resultWithReadLock<PulsePropertyGroup>([&] {
return _pulseProperties;
});
}

View file

@ -16,6 +16,7 @@
#include "EntityItem.h"
#include "ColorUtils.h"
#include "PulsePropertyGroup.h"
namespace particle {
static const float SCRIPT_MAXIMUM_PI = 3.1416f; // Round up so that reasonable property values work
@ -341,9 +342,11 @@ public:
virtual bool supportsDetailedIntersection() const override { return false; }
particle::Properties getParticleProperties() const;
PulsePropertyGroup getPulseProperties() const;
protected:
particle::Properties _particleProperties;
PulsePropertyGroup _pulseProperties;
bool _isEmitting { true };
ShapeType _shapeType { SHAPE_TYPE_NONE };

View file

@ -0,0 +1,249 @@
//
// PulsePropertyGroup.cpp
//
// Created by Sam Gondelman on 1/15/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
//
#include "PulsePropertyGroup.h"
#include <OctreePacketData.h>
#include "EntityItemProperties.h"
#include "EntityItemPropertiesMacros.h"
QHash<QString, PulseMode> stringToPulseModeLookup;
void addPulseMode(PulseMode mode) {
stringToPulseModeLookup[PulseModeHelpers::getNameForPulseMode(mode)] = mode;
}
void buildStringToPulseModeLookup() {
addPulseMode(PulseMode::NONE);
addPulseMode(PulseMode::IN_PHASE);
addPulseMode(PulseMode::OUT_PHASE);
}
QString PulsePropertyGroup::getColorModeAsString() const {
return PulseModeHelpers::getNameForPulseMode(_colorMode);
}
void PulsePropertyGroup::setColorModeFromString(const QString& pulseMode) {
if (stringToPulseModeLookup.empty()) {
buildStringToPulseModeLookup();
}
auto pulseModeItr = stringToPulseModeLookup.find(pulseMode.toLower());
if (pulseModeItr != stringToPulseModeLookup.end()) {
_colorMode = pulseModeItr.value();
_colorModeChanged = true;
}
}
QString PulsePropertyGroup::getAlphaModeAsString() const {
return PulseModeHelpers::getNameForPulseMode(_alphaMode);
}
void PulsePropertyGroup::setAlphaModeFromString(const QString& pulseMode) {
if (stringToPulseModeLookup.empty()) {
buildStringToPulseModeLookup();
}
auto pulseModeItr = stringToPulseModeLookup.find(pulseMode.toLower());
if (pulseModeItr != stringToPulseModeLookup.end()) {
_alphaMode = pulseModeItr.value();
_alphaModeChanged = true;
}
}
void PulsePropertyGroup::copyToScriptValue(const EntityPropertyFlags& desiredProperties, QScriptValue& properties,
QScriptEngine* engine, bool skipDefaults,
EntityItemProperties& defaultEntityProperties) const {
COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_PULSE_MIN, Pulse, pulse, Min, min);
COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_PULSE_MAX, Pulse, pulse, Max, max);
COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_PULSE_PERIOD, Pulse, pulse, Period, period);
COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_PULSE_COLOR_MODE, Pulse, pulse, ColorMode, colorMode, getColorModeAsString);
COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_PULSE_ALPHA_MODE, Pulse, pulse, AlphaMode, alphaMode, getAlphaModeAsString);
}
void PulsePropertyGroup::copyFromScriptValue(const QScriptValue& object, bool& _defaultSettings) {
COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(pulse, min, float, setMin);
COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(pulse, max, float, setMax);
COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(pulse, period, float, setPeriod);
COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE_ENUM(pulse, colorMode, ColorMode);
COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE_ENUM(pulse, alphaMode, AlphaMode);
}
void PulsePropertyGroup::merge(const PulsePropertyGroup& other) {
COPY_PROPERTY_IF_CHANGED(min);
COPY_PROPERTY_IF_CHANGED(max);
COPY_PROPERTY_IF_CHANGED(period);
COPY_PROPERTY_IF_CHANGED(colorMode);
COPY_PROPERTY_IF_CHANGED(alphaMode);
}
void PulsePropertyGroup::debugDump() const {
qCDebug(entities) << " PulsePropertyGroup: ---------------------------------------------";
qCDebug(entities) << " _min:" << _min;
qCDebug(entities) << " _max:" << _max;
qCDebug(entities) << " _period:" << _period;
qCDebug(entities) << " _colorMode:" << getColorModeAsString();
qCDebug(entities) << " _alphaMode:" << getAlphaModeAsString();
}
void PulsePropertyGroup::listChangedProperties(QList<QString>& out) {
if (minChanged()) {
out << "pulse-min";
}
if (maxChanged()) {
out << "pulse-max";
}
if (periodChanged()) {
out << "pulse-period";
}
if (colorModeChanged()) {
out << "pulse-colorMode";
}
if (alphaModeChanged()) {
out << "pulse-alphaMode";
}
}
bool PulsePropertyGroup::appendToEditPacket(OctreePacketData* packetData,
EntityPropertyFlags& requestedProperties,
EntityPropertyFlags& propertyFlags,
EntityPropertyFlags& propertiesDidntFit,
int& propertyCount,
OctreeElement::AppendState& appendState) const {
bool successPropertyFits = true;
APPEND_ENTITY_PROPERTY(PROP_PULSE_MIN, getMin());
APPEND_ENTITY_PROPERTY(PROP_PULSE_MAX, getMax());
APPEND_ENTITY_PROPERTY(PROP_PULSE_PERIOD, getPeriod());
APPEND_ENTITY_PROPERTY(PROP_PULSE_COLOR_MODE, (uint32_t)getColorMode());
APPEND_ENTITY_PROPERTY(PROP_PULSE_ALPHA_MODE, (uint32_t)getAlphaMode());
return true;
}
bool PulsePropertyGroup::decodeFromEditPacket(EntityPropertyFlags& propertyFlags,
const unsigned char*& dataAt , int& processedBytes) {
int bytesRead = 0;
bool overwriteLocalData = true;
bool somethingChanged = false;
READ_ENTITY_PROPERTY(PROP_PULSE_MIN, float, setMin);
READ_ENTITY_PROPERTY(PROP_PULSE_MAX, float, setMax);
READ_ENTITY_PROPERTY(PROP_PULSE_PERIOD, float, setPeriod);
READ_ENTITY_PROPERTY(PROP_PULSE_COLOR_MODE, PulseMode, setColorMode);
READ_ENTITY_PROPERTY(PROP_PULSE_ALPHA_MODE, PulseMode, setAlphaMode);
DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_PULSE_MIN, Min);
DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_PULSE_MAX, Max);
DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_PULSE_PERIOD, Period);
DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_PULSE_COLOR_MODE, ColorMode);
DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_PULSE_ALPHA_MODE, AlphaMode);
processedBytes += bytesRead;
Q_UNUSED(somethingChanged);
return true;
}
void PulsePropertyGroup::markAllChanged() {
_minChanged = true;
_maxChanged = true;
_periodChanged = true;
_colorModeChanged = true;
_alphaModeChanged = true;
}
EntityPropertyFlags PulsePropertyGroup::getChangedProperties() const {
EntityPropertyFlags changedProperties;
CHECK_PROPERTY_CHANGE(PROP_PULSE_MIN, min);
CHECK_PROPERTY_CHANGE(PROP_PULSE_MAX, max);
CHECK_PROPERTY_CHANGE(PROP_PULSE_PERIOD, period);
CHECK_PROPERTY_CHANGE(PROP_PULSE_COLOR_MODE, colorMode);
CHECK_PROPERTY_CHANGE(PROP_PULSE_ALPHA_MODE, alphaMode);
return changedProperties;
}
void PulsePropertyGroup::getProperties(EntityItemProperties& properties) const {
COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Pulse, Min, getMin);
COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Pulse, Max, getMax);
COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Pulse, Period, getPeriod);
COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Pulse, ColorMode, getColorMode);
COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Pulse, AlphaMode, getAlphaMode);
}
bool PulsePropertyGroup::setProperties(const EntityItemProperties& properties) {
bool somethingChanged = false;
SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Pulse, Min, min, setMin);
SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Pulse, Max, max, setMax);
SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Pulse, Period, period, setPeriod);
SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Pulse, ColorMode, colorMode, setColorMode);
SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Pulse, AlphaMode, alphaMode, setAlphaMode);
return somethingChanged;
}
EntityPropertyFlags PulsePropertyGroup::getEntityProperties(EncodeBitstreamParams& params) const {
EntityPropertyFlags requestedProperties;
requestedProperties += PROP_PULSE_MIN;
requestedProperties += PROP_PULSE_MAX;
requestedProperties += PROP_PULSE_PERIOD;
requestedProperties += PROP_PULSE_COLOR_MODE;
requestedProperties += PROP_PULSE_ALPHA_MODE;
return requestedProperties;
}
void PulsePropertyGroup::appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params,
EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData,
EntityPropertyFlags& requestedProperties,
EntityPropertyFlags& propertyFlags,
EntityPropertyFlags& propertiesDidntFit,
int& propertyCount,
OctreeElement::AppendState& appendState) const {
bool successPropertyFits = true;
APPEND_ENTITY_PROPERTY(PROP_PULSE_MIN, getMin());
APPEND_ENTITY_PROPERTY(PROP_PULSE_MAX, getMax());
APPEND_ENTITY_PROPERTY(PROP_PULSE_PERIOD, getPeriod());
APPEND_ENTITY_PROPERTY(PROP_PULSE_COLOR_MODE, (uint32_t)getColorMode());
APPEND_ENTITY_PROPERTY(PROP_PULSE_ALPHA_MODE, (uint32_t)getAlphaMode());
}
int PulsePropertyGroup::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead,
ReadBitstreamToTreeParams& args,
EntityPropertyFlags& propertyFlags, bool overwriteLocalData,
bool& somethingChanged) {
int bytesRead = 0;
const unsigned char* dataAt = data;
READ_ENTITY_PROPERTY(PROP_PULSE_MIN, float, setMin);
READ_ENTITY_PROPERTY(PROP_PULSE_MAX, float, setMax);
READ_ENTITY_PROPERTY(PROP_PULSE_PERIOD, float, setPeriod);
READ_ENTITY_PROPERTY(PROP_PULSE_COLOR_MODE, PulseMode, setColorMode);
READ_ENTITY_PROPERTY(PROP_PULSE_ALPHA_MODE, PulseMode, setAlphaMode);
return bytesRead;
}
bool PulsePropertyGroup::operator==(const PulsePropertyGroup& a) const {
return (a._min == _min) &&
(a._max == _max) &&
(a._period == _period) &&
(a._colorMode == _colorMode) &&
(a._alphaMode == _alphaMode);
}

View file

@ -0,0 +1,99 @@
//
// PulsePropertyGroup.h
//
// Created by Sam Gondelman on 1/15/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
//
#ifndef hifi_PulsePropertyGroup_h
#define hifi_PulsePropertyGroup_h
#include <stdint.h>
#include <QtScript/QScriptEngine>
#include <PulseMode.h>
#include "PropertyGroup.h"
#include "EntityItemPropertiesMacros.h"
class EntityItemProperties;
class EncodeBitstreamParams;
class OctreePacketData;
class ReadBitstreamToTreeParams;
/**jsdoc
* Pulse is defined by the following properties.
* @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
* <code>min</code> to <code>max</code>, then <code>max</code> to <code>min</code> in one period.
* @property {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"
* the alpha is pulsed out of phase with the pulse period.
*/
class PulsePropertyGroup : public PropertyGroup {
public:
// EntityItemProperty related helpers
virtual void copyToScriptValue(const EntityPropertyFlags& desiredProperties, QScriptValue& properties,
QScriptEngine* engine, bool skipDefaults,
EntityItemProperties& defaultEntityProperties) const override;
virtual void copyFromScriptValue(const QScriptValue& object, bool& _defaultSettings) override;
void merge(const PulsePropertyGroup& other);
virtual void debugDump() const override;
virtual void listChangedProperties(QList<QString>& out) override;
virtual bool appendToEditPacket(OctreePacketData* packetData,
EntityPropertyFlags& requestedProperties,
EntityPropertyFlags& propertyFlags,
EntityPropertyFlags& propertiesDidntFit,
int& propertyCount,
OctreeElement::AppendState& appendState) const override;
virtual bool decodeFromEditPacket(EntityPropertyFlags& propertyFlags,
const unsigned char*& dataAt, int& processedBytes) override;
virtual void markAllChanged() override;
virtual EntityPropertyFlags getChangedProperties() const override;
// EntityItem related helpers
// methods for getting/setting all properties of an entity
virtual void getProperties(EntityItemProperties& propertiesOut) const override;
// returns true if something changed
virtual bool setProperties(const EntityItemProperties& properties) override;
virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override;
virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params,
EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData,
EntityPropertyFlags& requestedProperties,
EntityPropertyFlags& propertyFlags,
EntityPropertyFlags& propertiesDidntFit,
int& propertyCount,
OctreeElement::AppendState& appendState) const override;
virtual int readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead,
ReadBitstreamToTreeParams& args,
EntityPropertyFlags& propertyFlags, bool overwriteLocalData,
bool& somethingChanged) override;
bool operator==(const PulsePropertyGroup& a) const;
bool operator!=(const PulsePropertyGroup& a) const { return !(*this == a); }
DEFINE_PROPERTY(PROP_PULSE_MIN, Min, min, float, 0.0f);
DEFINE_PROPERTY(PROP_PULSE_MAX, Max, max, float, 1.0f);
DEFINE_PROPERTY(PROP_PULSE_PERIOD, Period, period, float, 1.0f);
DEFINE_PROPERTY_REF_ENUM(PROP_PULSE_COLOR_MODE, ColorMode, colorMode, PulseMode, PulseMode::NONE);
DEFINE_PROPERTY_REF_ENUM(PROP_PULSE_ALPHA_MODE, AlphaMode, alphaMode, PulseMode, PulseMode::NONE);
};
#endif // hifi_PulsePropertyGroup_h

View file

@ -119,6 +119,9 @@ EntityItemProperties ShapeEntityItem::getProperties(const EntityPropertyFlags& d
COPY_ENTITY_PROPERTY_TO_PROPERTIES(color, getColor);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(alpha, getAlpha);
withReadLock([&] {
_pulseProperties.getProperties(properties);
});
properties.setShape(entity::stringFromShape(getShape()));
properties._shapeChanged = false;
@ -159,6 +162,10 @@ bool ShapeEntityItem::setProperties(const EntityItemProperties& properties) {
SET_ENTITY_PROPERTY_FROM_PROPERTIES(color, setColor);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(alpha, setAlpha);
withWriteLock([&] {
bool pulsePropertiesChanged = _pulseProperties.setProperties(properties);
somethingChanged |= pulsePropertiesChanged;
});
SET_ENTITY_PROPERTY_FROM_PROPERTIES(shape, setShape);
if (somethingChanged) {
@ -184,6 +191,13 @@ int ShapeEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data,
READ_ENTITY_PROPERTY(PROP_COLOR, glm::u8vec3, setColor);
READ_ENTITY_PROPERTY(PROP_ALPHA, float, setAlpha);
withWriteLock([&] {
int bytesFromPulse = _pulseProperties.readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args,
propertyFlags, overwriteLocalData,
somethingChanged);
bytesRead += bytesFromPulse;
dataAt += bytesFromPulse;
});
READ_ENTITY_PROPERTY(PROP_SHAPE, QString, setShape);
return bytesRead;
@ -193,12 +207,13 @@ EntityPropertyFlags ShapeEntityItem::getEntityProperties(EncodeBitstreamParams&
EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params);
requestedProperties += PROP_COLOR;
requestedProperties += PROP_ALPHA;
requestedProperties += _pulseProperties.getEntityProperties(params);
requestedProperties += PROP_SHAPE;
return requestedProperties;
}
void ShapeEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params,
EntityTreeElementExtraEncodeDataPointer modelTreeElementExtraEncodeData,
EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData,
EntityPropertyFlags& requestedProperties,
EntityPropertyFlags& propertyFlags,
EntityPropertyFlags& propertiesDidntFit,
@ -208,6 +223,10 @@ void ShapeEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBit
bool successPropertyFits = true;
APPEND_ENTITY_PROPERTY(PROP_COLOR, getColor());
APPEND_ENTITY_PROPERTY(PROP_ALPHA, getAlpha());
withReadLock([&] {
_pulseProperties.appendSubclassData(packetData, params, entityTreeElementExtraEncodeData, requestedProperties,
propertyFlags, propertiesDidntFit, propertyCount, appendState);
});
APPEND_ENTITY_PROPERTY(PROP_SHAPE, entity::stringFromShape(getShape()));
}
@ -416,4 +435,10 @@ void ShapeEntityItem::computeShapeInfo(ShapeInfo& info) {
// This value specifies how the shape should be treated by physics calculations.
ShapeType ShapeEntityItem::getShapeType() const {
return _collisionShapeType;
}
PulsePropertyGroup ShapeEntityItem::getPulseProperties() const {
return resultWithReadLock<PulsePropertyGroup>([&] {
return _pulseProperties;
});
}

View file

@ -11,6 +11,8 @@
#include "EntityItem.h"
#include "PulsePropertyGroup.h"
namespace entity {
enum Shape {
Triangle,
@ -58,7 +60,7 @@ public:
EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override;
void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params,
EntityTreeElementExtraEncodeDataPointer modelTreeElementExtraEncodeData,
EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData,
EntityPropertyFlags& requestedProperties,
EntityPropertyFlags& propertyFlags,
EntityPropertyFlags& propertiesDidntFit,
@ -99,9 +101,12 @@ public:
virtual void computeShapeInfo(ShapeInfo& info) override;
virtual ShapeType getShapeType() const override;
PulsePropertyGroup getPulseProperties() const;
protected:
glm::u8vec3 _color;
float _alpha { 1.0f };
PulsePropertyGroup _pulseProperties;
entity::Shape _shape { entity::Shape::Sphere };
//! This is SHAPE_TYPE_ELLIPSOID rather than SHAPE_TYPE_NONE to maintain

View file

@ -49,6 +49,10 @@ void TextEntityItem::setUnscaledDimensions(const glm::vec3& value) {
EntityItemProperties TextEntityItem::getProperties(const EntityPropertyFlags& desiredProperties, bool allowEmptyDesiredProperties) const {
EntityItemProperties properties = EntityItem::getProperties(desiredProperties, allowEmptyDesiredProperties); // get the properties from our base class
withReadLock([&] {
_pulseProperties.getProperties(properties);
});
COPY_ENTITY_PROPERTY_TO_PROPERTIES(text, getText);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(lineHeight, getLineHeight);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(textColor, getTextColor);
@ -67,6 +71,11 @@ bool TextEntityItem::setProperties(const EntityItemProperties& properties) {
bool somethingChanged = false;
somethingChanged = EntityItem::setProperties(properties); // set the properties in our base class
withWriteLock([&] {
bool pulsePropertiesChanged = _pulseProperties.setProperties(properties);
somethingChanged |= pulsePropertiesChanged;
});
SET_ENTITY_PROPERTY_FROM_PROPERTIES(text, setText);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(lineHeight, setLineHeight);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(textColor, setTextColor);
@ -101,6 +110,14 @@ int TextEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data,
int bytesRead = 0;
const unsigned char* dataAt = data;
withWriteLock([&] {
int bytesFromPulse = _pulseProperties.readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args,
propertyFlags, overwriteLocalData,
somethingChanged);
bytesRead += bytesFromPulse;
dataAt += bytesFromPulse;
});
READ_ENTITY_PROPERTY(PROP_TEXT, QString, setText);
READ_ENTITY_PROPERTY(PROP_LINE_HEIGHT, float, setLineHeight);
READ_ENTITY_PROPERTY(PROP_TEXT_COLOR, glm::u8vec3, setTextColor);
@ -118,6 +135,8 @@ int TextEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data,
EntityPropertyFlags TextEntityItem::getEntityProperties(EncodeBitstreamParams& params) const {
EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params);
requestedProperties += _pulseProperties.getEntityProperties(params);
requestedProperties += PROP_TEXT;
requestedProperties += PROP_LINE_HEIGHT;
requestedProperties += PROP_TEXT_COLOR;
@ -129,11 +148,12 @@ EntityPropertyFlags TextEntityItem::getEntityProperties(EncodeBitstreamParams& p
requestedProperties += PROP_RIGHT_MARGIN;
requestedProperties += PROP_TOP_MARGIN;
requestedProperties += PROP_BOTTOM_MARGIN;
return requestedProperties;
}
void TextEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params,
EntityTreeElementExtraEncodeDataPointer modelTreeElementExtraEncodeData,
EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData,
EntityPropertyFlags& requestedProperties,
EntityPropertyFlags& propertyFlags,
EntityPropertyFlags& propertiesDidntFit,
@ -142,6 +162,11 @@ void TextEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBits
bool successPropertyFits = true;
withReadLock([&] {
_pulseProperties.appendSubclassData(packetData, params, entityTreeElementExtraEncodeData, requestedProperties,
propertyFlags, propertiesDidntFit, propertyCount, appendState);
});
APPEND_ENTITY_PROPERTY(PROP_TEXT, getText());
APPEND_ENTITY_PROPERTY(PROP_LINE_HEIGHT, getLineHeight());
APPEND_ENTITY_PROPERTY(PROP_TEXT_COLOR, getTextColor());
@ -345,3 +370,9 @@ float TextEntityItem::getBottomMargin() const {
return _bottomMargin;
});
}
PulsePropertyGroup TextEntityItem::getPulseProperties() const {
return resultWithReadLock<PulsePropertyGroup>([&] {
return _pulseProperties;
});
}

View file

@ -14,6 +14,8 @@
#include "EntityItem.h"
#include "PulsePropertyGroup.h"
class TextEntityItem : public EntityItem {
public:
static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties);
@ -33,7 +35,7 @@ public:
virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override;
virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params,
EntityTreeElementExtraEncodeDataPointer modelTreeElementExtraEncodeData,
EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData,
EntityPropertyFlags& requestedProperties,
EntityPropertyFlags& propertyFlags,
EntityPropertyFlags& propertiesDidntFit,
@ -94,6 +96,8 @@ public:
float getBottomMargin() const;
void setBottomMargin(float value);
PulsePropertyGroup getPulseProperties() const;
private:
QString _text;
float _lineHeight;
@ -101,6 +105,7 @@ private:
float _textAlpha;
glm::u8vec3 _backgroundColor;
float _backgroundAlpha;
PulsePropertyGroup _pulseProperties;
BillboardMode _billboardMode;
float _leftMargin;
float _rightMargin;

View file

@ -45,6 +45,9 @@ EntityItemProperties WebEntityItem::getProperties(const EntityPropertyFlags& des
COPY_ENTITY_PROPERTY_TO_PROPERTIES(color, getColor);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(alpha, getAlpha);
withReadLock([&] {
_pulseProperties.getProperties(properties);
});
COPY_ENTITY_PROPERTY_TO_PROPERTIES(sourceUrl, getSourceUrl);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(dpi, getDPI);
@ -60,6 +63,10 @@ bool WebEntityItem::setProperties(const EntityItemProperties& properties) {
SET_ENTITY_PROPERTY_FROM_PROPERTIES(color, setColor);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(alpha, setAlpha);
withWriteLock([&] {
bool pulsePropertiesChanged = _pulseProperties.setProperties(properties);
somethingChanged |= pulsePropertiesChanged;
});
SET_ENTITY_PROPERTY_FROM_PROPERTIES(sourceUrl, setSourceUrl);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(dpi, setDPI);
@ -91,6 +98,13 @@ int WebEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, i
READ_ENTITY_PROPERTY(PROP_COLOR, glm::u8vec3, setColor);
READ_ENTITY_PROPERTY(PROP_ALPHA, float, setAlpha);
withWriteLock([&] {
int bytesFromPulse = _pulseProperties.readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args,
propertyFlags, overwriteLocalData,
somethingChanged);
bytesRead += bytesFromPulse;
dataAt += bytesFromPulse;
});
READ_ENTITY_PROPERTY(PROP_SOURCE_URL, QString, setSourceUrl);
READ_ENTITY_PROPERTY(PROP_DPI, uint16_t, setDPI);
@ -105,6 +119,7 @@ EntityPropertyFlags WebEntityItem::getEntityProperties(EncodeBitstreamParams& pa
EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params);
requestedProperties += PROP_COLOR;
requestedProperties += PROP_ALPHA;
requestedProperties += _pulseProperties.getEntityProperties(params);
requestedProperties += PROP_SOURCE_URL;
requestedProperties += PROP_DPI;
@ -115,7 +130,7 @@ EntityPropertyFlags WebEntityItem::getEntityProperties(EncodeBitstreamParams& pa
}
void WebEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params,
EntityTreeElementExtraEncodeDataPointer modelTreeElementExtraEncodeData,
EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData,
EntityPropertyFlags& requestedProperties,
EntityPropertyFlags& propertyFlags,
EntityPropertyFlags& propertiesDidntFit,
@ -125,6 +140,10 @@ void WebEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBitst
bool successPropertyFits = true;
APPEND_ENTITY_PROPERTY(PROP_COLOR, getColor());
APPEND_ENTITY_PROPERTY(PROP_ALPHA, getAlpha());
withReadLock([&] {
_pulseProperties.appendSubclassData(packetData, params, entityTreeElementExtraEncodeData, requestedProperties,
propertyFlags, propertiesDidntFit, propertyCount, appendState);
});
APPEND_ENTITY_PROPERTY(PROP_SOURCE_URL, getSourceUrl());
APPEND_ENTITY_PROPERTY(PROP_DPI, getDPI());
@ -285,4 +304,10 @@ WebInputMode WebEntityItem::getInputMode() const {
return resultWithReadLock<WebInputMode>([&] {
return _inputMode;
});
}
PulsePropertyGroup WebEntityItem::getPulseProperties() const {
return resultWithReadLock<PulsePropertyGroup>([&] {
return _pulseProperties;
});
}

View file

@ -11,6 +11,8 @@
#include "EntityItem.h"
#include "PulsePropertyGroup.h"
class WebEntityItem : public EntityItem {
public:
static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties);
@ -30,7 +32,7 @@ public:
virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override;
virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params,
EntityTreeElementExtraEncodeDataPointer modelTreeElementExtraEncodeData,
EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData,
EntityPropertyFlags& requestedProperties,
EntityPropertyFlags& propertyFlags,
EntityPropertyFlags& propertiesDidntFit,
@ -75,9 +77,12 @@ public:
void setInputMode(const WebInputMode& value);
WebInputMode getInputMode() const;
PulsePropertyGroup getPulseProperties() const;
protected:
glm::u8vec3 _color;
float _alpha { 1.0f };
PulsePropertyGroup _pulseProperties;
QString _sourceUrl;
uint16_t _dpi;

View file

@ -755,17 +755,17 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr
} else if (subobject.name == "Texture_Alpha_Source" && subobject.properties.length() >= TEXTURE_ALPHA_SOURCE_MIN_SIZE) {
tex.assign<uint8_t>(tex.alphaSource, subobject.properties.at(0).value<int>());
} else if (subobject.name == "ModelUVTranslation" && subobject.properties.length() >= MODEL_UV_TRANSLATION_MIN_SIZE) {
tex.assign(tex.UVTranslation, glm::vec2(subobject.properties.at(0).value<double>(),
subobject.properties.at(1).value<double>()));
auto newTranslation = glm::vec3(subobject.properties.at(0).value<double>(), subobject.properties.at(1).value<double>(), 0.0);
tex.assign(tex.translation, tex.translation + newTranslation);
} else if (subobject.name == "ModelUVScaling" && subobject.properties.length() >= MODEL_UV_SCALING_MIN_SIZE) {
tex.assign(tex.UVScaling, glm::vec2(subobject.properties.at(0).value<double>(),
subobject.properties.at(1).value<double>()));
if (tex.UVScaling.x == 0.0f) {
tex.UVScaling.x = 1.0f;
auto newScaling = glm::vec3(subobject.properties.at(0).value<double>(), subobject.properties.at(1).value<double>(), 1.0);
if (newScaling.x == 0.0f) {
newScaling.x = 1.0f;
}
if (tex.UVScaling.y == 0.0f) {
tex.UVScaling.y = 1.0f;
if (newScaling.y == 0.0f) {
newScaling.y = 1.0f;
}
tex.assign(tex.scaling, tex.scaling * newScaling);
} else if (subobject.name == "Cropping" && subobject.properties.length() >= CROPPING_MIN_SIZE) {
tex.assign(tex.cropping, glm::vec4(subobject.properties.at(0).value<int>(),
subobject.properties.at(1).value<int>(),
@ -793,20 +793,21 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr
} else if (property.properties.at(0) == USE_MATERIAL) {
tex.assign<bool>(tex.useMaterial, property.properties.at(index).value<int>());
} else if (property.properties.at(0) == TRANSLATION) {
tex.assign(tex.translation, getVec3(property.properties, index));
tex.assign(tex.translation, tex.translation + getVec3(property.properties, index));
} else if (property.properties.at(0) == ROTATION) {
tex.assign(tex.rotation, getVec3(property.properties, index));
} else if (property.properties.at(0) == SCALING) {
tex.assign(tex.scaling, getVec3(property.properties, index));
if (tex.scaling.x == 0.0f) {
tex.scaling.x = 1.0f;
auto newScaling = getVec3(property.properties, index);
if (newScaling.x == 0.0f) {
newScaling.x = 1.0f;
}
if (tex.scaling.y == 0.0f) {
tex.scaling.y = 1.0f;
if (newScaling.y == 0.0f) {
newScaling.y = 1.0f;
}
if (tex.scaling.z == 0.0f) {
tex.scaling.z = 1.0f;
if (newScaling.z == 0.0f) {
newScaling.z = 1.0f;
}
tex.assign(tex.scaling, tex.scaling * newScaling);
}
#if defined(DEBUG_FBXSERIALIZER)
else {
@ -851,6 +852,7 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr
}
} else if (object.name == "Material") {
HFMMaterial material;
MaterialParam materialParam;
material.name = (object.properties.at(1).toString());
foreach (const FBXNode& subobject, object.children) {
bool properties = false;
@ -895,6 +897,10 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr
static const QVariant MAYA_EMISSIVE_INTENSITY = QByteArray("Maya|emissive_intensity");
static const QVariant MAYA_USE_EMISSIVE_MAP = QByteArray("Maya|use_emissive_map");
static const QVariant MAYA_USE_AO_MAP = QByteArray("Maya|use_ao_map");
static const QVariant MAYA_UV_SCALE = QByteArray("Maya|uv_scale");
static const QVariant MAYA_UV_OFFSET = QByteArray("Maya|uv_offset");
static const int MAYA_UV_OFFSET_PROPERTY_LENGTH = 6;
static const int MAYA_UV_SCALE_PROPERTY_LENGTH = 6;
@ -983,6 +989,27 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr
material.isPBSMaterial = true;
material.useOcclusionMap = (bool)property.properties.at(index).value<double>();
} else if (property.properties.at(0) == MAYA_UV_SCALE) {
if (property.properties.size() == MAYA_UV_SCALE_PROPERTY_LENGTH) {
// properties: { "Maya|uv_scale", "Vector2D", "Vector2", nothing, double, double }
glm::vec3 scale = glm::vec3(property.properties.at(4).value<double>(), property.properties.at(5).value<double>(), 1.0);
if (scale.x == 0.0f) {
scale.x = 1.0f;
}
if (scale.y == 0.0f) {
scale.y = 1.0f;
}
if (scale.z == 0.0f) {
scale.z = 1.0f;
}
materialParam.scaling *= scale;
}
} else if (property.properties.at(0) == MAYA_UV_OFFSET) {
if (property.properties.size() == MAYA_UV_OFFSET_PROPERTY_LENGTH) {
// properties: { "Maya|uv_offset", "Vector2D", "Vector2", nothing, double, double }
glm::vec3 translation = glm::vec3(property.properties.at(4).value<double>(), property.properties.at(5).value<double>(), 1.0);
materialParam.translation += translation;
}
} else {
const QString propname = property.properties.at(0).toString();
unknowns.push_back(propname.toStdString());
@ -1004,6 +1031,7 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr
}
material.materialID = getID(object.properties);
_hfmMaterials.insert(material.materialID, material);
_materialParams.insert(material.materialID, materialParam);
} else if (object.name == "NodeAttribute") {
@ -1437,7 +1465,9 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr
materialIndex++;
} else if (_textureFilenames.contains(childID)) {
HFMTexture texture = getTexture(childID);
// NOTE (Sabrina 2019/01/11): getTextures now takes in the materialID as a second parameter, because FBX material nodes can sometimes have uv transform information (ex: "Maya|uv_scale")
// I'm leaving the second parameter blank right now as this code may never be used.
HFMTexture texture = getTexture(childID, "");
for (int j = 0; j < extracted.partMaterialTextures.size(); j++) {
int partTexture = extracted.partMaterialTextures.at(j).second;
if (partTexture == textureIndex && !(partTexture == 0 && materialsHaveTextures)) {

View file

@ -37,8 +37,6 @@ class FBXNode;
class TextureParam {
public:
glm::vec2 UVTranslation;
glm::vec2 UVScaling;
glm::vec4 cropping;
QString UVSet;
@ -63,8 +61,6 @@ public:
bool isDefault;
TextureParam() :
UVTranslation(0.0f),
UVScaling(1.0f),
cropping(0.0f),
UVSet("map1"),
translation(0.0f),
@ -77,8 +73,6 @@ public:
{}
TextureParam(const TextureParam& src) :
UVTranslation(src.UVTranslation),
UVScaling(src.UVScaling),
cropping(src.cropping),
UVSet(src.UVSet),
translation(src.translation),
@ -92,6 +86,22 @@ public:
};
class MaterialParam {
public:
glm::vec3 translation;
glm::vec3 scaling;
MaterialParam() :
translation(0.0),
scaling(1.0)
{}
MaterialParam(const MaterialParam& src) :
translation(src.translation),
scaling(src.scaling)
{}
};
class ExtractedMesh;
class FBXSerializer : public HFMSerializer {
@ -114,7 +124,7 @@ public:
static ExtractedMesh extractMesh(const FBXNode& object, unsigned int& meshIndex, bool deduplicate = true);
QHash<QString, ExtractedMesh> meshes;
HFMTexture getTexture(const QString& textureID);
HFMTexture getTexture(const QString& textureID, const QString& materialID);
QHash<QString, QString> _textureNames;
// Hashes the original RelativeFilename of textures
@ -141,6 +151,7 @@ public:
QHash<QString, QString> occlusionTextures;
QHash<QString, HFMMaterial> _hfmMaterials;
QHash<QString, MaterialParam> _materialParams;
void consolidateHFMMaterials(const QVariantHash& mapping);

View file

@ -27,7 +27,7 @@
#include <hfm/ModelFormatLogging.h>
HFMTexture FBXSerializer::getTexture(const QString& textureID) {
HFMTexture FBXSerializer::getTexture(const QString& textureID, const QString& materialID) {
HFMTexture texture;
const QByteArray& filepath = _textureFilepaths.value(textureID);
texture.content = _textureContent.value(filepath);
@ -45,8 +45,8 @@ HFMTexture FBXSerializer::getTexture(const QString& textureID) {
if (_textureParams.contains(textureID)) {
auto p = _textureParams.value(textureID);
texture.transform.setTranslation(p.translation);
texture.transform.setRotation(glm::quat(glm::radians(p.rotation)));
texture.transform.postTranslate(p.translation);
texture.transform.postRotate(glm::quat(glm::radians(p.rotation)));
auto scaling = p.scaling;
// Protect from bad scaling which should never happen
@ -59,13 +59,19 @@ HFMTexture FBXSerializer::getTexture(const QString& textureID) {
if (scaling.z == 0.0f) {
scaling.z = 1.0f;
}
texture.transform.setScale(scaling);
texture.transform.postScale(scaling);
if ((p.UVSet != "map1") && (p.UVSet != "UVSet0")) {
texture.texcoordSet = 1;
}
texture.texcoordSetName = p.UVSet;
}
auto materialParamItr = _materialParams.find(materialID);
if (materialParamItr != _materialParams.end()) {
auto& materialParam = materialParamItr.value();
texture.transform.postTranslate(materialParam.translation);
texture.transform.postScale(materialParam.scaling);
}
return texture;
}
@ -102,12 +108,12 @@ void FBXSerializer::consolidateHFMMaterials(const QVariantHash& mapping) {
material.diffuseFactor = 1.0;
}
diffuseTexture = getTexture(diffuseTextureID);
diffuseTexture = getTexture(diffuseTextureID, material.materialID);
// FBX files generated by 3DSMax have an intermediate texture parent, apparently
foreach(const QString& childTextureID, _connectionChildMap.values(diffuseTextureID)) {
if (_textureFilenames.contains(childTextureID)) {
diffuseTexture = getTexture(diffuseTextureID);
diffuseTexture = getTexture(diffuseTextureID, material.materialID);
}
}
@ -122,7 +128,7 @@ void FBXSerializer::consolidateHFMMaterials(const QVariantHash& mapping) {
transparentTextureID = diffuseTextureID;
}
if (!transparentTextureID.isNull()) {
transparentTexture = getTexture(transparentTextureID);
transparentTexture = getTexture(transparentTextureID, material.materialID);
material.opacityTexture = transparentTexture;
detectDifferentUVs |= (transparentTexture.texcoordSet != 0) || (!transparentTexture.transform.isIdentity());
}
@ -131,13 +137,13 @@ void FBXSerializer::consolidateHFMMaterials(const QVariantHash& mapping) {
QString bumpTextureID = bumpTextures.value(material.materialID);
QString normalTextureID = normalTextures.value(material.materialID);
if (!normalTextureID.isNull()) {
normalTexture = getTexture(normalTextureID);
normalTexture = getTexture(normalTextureID, material.materialID);
normalTexture.isBumpmap = false;
material.normalTexture = normalTexture;
detectDifferentUVs |= (normalTexture.texcoordSet != 0) || (!normalTexture.transform.isIdentity());
} else if (!bumpTextureID.isNull()) {
normalTexture = getTexture(bumpTextureID);
normalTexture = getTexture(bumpTextureID, material.materialID);
normalTexture.isBumpmap = true;
material.normalTexture = normalTexture;
@ -147,7 +153,7 @@ void FBXSerializer::consolidateHFMMaterials(const QVariantHash& mapping) {
HFMTexture specularTexture;
QString specularTextureID = specularTextures.value(material.materialID);
if (!specularTextureID.isNull()) {
specularTexture = getTexture(specularTextureID);
specularTexture = getTexture(specularTextureID, material.materialID);
detectDifferentUVs |= (specularTexture.texcoordSet != 0) || (!specularTexture.transform.isIdentity());
material.specularTexture = specularTexture;
}
@ -155,7 +161,7 @@ void FBXSerializer::consolidateHFMMaterials(const QVariantHash& mapping) {
HFMTexture metallicTexture;
QString metallicTextureID = metallicTextures.value(material.materialID);
if (!metallicTextureID.isNull()) {
metallicTexture = getTexture(metallicTextureID);
metallicTexture = getTexture(metallicTextureID, material.materialID);
detectDifferentUVs |= (metallicTexture.texcoordSet != 0) || (!metallicTexture.transform.isIdentity());
material.metallicTexture = metallicTexture;
}
@ -163,7 +169,7 @@ void FBXSerializer::consolidateHFMMaterials(const QVariantHash& mapping) {
HFMTexture roughnessTexture;
QString roughnessTextureID = roughnessTextures.value(material.materialID);
if (!roughnessTextureID.isNull()) {
roughnessTexture = getTexture(roughnessTextureID);
roughnessTexture = getTexture(roughnessTextureID, material.materialID);
material.roughnessTexture = roughnessTexture;
detectDifferentUVs |= (roughnessTexture.texcoordSet != 0) || (!roughnessTexture.transform.isIdentity());
}
@ -171,7 +177,7 @@ void FBXSerializer::consolidateHFMMaterials(const QVariantHash& mapping) {
HFMTexture shininessTexture;
QString shininessTextureID = shininessTextures.value(material.materialID);
if (!shininessTextureID.isNull()) {
shininessTexture = getTexture(shininessTextureID);
shininessTexture = getTexture(shininessTextureID, material.materialID);
material.glossTexture = shininessTexture;
detectDifferentUVs |= (shininessTexture.texcoordSet != 0) || (!shininessTexture.transform.isIdentity());
}
@ -179,7 +185,7 @@ void FBXSerializer::consolidateHFMMaterials(const QVariantHash& mapping) {
HFMTexture emissiveTexture;
QString emissiveTextureID = emissiveTextures.value(material.materialID);
if (!emissiveTextureID.isNull()) {
emissiveTexture = getTexture(emissiveTextureID);
emissiveTexture = getTexture(emissiveTextureID, material.materialID);
detectDifferentUVs |= (emissiveTexture.texcoordSet != 0) || (!emissiveTexture.transform.isIdentity());
material.emissiveTexture = emissiveTexture;
@ -202,7 +208,7 @@ void FBXSerializer::consolidateHFMMaterials(const QVariantHash& mapping) {
}
if (!occlusionTextureID.isNull()) {
occlusionTexture = getTexture(occlusionTextureID);
occlusionTexture = getTexture(occlusionTextureID, material.materialID);
detectDifferentUVs |= (occlusionTexture.texcoordSet != 0) || (!emissiveTexture.transform.isIdentity());
material.occlusionTexture = occlusionTexture;
}
@ -222,7 +228,7 @@ void FBXSerializer::consolidateHFMMaterials(const QVariantHash& mapping) {
}
if (_loadLightmaps && !ambientTextureID.isNull()) {
ambientTexture = getTexture(ambientTextureID);
ambientTexture = getTexture(ambientTextureID, material.materialID);
detectDifferentUVs |= (ambientTexture.texcoordSet != 0) || (!ambientTexture.transform.isIdentity());
material.lightmapTexture = ambientTexture;
material.lightmapParams = lightmapParams;

View file

@ -205,6 +205,8 @@ bool HFMModel::convexHullContains(const glm::vec3& point) const {
auto checkEachPrimitive = [=](HFMMesh& mesh, QVector<int> indices, int primitiveSize) -> bool {
// Check whether the point is "behind" all the primitives.
// But first must transform from model-frame into mesh-frame
glm::vec3 transformedPoint = glm::vec3(glm::inverse(mesh.modelTransform) * glm::vec4(point, 1.0f));
int verticesSize = mesh.vertices.size();
for (int j = 0;
j < indices.size() - 2; // -2 in case the vertices aren't the right size -- we access j + 2 below
@ -212,7 +214,7 @@ bool HFMModel::convexHullContains(const glm::vec3& point) const {
if (indices[j] < verticesSize &&
indices[j + 1] < verticesSize &&
indices[j + 2] < verticesSize &&
!isPointBehindTrianglesPlane(point,
!isPointBehindTrianglesPlane(transformedPoint,
mesh.vertices[indices[j]],
mesh.vertices[indices[j + 1]],
mesh.vertices[indices[j + 2]])) {

View file

@ -590,6 +590,29 @@ void AccountManager::requestAccessTokenWithSteam(QByteArray authSessionTicket) {
connect(requestReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(requestAccessTokenError(QNetworkReply::NetworkError)));
}
void AccountManager::requestAccessTokenWithOculus(const QString& nonce, const QString &oculusID) {
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
QNetworkRequest request;
request.setHeader(QNetworkRequest::UserAgentHeader, _userAgentGetter());
QUrl grantURL = _authURL;
grantURL.setPath("/oauth/token");
QByteArray postData;
postData.append("grant_type=password&");
postData.append("oculus_nonce=" + nonce + "&");
postData.append("oculus_id=" + oculusID + "&");
postData.append("scope=" + ACCOUNT_MANAGER_REQUESTED_SCOPE);
request.setUrl(grantURL);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
QNetworkReply* requestReply = networkAccessManager.post(request, postData);
connect(requestReply, &QNetworkReply::finished, this, &AccountManager::requestAccessTokenFinished);
connect(requestReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(requestAccessTokenError(QNetworkReply::NetworkError)));
}
void AccountManager::refreshAccessToken() {
// we can't refresh our access token if we don't have a refresh token, so check for that first

View file

@ -106,6 +106,7 @@ public:
public slots:
void requestAccessToken(const QString& login, const QString& password);
void requestAccessTokenWithSteam(QByteArray authSessionTicket);
void requestAccessTokenWithOculus(const QString& nonce, const QString& oculusID);
void requestAccessTokenWithAuthCode(const QString& authCode,
const QString& clientId,
const QString& clientSecret,

View file

@ -258,6 +258,7 @@ enum class EntityVersion : PacketVersion {
FixProtocolVersionBumpMismatch,
MigrateOverlayRenderProperties,
MissingWebEntityProperties,
PulseProperties,
// Add new versions above here
NUM_PACKET_TYPE,

43
libraries/octree/src/OctreePacketData.cpp Normal file → Executable file
View file

@ -77,8 +77,8 @@ bool OctreePacketData::append(const unsigned char* data, int length) {
_bytesAvailable -= length;
success = true;
_dirty = true;
}
}
#ifdef WANT_DEBUG
if (!success) {
qCDebug(octree) << "OctreePacketData::append(const unsigned char* data, int length) FAILING....";
@ -97,7 +97,7 @@ bool OctreePacketData::append(unsigned char byte) {
if (_bytesAvailable > 0) {
_uncompressed[_bytesInUse] = byte;
_bytesInUse++;
_bytesAvailable--;
_bytesAvailable--;
success = true;
_dirty = true;
}
@ -110,13 +110,13 @@ bool OctreePacketData::reserveBitMask() {
bool OctreePacketData::reserveBytes(int numberOfBytes) {
bool success = false;
if (_bytesAvailable >= numberOfBytes) {
_bytesReserved += numberOfBytes;
_bytesAvailable -= numberOfBytes;
success = true;
}
return success;
}
@ -188,7 +188,7 @@ bool OctreePacketData::startSubTree(const unsigned char* octcode) {
const unsigned char* OctreePacketData::getFinalizedData() {
if (!_enableCompression) {
return &_uncompressed[0];
return &_uncompressed[0];
}
if (_dirty) {
@ -197,7 +197,7 @@ const unsigned char* OctreePacketData::getFinalizedData() {
}
compressContent();
}
return &_compressed[0];
return &_compressed[0];
}
int OctreePacketData::getFinalizedSize() {
@ -223,7 +223,7 @@ void OctreePacketData::endSubTree() {
void OctreePacketData::discardSubTree() {
int bytesInSubTree = _bytesInUse - _subTreeAt;
_bytesInUse -= bytesInSubTree;
_bytesAvailable += bytesInSubTree;
_bytesAvailable += bytesInSubTree;
_subTreeAt = _bytesInUse; // should be the same actually...
_dirty = true;
@ -231,7 +231,7 @@ void OctreePacketData::discardSubTree() {
int reduceBytesOfOctalCodes = _bytesOfOctalCodes - _bytesOfOctalCodesCurrentSubTree;
_bytesOfOctalCodes = _bytesOfOctalCodesCurrentSubTree;
_totalBytesOfOctalCodes -= reduceBytesOfOctalCodes;
// if we discard the subtree then reset reserved bytes to the value when we started the subtree
_bytesReserved = _subTreeBytesReserved;
}
@ -243,7 +243,7 @@ LevelDetails OctreePacketData::startLevel() {
void OctreePacketData::discardLevel(LevelDetails key) {
int bytesInLevel = _bytesInUse - key._startIndex;
// reset statistics...
int reduceBytesOfOctalCodes = _bytesOfOctalCodes - key._bytesOfOctalCodes;
int reduceBytesOfBitMasks = _bytesOfBitMasks - key._bytesOfBitmasks;
@ -261,11 +261,11 @@ void OctreePacketData::discardLevel(LevelDetails key) {
qCDebug(octree, "discardLevel() BEFORE _dirty=%s bytesInLevel=%d _compressedBytes=%d _bytesInUse=%d",
debug::valueOf(_dirty), bytesInLevel, _compressedBytes, _bytesInUse);
}
_bytesInUse -= bytesInLevel;
_bytesAvailable += bytesInLevel;
_bytesAvailable += bytesInLevel;
_dirty = true;
// reserved bytes are reset to the value when the level started
_bytesReserved = key._bytesReservedAtStart;
@ -333,7 +333,7 @@ bool OctreePacketData::appendValue(uint8_t value) {
bool OctreePacketData::appendValue(uint16_t value) {
const unsigned char* data = (const unsigned char*)&value;
int length = sizeof(value);
bool success = append(data, length);
if (success) {
@ -506,10 +506,11 @@ bool OctreePacketData::appendValue(bool value) {
bool OctreePacketData::appendValue(const QString& string) {
// TODO: make this a ByteCountCoded leading byte
uint16_t length = string.size() + 1; // include NULL
QByteArray utf8Array = string.toUtf8();
uint16_t length = utf8Array.length(); // no NULL
bool success = appendValue(length);
if (success) {
success = appendRawData((const unsigned char*)qPrintable(string), length);
success = appendRawData((const unsigned char*)utf8Array.constData(), length);
}
return success;
}
@ -666,7 +667,7 @@ void OctreePacketData::debugContent() {
}
}
printf("\n");
qCDebug(octree, "OctreePacketData::debugContent()... UNCOMPRESSED DATA.... size=%d",_bytesInUse);
perline=0;
for (int i = 0; i < _bytesInUse; i++) {
@ -702,16 +703,16 @@ int OctreePacketData::unpackDataFromBytes(const unsigned char* dataBytes, glm::u
return sizeof(result);
}
int OctreePacketData::unpackDataFromBytes(const unsigned char* dataBytes, QString& result) {
int OctreePacketData::unpackDataFromBytes(const unsigned char* dataBytes, QString& result) {
uint16_t length;
memcpy(&length, dataBytes, sizeof(length));
dataBytes += sizeof(length);
QString value((const char*)dataBytes);
QString value = QString::fromUtf8((const char*)dataBytes, length);
result = value;
return sizeof(length) + length;
}
int OctreePacketData::unpackDataFromBytes(const unsigned char* dataBytes, QUuid& result) {
int OctreePacketData::unpackDataFromBytes(const unsigned char* dataBytes, QUuid& result) {
uint16_t length;
memcpy(&length, dataBytes, sizeof(length));
dataBytes += sizeof(length);
@ -819,4 +820,4 @@ int OctreePacketData::unpackDataFromBytes(const unsigned char* dataBytes, AACube
int OctreePacketData::unpackDataFromBytes(const unsigned char* dataBytes, QRect& result) {
memcpy(&result, dataBytes, sizeof(result));
return sizeof(result);
}
}

View file

@ -38,6 +38,7 @@
#include "RenderLayer.h"
#include "PrimitiveMode.h"
#include "WebInputMode.h"
#include "PulseMode.h"
#include "OctreeConstants.h"
#include "OctreeElement.h"
@ -269,6 +270,7 @@ public:
static int unpackDataFromBytes(const unsigned char* dataBytes, RenderLayer& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); }
static int unpackDataFromBytes(const unsigned char* dataBytes, PrimitiveMode& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); }
static int unpackDataFromBytes(const unsigned char* dataBytes, WebInputMode& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); }
static int unpackDataFromBytes(const unsigned char* dataBytes, PulseMode& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); }
static int unpackDataFromBytes(const unsigned char* dataBytes, glm::vec2& result);
static int unpackDataFromBytes(const unsigned char* dataBytes, glm::vec3& result);
static int unpackDataFromBytes(const unsigned char* dataBytes, glm::u8vec3& result);

View file

@ -217,6 +217,9 @@ public:
static const QString& MENU_PATH();
// for updating plugin-related commands. Mimics the input plugin.
virtual void pluginUpdate() = 0;
signals:
void recommendedFramebufferSizeChanged(const QSize& size);
void resetSensorsRequested();

View file

@ -21,6 +21,7 @@ class DisplayPlugin;
class InputPlugin;
class CodecPlugin;
class SteamClientPlugin;
class OculusPlatformPlugin;
class Plugin;
class PluginContainer;
class PluginManager;
@ -35,4 +36,5 @@ using CodecPluginPointer = std::shared_ptr<CodecPlugin>;
using CodecPluginList = std::vector<CodecPluginPointer>;
using CodecPluginProvider = std::function<CodecPluginList()>;
using SteamClientPluginPointer = std::shared_ptr<SteamClientPlugin>;
using OculusPlatformPluginPointer = std::shared_ptr<OculusPlatformPlugin>;
using InputPluginSettingsPersister = std::function<void(const InputPluginList&)>;

View file

@ -0,0 +1,28 @@
//
// Created by Wayne Chen on 2018/12/20
// 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
//
#pragma once
#include <QtCore/QString>
#include <functional>
using NonceUserIDCallback = std::function<void(QString, QString)>;
class OculusPlatformPlugin {
public:
virtual ~OculusPlatformPlugin() = default;
virtual QString getName() const = 0;
virtual QString getOculusUserID() const = 0;
virtual bool isRunning() const = 0;
virtual void requestNonceAndUserID(NonceUserIDCallback callback) = 0;
virtual void handleOVREvents() = 0;
};

View file

@ -188,6 +188,22 @@ const SteamClientPluginPointer PluginManager::getSteamClientPlugin() {
return steamClientPlugin;
}
const OculusPlatformPluginPointer PluginManager::getOculusPlatformPlugin() {
static OculusPlatformPluginPointer oculusPlatformPlugin;
static std::once_flag once;
std::call_once(once, [&] {
// Now grab the dynamic plugins
for (auto loader : getLoadedPlugins()) {
OculusPlatformProvider* oculusPlatformProvider = qobject_cast<OculusPlatformProvider*>(loader->instance());
if (oculusPlatformProvider) {
oculusPlatformPlugin = oculusPlatformProvider->getOculusPlatformPlugin();
break;
}
}
});
return oculusPlatformPlugin;
}
const DisplayPluginList& PluginManager::getDisplayPlugins() {
static std::once_flag once;
static auto deviceAddedCallback = [](QString deviceName) {

View file

@ -27,6 +27,7 @@ public:
const InputPluginList& getInputPlugins();
const CodecPluginList& getCodecPlugins();
const SteamClientPluginPointer getSteamClientPlugin();
const OculusPlatformPluginPointer getOculusPlatformPlugin();
DisplayPluginList getPreferredDisplayPlugins();
void setPreferredDisplayPlugins(const QStringList& displays);

View file

@ -51,5 +51,13 @@ public:
virtual SteamClientPluginPointer getSteamClientPlugin() = 0;
};
class OculusPlatformProvider {
public:
virtual OculusPlatformPluginPointer getOculusPlatformPlugin() = 0;
};
#define SteamClientProvider_iid "com.highfidelity.plugins.steamclient"
Q_DECLARE_INTERFACE(SteamClientProvider, SteamClientProvider_iid)
#define OculusPlatformProvider_iid "com.highfidelity.plugins.oculusplatform"
Q_DECLARE_INTERFACE(OculusPlatformProvider, OculusPlatformProvider_iid)

View file

@ -44,7 +44,7 @@ const float DEFAULT_AVATAR_RIGHTHAND_MASS = 2.0f;
// Used when avatar is missing joints... (avatar space)
const glm::quat DEFAULT_AVATAR_MIDDLE_EYE_ROT { Quaternions::Y_180 };
const glm::vec3 DEFAULT_AVATAR_HEAD_TO_MIDDLE_EYE_OFFSET = { 0.0f, 0.06f, -0.09f };
const glm::vec3 DEFAULT_AVATAR_HEAD_TO_MIDDLE_EYE_OFFSET = { 0.0f, 0.064f, 0.084f };
const glm::vec3 DEFAULT_AVATAR_HEAD_POS { 0.0f, 0.53f, 0.0f };
const glm::quat DEFAULT_AVATAR_HEAD_ROT { Quaternions::Y_180 };
const glm::vec3 DEFAULT_AVATAR_RIGHTARM_POS { -0.134824f, 0.396348f, -0.0515777f };

View file

@ -0,0 +1,25 @@
//
// Created by Sam Gondelman on 1/15/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
//
#include "PulseMode.h"
const char* pulseModeNames[] = {
"none",
"in",
"out"
};
static const size_t PULSE_MODE_NAMES = (sizeof(pulseModeNames) / sizeof(pulseModeNames[0]));
QString PulseModeHelpers::getNameForPulseMode(PulseMode mode) {
if (((int)mode <= 0) || ((int)mode >= (int)PULSE_MODE_NAMES)) {
mode = (PulseMode)0;
}
return pulseModeNames[(int)mode];
}

View file

@ -0,0 +1,41 @@
//
// Created by Sam Gondelman on 1/15/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
//
#ifndef hifi_PulseMode_h
#define hifi_PulseMode_h
#include "QString"
/**jsdoc
* <p>Pulse modes for color and alpha pulsing.</p>
* <table>
* <thead>
* <tr><th>Value</th><th>Description</th></tr>
* </thead>
* <tbody>
* <tr><td><code>none</code></td><td>No pulsing.</td></tr>
* <tr><td><code>in</code></td><td>Pulse in phase with the pulse period.</td></tr>
* <tr><td><code>out</code></td><td>Pulse out of phase with the pulse period.</td></tr>
* </tbody>
* </table>
* @typedef {string} PulseMode
*/
enum class PulseMode {
NONE = 0,
IN_PHASE,
OUT_PHASE
};
class PulseModeHelpers {
public:
static QString getNameForPulseMode(PulseMode mode);
};
#endif // hifi_PulseMode_h

View file

@ -12,6 +12,7 @@
#include <display-plugins/CompositorHelper.h>
#include <gpu/Frame.h>
#include <gl/Config.h>
#include <shared/GlobalAppProperties.h>
#include "OculusHelpers.h"
@ -30,7 +31,7 @@ bool OculusBaseDisplayPlugin::beginFrameRender(uint32_t frameIndex) {
return false;
}
if (ovr::quitRequested(status) || ovr::displayLost(status) || !ovr::handleOVREvents()) {
if (ovr::quitRequested(status) || ovr::displayLost(status)) {
QMetaObject::invokeMethod(qApp, "quit");
return false;
}

View file

@ -13,6 +13,9 @@
#include <OVR_CAPI_GL.h>
#define OVRPL_DISABLED
#include <OVR_Platform.h>
class OculusBaseDisplayPlugin : public HmdDisplayPlugin {
using Parent = HmdDisplayPlugin;
public:
@ -30,7 +33,7 @@ public:
QRectF getPlayAreaRect() override;
QVector<glm::vec3> getSensorPositions() override;
protected:
void customizeContext() override;
void uncustomizeContext() override;

View file

@ -17,6 +17,7 @@
#include <controllers/UserInputMapper.h>
#include <controllers/StandardControls.h>
#include <AvatarConstants.h>
#include <PerfStat.h>
#include <PathUtils.h>
#include <NumericalConstants.h>
@ -327,8 +328,14 @@ void OculusControllerManager::TouchDevice::handleHeadPose(float deltaTime,
ovr::toGlm(headPose.AngularVelocity));
glm::mat4 sensorToAvatar = glm::inverse(inputCalibrationData.avatarMat) * inputCalibrationData.sensorToWorldMat;
glm::mat4 defaultHeadOffset = glm::inverse(inputCalibrationData.defaultCenterEyeMat) *
inputCalibrationData.defaultHeadMat;
glm::mat4 defaultHeadOffset;
if (inputCalibrationData.hmdAvatarAlignmentType == controller::HmdAvatarAlignmentType::Eyes) {
// align the eyes of the user with the eyes of the avatar
defaultHeadOffset = glm::inverse(inputCalibrationData.defaultCenterEyeMat) * inputCalibrationData.defaultHeadMat;
} else {
// align the head of the user with the head of the avatar
defaultHeadOffset = createMatFromQuatAndPos(Quaternions::IDENTITY, -DEFAULT_AVATAR_HEAD_TO_MIDDLE_EYE_OFFSET);
}
pose.valid = true;
_poseStateMap[controller::HEAD] = pose.postTransform(defaultHeadOffset).transform(sensorToAvatar);

View file

@ -296,34 +296,3 @@ controller::Pose hifi::ovr::toControllerPose(ovrHandType hand,
pose.valid = true;
return pose;
}
bool hifi::ovr::handleOVREvents() {
#ifdef OCULUS_APP_ID
if (qApp->property(hifi::properties::OCULUS_STORE).toBool()) {
// pop messages to see if we got a return for an entitlement check
ovrMessageHandle message = ovr_PopMessage();
while (message) {
switch (ovr_Message_GetType(message)) {
case ovrMessage_Entitlement_GetIsViewerEntitled: {
if (!ovr_Message_IsError(message)) {
// this viewer is entitled, no need to flag anything
qCDebug(oculusLog) << "Oculus Platform entitlement check succeeded, proceeding normally";
} else {
// we failed the entitlement check, quit
qCDebug(oculusLog) << "Oculus Platform entitlement check failed, app will now quit" << OCULUS_APP_ID;
return false;
}
}
}
// free the message handle to cleanup and not leak
ovr_FreeMessage(message);
// pop the next message to check, if there is one
message = ovr_PopMessage();
}
}
#endif
return true;
}

View file

@ -30,7 +30,6 @@ struct ovr {
static ovrSessionStatus getStatus(ovrResult& result);
static ovrTrackingState getTrackingState(double absTime = 0.0, ovrBool latencyMarker = ovrFalse);
static QString getError();
static bool handleOVREvents();
static inline bool quitRequested() { return quitRequested(getStatus()); }
static inline bool reorientRequested() { return reorientRequested(getStatus()); }

View file

@ -0,0 +1,109 @@
//
// Created by Wayne Chen on 2019/01/08
// 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
//
#include "OculusPlatformPlugin.h"
#include <shared/GlobalAppProperties.h>
#include <QMetaObject>
#include "OculusHelpers.h"
QString OculusAPIPlugin::NAME { "Oculus Rift" };
OculusAPIPlugin::OculusAPIPlugin() {
_session = hifi::ovr::acquireRenderSession();
}
OculusAPIPlugin::~OculusAPIPlugin() {
hifi::ovr::releaseRenderSession(_session);
}
bool OculusAPIPlugin::isRunning() const {
return (qApp->property(hifi::properties::OCULUS_STORE).toBool());
}
void OculusAPIPlugin::requestNonceAndUserID(NonceUserIDCallback callback) {
#ifdef OCULUS_APP_ID
_nonceUserIDCallback = callback;
ovr_User_GetUserProof();
ovr_User_GetLoggedInUser();
#endif
}
void OculusAPIPlugin::handleOVREvents() {
#ifdef OCULUS_APP_ID
if (qApp->property(hifi::properties::OCULUS_STORE).toBool()) {
// pop messages to see if we got a return for an entitlement check
ovrMessageHandle message { nullptr };
// pop the next message to check, if there is one
while ((message = ovr_PopMessage())) {
switch (ovr_Message_GetType(message)) {
case ovrMessage_Entitlement_GetIsViewerEntitled: {
if (!ovr_Message_IsError(message)) {
// this viewer is entitled, no need to flag anything
qCDebug(oculusLog) << "Oculus Platform entitlement check succeeded, proceeding normally";
} else {
// we failed the entitlement check, quit
qCDebug(oculusLog) << "Oculus Platform entitlement check failed, app will now quit" << OCULUS_APP_ID;
QMetaObject::invokeMethod(qApp, "quit");
}
break;
}
case ovrMessage_User_Get: {
if (!ovr_Message_IsError(message)) {
qCDebug(oculusLog) << "Oculus Platform user retrieval succeeded";
ovrUserHandle user = ovr_Message_GetUser(message);
_user = ovr_User_GetOculusID(user);
// went all the way through the `requestNonceAndUserID()` pipeline successfully.
} else {
qCDebug(oculusLog) << "Oculus Platform user retrieval failed" << QString(ovr_Error_GetMessage(ovr_Message_GetError(message)));
// emit the signal so we don't hang for it anywhere else.
_user = "";
}
break;
}
case ovrMessage_User_GetLoggedInUser: {
if (!ovr_Message_IsError(message)) {
ovrUserHandle user = ovr_Message_GetUser(message);
_userID = ovr_User_GetID(user);
ovr_User_Get(_userID);
} else {
qCDebug(oculusLog) << "Oculus Platform user ID retrieval failed" << QString(ovr_Error_GetMessage(ovr_Message_GetError(message)));
// emit the signal so we don't hang for it anywhere else.
}
_userIDChanged = true;
break;
}
case ovrMessage_User_GetUserProof: {
if (!ovr_Message_IsError(message)) {
ovrUserProofHandle userProof = ovr_Message_GetUserProof(message);
_nonce = ovr_UserProof_GetNonce(userProof);
qCDebug(oculusLog) << "Oculus Platform nonce retrieval succeeded: " << _nonce;
} else {
qCDebug(oculusLog) << "Oculus Platform nonce retrieval failed" << QString(ovr_Error_GetMessage(ovr_Message_GetError(message)));
_nonce = "";
// emit the signal so we don't hang for it anywhere else.
}
_nonceChanged = true;
break;
}
}
if (_nonceChanged && _userIDChanged) {
_nonceUserIDCallback(_nonce, QString::number(_userID));
_nonceChanged = _userIDChanged = false;
}
// free the message handle to cleanup and not leak
ovr_FreeMessage(message);
}
}
#endif
}

View file

@ -0,0 +1,39 @@
//
// Created by Wayne Chen on 2019/01/08
// 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
//
#pragma once
#include <plugins/OculusPlatformPlugin.h>
#include <OVR_CAPI_GL.h>
#define OVRPL_DISABLED
#include <OVR_Platform.h>
class OculusAPIPlugin : public OculusPlatformPlugin {
public:
OculusAPIPlugin();
virtual ~OculusAPIPlugin();
QString getName() const { return NAME; }
QString getOculusUserID() const { return _user; };
bool isRunning() const;
virtual void requestNonceAndUserID(NonceUserIDCallback callback);
virtual void handleOVREvents();
private:
static QString NAME;
NonceUserIDCallback _nonceUserIDCallback;
QString _nonce;
bool _nonceChanged{ false };
bool _userIDChanged{ false };
QString _user;
ovrID _userID;
ovrSession _session;
};

View file

@ -18,15 +18,18 @@
#include "OculusDisplayPlugin.h"
#include "OculusDebugDisplayPlugin.h"
#include "OculusPlatformPlugin.h"
#include "OculusControllerManager.h"
class OculusProvider : public QObject, public DisplayProvider, InputProvider
class OculusProvider : public QObject, public DisplayProvider, InputProvider, OculusPlatformProvider
{
Q_OBJECT
Q_PLUGIN_METADATA(IID DisplayProvider_iid FILE "oculus.json")
Q_INTERFACES(DisplayProvider)
Q_PLUGIN_METADATA(IID InputProvider_iid FILE "oculus.json")
Q_INTERFACES(InputProvider)
Q_PLUGIN_METADATA(IID OculusPlatformProvider_iid FILE "oculus.json")
Q_INTERFACES(OculusPlatformProvider)
public:
OculusProvider(QObject* parent = nullptr) : QObject(parent) {}
@ -62,6 +65,15 @@ public:
return _inputPlugins;
}
virtual OculusPlatformPluginPointer getOculusPlatformPlugin() override {
static std::once_flag once;
std::call_once(once, [&] {
_oculusPlatformPlugin = std::make_shared<OculusAPIPlugin>();
});
return _oculusPlatformPlugin;
}
virtual void destroyInputPlugins() override {
_inputPlugins.clear();
}
@ -73,6 +85,7 @@ public:
private:
DisplayPluginList _displayPlugins;
InputPluginList _inputPlugins;
OculusPlatformPluginPointer _oculusPlatformPlugin;
};
#include "OculusProvider.moc"

View file

@ -27,6 +27,7 @@
#include <SettingHandle.h>
#include <OffscreenUi.h>
#include <GLMHelpers.h>
#include <AvatarConstants.h>
#include <glm/ext.hpp>
#include <glm/gtc/quaternion.hpp>
#include <ui-plugins/PluginContainer.h>
@ -1024,7 +1025,16 @@ void ViveControllerManager::InputDevice::handleHeadPoseEvent(const controller::I
//perform a 180 flip to make the HMD face the +z instead of -z, beacuse the head faces +z
glm::mat4 matYFlip = mat * Matrices::Y_180;
controller::Pose pose(extractTranslation(matYFlip), glmExtractRotation(matYFlip), linearVelocity, angularVelocity);
glm::mat4 defaultHeadOffset = glm::inverse(inputCalibrationData.defaultCenterEyeMat) * inputCalibrationData.defaultHeadMat;
glm::mat4 defaultHeadOffset;
if (inputCalibrationData.hmdAvatarAlignmentType == controller::HmdAvatarAlignmentType::Eyes) {
// align the eyes of the user with the eyes of the avatar
defaultHeadOffset = glm::inverse(inputCalibrationData.defaultCenterEyeMat) * inputCalibrationData.defaultHeadMat;
} else {
// align the head of the user with the head of the avatar
defaultHeadOffset = createMatFromQuatAndPos(Quaternions::IDENTITY, -DEFAULT_AVATAR_HEAD_TO_MIDDLE_EYE_OFFSET);
}
glm::mat4 sensorToAvatar = glm::inverse(inputCalibrationData.avatarMat) * inputCalibrationData.sensorToWorldMat;
_poseStateMap[controller::HEAD] = pose.postTransform(defaultHeadOffset).transform(sensorToAvatar);
}

View file

@ -14,7 +14,6 @@ import QtQuick.Layouts 1.3
import stylesUit 1.0
import controlsUit 1.0 as HifiControls
import "configSlider"
import "../lib/jet/qml" as Jet
Rectangle {
HifiConstants { id: hifi;}
@ -249,12 +248,6 @@ Rectangle {
checked: render.mainViewTask.getConfig("DrawOverlayHUDOpaqueBounds")["enabled"]
onCheckedChanged: { render.mainViewTask.getConfig("DrawOverlayHUDOpaqueBounds")["enabled"] = checked }
}
HifiControls.CheckBox {
boxSize: 20
text: "Transparents in HUD"
checked: render.mainViewTask.getConfig("DrawOverlayHUDTransparentBounds")["enabled"]
onCheckedChanged: { render.mainViewTask.getConfig("DrawOverlayHUDTransparentBounds")["enabled"] = checked }
}
}
Column {
@ -277,6 +270,12 @@ Rectangle {
checked: render.mainViewTask.getConfig("DrawZones")["enabled"]
onCheckedChanged: { render.mainViewTask.getConfig("ZoneRenderer")["enabled"] = checked; render.mainViewTask.getConfig("DrawZones")["enabled"] = checked; }
}
HifiControls.CheckBox {
boxSize: 20
text: "Transparents in HUD"
checked: render.mainViewTask.getConfig("DrawOverlayHUDTransparentBounds")["enabled"]
onCheckedChanged: { render.mainViewTask.getConfig("DrawOverlayHUDTransparentBounds")["enabled"] = checked }
}
}
}
Separator {}
@ -303,5 +302,13 @@ Rectangle {
}
}
}
Row {
HifiControls.Button {
text: "Material"
onClicked: {
sendToScript({method: "openMaterialInspectorView"});
}
}
}
}
}

View file

@ -12,6 +12,8 @@
(function() {
var AppUi = Script.require('appUi');
var MaterialInspector = Script.require('./materialInspector.js');
var moveDebugCursor = false;
var onMousePressEvent = function (e) {
@ -41,11 +43,12 @@
Render.getConfig("RenderMainView").getConfig("DebugDeferredBuffer").size = { x: nx, y: ny, z: 1.0, w: 1.0 };
}
function Page(title, qmlurl, width, height) {
function Page(title, qmlurl, width, height, handleWindowFunc) {
this.title = title;
this.qml = qmlurl;
this.width = width;
this.height = height;
this.handleWindowFunc = handleWindowFunc;
this.window;
@ -73,8 +76,10 @@
presentationMode: Desktop.PresentationMode.NATIVE,
size: {x: this.width, y: this.height}
});
this.handleWindowFunc(this.window);
this.window.closed.connect(function () {
that.killView();
this.handleWindowFunc(undefined);
});
}
};
@ -84,8 +89,12 @@
this._pages = {};
};
Pages.prototype.addPage = function (command, title, qmlurl, width, height) {
this._pages[command] = new Page(title, qmlurl, width, height);
Pages.prototype.addPage = function (command, title, qmlurl, width, height, handleWindowFunc) {
if (handleWindowFunc === undefined) {
// Workaround for bad linter
handleWindowFunc = function(window){};
}
this._pages[command] = new Page(title, qmlurl, width, height, handleWindowFunc);
};
Pages.prototype.open = function (command) {
@ -110,6 +119,7 @@
pages.addPage('openEngineView', 'Render Engine', 'engineInspector.qml', 300, 400);
pages.addPage('openEngineLODView', 'Render LOD', 'lod.qml', 300, 400);
pages.addPage('openCullInspectorView', 'Cull Inspector', 'culling.qml', 300, 400);
pages.addPage('openMaterialInspectorView', 'Material Inspector', 'materialInspector.qml', 300, 400, MaterialInspector.setWindow);
function fromQml(message) {
if (pages.open(message.method)) {

View file

@ -0,0 +1,165 @@
//
// materialInspector.js
//
// Created by Sabrina Shanman on 2019-01-17
// Copyright 2019 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html
//
"use strict";
var activeWindow;
// Adapted from Samuel G's material painting script
function getTopMaterial(multiMaterial) {
// For non-models: multiMaterial[0] will be the top material
// For models, multiMaterial[0] is the base material, and multiMaterial[1] is the highest priority applied material
if (multiMaterial.length > 1) {
if (multiMaterial[1].priority > multiMaterial[0].priority) {
return multiMaterial[1];
}
}
return multiMaterial[0];
}
function updateMaterial(type, id, meshPart) {
var mesh = Graphics.getModel(id);
var meshPartString = meshPart.toString();
if (!mesh) {
return;
}
var materials = mesh.materialLayers;
if (!materials[meshPartString] || materials[meshPartString].length <= 0) {
return;
}
var topMaterial = getTopMaterial(materials[meshPartString]);
var materialJSONText = JSON.stringify({
materialVersion: 1,
materials: topMaterial.material
}, null, 2);
toQml({method: "setObjectInfo", params: {id: id, type: type, meshPart: meshPart}});
toQml({method: "setMaterialJSON", params: {materialJSONText: materialJSONText}});
}
// Adapted from Samuel G's material painting script
function getHoveredMaterialLocation(event) {
var pickRay = Camera.computePickRay(event.x, event.y);
var closest;
var id;
var type = "Entity";
var avatar = AvatarManager.findRayIntersection(pickRay);
var entity = Entities.findRayIntersection(pickRay, true);
var overlay = Overlays.findRayIntersection(pickRay, true);
closest = entity;
id = entity.entityID;
if (avatar.intersects && avatar.distance < closest.distance) {
closest = avatar;
id = avatar.avatarID;
type = "Avatar";
} else if (overlay.intersects && overlay.distance < closest.distance) {
closest = overlay;
id = overlay.overlayID;
type = "Overlay";
}
if (closest.intersects) {
return {
type: type,
id: id,
meshPart: (closest.extraInfo.shapeID ? closest.extraInfo.shapeID : 0)
};
} else {
return undefined;
}
}
var pressedID;
var pressedMeshPart;
function mousePressEvent(event) {
if (!event.isLeftButton) {
return;
}
var result = getHoveredMaterialLocation(event);
if (result !== undefined) {
pressedID = result.id;
pressedMeshPart = result.meshPart;
}
}
function mouseReleaseEvent(event) {
if (!event.isLeftButton) {
return;
}
var result = getHoveredMaterialLocation(event);
if (result !== undefined && result.id === pressedID && result.meshPart === pressedMeshPart) {
updateMaterial(result.type, result.id, result.meshPart);
setSelectedObject(result.id, result.type);
}
}
function killWindow() {
setWindow(undefined);
}
function toQml(message) {
if (activeWindow === undefined) {
return; // Shouldn't happen
}
activeWindow.sendToQml(message);
}
function fromQml(message) {
// No cases currently
}
var SELECT_LIST = "luci_materialInspector_SelectionList";
Selection.enableListHighlight(SELECT_LIST, {
outlineUnoccludedColor: { red: 255, green: 255, blue: 255 }
});
function setSelectedObject(id, type) {
Selection.clearSelectedItemsList(SELECT_LIST);
if (id !== undefined && !Uuid.isNull(id)) {
Selection.addToSelectedItemsList(SELECT_LIST, type.toLowerCase(), id);
}
}
function setWindow(window) {
if (activeWindow !== undefined) {
setSelectedObject(Uuid.NULL, "");
activeWindow.closed.disconnect(killWindow);
activeWindow.fromQml.disconnect(fromQml);
Controller.mousePressEvent.disconnect(mousePressEvent);
Controller.mouseReleaseEvent.disconnect(mouseReleaseEvent);
activeWindow.close();
}
if (window !== undefined) {
window.closed.connect(killWindow);
window.fromQml.connect(fromQml);
Controller.mousePressEvent.connect(mousePressEvent);
Controller.mouseReleaseEvent.connect(mouseReleaseEvent);
}
activeWindow = window;
}
function cleanup() {
setWindow(undefined);
Selection.disableListHighlight(SELECT_LIST);
}
Script.scriptEnding.connect(cleanup);
module.exports = {
setWindow: setWindow
};

View file

@ -0,0 +1,65 @@
//
// materialInspector.qml
//
// Created by Sabrina Shanman on 2019-01-16
// Copyright 2019 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html
//
import QtQuick 2.7
import QtQuick.Controls 2.3 as Original
import QtQuick.Layouts 1.3
import stylesUit 1.0
import controlsUit 1.0 as HifiControls
Rectangle {
HifiConstants { id: hifi;}
color: Qt.rgba(hifi.colors.baseGray.r, hifi.colors.baseGray.g, hifi.colors.baseGray.b, 0.8);
id: root;
function fromScript(message) {
switch (message.method) {
case "setObjectInfo":
entityIDInfo.text = "Type: " + message.params.type + "\nID: " + message.params.id + "\nMesh Part: " + message.params.meshPart;
break;
case "setMaterialJSON":
materialJSONText.text = message.params.materialJSONText;
break;
}
}
Rectangle {
id: entityIDContainer
height: 52
width: root.width
color: Qt.rgba(root.color.r * 0.7, root.color.g * 0.7, root.color.b * 0.7, 0.8);
TextEdit {
id: entityIDInfo
text: "Type: Unknown\nID: None\nMesh Part: Unknown"
font.pointSize: 9
color: "#FFFFFF"
readOnly: true
selectByMouse: true
}
}
Original.ScrollView {
anchors.top: entityIDContainer.bottom
height: root.height - entityIDContainer.height
width: root.width
clip: true
Original.ScrollBar.horizontal.policy: Original.ScrollBar.AlwaysOff
TextEdit {
id: materialJSONText
text: "Click an object to get material JSON"
width: root.width
font.pointSize: 10
color: "#FFFFFF"
readOnly: true
selectByMouse: true
wrapMode: Text.WordWrap
}
}
}

View file

@ -62,6 +62,7 @@ function getMyAvatar() {
function getMyAvatarSettings() {
return {
dominantHand: MyAvatar.getDominantHand(),
hmdAvatarAlignmentType: MyAvatar.getHmdAvatarAlignmentType(),
collisionsEnabled: MyAvatar.getCollisionsEnabled(),
otherAvatarsCollisionsEnabled: MyAvatar.getOtherAvatarsCollisionsEnabled(),
collisionSoundUrl : MyAvatar.collisionSoundURL,
@ -129,6 +130,13 @@ function onDominantHandChanged(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;
@ -331,6 +339,7 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See
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;
@ -521,6 +530,7 @@ function off() {
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);
@ -542,6 +552,7 @@ function on() {
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);

View file

@ -44,6 +44,7 @@ const COLUMNS = {
initialWidth: 0.16,
initiallyShown: true,
alwaysShown: true,
defaultSortOrder: ASCENDING_SORT,
},
name: {
columnHeader: "Name",
@ -51,6 +52,7 @@ const COLUMNS = {
initialWidth: 0.34,
initiallyShown: true,
alwaysShown: true,
defaultSortOrder: ASCENDING_SORT,
},
url: {
columnHeader: "File",
@ -58,6 +60,7 @@ const COLUMNS = {
propertyID: "url",
initialWidth: 0.34,
initiallyShown: true,
defaultSortOrder: ASCENDING_SORT,
},
locked: {
columnHeader: "&#xe006;",
@ -66,6 +69,7 @@ const COLUMNS = {
initialWidth: 0.08,
initiallyShown: true,
alwaysShown: true,
defaultSortOrder: DESCENDING_SORT,
},
visible: {
columnHeader: "&#xe007;",
@ -74,25 +78,29 @@ const COLUMNS = {
initialWidth: 0.08,
initiallyShown: true,
alwaysShown: true,
defaultSortOrder: DESCENDING_SORT,
},
verticesCount: {
columnHeader: "Verts",
dropdownLabel: "Vertices",
propertyID: "verticesCount",
initialWidth: 0.08,
defaultSortOrder: DESCENDING_SORT,
},
texturesCount: {
columnHeader: "Texts",
dropdownLabel: "Textures",
propertyID: "texturesCount",
initialWidth: 0.08,
defaultSortOrder: DESCENDING_SORT,
},
texturesSize: {
columnHeader: "Text MB",
dropdownLabel: "Texture Size",
propertyID: "texturesSize",
initialWidth: 0.10,
format: decimalMegabytes
format: decimalMegabytes,
defaultSortOrder: DESCENDING_SORT,
},
hasTransparent: {
columnHeader: "&#xe00b;",
@ -100,6 +108,7 @@ const COLUMNS = {
dropdownLabel: "Transparency",
propertyID: "hasTransparent",
initialWidth: 0.04,
defaultSortOrder: DESCENDING_SORT,
},
isBaked: {
columnHeader: "&#xe01a;",
@ -107,12 +116,14 @@ const COLUMNS = {
dropdownLabel: "Baked",
propertyID: "isBaked",
initialWidth: 0.08,
defaultSortOrder: DESCENDING_SORT,
},
drawCalls: {
columnHeader: "Draws",
dropdownLabel: "Draws",
propertyID: "drawCalls",
initialWidth: 0.08,
defaultSortOrder: DESCENDING_SORT,
},
hasScript: {
columnHeader: "k",
@ -120,27 +131,10 @@ const COLUMNS = {
dropdownLabel: "Script",
propertyID: "hasScript",
initialWidth: 0.06,
defaultSortOrder: DESCENDING_SORT,
},
};
const COMPARE_ASCENDING = function(a, b) {
let va = a[currentSortColumnID];
let vb = b[currentSortColumnID];
if (va < vb) {
return -1;
} else if (va > vb) {
return 1;
} else if (a.id < b.id) {
return -1;
}
return 1;
};
const COMPARE_DESCENDING = function(a, b) {
return COMPARE_ASCENDING(b, a);
};
const FILTER_TYPES = [
"Shape",
"Model",
@ -643,6 +637,10 @@ function loaded() {
refreshEntityList();
}
const isNullOrEmpty = function(value) {
return value === undefined || value === null || value === "";
};
function refreshEntityList() {
PROFILE("refresh-entity-list", function() {
@ -660,8 +658,31 @@ function loaded() {
});
PROFILE("sort", function() {
let cmp = currentSortOrder === ASCENDING_SORT ? COMPARE_ASCENDING : COMPARE_DESCENDING;
visibleEntities.sort(cmp);
let isAscendingSort = currentSortOrder === ASCENDING_SORT;
let isDefaultSort = currentSortOrder === COLUMNS[currentSortColumnID].defaultSortOrder;
visibleEntities.sort((entityA, entityB) => {
/**
* If the default sort is ascending, empty should be considered largest.
* If the default sort is descending, empty should be considered smallest.
*/
if (!isAscendingSort) {
[entityA, entityB] = [entityB, entityA];
}
let valueA = entityA[currentSortColumnID];
let valueB = entityB[currentSortColumnID];
if (valueA === valueB) {
return entityA.id < entityB.id ? -1 : 1;
}
if (isNullOrEmpty(valueA)) {
return (isDefaultSort ? 1 : -1) * (isAscendingSort ? 1 : -1);
}
if (isNullOrEmpty(valueB)) {
return (isDefaultSort ? -1 : 1) * (isAscendingSort ? 1 : -1);
}
return valueA < valueB ? -1 : 1;
});
});
PROFILE("update-dom", function() {
@ -757,7 +778,7 @@ function loaded() {
} else {
elSortOrders[currentSortColumnID].innerHTML = "";
currentSortColumnID = columnID;
currentSortOrder = ASCENDING_SORT;
currentSortOrder = COLUMNS[currentSortColumnID].defaultSortOrder;
}
refreshSortOrder();
refreshEntityList();

File diff suppressed because it is too large Load diff

View file

@ -12,7 +12,43 @@ using System;
using System.IO;
using System.Collections.Generic;
class AvatarExporter : MonoBehaviour {
class AvatarExporter : MonoBehaviour {
// update version number for every PR that changes this file, also set updated version in README file
static readonly string AVATAR_EXPORTER_VERSION = "0.1";
static readonly float HIPS_GROUND_MIN_Y = 0.01f;
static readonly float HIPS_SPINE_CHEST_MIN_SEPARATION = 0.001f;
static readonly int MAXIMUM_USER_BONE_COUNT = 256;
static readonly string EMPTY_WARNING_TEXT = "None";
static readonly string[] RECOMMENDED_UNITY_VERSIONS = new string[] {
"2018.2.12f1",
"2018.2.11f1",
"2018.2.10f1",
"2018.2.9f1",
"2018.2.8f1",
"2018.2.7f1",
"2018.2.6f1",
"2018.2.5f1",
"2018.2.4f1",
"2018.2.3f1",
"2018.2.2f1",
"2018.2.1f1",
"2018.2.0f2",
"2018.1.9f2",
"2018.1.8f1",
"2018.1.7f1",
"2018.1.6f1",
"2018.1.5f1",
"2018.1.4f1",
"2018.1.3f1",
"2018.1.2f1",
"2018.1.1f1",
"2018.1.0f2",
"2017.4.18f1",
"2017.4.17f1",
};
static readonly Dictionary<string, string> HUMANOID_TO_HIFI_JOINT_NAME = new Dictionary<string, string> {
{"Chest", "Spine1"},
{"Head", "Head"},
@ -70,70 +106,163 @@ class AvatarExporter : MonoBehaviour {
{"UpperChest", "Spine2"},
};
static readonly Dictionary<string, Quaternion> referenceAbsoluteRotations = new Dictionary<string, Quaternion> {
// absolute reference rotations for each Humanoid bone using Artemis fbx in Unity 2018.2.12f1
static readonly Dictionary<string, Quaternion> REFERENCE_ROTATIONS = new Dictionary<string, Quaternion> {
{"Chest", new Quaternion(-0.0824653f, 1.25274e-7f, -6.75759e-6f, 0.996594f)},
{"Head", new Quaternion(-2.509889e-9f, -3.379446e-12f, 2.306033e-13f, 1f)},
{"Hips", new Quaternion(-3.043941e-10f, -1.573706e-7f, 5.112975e-6f, 1f)},
{"LeftHandIndex3", new Quaternion(-0.5086057f, 0.4908088f, -0.4912299f, -0.5090388f)},
{"LeftHandIndex2", new Quaternion(-0.4934928f, 0.5062312f, -0.5064303f, -0.4936835f)},
{"LeftHandIndex1", new Quaternion(-0.4986293f, 0.5017503f, -0.5013659f, -0.4982448f)},
{"LeftHandPinky3", new Quaternion(-0.490056f, 0.5143053f, -0.5095307f, -0.4855038f)},
{"LeftHandPinky2", new Quaternion(-0.5083722f, 0.4954255f, -0.4915887f, -0.5044324f)},
{"LeftHandPinky1", new Quaternion(-0.5062528f, 0.497324f, -0.4937346f, -0.5025966f)},
{"LeftHandMiddle3", new Quaternion(-0.4871885f, 0.5123404f, -0.5125002f, -0.4873383f)},
{"LeftHandMiddle2", new Quaternion(-0.5171652f, 0.4827828f, -0.4822642f, -0.5166069f)},
{"LeftHandMiddle1", new Quaternion(-0.4955998f, 0.5041052f, -0.5043675f, -0.4958555f)},
{"LeftHandRing3", new Quaternion(-0.4936301f, 0.5097645f, -0.5061787f, -0.4901562f)},
{"LeftHandRing2", new Quaternion(-0.5089865f, 0.4943658f, -0.4909532f, -0.5054707f)},
{"LeftHandRing1", new Quaternion(-0.5020972f, 0.5005084f, -0.4979034f, -0.4994819f)},
{"LeftHandThumb3", new Quaternion(-0.6617184f, 0.2884935f, -0.3604706f, -0.5907297f)},
{"LeftHandThumb2", new Quaternion(-0.6935627f, 0.1995147f, -0.2805665f, -0.6328092f)},
{"LeftHandThumb1", new Quaternion(-0.6663674f, 0.278572f, -0.3507071f, -0.5961183f)},
{"Left Index Distal", new Quaternion(-0.5086057f, 0.4908088f, -0.4912299f, -0.5090388f)},
{"Left Index Intermediate", new Quaternion(-0.4934928f, 0.5062312f, -0.5064303f, -0.4936835f)},
{"Left Index Proximal", new Quaternion(-0.4986293f, 0.5017503f, -0.5013659f, -0.4982448f)},
{"Left Little Distal", new Quaternion(-0.490056f, 0.5143053f, -0.5095307f, -0.4855038f)},
{"Left Little Intermediate", new Quaternion(-0.5083722f, 0.4954255f, -0.4915887f, -0.5044324f)},
{"Left Little Proximal", new Quaternion(-0.5062528f, 0.497324f, -0.4937346f, -0.5025966f)},
{"Left Middle Distal", new Quaternion(-0.4871885f, 0.5123404f, -0.5125002f, -0.4873383f)},
{"Left Middle Intermediate", new Quaternion(-0.5171652f, 0.4827828f, -0.4822642f, -0.5166069f)},
{"Left Middle Proximal", new Quaternion(-0.4955998f, 0.5041052f, -0.5043675f, -0.4958555f)},
{"Left Ring Distal", new Quaternion(-0.4936301f, 0.5097645f, -0.5061787f, -0.4901562f)},
{"Left Ring Intermediate", new Quaternion(-0.5089865f, 0.4943658f, -0.4909532f, -0.5054707f)},
{"Left Ring Proximal", new Quaternion(-0.5020972f, 0.5005084f, -0.4979034f, -0.4994819f)},
{"Left Thumb Distal", new Quaternion(-0.6617184f, 0.2884935f, -0.3604706f, -0.5907297f)},
{"Left Thumb Intermediate", new Quaternion(-0.6935627f, 0.1995147f, -0.2805665f, -0.6328092f)},
{"Left Thumb Proximal", new Quaternion(-0.6663674f, 0.278572f, -0.3507071f, -0.5961183f)},
{"LeftEye", new Quaternion(-2.509889e-9f, -3.379446e-12f, 2.306033e-13f, 1f)},
{"LeftFoot", new Quaternion(0.009215056f, 0.3612514f, 0.9323555f, -0.01121602f)},
{"LeftHand", new Quaternion(-0.4797408f, 0.5195366f, -0.5279632f, -0.4703038f)},
{"LeftForeArm", new Quaternion(-0.4594738f, 0.4594729f, -0.5374805f, -0.5374788f)},
{"LeftLeg", new Quaternion(-0.0005380471f, -0.03154583f, 0.9994993f, 0.002378627f)},
{"LeftLowerArm", new Quaternion(-0.4594738f, 0.4594729f, -0.5374805f, -0.5374788f)},
{"LeftLowerLeg", new Quaternion(-0.0005380471f, -0.03154583f, 0.9994993f, 0.002378627f)},
{"LeftShoulder", new Quaternion(-0.3840606f, 0.525857f, -0.5957767f, -0.47013f)},
{"LeftToeBase", new Quaternion(-0.0002536641f, 0.7113448f, 0.7027079f, -0.01379319f)},
{"LeftArm", new Quaternion(-0.4591927f, 0.4591916f, -0.5377204f, -0.5377193f)},
{"LeftUpLeg", new Quaternion(-0.0006682819f, 0.0006864658f, 0.9999968f, -0.002333928f)},
{"LeftToes", new Quaternion(-0.0002536641f, 0.7113448f, 0.7027079f, -0.01379319f)},
{"LeftUpperArm", new Quaternion(-0.4591927f, 0.4591916f, -0.5377204f, -0.5377193f)},
{"LeftUpperLeg", new Quaternion(-0.0006682819f, 0.0006864658f, 0.9999968f, -0.002333928f)},
{"Neck", new Quaternion(-2.509889e-9f, -3.379446e-12f, 2.306033e-13f, 1f)},
{"RightHandIndex3", new Quaternion(0.5083892f, 0.4911618f, -0.4914584f, 0.5086939f)},
{"RightHandIndex2", new Quaternion(0.4931984f, 0.5065879f, -0.5067145f, 0.4933202f)},
{"RightHandIndex1", new Quaternion(0.4991491f, 0.5012957f, -0.5008481f, 0.4987026f)},
{"RightHandPinky3", new Quaternion(0.4890696f, 0.5154139f, -0.5104482f, 0.4843578f)},
{"RightHandPinky2", new Quaternion(0.5084175f, 0.495413f, -0.4915423f, 0.5044444f)},
{"RightHandPinky1", new Quaternion(0.5069782f, 0.4965974f, -0.4930001f, 0.5033045f)},
{"RightHandMiddle3", new Quaternion(0.4867662f, 0.5129694f, -0.5128888f, 0.4866894f)},
{"RightHandMiddle2", new Quaternion(0.5167004f, 0.4833596f, -0.4827653f, 0.5160643f)},
{"RightHandMiddle1", new Quaternion(0.4965845f, 0.5031784f, -0.5033959f, 0.4967981f)},
{"RightHandRing3", new Quaternion(0.4933217f, 0.5102056f, -0.5064691f, 0.4897075f)},
{"RightHandRing2", new Quaternion(0.5085972f, 0.494844f, -0.4913519f, 0.505007f)},
{"RightHandRing1", new Quaternion(0.502959f, 0.4996676f, -0.4970418f, 0.5003144f)},
{"RightHandThumb3", new Quaternion(0.6611374f, 0.2896575f, -0.3616535f, 0.5900872f)},
{"RightHandThumb2", new Quaternion(0.6937408f, 0.1986776f, -0.279922f, 0.6331626f)},
{"RightHandThumb1", new Quaternion(0.6664271f, 0.2783172f, -0.3505667f, 0.596253f)},
{"Right Index Distal", new Quaternion(0.5083892f, 0.4911618f, -0.4914584f, 0.5086939f)},
{"Right Index Intermediate", new Quaternion(0.4931984f, 0.5065879f, -0.5067145f, 0.4933202f)},
{"Right Index Proximal", new Quaternion(0.4991491f, 0.5012957f, -0.5008481f, 0.4987026f)},
{"Right Little Distal", new Quaternion(0.4890696f, 0.5154139f, -0.5104482f, 0.4843578f)},
{"Right Little Intermediate", new Quaternion(0.5084175f, 0.495413f, -0.4915423f, 0.5044444f)},
{"Right Little Proximal", new Quaternion(0.5069782f, 0.4965974f, -0.4930001f, 0.5033045f)},
{"Right Middle Distal", new Quaternion(0.4867662f, 0.5129694f, -0.5128888f, 0.4866894f)},
{"Right Middle Intermediate", new Quaternion(0.5167004f, 0.4833596f, -0.4827653f, 0.5160643f)},
{"Right Middle Proximal", new Quaternion(0.4965845f, 0.5031784f, -0.5033959f, 0.4967981f)},
{"Right Ring Distal", new Quaternion(0.4933217f, 0.5102056f, -0.5064691f, 0.4897075f)},
{"Right Ring Intermediate", new Quaternion(0.5085972f, 0.494844f, -0.4913519f, 0.505007f)},
{"Right Ring Proximal", new Quaternion(0.502959f, 0.4996676f, -0.4970418f, 0.5003144f)},
{"Right Thumb Distal", new Quaternion(0.6611374f, 0.2896575f, -0.3616535f, 0.5900872f)},
{"Right Thumb Intermediate", new Quaternion(0.6937408f, 0.1986776f, -0.279922f, 0.6331626f)},
{"Right Thumb Proximal", new Quaternion(0.6664271f, 0.2783172f, -0.3505667f, 0.596253f)},
{"RightEye", new Quaternion(-2.509889e-9f, -3.379446e-12f, 2.306033e-13f, 1f)},
{"RightFoot", new Quaternion(-0.009482829f, 0.3612484f, 0.9323512f, 0.01144584f)},
{"RightHand", new Quaternion(0.4797273f, 0.5195542f, -0.5279628f, 0.4702987f)},
{"RightForeArm", new Quaternion(0.4594217f, 0.4594215f, -0.5375242f, 0.5375237f)},
{"RightLeg", new Quaternion(0.0005446263f, -0.03177159f, 0.9994922f, -0.002395923f)},
{"RightLowerArm", new Quaternion(0.4594217f, 0.4594215f, -0.5375242f, 0.5375237f)},
{"RightLowerLeg", new Quaternion(0.0005446263f, -0.03177159f, 0.9994922f, -0.002395923f)},
{"RightShoulder", new Quaternion(0.3841222f, 0.5257177f, -0.5957286f, 0.4702966f)},
{"RightToeBase", new Quaternion(0.0001034f, 0.7113398f, 0.7027067f, 0.01411251f)},
{"RightArm", new Quaternion(0.4591419f, 0.4591423f, -0.537763f, 0.5377624f)},
{"RightUpLeg", new Quaternion(0.0006750703f, 0.0008973633f, 0.9999966f, 0.002352045f)},
{"RightToes", new Quaternion(0.0001034f, 0.7113398f, 0.7027067f, 0.01411251f)},
{"RightUpperArm", new Quaternion(0.4591419f, 0.4591423f, -0.537763f, 0.5377624f)},
{"RightUpperLeg", new Quaternion(0.0006750703f, 0.0008973633f, 0.9999966f, 0.002352045f)},
{"Spine", new Quaternion(-0.05427956f, 1.508558e-7f, -2.775203e-6f, 0.9985258f)},
{"Spine1", new Quaternion(-0.0824653f, 1.25274e-7f, -6.75759e-6f, 0.996594f)},
{"Spine2", new Quaternion(-0.0824653f, 1.25274e-7f, -6.75759e-6f, 0.996594f)},
{"UpperChest", new Quaternion(-0.0824653f, 1.25274e-7f, -6.75759e-6f, 0.996594f)},
};
// Humanoid mapping name suffixes for each set of appendages
static readonly string[] LEG_MAPPING_SUFFIXES = new string[] {
"UpperLeg",
"LowerLeg",
"Foot",
"Toes",
};
static readonly string[] ARM_MAPPING_SUFFIXES = new string[] {
"Shoulder",
"UpperArm",
"LowerArm",
"Hand",
};
static readonly string[] HAND_MAPPING_SUFFIXES = new string[] {
" Index Distal",
" Index Intermediate",
" Index Proximal",
" Little Distal",
" Little Intermediate",
" Little Proximal",
" Middle Distal",
" Middle Intermediate",
" Middle Proximal",
" Ring Distal",
" Ring Intermediate",
" Ring Proximal",
" Thumb Distal",
" Thumb Intermediate",
" Thumb Proximal",
};
static Dictionary<string, string> userBoneToHumanoidMappings = new Dictionary<string, string>();
static Dictionary<string, string> userParentNames = new Dictionary<string, string>();
static Dictionary<string, Quaternion> userAbsoluteRotations = new Dictionary<string, Quaternion>();
enum BoneRule {
RecommendedUnityVersion,
SingleRoot,
NoDuplicateMapping,
NoAsymmetricalLegMapping,
NoAsymmetricalArmMapping,
NoAsymmetricalHandMapping,
HipsMapped,
SpineMapped,
SpineDescendantOfHips,
ChestMapped,
ChestDescendantOfSpine,
NeckMapped,
HeadMapped,
HeadDescendantOfChest,
EyesMapped,
HipsNotOnGround,
HipsSpineChestNotCoincident,
TotalBoneCountUnderLimit,
BoneRuleEnd,
};
// rules that are treated as errors and prevent exporting, otherwise rules will show as warnings
static readonly BoneRule[] EXPORT_BLOCKING_BONE_RULES = new BoneRule[] {
BoneRule.HipsMapped,
BoneRule.SpineMapped,
BoneRule.ChestMapped,
BoneRule.HeadMapped,
};
class UserBoneInformation {
public string humanName; // bone name in Humanoid if it is mapped, otherwise ""
public string parentName; // parent user bone name
public int mappingCount; // number of times this bone is mapped in Humanoid
public Vector3 position; // absolute position
public Quaternion rotation; // absolute rotation
public BoneTreeNode boneTreeNode;
public UserBoneInformation() {
humanName = "";
parentName = "";
mappingCount = 0;
position = new Vector3();
rotation = new Quaternion();
boneTreeNode = new BoneTreeNode();
}
public bool HasHumanMapping() { return !string.IsNullOrEmpty(humanName); }
}
class BoneTreeNode {
public string boneName;
public List<BoneTreeNode> children = new List<BoneTreeNode>();
public BoneTreeNode() {}
public BoneTreeNode(string name) {
boneName = name;
}
}
static Dictionary<string, UserBoneInformation> userBoneInfos = new Dictionary<string, UserBoneInformation>();
static Dictionary<string, string> humanoidToUserBoneMappings = new Dictionary<string, string>();
static BoneTreeNode userBoneTree = new BoneTreeNode();
static Dictionary<BoneRule, string> failedBoneRules = new Dictionary<BoneRule, string>();
static string assetPath = "";
static string assetName = "";
static HumanDescription humanDescription;
[MenuItem("High Fidelity/Export New Avatar")]
static void ExportNewAvatar() {
@ -144,6 +273,11 @@ class AvatarExporter : MonoBehaviour {
static void UpdateAvatar() {
ExportSelectedAvatar(true);
}
[MenuItem("High Fidelity/About")]
static void About() {
EditorUtility.DisplayDialog("About", "High Fidelity, Inc.\nAvatar Exporter\nVersion " + AVATAR_EXPORTER_VERSION, "Ok");
}
static void ExportSelectedAvatar(bool updateAvatar) {
string[] guids = Selection.assetGUIDs;
@ -163,14 +297,58 @@ class AvatarExporter : MonoBehaviour {
return;
}
if (modelImporter.animationType != ModelImporterAnimationType.Human) {
EditorUtility.DisplayDialog("Error", "Please set model's Animation Type to Humanoid in the Rig section of it's Inspector window.", "Ok");
EditorUtility.DisplayDialog("Error", "Please set model's Animation Type to Humanoid in " +
" the Rig section of it's Inspector window.", "Ok");
return;
}
humanDescription = modelImporter.humanDescription;
if (!SetJointMappingsAndParentNames()) {
humanDescription = modelImporter.humanDescription;
SetUserBoneInformation();
// format resulting bone rule failure strings
// consider export-blocking bone rules to be errors and show them in an error dialog,
// and also include any other bone rule failures as warnings in the dialog
string boneErrors = "";
string boneWarnings = "";
foreach (var failedBoneRule in failedBoneRules) {
if (Array.IndexOf(EXPORT_BLOCKING_BONE_RULES, failedBoneRule.Key) >= 0) {
boneErrors += failedBoneRule.Value + "\n\n";
} else {
boneWarnings += failedBoneRule.Value + "\n\n";
}
}
if (!string.IsNullOrEmpty(boneErrors)) {
// if there are both errors and warnings then warnings will be displayed with errors in the error dialog
if (!string.IsNullOrEmpty(boneWarnings)) {
boneErrors = "Errors:\n\n" + boneErrors;
boneErrors += "Warnings:\n\n" + boneWarnings;
}
// remove ending newlines from the last rule failure string that was added above
boneErrors = boneErrors.Substring(0, boneErrors.LastIndexOf("\n\n"));
EditorUtility.DisplayDialog("Error", boneErrors, "Ok");
return;
}
if (!humanoidToUserBoneMappings.ContainsKey("UpperChest")) {
// if parent of Neck is not Chest then map the parent to UpperChest
string neckUserBone;
if (humanoidToUserBoneMappings.TryGetValue("Neck", out neckUserBone)) {
UserBoneInformation neckParentBoneInfo;
string neckParentUserBone = userBoneInfos[neckUserBone].parentName;
if (userBoneInfos.TryGetValue(neckParentUserBone, out neckParentBoneInfo) && !neckParentBoneInfo.HasHumanMapping()) {
neckParentBoneInfo.humanName = "UpperChest";
humanoidToUserBoneMappings.Add("UpperChest", neckParentUserBone);
}
}
// if there is still no UpperChest bone but there is a Chest bone then we remap Chest to UpperChest
string chestUserBone;
if (!humanoidToUserBoneMappings.ContainsKey("UpperChest") &&
humanoidToUserBoneMappings.TryGetValue("Chest", out chestUserBone)) {
userBoneInfos[chestUserBone].humanName = "UpperChest";
humanoidToUserBoneMappings.Remove("Chest");
humanoidToUserBoneMappings.Add("UpperChest", chestUserBone);
}
}
string documentsFolder = System.Environment.GetFolderPath(System.Environment.SpecialFolder.MyDocuments);
string hifiFolder = documentsFolder + "\\High Fidelity Projects";
@ -236,11 +414,12 @@ class AvatarExporter : MonoBehaviour {
modelImporter = ModelImporter.GetAtPath(assetPath) as ModelImporter;
modelImporter.animationType = ModelImporterAnimationType.Human;
EditorUtility.SetDirty(modelImporter);
modelImporter.SaveAndReimport();
humanDescription = modelImporter.humanDescription;
modelImporter.SaveAndReimport();
// redo joint mappings and parent names due to the fbx change
SetJointMappingsAndParentNames();
// redo parent names, joint mappings, and user bone positions due to the fbx change
// as well as re-check the bone rules for failures
humanDescription = modelImporter.humanDescription;
SetUserBoneInformation();
}
}
} else {
@ -277,19 +456,30 @@ class AvatarExporter : MonoBehaviour {
// write out a new fst file in place of the old file
WriteFST(exportFstPath, projectName);
// display success dialog with any bone rule warnings
string successDialog = "Avatar successfully updated!";
if (!string.IsNullOrEmpty(boneWarnings)) {
successDialog += "\n\nWarnings:\n" + boneWarnings;
}
EditorUtility.DisplayDialog("Success!", successDialog, "Ok");
} else { // Export New Avatar menu option
// create High Fidelity Projects folder in user documents folder if it doesn't exist
if (!Directory.Exists(hifiFolder)) {
Directory.CreateDirectory(hifiFolder);
}
if (string.IsNullOrEmpty(boneWarnings)) {
boneWarnings = EMPTY_WARNING_TEXT;
}
// open a popup window to enter new export project name and project location
ExportProjectWindow window = ScriptableObject.CreateInstance<ExportProjectWindow>();
window.Init(hifiFolder, OnExportProjectWindowClose);
window.Init(hifiFolder, boneWarnings, OnExportProjectWindowClose);
}
}
static void OnExportProjectWindowClose(string projectDirectory, string projectName) {
static void OnExportProjectWindowClose(string projectDirectory, string projectName, string warnings) {
// copy the fbx from the Unity Assets folder to the project directory
string exportModelPath = projectDirectory + assetName + ".fbx";
File.Copy(assetPath, exportModelPath);
@ -304,94 +494,19 @@ class AvatarExporter : MonoBehaviour {
string exportFstPath = projectDirectory + "avatar.fst";
WriteFST(exportFstPath, projectName);
// remove any double slashes in texture directory path and warn user to copy external textures over
// remove any double slashes in texture directory path, display success dialog with any
// bone warnings previously mentioned, and suggest user to copy external textures over
texturesDirectory = texturesDirectory.Replace("\\\\", "\\");
EditorUtility.DisplayDialog("Warning", "If you are using any external textures with your model, " +
"please copy those textures to " + texturesDirectory, "Ok");
string successDialog = "Avatar successfully exported!\n\n";
if (warnings != EMPTY_WARNING_TEXT) {
successDialog += "Warnings:\n" + warnings;
}
successDialog += "Note: If you are using any external textures with your model, " +
"please copy those textures to " + texturesDirectory;
EditorUtility.DisplayDialog("Success!", successDialog, "Ok");
}
static bool SetJointMappingsAndParentNames() {
userParentNames.Clear();
userBoneToHumanoidMappings.Clear();
// instantiate a game object of the user avatar to save out bone parents then destroy it
UnityEngine.Object avatarResource = AssetDatabase.LoadAssetAtPath(assetPath, typeof(UnityEngine.Object));
GameObject assetGameObject = (GameObject)Instantiate(avatarResource);
SetParentNames(assetGameObject.transform, userParentNames);
DestroyImmediate(assetGameObject);
// store joint mappings only for joints that exist in hifi and verify missing required joints
HumanBone[] boneMap = humanDescription.human;
string chestUserBone = "";
string neckUserBone = "";
foreach (HumanBone bone in boneMap) {
string humanName = bone.humanName;
string boneName = bone.boneName;
string hifiJointName;
if (HUMANOID_TO_HIFI_JOINT_NAME.TryGetValue(humanName, out hifiJointName)) {
userBoneToHumanoidMappings.Add(boneName, humanName);
if (humanName == "Chest") {
chestUserBone = boneName;
} else if (humanName == "Neck") {
neckUserBone = boneName;
}
}
}
if (!userBoneToHumanoidMappings.ContainsValue("Hips")) {
EditorUtility.DisplayDialog("Error", "There is no Hips bone in selected avatar", "Ok");
return false;
}
if (!userBoneToHumanoidMappings.ContainsValue("Spine")) {
EditorUtility.DisplayDialog("Error", "There is no Spine bone in selected avatar", "Ok");
return false;
}
if (!userBoneToHumanoidMappings.ContainsValue("Chest")) {
// check to see if there is a child of Spine that could be mapped to Chest
string spineChild = "";
foreach (var parentRelation in userParentNames) {
string humanName;
if (userBoneToHumanoidMappings.TryGetValue(parentRelation.Value, out humanName) && humanName == "Spine") {
if (spineChild == "") {
spineChild = parentRelation.Key;
} else {
// found more than one Spine child so we can't choose one to remap
spineChild = "";
break;
}
}
}
if (spineChild != "" && !userBoneToHumanoidMappings.ContainsKey(spineChild)) {
// use child of Spine as Chest
userBoneToHumanoidMappings.Add(spineChild, "Chest");
chestUserBone = spineChild;
} else {
EditorUtility.DisplayDialog("Error", "There is no Chest bone in selected avatar", "Ok");
return false;
}
}
if (!userBoneToHumanoidMappings.ContainsValue("UpperChest")) {
//if parent of Neck is not Chest then map the parent to UpperChest
if (neckUserBone != "") {
string neckParentUserBone, neckParentHuman;
userParentNames.TryGetValue(neckUserBone, out neckParentUserBone);
userBoneToHumanoidMappings.TryGetValue(neckParentUserBone, out neckParentHuman);
if (neckParentHuman != "Chest" && !userBoneToHumanoidMappings.ContainsKey(neckParentUserBone)) {
userBoneToHumanoidMappings.Add(neckParentUserBone, "UpperChest");
}
}
// if there is still no UpperChest bone but there is a Chest bone then we remap Chest to UpperChest
if (!userBoneToHumanoidMappings.ContainsValue("UpperChest") && chestUserBone != "") {
userBoneToHumanoidMappings[chestUserBone] = "UpperChest";
}
}
return true;
}
static void WriteFST(string exportFstPath, string projectName) {
userAbsoluteRotations.Clear();
static void WriteFST(string exportFstPath, string projectName) {
// write out core fields to top of fst file
try {
File.WriteAllText(exportFstPath, "name = " + projectName + "\ntype = body+head\nscale = 1\nfilename = " +
@ -403,49 +518,53 @@ class AvatarExporter : MonoBehaviour {
}
// write out joint mappings to fst file
foreach (var jointMapping in userBoneToHumanoidMappings) {
string hifiJointName = HUMANOID_TO_HIFI_JOINT_NAME[jointMapping.Value];
File.AppendAllText(exportFstPath, "jointMap = " + hifiJointName + " = " + jointMapping.Key + "\n");
foreach (var userBoneInfo in userBoneInfos) {
if (userBoneInfo.Value.HasHumanMapping()) {
string hifiJointName = HUMANOID_TO_HIFI_JOINT_NAME[userBoneInfo.Value.humanName];
File.AppendAllText(exportFstPath, "jointMap = " + hifiJointName + " = " + userBoneInfo.Key + "\n");
}
}
// calculate and write out joint rotation offsets to fst file
SkeletonBone[] skeletonMap = humanDescription.skeleton;
foreach (SkeletonBone userBone in skeletonMap) {
string userBoneName = userBone.name;
Quaternion userBoneRotation = userBone.rotation;
string parentName;
userParentNames.TryGetValue(userBoneName, out parentName);
if (parentName == "root") {
// if the parent is root then use bone's rotation
userAbsoluteRotations.Add(userBoneName, userBoneRotation);
} else {
// otherwise multiply bone's rotation by parent bone's absolute rotation
userAbsoluteRotations.Add(userBoneName, userAbsoluteRotations[parentName] * userBoneRotation);
UserBoneInformation userBoneInfo;
if (!userBoneInfos.TryGetValue(userBoneName, out userBoneInfo)) {
continue;
}
// generate joint rotation offsets for both humanoid-mapped bones as well as extra unmapped bones in user avatar
Quaternion userBoneRotation = userBone.rotation;
string parentName = userBoneInfo.parentName;
if (parentName == "root") {
// if the parent is root then use bone's rotation
userBoneInfo.rotation = userBoneRotation;
} else {
// otherwise multiply bone's rotation by parent bone's absolute rotation
userBoneInfo.rotation = userBoneInfos[parentName].rotation * userBoneRotation;
}
// generate joint rotation offsets for both humanoid-mapped bones as well as extra unmapped bones
Quaternion jointOffset = new Quaternion();
string humanName, outputJointName = "";
if (userBoneToHumanoidMappings.TryGetValue(userBoneName, out humanName)) {
outputJointName = HUMANOID_TO_HIFI_JOINT_NAME[humanName];
Quaternion rotation = referenceAbsoluteRotations[outputJointName];
jointOffset = Quaternion.Inverse(userAbsoluteRotations[userBoneName]) * rotation;
} else if (userAbsoluteRotations.ContainsKey(userBoneName)) {
string outputJointName = "";
if (userBoneInfo.HasHumanMapping()) {
outputJointName = HUMANOID_TO_HIFI_JOINT_NAME[userBoneInfo.humanName];
Quaternion rotation = REFERENCE_ROTATIONS[userBoneInfo.humanName];
jointOffset = Quaternion.Inverse(userBoneInfo.rotation) * rotation;
} else {
outputJointName = userBoneName;
string lastRequiredParent = FindLastRequiredParentBone(userBoneName);
if (lastRequiredParent == "root") {
jointOffset = Quaternion.Inverse(userAbsoluteRotations[userBoneName]);
} else {
jointOffset = Quaternion.Inverse(userBoneInfo.rotation);
string lastRequiredParent = FindLastRequiredAncestorBone(userBoneName);
if (lastRequiredParent != "root") {
// take the previous offset and multiply it by the current local when we have an extra joint
string lastRequiredParentHifiName = HUMANOID_TO_HIFI_JOINT_NAME[userBoneToHumanoidMappings[lastRequiredParent]];
Quaternion lastRequiredParentRotation = referenceAbsoluteRotations[lastRequiredParentHifiName];
jointOffset = Quaternion.Inverse(userAbsoluteRotations[userBoneName]) * lastRequiredParentRotation;
string lastRequiredParentHumanName = userBoneInfos[lastRequiredParent].humanName;
Quaternion lastRequiredParentRotation = REFERENCE_ROTATIONS[lastRequiredParentHumanName];
jointOffset *= lastRequiredParentRotation;
}
}
// swap from left-handed (Unity) to right-handed (HiFi) coordinates and write out joint rotation offset to fst
if (outputJointName != "") {
if (!string.IsNullOrEmpty(outputJointName)) {
jointOffset = new Quaternion(-jointOffset.x, jointOffset.y, jointOffset.z, -jointOffset.w);
File.AppendAllText(exportFstPath, "jointRotationOffset = " + outputJointName + " = (" + jointOffset.x + ", " +
jointOffset.y + ", " + jointOffset.z + ", " + jointOffset.w + ")\n");
@ -455,48 +574,326 @@ class AvatarExporter : MonoBehaviour {
// open File Explorer to the project directory once finished
System.Diagnostics.Process.Start("explorer.exe", "/select," + exportFstPath);
}
static void SetParentNames(Transform modelBone, Dictionary<string, string> parentNames) {
for (int i = 0; i < modelBone.childCount; i++) {
SetParentNames(modelBone.GetChild(i), parentNames);
static void SetUserBoneInformation() {
userBoneInfos.Clear();
humanoidToUserBoneMappings.Clear();
userBoneTree = new BoneTreeNode();
// instantiate a game object of the user avatar to traverse the bone tree to gather
// bone parents and positions as well as build a bone tree, then destroy it
UnityEngine.Object avatarResource = AssetDatabase.LoadAssetAtPath(assetPath, typeof(UnityEngine.Object));
GameObject assetGameObject = (GameObject)Instantiate(avatarResource);
TraverseUserBoneTree(assetGameObject.transform);
DestroyImmediate(assetGameObject);
// iterate over Humanoid bones and update user bone info to increase human mapping counts for each bone
// as well as set their Humanoid name and build a Humanoid to user bone mapping
HumanBone[] boneMap = humanDescription.human;
foreach (HumanBone bone in boneMap) {
string humanName = bone.humanName;
string userBoneName = bone.boneName;
string hifiJointName;
if (userBoneInfos.ContainsKey(userBoneName)) {
++userBoneInfos[userBoneName].mappingCount;
if (HUMANOID_TO_HIFI_JOINT_NAME.TryGetValue(humanName, out hifiJointName)) {
userBoneInfos[userBoneName].humanName = humanName;
humanoidToUserBoneMappings.Add(humanName, userBoneName);
}
}
}
if (modelBone.parent != null) {
parentNames.Add(modelBone.name, modelBone.parent.name);
} else {
parentNames.Add(modelBone.name, "root");
// generate the list of bone rule failure strings for any bone rules that are not satisfied by this avatar
SetFailedBoneRules();
}
static void TraverseUserBoneTree(Transform modelBone) {
GameObject gameObject = modelBone.gameObject;
// check if this transform is a node containing mesh, light, or camera instead of a bone
bool mesh = gameObject.GetComponent<MeshRenderer>() != null || gameObject.GetComponent<SkinnedMeshRenderer>() != null;
bool light = gameObject.GetComponent<Light>() != null;
bool camera = gameObject.GetComponent<Camera>() != null;
// if it is in fact a bone, add it to the bone tree as well as user bone infos list with position and parent name
if (!mesh && !light && !camera) {
UserBoneInformation userBoneInfo = new UserBoneInformation();
userBoneInfo.position = modelBone.position; // bone's absolute position
string boneName = modelBone.name;
if (modelBone.parent == null) {
// if no parent then this is actual root bone node of the user avatar, so consider it's parent as "root"
userBoneTree = new BoneTreeNode(boneName); // initialize root of tree
userBoneInfo.parentName = "root";
userBoneInfo.boneTreeNode = userBoneTree;
} else {
// otherwise add this bone node as a child to it's parent's children list
string parentName = modelBone.parent.name;
BoneTreeNode boneTreeNode = new BoneTreeNode(boneName);
userBoneInfos[parentName].boneTreeNode.children.Add(boneTreeNode);
userBoneInfo.parentName = parentName;
}
userBoneInfos.Add(boneName, userBoneInfo);
}
// recurse over transform node's children
for (int i = 0; i < modelBone.childCount; ++i) {
TraverseUserBoneTree(modelBone.GetChild(i));
}
}
static string FindLastRequiredParentBone(string currentBone) {
static string FindLastRequiredAncestorBone(string currentBone) {
string result = currentBone;
while (result != "root" && !userBoneToHumanoidMappings.ContainsKey(result)) {
result = userParentNames[result];
// iterating upward through user bone info parent names, find the first ancestor bone that is mapped in Humanoid
while (result != "root" && userBoneInfos.ContainsKey(result) && !userBoneInfos[result].HasHumanMapping()) {
result = userBoneInfos[result].parentName;
}
return result;
}
static void SetFailedBoneRules() {
failedBoneRules.Clear();
string hipsUserBone = "";
string spineUserBone = "";
string chestUserBone = "";
string headUserBone = "";
Vector3 hipsPosition = new Vector3();
// iterate over all bone rules in order and add any rules that fail
// to the failed bone rules map with appropriate error or warning text
for (BoneRule boneRule = 0; boneRule < BoneRule.BoneRuleEnd; ++boneRule) {
switch (boneRule) {
case BoneRule.RecommendedUnityVersion:
if (Array.IndexOf(RECOMMENDED_UNITY_VERSIONS, Application.unityVersion) == -1) {
failedBoneRules.Add(boneRule, "The current version of Unity is not one of the recommended Unity " +
"versions. If you are using a version of Unity later than 2018.2.12f1, " +
"it is recommended to apply Enforce T-Pose under the Pose dropdown " +
"in Humanoid configuration.");
}
break;
case BoneRule.SingleRoot:
// bone rule fails if the root bone node has more than one child bone
if (userBoneTree.children.Count > 1) {
failedBoneRules.Add(boneRule, "There is more than one bone at the top level of the selected avatar's " +
"bone hierarchy. Please ensure all bones for Humanoid mappings are " +
"under the same bone hierarchy.");
}
break;
case BoneRule.NoDuplicateMapping:
// bone rule fails if any user bone is mapped to more than one Humanoid bone
foreach (var userBoneInfo in userBoneInfos) {
string boneName = userBoneInfo.Key;
int mappingCount = userBoneInfo.Value.mappingCount;
if (mappingCount > 1) {
string text = "The " + boneName + " bone is mapped to more than one bone in Humanoid.";
if (failedBoneRules.ContainsKey(boneRule)) {
failedBoneRules[boneRule] += "\n" + text;
} else {
failedBoneRules.Add(boneRule, text);
}
}
}
break;
case BoneRule.NoAsymmetricalLegMapping:
CheckAsymmetricalMappingRule(boneRule, LEG_MAPPING_SUFFIXES, "leg");
break;
case BoneRule.NoAsymmetricalArmMapping:
CheckAsymmetricalMappingRule(boneRule, ARM_MAPPING_SUFFIXES, "arm");
break;
case BoneRule.NoAsymmetricalHandMapping:
CheckAsymmetricalMappingRule(boneRule, HAND_MAPPING_SUFFIXES, "hand");
break;
case BoneRule.HipsMapped:
hipsUserBone = CheckHumanBoneMappingRule(boneRule, "Hips");
break;
case BoneRule.SpineMapped:
spineUserBone = CheckHumanBoneMappingRule(boneRule, "Spine");
break;
case BoneRule.SpineDescendantOfHips:
CheckUserBoneDescendantOfHumanRule(boneRule, spineUserBone, "Hips");
break;
case BoneRule.ChestMapped:
if (!humanoidToUserBoneMappings.TryGetValue("Chest", out chestUserBone)) {
// check to see if there is a child of Spine that we can suggest to be mapped to Chest
string spineChild = "";
if (!string.IsNullOrEmpty(spineUserBone)) {
BoneTreeNode spineTreeNode = userBoneInfos[spineUserBone].boneTreeNode;
if (spineTreeNode.children.Count == 1) {
spineChild = spineTreeNode.children[0].boneName;
}
}
failedBoneRules.Add(boneRule, "There is no Chest bone mapped in Humanoid for the selected avatar.");
// if the only found child of Spine is not yet mapped then add it as a suggestion for Chest mapping
if (!string.IsNullOrEmpty(spineChild) && !userBoneInfos[spineChild].HasHumanMapping()) {
failedBoneRules[boneRule] += " It is suggested that you map bone " + spineChild +
" to Chest in Humanoid.";
}
}
break;
case BoneRule.ChestDescendantOfSpine:
CheckUserBoneDescendantOfHumanRule(boneRule, chestUserBone, "Spine");
break;
case BoneRule.NeckMapped:
CheckHumanBoneMappingRule(boneRule, "Neck");
break;
case BoneRule.HeadMapped:
headUserBone = CheckHumanBoneMappingRule(boneRule, "Head");
break;
case BoneRule.HeadDescendantOfChest:
CheckUserBoneDescendantOfHumanRule(boneRule, headUserBone, "Chest");
break;
case BoneRule.EyesMapped:
bool leftEyeMapped = humanoidToUserBoneMappings.ContainsKey("LeftEye");
bool rightEyeMapped = humanoidToUserBoneMappings.ContainsKey("RightEye");
if (!leftEyeMapped || !rightEyeMapped) {
if (leftEyeMapped && !rightEyeMapped) {
failedBoneRules.Add(boneRule, "There is no RightEye bone mapped in Humanoid " +
"for the selected avatar.");
} else if (!leftEyeMapped && rightEyeMapped) {
failedBoneRules.Add(boneRule, "There is no LeftEye bone mapped in Humanoid " +
"for the selected avatar.");
} else {
failedBoneRules.Add(boneRule, "There is no LeftEye or RightEye bone mapped in Humanoid " +
"for the selected avatar.");
}
}
break;
case BoneRule.HipsNotOnGround:
// ensure the absolute Y position for the bone mapped to Hips (if its mapped) is at least HIPS_GROUND_MIN_Y
if (!string.IsNullOrEmpty(hipsUserBone)) {
UserBoneInformation hipsBoneInfo = userBoneInfos[hipsUserBone];
hipsPosition = hipsBoneInfo.position;
if (hipsPosition.y < HIPS_GROUND_MIN_Y) {
failedBoneRules.Add(boneRule, "The bone mapped to Hips in Humanoid (" + hipsUserBone +
") should not be at ground level.");
}
}
break;
case BoneRule.HipsSpineChestNotCoincident:
// ensure the bones mapped to Hips, Spine, and Chest are all not in the same position,
// check Hips to Spine and Spine to Chest lengths are within HIPS_SPINE_CHEST_MIN_SEPARATION
if (!string.IsNullOrEmpty(spineUserBone) && !string.IsNullOrEmpty(chestUserBone) &&
!string.IsNullOrEmpty(hipsUserBone)) {
UserBoneInformation spineBoneInfo = userBoneInfos[spineUserBone];
UserBoneInformation chestBoneInfo = userBoneInfos[chestUserBone];
Vector3 hipsToSpine = hipsPosition - spineBoneInfo.position;
Vector3 spineToChest = spineBoneInfo.position - chestBoneInfo.position;
if (hipsToSpine.magnitude < HIPS_SPINE_CHEST_MIN_SEPARATION &&
spineToChest.magnitude < HIPS_SPINE_CHEST_MIN_SEPARATION) {
failedBoneRules.Add(boneRule, "The bone mapped to Hips in Humanoid (" + hipsUserBone +
"), the bone mapped to Spine in Humanoid (" + spineUserBone +
"), and the bone mapped to Chest in Humanoid (" + chestUserBone +
") should not be coincidental.");
}
}
break;
case BoneRule.TotalBoneCountUnderLimit:
int userBoneCount = userBoneInfos.Count;
if (userBoneCount > MAXIMUM_USER_BONE_COUNT) {
failedBoneRules.Add(boneRule, "The total number of bones in the avatar (" + userBoneCount +
") exceeds the maximum bone limit (" + MAXIMUM_USER_BONE_COUNT + ").");
}
break;
}
}
}
static string CheckHumanBoneMappingRule(BoneRule boneRule, string humanBoneName) {
string userBoneName = "";
// bone rule fails if bone is not mapped in Humanoid
if (!humanoidToUserBoneMappings.TryGetValue(humanBoneName, out userBoneName)) {
failedBoneRules.Add(boneRule, "There is no " + humanBoneName + " bone mapped in Humanoid for the selected avatar.");
}
return userBoneName;
}
static void CheckUserBoneDescendantOfHumanRule(BoneRule boneRule, string userBoneName, string descendantOfHumanName) {
if (string.IsNullOrEmpty(userBoneName)) {
return;
}
string descendantOfUserBoneName = "";
if (!humanoidToUserBoneMappings.TryGetValue(descendantOfHumanName, out descendantOfUserBoneName)) {
return;
}
string userBone = userBoneName;
string ancestorUserBone = "";
UserBoneInformation userBoneInfo = new UserBoneInformation();
// iterate upward from user bone through user bone info parent names until root
// is reached or the ancestor bone name matches the target descendant of name
while (ancestorUserBone != "root") {
if (userBoneInfos.TryGetValue(userBone, out userBoneInfo)) {
ancestorUserBone = userBoneInfo.parentName;
if (ancestorUserBone == descendantOfUserBoneName) {
return;
}
userBone = ancestorUserBone;
} else {
break;
}
}
// bone rule fails if no ancestor of given user bone matched the descendant of name (no early return)
failedBoneRules.Add(boneRule, "The bone mapped to " + userBoneInfo.humanName + " in Humanoid (" + userBoneName +
") is not a child of the bone mapped to " + descendantOfHumanName + " in Humanoid (" +
descendantOfUserBoneName + ").");
}
static void CheckAsymmetricalMappingRule(BoneRule boneRule, string[] mappingSuffixes, string appendage) {
int leftCount = 0;
int rightCount = 0;
// add Left/Right to each mapping suffix to make Humanoid mapping names,
// and count the number of bones mapped in Humanoid on each side
foreach (string mappingSuffix in mappingSuffixes) {
string leftMapping = "Left" + mappingSuffix;
string rightMapping = "Right" + mappingSuffix;
if (humanoidToUserBoneMappings.ContainsKey(leftMapping)) {
++leftCount;
}
if (humanoidToUserBoneMappings.ContainsKey(rightMapping)) {
++rightCount;
}
}
// bone rule fails if number of left appendage mappings doesn't match number of right appendage mappings
if (leftCount != rightCount) {
failedBoneRules.Add(boneRule, "The number of bones mapped in Humanoid for the left " + appendage + " (" +
leftCount + ") does not match the number of bones mapped in Humanoid for the right " +
appendage + " (" + rightCount + ").");
}
}
}
class ExportProjectWindow : EditorWindow {
const int MIN_WIDTH = 450;
const int MIN_HEIGHT = 250;
const int WINDOW_WIDTH = 500;
const int WINDOW_HEIGHT = 460;
const int BUTTON_FONT_SIZE = 16;
const int LABEL_FONT_SIZE = 16;
const int TEXT_FIELD_FONT_SIZE = 14;
const int TEXT_FIELD_HEIGHT = 20;
const int ERROR_FONT_SIZE = 12;
const int WARNING_SCROLL_HEIGHT = 170;
const string EMPTY_ERROR_TEXT = "None\n";
string projectName = "";
string projectLocation = "";
string projectDirectory = "";
string errorLabel = "\n";
string errorText = EMPTY_ERROR_TEXT;
string warningText = "";
Vector2 warningScrollPosition = new Vector2(0, 0);
public delegate void OnCloseDelegate(string projectDirectory, string projectName);
public delegate void OnCloseDelegate(string projectDirectory, string projectName, string warnings);
OnCloseDelegate onCloseCallback;
public void Init(string initialPath, OnCloseDelegate closeCallback) {
minSize = new Vector2(MIN_WIDTH, MIN_HEIGHT);
public void Init(string initialPath, string warnings, OnCloseDelegate closeCallback) {
minSize = new Vector2(WINDOW_WIDTH, WINDOW_HEIGHT);
maxSize = new Vector2(WINDOW_WIDTH, WINDOW_HEIGHT);
titleContent.text = "Export New Avatar";
projectLocation = initialPath;
warningText = warnings;
onCloseCallback = closeCallback;
ShowUtility();
}
@ -513,6 +910,9 @@ class ExportProjectWindow : EditorWindow {
GUIStyle errorStyle = new GUIStyle(GUI.skin.label);
errorStyle.fontSize = ERROR_FONT_SIZE;
errorStyle.normal.textColor = Color.red;
errorStyle.wordWrap = true;
GUIStyle warningStyle = new GUIStyle(errorStyle);
warningStyle.normal.textColor = Color.yellow;
GUILayout.Space(10);
@ -534,10 +934,20 @@ class ExportProjectWindow : EditorWindow {
}
}
// Red error label text to display any issues under text fields and Browse button
GUILayout.Label(errorLabel, errorStyle);
// Red error label text to display any file-related errors
GUILayout.Label("Error:", errorStyle);
GUILayout.Label(errorText, errorStyle);
GUILayout.Space(20);
GUILayout.Space(10);
// Yellow warning label text to display scrollable list of any bone-related warnings
GUILayout.Label("Warnings:", warningStyle);
warningScrollPosition = GUILayout.BeginScrollView(warningScrollPosition, GUILayout.Width(WINDOW_WIDTH),
GUILayout.Height(WARNING_SCROLL_HEIGHT));
GUILayout.Label(warningText, warningStyle);
GUILayout.EndScrollView();
GUILayout.Space(10);
// Export button which will verify project folder can actually be created
// before closing popup window and calling back to initiate the export
@ -546,7 +956,7 @@ class ExportProjectWindow : EditorWindow {
export = true;
if (!CheckForErrors(true)) {
Close();
onCloseCallback(projectDirectory, projectName);
onCloseCallback(projectDirectory, projectName, warningText);
}
}
@ -562,12 +972,12 @@ class ExportProjectWindow : EditorWindow {
}
bool CheckForErrors(bool exporting) {
errorLabel = "\n"; // default to no error
errorText = EMPTY_ERROR_TEXT; // default to None if no errors found
projectDirectory = projectLocation + "\\" + projectName + "\\";
if (projectName.Length > 0) {
// new project must have a unique folder name since the folder will be created for it
if (Directory.Exists(projectDirectory)) {
errorLabel = "A folder with the name " + projectName +
errorText = "A folder with the name " + projectName +
" already exists at that location.\nPlease choose a different project name or location.";
return true;
}
@ -575,7 +985,7 @@ class ExportProjectWindow : EditorWindow {
if (projectLocation.Length > 0) {
// before clicking Export we can verify that the project location at least starts with a drive
if (!Char.IsLetter(projectLocation[0]) || projectLocation.Length == 1 || projectLocation[1] != ':') {
errorLabel = "Project location is invalid. Please choose a different project location.\n";
errorText = "Project location is invalid. Please choose a different project location.\n";
return true;
}
}
@ -583,16 +993,16 @@ class ExportProjectWindow : EditorWindow {
// when exporting, project name and location must both be defined, and project location must
// be valid and accessible (we attempt to create the project folder at this time to verify this)
if (projectName.Length == 0) {
errorLabel = "Please define a project name.\n";
errorText = "Please define a project name.\n";
return true;
} else if (projectLocation.Length == 0) {
errorLabel = "Please define a project location.\n";
errorText = "Please define a project location.\n";
return true;
} else {
try {
Directory.CreateDirectory(projectDirectory);
} catch {
errorLabel = "Project location is invalid. Please choose a different project location.\n";
errorText = "Project location is invalid. Please choose a different project location.\n";
return true;
}
}

View file

@ -1,3 +1,7 @@
High Fidelity, Inc.
Avatar Exporter
Version 0.1
Note: It is recommended to use Unity versions between 2017.4.17f1 and 2018.2.12f1 for this Avatar Exporter.
To create a new avatar project: