Merge branch 'master' of github.com:highfidelity/hifi into 20816-installOnAndroid

This commit is contained in:
NissimHadar 2019-01-25 18:02:59 -08:00
commit 79f096fb7e
166 changed files with 4251 additions and 3265 deletions

View file

@ -81,7 +81,7 @@ if (ANDROID)
set(GLES_OPTION ON)
set(PLATFORM_QT_COMPONENTS AndroidExtras WebView)
else ()
set(PLATFORM_QT_COMPONENTS WebEngine Xml)
set(PLATFORM_QT_COMPONENTS WebEngine)
endif ()
if (USE_GLES AND (NOT ANDROID))

View file

@ -32,8 +32,8 @@ To produce an executable installer on Windows, the following are required:
1. [InetC Plug-in for Nullsoft](http://nsis.sourceforge.net/Inetc_plug-in) - 1.0
1. Extract Zip
1. Copy `Plugins\x86-ansi\InetC.dll` to `C:\Program Files (x86)\NSIS\Plugins\x86-ansi\`
1. Copy `Plugins\x86-unicode\InetC.dll` to `C:\Program Files (x86)\NSIS\Plugins\x86-unicode\`
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

View file

@ -68,6 +68,13 @@ AudioMixer::AudioMixer(ReceivedMessage& message) :
// hash the available codecs (on the mixer)
_availableCodecs.clear(); // Make sure struct is clean
auto pluginManager = DependencyManager::set<PluginManager>();
// Only load codec plugins; for now assume codec plugins have 'codec' in their name.
auto codecPluginFilter = [](const QJsonObject& metaData) {
QJsonValue nameValue = metaData["MetaData"]["name"];
return nameValue.toString().contains("codec", Qt::CaseInsensitive);
};
pluginManager->setPluginFilter(codecPluginFilter);
auto codecPlugins = pluginManager->getCodecPlugins();
for_each(codecPlugins.cbegin(), codecPlugins.cend(),
[&](const CodecPluginPointer& codec) {

View file

@ -1,36 +0,0 @@
#
# FixupNitpick.cmake
# cmake/macros
#
# Copyright 2019 High Fidelity, Inc.
# Created by Nissim Hadar on January 14th, 2016
#
# Distributed under the Apache License, Version 2.0.
# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
#
macro(fixup_nitpick)
if (APPLE)
string(REPLACE " " "\\ " ESCAPED_BUNDLE_NAME ${NITPICK_BUNDLE_NAME})
string(REPLACE " " "\\ " ESCAPED_INSTALL_PATH ${NITPICK_INSTALL_DIR})
set(_NITPICK_INSTALL_PATH "${ESCAPED_INSTALL_PATH}/${ESCAPED_BUNDLE_NAME}.app")
find_program(MACDEPLOYQT_COMMAND macdeployqt PATHS "${QT_DIR}/bin" NO_DEFAULT_PATH)
if (NOT MACDEPLOYQT_COMMAND AND (PRODUCTION_BUILD OR PR_BUILD))
message(FATAL_ERROR "Could not find macdeployqt at ${QT_DIR}/bin.\
It is required to produce a relocatable nitpick application.\
Check that the environment variable QT_DIR points to your Qt installation.\
")
endif ()
install(CODE "
execute_process(COMMAND ${MACDEPLOYQT_COMMAND}\
\${CMAKE_INSTALL_PREFIX}/${_NITPICK_INSTALL_PATH}/\
-verbose=2 -qmldir=${CMAKE_SOURCE_DIR}/interface/resources/qml/\
)"
COMPONENT ${CLIENT_COMPONENT}
)
endif ()
endmacro()

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

@ -54,7 +54,8 @@ FocusScope {
Image {
z: -10
id: loginDialogBackground
source: "LoginDialog/images/background.jpg"
fillMode: Image.PreserveAspectCrop
source: "LoginDialog/images/background.png"
anchors.fill: parent
}
@ -119,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)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 960 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 272 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 456 KiB

View file

@ -55,7 +55,8 @@ FocusScope {
Image {
z: -10
id: loginDialogBackground
source: "LoginDialog/images/background.jpg"
fillMode: Image.PreserveAspectCrop
source: "LoginDialog/images/background.png"
anchors.fill: parent
}
@ -149,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

@ -1,76 +0,0 @@
//
// Web3DOverlay.qml
//
// Created by Gabriel Calero & Cristian Duarte on Jun 22, 2018
// Copyright 2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
import QtQuick 2.5
import QtGraphicalEffects 1.0
Item {
property string url
RadialGradient {
anchors.fill: parent
gradient: Gradient {
GradientStop { position: 0.0; color: "#262626" }
GradientStop { position: 1.0; color: "#000000" }
}
}
function shortUrl(url) {
var hostBegin = url.indexOf("://");
if (hostBegin > -1) {
url = url.substring(hostBegin + 3);
}
var portBegin = url.indexOf(":");
if (portBegin > -1) {
url = url.substring(0, portBegin);
}
var pathBegin = url.indexOf("/");
if (pathBegin > -1) {
url = url.substring(0, pathBegin);
}
if (url.length > 45) {
url = url.substring(0, 45);
}
return url;
}
Text {
id: urlText
text: shortUrl(url)
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
anchors.fill: parent
anchors.rightMargin: 10
anchors.leftMargin: 10
font.family: "Cairo"
font.weight: Font.DemiBold
font.pointSize: 48
fontSizeMode: Text.Fit
color: "#FFFFFF"
minimumPixelSize: 5
}
Image {
id: hand
source: "../../../icons/hand.svg"
width: 300
height: 300
anchors.bottom: parent.bottom
anchors.right: parent.right
anchors.bottomMargin: 100
anchors.rightMargin: 100
}
}

View file

@ -1,18 +0,0 @@
//
// WebEntityView.qml
//
// Created by Kunal Gosar on 16 March 2017
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
import QtQuick 2.5
import "."
WebView {
viewProfile: FileTypeProfile;
urlTag: "noDownload=true";
}

View file

@ -58,7 +58,7 @@ Item {
keyItem.state = "mouseOver";
var globalPosition = keyItem.mapToGlobal(mouseArea1.mouseX, mouseArea1.mouseY);
var pointerID = Web3DOverlay.deviceIdByTouchPoint(globalPosition.x, globalPosition.y);
var pointerID = QmlSurface.deviceIdByTouchPoint(globalPosition.x, globalPosition.y);
if (Pointers.isLeftHand(pointerID)) {
Controller.triggerHapticPulse(_HAPTIC_STRENGTH, _HAPTIC_DURATION, leftHand);

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;
}
}
@ -95,7 +97,8 @@ FocusScope {
Image {
z: -10
id: loginDialogBackground
source: "../LoginDialog/images/background_tablet.jpg"
fillMode: Image.PreserveAspectCrop
source: "../LoginDialog/images/background_tablet.png"
anchors.fill: parent
}
@ -168,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

@ -148,7 +148,7 @@ Windows.ScrollingWindow {
}
function canAddToWorld(path) {
var supportedExtensions = [/\.fbx\b/i, /\.obj\b/i, /\.jpg\b/i, /\.png\b/i];
var supportedExtensions = [/\.fbx\b/i, /\.obj\b/i, /\.jpg\b/i, /\.png\b/i, /\.gltf\b/i];
if (selectedItemCount > 1) {
return 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

@ -148,7 +148,7 @@ Rectangle {
}
function canAddToWorld(path) {
var supportedExtensions = [/\.fbx\b/i, /\.obj\b/i, /\.jpg\b/i, /\.png\b/i];
var supportedExtensions = [/\.fbx\b/i, /\.obj\b/i, /\.jpg\b/i, /\.png\b/i, /\.gltf\b/i];
if (selectedItemCount > 1) {
return false;

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>
@ -151,6 +152,7 @@
#include <trackers/EyeTracker.h>
#include <avatars-renderer/ScriptAvatar.h>
#include <RenderableEntityItem.h>
#include <RenderableWebEntityItem.h>
#include <model-networking/MaterialCache.h>
#include "recording/ClipCache.h"
@ -799,7 +801,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)
@ -2320,6 +2321,76 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
return DependencyManager::get<AvatarManager>()->getMyAvatar()->getWorldOrientation() * Vectors::UP;
});
render::entities::WebEntityRenderer::setAcquireWebSurfaceOperator([this](const QString& url, bool htmlContent, QSharedPointer<OffscreenQmlSurface>& webSurface, bool& cachedWebSurface) {
bool isTablet = url == TabletScriptingInterface::QML;
if (htmlContent) {
webSurface = DependencyManager::get<OffscreenQmlSurfaceCache>()->acquire(render::entities::WebEntityRenderer::QML);
cachedWebSurface = true;
auto rootItemLoadedFunctor = [url, webSurface] {
webSurface->getRootItem()->setProperty(render::entities::WebEntityRenderer::URL_PROPERTY, url);
};
if (webSurface->getRootItem()) {
rootItemLoadedFunctor();
} else {
QObject::connect(webSurface.data(), &hifi::qml::OffscreenSurface::rootContextCreated, rootItemLoadedFunctor);
}
} else {
// FIXME: the tablet should use the OffscreenQmlSurfaceCache
webSurface = QSharedPointer<OffscreenQmlSurface>(new OffscreenQmlSurface(), [](OffscreenQmlSurface* webSurface) {
AbstractViewStateInterface::instance()->sendLambdaEvent([webSurface] {
// WebEngineView may run other threads (wasapi), so they must be deleted for a clean shutdown
// if the application has already stopped its event loop, delete must be explicit
delete webSurface;
});
});
auto rootItemLoadedFunctor = [webSurface, url, isTablet] {
Application::setupQmlSurface(webSurface->getSurfaceContext(), isTablet || url == OVERLAY_LOGIN_DIALOG.toString());
};
if (webSurface->getRootItem()) {
rootItemLoadedFunctor();
} else {
QObject::connect(webSurface.data(), &hifi::qml::OffscreenSurface::rootContextCreated, rootItemLoadedFunctor);
}
webSurface->load(url);
cachedWebSurface = false;
}
const uint8_t DEFAULT_MAX_FPS = 10;
const uint8_t TABLET_FPS = 90;
webSurface->setMaxFps(isTablet ? TABLET_FPS : DEFAULT_MAX_FPS);
});
render::entities::WebEntityRenderer::setReleaseWebSurfaceOperator([this](QSharedPointer<OffscreenQmlSurface>& webSurface, bool& cachedWebSurface, std::vector<QMetaObject::Connection>& connections) {
QQuickItem* rootItem = webSurface->getRootItem();
// Fix for crash in QtWebEngineCore when rapidly switching domains
// Call stop on the QWebEngineView before destroying OffscreenQMLSurface.
if (rootItem) {
// stop loading
QMetaObject::invokeMethod(rootItem, "stop");
}
webSurface->pause();
for (auto& connection : connections) {
QObject::disconnect(connection);
}
connections.clear();
// If the web surface was fetched out of the cache, release it back into the cache
if (cachedWebSurface) {
// If it's going back into the cache make sure to explicitly set the URL to a blank page
// in order to stop any resource consumption or audio related to the page.
if (rootItem) {
rootItem->setProperty("url", "about:blank");
}
auto offscreenCache = DependencyManager::get<OffscreenQmlSurfaceCache>();
if (offscreenCache) {
offscreenCache->release(render::entities::WebEntityRenderer::QML, webSurface);
}
cachedWebSurface = false;
}
webSurface.reset();
});
// Preload Tablet sounds
DependencyManager::get<TabletScriptingInterface>()->preloadSounds();
DependencyManager::get<Keyboard>()->createKeyboard();
@ -2666,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
@ -3021,7 +3093,7 @@ void Application::initializeUi() {
});
offscreenSurfaceCache->reserve(TabletScriptingInterface::QML, 1);
offscreenSurfaceCache->reserve(Web3DOverlay::QML, 2);
offscreenSurfaceCache->reserve(render::entities::WebEntityRenderer::QML, 2);
#endif
flushMenuUpdates();
@ -3161,6 +3233,61 @@ void Application::onDesktopRootItemCreated(QQuickItem* rootItem) {
#endif
}
void Application::setupQmlSurface(QQmlContext* surfaceContext, bool setAdditionalContextProperties) {
surfaceContext->setContextProperty("Users", DependencyManager::get<UsersScriptingInterface>().data());
surfaceContext->setContextProperty("HMD", DependencyManager::get<HMDScriptingInterface>().data());
surfaceContext->setContextProperty("UserActivityLogger", DependencyManager::get<UserActivityLoggerScriptingInterface>().data());
surfaceContext->setContextProperty("Preferences", DependencyManager::get<Preferences>().data());
surfaceContext->setContextProperty("Vec3", new Vec3());
surfaceContext->setContextProperty("Quat", new Quat());
surfaceContext->setContextProperty("MyAvatar", DependencyManager::get<AvatarManager>()->getMyAvatar().get());
surfaceContext->setContextProperty("Entities", DependencyManager::get<EntityScriptingInterface>().data());
surfaceContext->setContextProperty("Snapshot", DependencyManager::get<Snapshot>().data());
if (setAdditionalContextProperties) {
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
auto flags = tabletScriptingInterface->getFlags();
surfaceContext->setContextProperty("offscreenFlags", flags);
surfaceContext->setContextProperty("AddressManager", DependencyManager::get<AddressManager>().data());
surfaceContext->setContextProperty("Settings", SettingsScriptingInterface::getInstance());
surfaceContext->setContextProperty("MenuInterface", MenuScriptingInterface::getInstance());
surfaceContext->setContextProperty("KeyboardScriptingInterface", DependencyManager::get<KeyboardScriptingInterface>().data());
surfaceContext->setContextProperty("Account", AccountServicesScriptingInterface::getInstance()); // DEPRECATED - TO BE REMOVED
surfaceContext->setContextProperty("GlobalServices", AccountServicesScriptingInterface::getInstance()); // DEPRECATED - TO BE REMOVED
surfaceContext->setContextProperty("AccountServices", AccountServicesScriptingInterface::getInstance());
// in Qt 5.10.0 there is already an "Audio" object in the QML context
// though I failed to find it (from QtMultimedia??). So.. let it be "AudioScriptingInterface"
surfaceContext->setContextProperty("AudioScriptingInterface", DependencyManager::get<AudioScriptingInterface>().data());
surfaceContext->setContextProperty("AudioStats", DependencyManager::get<AudioClient>()->getStats().data());
surfaceContext->setContextProperty("fileDialogHelper", new FileDialogHelper());
surfaceContext->setContextProperty("ScriptDiscoveryService", DependencyManager::get<ScriptEngines>().data());
surfaceContext->setContextProperty("Assets", DependencyManager::get<AssetMappingsScriptingInterface>().data());
surfaceContext->setContextProperty("LODManager", DependencyManager::get<LODManager>().data());
surfaceContext->setContextProperty("OctreeStats", DependencyManager::get<OctreeStatsProvider>().data());
surfaceContext->setContextProperty("DCModel", DependencyManager::get<DomainConnectionModel>().data());
surfaceContext->setContextProperty("AvatarInputs", AvatarInputs::getInstance());
surfaceContext->setContextProperty("AvatarList", DependencyManager::get<AvatarManager>().data());
surfaceContext->setContextProperty("DialogsManager", DialogsManagerScriptingInterface::getInstance());
surfaceContext->setContextProperty("InputConfiguration", DependencyManager::get<InputConfiguration>().data());
surfaceContext->setContextProperty("SoundCache", DependencyManager::get<SoundCacheScriptingInterface>().data());
surfaceContext->setContextProperty("AvatarBookmarks", DependencyManager::get<AvatarBookmarks>().data());
surfaceContext->setContextProperty("Render", AbstractViewStateInterface::instance()->getRenderEngine()->getConfiguration().get());
surfaceContext->setContextProperty("Workload", qApp->getGameWorkload()._engine->getConfiguration().get());
surfaceContext->setContextProperty("Controller", DependencyManager::get<controller::ScriptingInterface>().data());
surfaceContext->setContextProperty("Pointers", DependencyManager::get<PointerScriptingInterface>().data());
surfaceContext->setContextProperty("Window", DependencyManager::get<WindowScriptingInterface>().data());
surfaceContext->setContextProperty("Reticle", qApp->getApplicationCompositor().getReticleInterface());
surfaceContext->setContextProperty("HiFiAbout", AboutUtil::getInstance());
surfaceContext->setContextProperty("WalletScriptingInterface", DependencyManager::get<WalletScriptingInterface>().data());
surfaceContext->setContextProperty("ResourceRequestObserver", DependencyManager::get<ResourceRequestObserver>().data());
}
}
void Application::updateCamera(RenderArgs& renderArgs, float deltaTime) {
PROFILE_RANGE(render, __FUNCTION__);
PerformanceTimer perfTimer("updateCamera");
@ -4747,6 +4874,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();
@ -5931,6 +6062,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()),
@ -5944,7 +6082,8 @@ void Application::update(float deltaTime) {
myAvatar->getRightArmCalibrationMat(),
myAvatar->getLeftArmCalibrationMat(),
myAvatar->getRightHandCalibrationMat(),
myAvatar->getLeftHandCalibrationMat()
myAvatar->getLeftHandCalibrationMat(),
hmdAvatarAlignmentType
};
InputPluginPointer keyboardMousePlugin;

View file

@ -589,6 +589,8 @@ private:
void maybeToggleMenuVisible(QMouseEvent* event) const;
void toggleTabletUI(bool shouldOpen = false) const;
static void setupQmlSurface(QQmlContext* surfaceContext, bool setAdditionalContextProperties);
MainWindow* _window;
QElapsedTimer& _sessionRunTimer;

2
interface/src/avatar/AvatarManager.cpp Normal file → Executable file
View file

@ -270,7 +270,6 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
if (avatar->getSkeletonModel()->isLoaded()) {
// remove the orb if it is there
avatar->removeOrb();
avatar->updateCollisionGroup(_myAvatar->getOtherAvatarsCollisionsEnabled());
if (avatar->needsPhysicsUpdate()) {
_avatarsToChangeInPhysics.insert(avatar);
}
@ -376,7 +375,6 @@ void AvatarManager::simulateAvatarFades(float deltaTime) {
}
AvatarSharedPointer AvatarManager::newSharedAvatar(const QUuid& sessionUUID) {
auto otherAvatar = new OtherAvatar(qApp->thread());
otherAvatar->setSessionUUID(sessionUUID);
auto nodeList = DependencyManager::get<NodeList>();

6
interface/src/avatar/AvatarMotionState.cpp Normal file → Executable file
View file

@ -15,7 +15,6 @@
#include <PhysicsEngine.h>
#include <PhysicsHelpers.h>
AvatarMotionState::AvatarMotionState(OtherAvatarPointer avatar, const btCollisionShape* shape) : ObjectMotionState(shape), _avatar(avatar) {
assert(_avatar);
_type = MOTIONSTATE_TYPE_AVATAR;
@ -172,7 +171,10 @@ QUuid AvatarMotionState::getSimulatorID() const {
// virtual
void AvatarMotionState::computeCollisionGroupAndMask(int32_t& group, int32_t& mask) const {
group = _collisionGroup;
mask = _collisionGroup == BULLET_COLLISION_GROUP_COLLISIONLESS ? 0 : Physics::getDefaultCollisionMask(group);
mask = Physics::getDefaultCollisionMask(group);
if (!_avatar->getCollideWithOtherAvatars()) {
mask &= ~(BULLET_COLLISION_GROUP_MY_AVATAR | BULLET_COLLISION_GROUP_OTHER_AVATAR);
}
}
// virtual

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));
@ -205,12 +238,12 @@ MyAvatar::MyAvatar(QThread* thread) :
if (recordingInterface->getPlayFromCurrentLocation()) {
setRecordingBasis();
}
_previousCollisionGroup = _characterController.computeCollisionGroup();
_previousCollisionMask = _characterController.computeCollisionMask();
_characterController.setCollisionless(true);
} else {
clearRecordingBasis();
useFullAvatarURL(_fullAvatarURLFromPreferences, _fullAvatarModelName);
if (_previousCollisionGroup != BULLET_COLLISION_GROUP_COLLISIONLESS) {
if (_previousCollisionMask != BULLET_COLLISION_MASK_COLLISIONLESS) {
_characterController.setCollisionless(false);
}
}
@ -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));
@ -2534,7 +2588,7 @@ void MyAvatar::updateMotors() {
float verticalMotorTimescale;
if (_characterController.getState() == CharacterController::State::Hover ||
_characterController.computeCollisionGroup() == BULLET_COLLISION_GROUP_COLLISIONLESS) {
_characterController.computeCollisionMask() == BULLET_COLLISION_MASK_COLLISIONLESS) {
horizontalMotorTimescale = FLYING_MOTOR_TIMESCALE;
verticalMotorTimescale = FLYING_MOTOR_TIMESCALE;
} else {
@ -2544,7 +2598,7 @@ void MyAvatar::updateMotors() {
if (_motionBehaviors & AVATAR_MOTION_ACTION_MOTOR_ENABLED) {
if (_characterController.getState() == CharacterController::State::Hover ||
_characterController.computeCollisionGroup() == BULLET_COLLISION_GROUP_COLLISIONLESS) {
_characterController.computeCollisionMask() == BULLET_COLLISION_MASK_COLLISIONLESS) {
motorRotation = getMyHead()->getHeadOrientation();
} else {
// non-hovering = walking: follow camera twist about vertical but not lift
@ -2599,7 +2653,7 @@ void MyAvatar::prepareForPhysicsSimulation() {
qDebug() << "Warning: getParentVelocity failed" << getID();
parentVelocity = glm::vec3();
}
_characterController.handleChangedCollisionGroup();
_characterController.handleChangedCollisionMask();
_characterController.setParentVelocity(parentVelocity);
_characterController.setScaleFactor(getSensorToWorldScale());
@ -3279,7 +3333,7 @@ void MyAvatar::updateOrientation(float deltaTime) {
head->setBaseRoll(ROLL(euler));
} else {
head->setBaseYaw(0.0f);
head->setBasePitch(getHead()->getBasePitch() + getDriveKey(PITCH) * _pitchSpeed * deltaTime
head->setBasePitch(getHead()->getBasePitch() + getDriveKey(PITCH) * _pitchSpeed * deltaTime
+ getDriveKey(DELTA_PITCH) * _pitchSpeed / PITCH_SPEED_DEFAULT);
head->setBaseRoll(0.0f);
}
@ -3325,7 +3379,7 @@ void MyAvatar::updateActionMotor(float deltaTime) {
glm::vec3 direction = forward + right;
if (state == CharacterController::State::Hover ||
_characterController.computeCollisionGroup() == BULLET_COLLISION_GROUP_COLLISIONLESS) {
_characterController.computeCollisionMask() == BULLET_COLLISION_MASK_COLLISIONLESS) {
glm::vec3 up = (getDriveKey(TRANSLATE_Y)) * IDENTITY_UP;
direction += up;
}
@ -3881,7 +3935,7 @@ void MyAvatar::setCollisionsEnabled(bool enabled) {
bool MyAvatar::getCollisionsEnabled() {
// may return 'false' even though the collisionless option was requested
// because the zone may disallow collisionless avatars
return _characterController.computeCollisionGroup() != BULLET_COLLISION_GROUP_COLLISIONLESS;
return _characterController.computeCollisionMask() != BULLET_COLLISION_MASK_COLLISIONLESS;
}
void MyAvatar::setOtherAvatarsCollisionsEnabled(bool enabled) {
@ -3890,7 +3944,11 @@ void MyAvatar::setOtherAvatarsCollisionsEnabled(bool enabled) {
QMetaObject::invokeMethod(this, "setOtherAvatarsCollisionsEnabled", Q_ARG(bool, enabled));
return;
}
bool change = _collideWithOtherAvatars != enabled;
_collideWithOtherAvatars = enabled;
if (change) {
setCollisionWithOtherAvatarsFlags();
}
emit otherAvatarsCollisionsEnabledChanged(enabled);
}
@ -3898,6 +3956,11 @@ bool MyAvatar::getOtherAvatarsCollisionsEnabled() {
return _collideWithOtherAvatars;
}
void MyAvatar::setCollisionWithOtherAvatarsFlags() {
_characterController.setCollideWithOtherAvatars(_collideWithOtherAvatars);
_characterController.setPendingFlagsUpdateCollisionMask();
}
void MyAvatar::updateCollisionCapsuleCache() {
glm::vec3 start, end;
float radius;

30
interface/src/avatar/MyAvatar.h Normal file → Executable file
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;
@ -297,6 +298,8 @@ public:
void reset(bool andRecenter = false, bool andReload = true, bool andHead = true);
void setCollisionWithOtherAvatarsFlags() override;
/**jsdoc
* @function MyAvatar.resetSensorsAndBody
*/
@ -517,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
@ -1583,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
@ -1732,7 +1753,7 @@ private:
SharedSoundPointer _collisionSound;
MyCharacterController _characterController;
int32_t _previousCollisionGroup { BULLET_COLLISION_GROUP_MY_AVATAR };
int32_t _previousCollisionMask { BULLET_COLLISION_MASK_MY_AVATAR };
AvatarWeakPointer _lookAtTargetAvatar;
glm::vec3 _targetAvatarPosition;
@ -1771,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
@ -1944,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;
@ -1960,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

@ -202,6 +202,29 @@ bool MyCharacterController::testRayShotgun(const glm::vec3& position, const glm:
return result.hitFraction < 1.0f;
}
int32_t MyCharacterController::computeCollisionMask() const {
int32_t collisionMask = BULLET_COLLISION_MASK_MY_AVATAR;
if (_collisionless && _collisionlessAllowed) {
collisionMask = BULLET_COLLISION_MASK_COLLISIONLESS;
} else if (!_collideWithOtherAvatars) {
collisionMask &= ~BULLET_COLLISION_GROUP_OTHER_AVATAR;
}
return collisionMask;
}
void MyCharacterController::handleChangedCollisionMask() {
if (_pendingFlags & PENDING_FLAG_UPDATE_COLLISION_MASK) {
// ATM the easiest way to update collision groups/masks is to remove/re-add the RigidBody
if (_dynamicsWorld) {
_dynamicsWorld->removeRigidBody(_rigidBody);
int32_t collisionMask = computeCollisionMask();
_dynamicsWorld->addRigidBody(_rigidBody, BULLET_COLLISION_GROUP_MY_AVATAR, collisionMask);
}
_pendingFlags &= ~PENDING_FLAG_UPDATE_COLLISION_MASK;
updateCurrentGravity();
}
}
btConvexHullShape* MyCharacterController::computeShape() const {
// HACK: the avatar collides using convex hull with a collision margin equal to
// the old capsule radius. Two points define a capsule and additional points are

6
interface/src/avatar/MyCharacterController.h Normal file → Executable file
View file

@ -42,6 +42,12 @@ public:
void setDensity(btScalar density) { _density = density; }
int32_t computeCollisionMask() const override;
void handleChangedCollisionMask() override;
bool _collideWithOtherAvatars{ true };
void setCollideWithOtherAvatars(bool collideWithOtherAvatars) { _collideWithOtherAvatars = collideWithOtherAvatars; }
protected:
void initRayShotgun(const btCollisionWorld* world);
void updateMassProperties() override;

4
interface/src/avatar/MySkeletonModel.cpp Normal file → Executable file
View file

@ -187,7 +187,7 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
}
}
bool isFlying = (myAvatar->getCharacterController()->getState() == CharacterController::State::Hover || myAvatar->getCharacterController()->computeCollisionGroup() == BULLET_COLLISION_GROUP_COLLISIONLESS);
bool isFlying = (myAvatar->getCharacterController()->getState() == CharacterController::State::Hover || myAvatar->getCharacterController()->computeCollisionMask() == BULLET_COLLISION_MASK_COLLISIONLESS);
if (isFlying != _prevIsFlying) {
const float FLY_TO_IDLE_HIPS_TRANSITION_TIME = 0.5f;
_flyIdleTimer = FLY_TO_IDLE_HIPS_TRANSITION_TIME;
@ -198,7 +198,7 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
// if hips are not under direct control, estimate the hips position.
if (avatarHeadPose.isValid() && !(params.primaryControllerFlags[Rig::PrimaryControllerType_Hips] & (uint8_t)Rig::ControllerFlags::Enabled)) {
bool isFlying = (myAvatar->getCharacterController()->getState() == CharacterController::State::Hover || myAvatar->getCharacterController()->computeCollisionGroup() == BULLET_COLLISION_GROUP_COLLISIONLESS);
bool isFlying = (myAvatar->getCharacterController()->getState() == CharacterController::State::Hover || myAvatar->getCharacterController()->computeCollisionMask() == BULLET_COLLISION_MASK_COLLISIONLESS);
// timescale in seconds
const float TRANS_HORIZ_TIMESCALE = 0.15f;

12
interface/src/avatar/OtherAvatar.cpp Normal file → Executable file
View file

@ -135,17 +135,9 @@ void OtherAvatar::rebuildCollisionShape() {
}
}
void OtherAvatar::updateCollisionGroup(bool myAvatarCollide) {
void OtherAvatar::setCollisionWithOtherAvatarsFlags() {
if (_motionState) {
bool collides = _motionState->getCollisionGroup() == BULLET_COLLISION_GROUP_OTHER_AVATAR && myAvatarCollide;
if (_collideWithOtherAvatars != collides) {
if (!myAvatarCollide) {
_collideWithOtherAvatars = false;
}
auto newCollisionGroup = _collideWithOtherAvatars ? BULLET_COLLISION_GROUP_OTHER_AVATAR : BULLET_COLLISION_GROUP_COLLISIONLESS;
_motionState->setCollisionGroup(newCollisionGroup);
_motionState->addDirtyFlags(Simulation::DIRTY_COLLISION_GROUP);
}
_motionState->addDirtyFlags(Simulation::DIRTY_COLLISION_GROUP);
}
}

4
interface/src/avatar/OtherAvatar.h Normal file → Executable file
View file

@ -46,7 +46,9 @@ public:
bool shouldBeInPhysicsSimulation() const;
bool needsPhysicsUpdate() const;
void updateCollisionGroup(bool myAvatarCollide);
bool getCollideWithOtherAvatars() const { return _collideWithOtherAvatars; }
void setCollisionWithOtherAvatarsFlags() override;
void simulate(float deltaTime, bool inView) override;

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

@ -114,14 +114,9 @@ void ModelOverlay::update(float deltatime) {
_model->setVisibleInScene(getVisible(), scene);
metaDirty = true;
}
if (_drawInFrontDirty) {
_drawInFrontDirty = false;
_model->setLayeredInFront(getDrawInFront(), scene);
metaDirty = true;
}
if (_drawInHUDDirty) {
_drawInHUDDirty = false;
_model->setLayeredInHUD(getDrawHUDLayer(), scene);
if (_renderLayerDirty) {
_renderLayerDirty = false;
_model->setHifiRenderLayer(_drawHUDLayer ? render::hifi::LAYER_3D_HUD : (_drawInFront ? render::hifi::LAYER_3D_FRONT : render::hifi::LAYER_3D), scene);
metaDirty = true;
}
if (_groupCulledDirty) {
@ -175,14 +170,14 @@ void ModelOverlay::setVisible(bool visible) {
void ModelOverlay::setDrawInFront(bool drawInFront) {
if (drawInFront != getDrawInFront()) {
Base3DOverlay::setDrawInFront(drawInFront);
_drawInFrontDirty = true;
_renderLayerDirty = true;
}
}
void ModelOverlay::setDrawHUDLayer(bool drawHUDLayer) {
if (drawHUDLayer != getDrawHUDLayer()) {
Base3DOverlay::setDrawHUDLayer(drawHUDLayer);
_drawInHUDDirty = true;
_renderLayerDirty = true;
}
}

View file

@ -126,8 +126,7 @@ private:
QVector<int> _jointMapping; // domain is index into model-joints, range is index into animation-joints
bool _visibleDirty { true };
bool _drawInFrontDirty { false };
bool _drawInHUDDirty { false };
bool _renderLayerDirty { false };
bool _isGroupCulled { false };
bool _groupCulledDirty { false };

View file

@ -64,20 +64,13 @@
#include "AboutUtil.h"
#include "ResourceRequestObserver.h"
#include <RenderableWebEntityItem.h>
static int MAX_WINDOW_SIZE = 4096;
static const float METERS_TO_INCHES = 39.3701f;
static const float OPAQUE_ALPHA_THRESHOLD = 0.99f;
const QString Web3DOverlay::TYPE = "web3d";
const QString Web3DOverlay::QML = "Web3DOverlay.qml";
static auto qmlSurfaceDeleter = [](OffscreenQmlSurface* surface) {
AbstractViewStateInterface::instance()->sendLambdaEvent([surface] {
// WebEngineView may run other threads (wasapi), so they must be deleted for a clean shutdown
// if the application has already stopped its event loop, delete must be explicit
delete surface;
});
};
Web3DOverlay::Web3DOverlay() {
_touchDevice.setCapabilities(QTouchDevice::Position);
@ -87,34 +80,21 @@ Web3DOverlay::Web3DOverlay() {
_geometryId = DependencyManager::get<GeometryCache>()->allocateID();
connect(this, &Web3DOverlay::requestWebSurface, this, &Web3DOverlay::buildWebSurface);
connect(this, &Web3DOverlay::releaseWebSurface, this, &Web3DOverlay::destroyWebSurface);
connect(this, &Web3DOverlay::resizeWebSurface, this, &Web3DOverlay::onResizeWebSurface);
//need to be intialized before Tablet 1st open
_webSurface = DependencyManager::get<OffscreenQmlSurfaceCache>()->acquire(QML);
_cachedWebSurface = true;
_webSurface->getSurfaceContext()->setContextProperty("HMD", DependencyManager::get<HMDScriptingInterface>().data());
_webSurface->getSurfaceContext()->setContextProperty("Account", AccountServicesScriptingInterface::getInstance()); // DEPRECATED - TO BE REMOVED
_webSurface->getSurfaceContext()->setContextProperty("GlobalServices", AccountServicesScriptingInterface::getInstance()); // DEPRECATED - TO BE REMOVED
_webSurface->getSurfaceContext()->setContextProperty("AccountServices", AccountServicesScriptingInterface::getInstance());
_webSurface->getSurfaceContext()->setContextProperty("AddressManager", DependencyManager::get<AddressManager>().data());
buildWebSurface(true);
}
Web3DOverlay::Web3DOverlay(const Web3DOverlay* Web3DOverlay) :
Billboard3DOverlay(Web3DOverlay),
_url(Web3DOverlay->_url),
_scriptURL(Web3DOverlay->_scriptURL),
_dpi(Web3DOverlay->_dpi),
_showKeyboardFocusHighlight(Web3DOverlay->_showKeyboardFocusHighlight)
_dpi(Web3DOverlay->_dpi)
{
_geometryId = DependencyManager::get<GeometryCache>()->allocateID();
}
Web3DOverlay::~Web3DOverlay() {
disconnect(this, &Web3DOverlay::requestWebSurface, this, nullptr);
disconnect(this, &Web3DOverlay::releaseWebSurface, this, nullptr);
disconnect(this, &Web3DOverlay::resizeWebSurface, this, nullptr);
destroyWebSurface();
auto geometryCache = DependencyManager::get<GeometryCache>();
if (geometryCache) {
@ -128,74 +108,22 @@ void Web3DOverlay::rebuildWebSurface() {
}
void Web3DOverlay::destroyWebSurface() {
if (!_webSurface) {
return;
if (_webSurface) {
render::entities::WebEntityRenderer::releaseWebSurface(_webSurface, _cachedWebSurface, _connections);
}
QQuickItem* rootItem = _webSurface->getRootItem();
// Fix for crash in QtWebEngineCore when rapidly switching domains
// Call stop on the QWebEngineView before destroying OffscreenQMLSurface.
if (rootItem) {
// stop loading
QMetaObject::invokeMethod(rootItem, "stop");
}
_webSurface->pause();
QObject::disconnect(this, &Web3DOverlay::scriptEventReceived, _webSurface.data(), &OffscreenQmlSurface::emitScriptEvent);
QObject::disconnect(_webSurface.data(), &OffscreenQmlSurface::webEventReceived, this, &Web3DOverlay::webEventReceived);
// If the web surface was fetched out of the cache, release it back into the cache
if (_cachedWebSurface) {
// If it's going back into the cache make sure to explicitly set the URL to a blank page
// in order to stop any resource consumption or audio related to the page.
if (rootItem) {
rootItem->setProperty("url", "about:blank");
}
auto offscreenCache = DependencyManager::get<OffscreenQmlSurfaceCache>();
// FIXME prevents crash on shutdown, but we shoudln't have to do this check
if (offscreenCache) {
offscreenCache->release(QML, _webSurface);
}
_cachedWebSurface = false;
}
_webSurface.reset();
}
void Web3DOverlay::buildWebSurface() {
void Web3DOverlay::buildWebSurface(bool overrideWeb) {
if (_webSurface) {
return;
}
// FIXME the context save here is most likely unecessary since the QML surfaces now render
// off the main thread, and all GL context work is done off the main thread (I *think*)
gl::withSavedContext([&] {
// FIXME, the max FPS could be better managed by being dynamic (based on the number of current surfaces
// and the current rendering load)
if (_currentMaxFPS != _desiredMaxFPS) {
setMaxFPS(_desiredMaxFPS);
}
if (isWebContent()) {
_webSurface = DependencyManager::get<OffscreenQmlSurfaceCache>()->acquire(QML);
_cachedWebSurface = true;
_webSurface->getRootItem()->setProperty("url", _url);
_webSurface->getRootItem()->setProperty("scriptURL", _scriptURL);
} else {
_webSurface = QSharedPointer<OffscreenQmlSurface>(new OffscreenQmlSurface(), qmlSurfaceDeleter);
connect(_webSurface.data(), &hifi::qml::OffscreenSurface::rootContextCreated, [this](QQmlContext* surfaceContext) {
setupQmlSurface(_url == TabletScriptingInterface::QML, _url == OVERLAY_LOGIN_DIALOG.toString());
});
_webSurface->load(_url);
_cachedWebSurface = false;
}
_webSurface->getSurfaceContext()->setContextProperty("globalPosition", vec3toVariant(getWorldPosition()));
onResizeWebSurface();
_webSurface->resume();
});
render::entities::WebEntityRenderer::acquireWebSurface(_url, overrideWeb || isWebContent(), _webSurface, _cachedWebSurface);
onResizeWebSurface();
_webSurface->resume();
QObject::connect(this, &Web3DOverlay::scriptEventReceived, _webSurface.data(), &OffscreenQmlSurface::emitScriptEvent);
QObject::connect(_webSurface.data(), &OffscreenQmlSurface::webEventReceived, this, &Web3DOverlay::webEventReceived);
_connections.push_back(QObject::connect(this, &Web3DOverlay::scriptEventReceived, _webSurface.data(), &OffscreenQmlSurface::emitScriptEvent));
_connections.push_back(QObject::connect(_webSurface.data(), &OffscreenQmlSurface::webEventReceived, this, &Web3DOverlay::webEventReceived));
}
void Web3DOverlay::update(float deltatime) {
@ -215,69 +143,8 @@ bool Web3DOverlay::isWebContent() const {
return false;
}
void Web3DOverlay::setupQmlSurface(bool isTablet, bool isLoginDialog) {
_webSurface->getSurfaceContext()->setContextProperty("Users", DependencyManager::get<UsersScriptingInterface>().data());
_webSurface->getSurfaceContext()->setContextProperty("HMD", DependencyManager::get<HMDScriptingInterface>().data());
_webSurface->getSurfaceContext()->setContextProperty("UserActivityLogger", DependencyManager::get<UserActivityLoggerScriptingInterface>().data());
_webSurface->getSurfaceContext()->setContextProperty("Preferences", DependencyManager::get<Preferences>().data());
_webSurface->getSurfaceContext()->setContextProperty("Vec3", new Vec3());
_webSurface->getSurfaceContext()->setContextProperty("Quat", new Quat());
_webSurface->getSurfaceContext()->setContextProperty("MyAvatar", DependencyManager::get<AvatarManager>()->getMyAvatar().get());
_webSurface->getSurfaceContext()->setContextProperty("Entities", DependencyManager::get<EntityScriptingInterface>().data());
_webSurface->getSurfaceContext()->setContextProperty("Snapshot", DependencyManager::get<Snapshot>().data());
if (isTablet || isLoginDialog) {
_webSurface->getSurfaceContext()->setContextProperty("Account", AccountServicesScriptingInterface::getInstance()); // DEPRECATED - TO BE REMOVED
_webSurface->getSurfaceContext()->setContextProperty("Settings", SettingsScriptingInterface::getInstance());
_webSurface->getSurfaceContext()->setContextProperty("MenuInterface", MenuScriptingInterface::getInstance());
_webSurface->getSurfaceContext()->setContextProperty("KeyboardScriptingInterface", DependencyManager::get<KeyboardScriptingInterface>().data());
}
if (isTablet) {
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
auto flags = tabletScriptingInterface->getFlags();
_webSurface->getSurfaceContext()->setContextProperty("offscreenFlags", flags);
_webSurface->getSurfaceContext()->setContextProperty("AddressManager", DependencyManager::get<AddressManager>().data());
_webSurface->getSurfaceContext()->setContextProperty("GlobalServices", AccountServicesScriptingInterface::getInstance()); // DEPRECATED - TO BE REMOVED
_webSurface->getSurfaceContext()->setContextProperty("AccountServices", AccountServicesScriptingInterface::getInstance());
// in Qt 5.10.0 there is already an "Audio" object in the QML context
// though I failed to find it (from QtMultimedia??). So.. let it be "AudioScriptingInterface"
_webSurface->getSurfaceContext()->setContextProperty("AudioScriptingInterface", DependencyManager::get<AudioScriptingInterface>().data());
_webSurface->getSurfaceContext()->setContextProperty("AudioStats", DependencyManager::get<AudioClient>()->getStats().data());
_webSurface->getSurfaceContext()->setContextProperty("HMD", DependencyManager::get<HMDScriptingInterface>().data());
_webSurface->getSurfaceContext()->setContextProperty("fileDialogHelper", new FileDialogHelper());
_webSurface->getSurfaceContext()->setContextProperty("MyAvatar", DependencyManager::get<AvatarManager>()->getMyAvatar().get());
_webSurface->getSurfaceContext()->setContextProperty("ScriptDiscoveryService", DependencyManager::get<ScriptEngines>().data());
_webSurface->getSurfaceContext()->setContextProperty("Assets", DependencyManager::get<AssetMappingsScriptingInterface>().data());
_webSurface->getSurfaceContext()->setContextProperty("LODManager", DependencyManager::get<LODManager>().data());
_webSurface->getSurfaceContext()->setContextProperty("OctreeStats", DependencyManager::get<OctreeStatsProvider>().data());
_webSurface->getSurfaceContext()->setContextProperty("DCModel", DependencyManager::get<DomainConnectionModel>().data());
_webSurface->getSurfaceContext()->setContextProperty("AvatarInputs", AvatarInputs::getInstance());
_webSurface->getSurfaceContext()->setContextProperty("AvatarList", DependencyManager::get<AvatarManager>().data());
_webSurface->getSurfaceContext()->setContextProperty("DialogsManager", DialogsManagerScriptingInterface::getInstance());
_webSurface->getSurfaceContext()->setContextProperty("InputConfiguration", DependencyManager::get<InputConfiguration>().data());
_webSurface->getSurfaceContext()->setContextProperty("SoundCache", DependencyManager::get<SoundCacheScriptingInterface>().data());
_webSurface->getSurfaceContext()->setContextProperty("AvatarBookmarks", DependencyManager::get<AvatarBookmarks>().data());
_webSurface->getSurfaceContext()->setContextProperty("Render", AbstractViewStateInterface::instance()->getRenderEngine()->getConfiguration().get());
_webSurface->getSurfaceContext()->setContextProperty("Workload", qApp->getGameWorkload()._engine->getConfiguration().get());
_webSurface->getSurfaceContext()->setContextProperty("Controller", DependencyManager::get<controller::ScriptingInterface>().data());
_webSurface->getSurfaceContext()->setContextProperty("Pointers", DependencyManager::get<PointerScriptingInterface>().data());
_webSurface->getSurfaceContext()->setContextProperty("Web3DOverlay", this);
_webSurface->getSurfaceContext()->setContextProperty("Window", DependencyManager::get<WindowScriptingInterface>().data());
_webSurface->getSurfaceContext()->setContextProperty("Reticle", qApp->getApplicationCompositor().getReticleInterface());
_webSurface->getSurfaceContext()->setContextProperty("HiFiAbout", AboutUtil::getInstance());
_webSurface->getSurfaceContext()->setContextProperty("WalletScriptingInterface", DependencyManager::get<WalletScriptingInterface>().data());
_webSurface->getSurfaceContext()->setContextProperty("ResourceRequestObserver", DependencyManager::get<ResourceRequestObserver>().data());
// Override min fps for tablet UI, for silky smooth scrolling
setMaxFPS(90);
}
}
void Web3DOverlay::setMaxFPS(uint8_t maxFPS) {
// FIXME, the max FPS could be better managed by being dynamic (based on the number of current surfaces and the current rendering load)
_desiredMaxFPS = maxFPS;
if (_webSurface) {
_webSurface->setMaxFps(_desiredMaxFPS);
@ -298,21 +165,13 @@ void Web3DOverlay::onResizeWebSurface() {
_webSurface->resize(QSize(dims.x, dims.y));
}
unsigned int Web3DOverlay::deviceIdByTouchPoint(qreal x, qreal y) {
if (_webSurface) {
return _webSurface->deviceIdByTouchPoint(x, y);
} else {
return PointerEvent::INVALID_POINTER_ID;
}
}
void Web3DOverlay::render(RenderArgs* args) {
if (!_renderVisible || !getParentVisible()) {
return;
}
if (!_webSurface) {
emit requestWebSurface();
emit requestWebSurface(false);
return;
}
@ -499,11 +358,6 @@ void Web3DOverlay::setProperties(const QVariantMap& properties) {
_desiredMaxFPS = maxFPS.toInt();
}
auto showKeyboardFocusHighlight = properties["showKeyboardFocusHighlight"];
if (showKeyboardFocusHighlight.isValid()) {
_showKeyboardFocusHighlight = showKeyboardFocusHighlight.toBool();
}
auto inputModeValue = properties["inputMode"];
if (inputModeValue.isValid()) {
QString inputModeStr = inputModeValue.toString();
@ -566,8 +420,6 @@ void Web3DOverlay::setProperties(const QVariantMap& properties) {
* @property {Vec2} dimensions=1,1 - The size of the overlay to display the Web page on, in meters. Synonyms:
* <code>scale</code>, <code>size</code>.
* @property {number} maxFPS=10 - The maximum update rate for the Web overlay content, in frames/second.
* @property {boolean} showKeyboardFocusHighlight=true - If <code>true</code>, the Web overlay is highlighted when it has
* keyboard focus.
* @property {string} inputMode=Touch - The user input mode to use - either <code>"Touch"</code> or <code>"Mouse"</code>.
*/
QVariant Web3DOverlay::getProperty(const QString& property) {
@ -583,9 +435,6 @@ QVariant Web3DOverlay::getProperty(const QString& property) {
if (property == "maxFPS") {
return _desiredMaxFPS;
}
if (property == "showKeyboardFocusHighlight") {
return _showKeyboardFocusHighlight;
}
if (property == "inputMode") {
if (_inputMode == Mouse) {
@ -605,14 +454,14 @@ void Web3DOverlay::setURL(const QString& url) {
if (wasWebContent && isWebContent()) {
// If we're just targeting a new web URL, then switch to that without messing around
// with the underlying QML
AbstractViewStateInterface::instance()->postLambdaEvent([this, url] {
_webSurface->getRootItem()->setProperty("url", _url);
AbstractViewStateInterface::instance()->postLambdaEvent([this] {
_webSurface->getRootItem()->setProperty(render::entities::WebEntityRenderer::URL_PROPERTY, _url);
_webSurface->getRootItem()->setProperty("scriptURL", _scriptURL);
});
} else {
// If we're switching to or from web content, or between different QML content
// we need to destroy and rebuild the entire QML surface
AbstractViewStateInterface::instance()->postLambdaEvent([this, url] {
AbstractViewStateInterface::instance()->postLambdaEvent([this] {
rebuildWebSurface();
});
}

View file

@ -22,8 +22,6 @@ class Web3DOverlay : public Billboard3DOverlay {
using Parent = Billboard3DOverlay;
public:
static const QString QML;
static QString const TYPE;
virtual QString getType() const override { return TYPE; }
@ -59,12 +57,10 @@ public:
Mouse
};
void buildWebSurface();
void buildWebSurface(bool overrideWeb = false);
void destroyWebSurface();
void onResizeWebSurface();
Q_INVOKABLE unsigned int deviceIdByTouchPoint(qreal x, qreal y);
public slots:
void emitScriptEvent(const QVariant& scriptMessage);
@ -72,26 +68,23 @@ signals:
void scriptEventReceived(const QVariant& message);
void webEventReceived(const QVariant& message);
void resizeWebSurface();
void requestWebSurface();
void releaseWebSurface();
void requestWebSurface(bool overrideWeb);
protected:
Transform evalRenderTransform() override;
private:
void setupQmlSurface(bool isTablet, bool isLoginDialog);
void rebuildWebSurface();
bool isWebContent() const;
InputMode _inputMode { Touch };
QSharedPointer<OffscreenQmlSurface> _webSurface;
bool _cachedWebSurface{ false };
bool _cachedWebSurface { false };
gpu::TexturePointer _texture;
QString _url;
QString _scriptURL;
float _dpi { 30.0f };
int _geometryId { 0 };
bool _showKeyboardFocusHighlight { true };
QTouchDevice _touchDevice;
@ -99,6 +92,8 @@ private:
uint8_t _currentMaxFPS { 0 };
bool _mayNeedResize { false };
std::vector<QMetaObject::Connection> _connections;
};
#endif // hifi_Web3DOverlay_h

View file

@ -237,8 +237,17 @@ void AnimSkeleton::buildSkeletonFromJoints(const std::vector<HFMJoint>& joints,
_relativeDefaultPoses = _absoluteDefaultPoses;
convertAbsolutePosesToRelative(_relativeDefaultPoses);
// build _jointIndicesByName hash
for (int i = 0; i < _jointsSize; i++) {
_jointIndicesByName[_joints[i].name] = i;
auto iter = _jointIndicesByName.find(_joints[i].name);
if (iter != _jointIndicesByName.end()) {
// prefer joints over meshes if there is a name collision.
if (_joints[i].isSkeletonJoint && !_joints[iter.value()].isSkeletonJoint) {
iter.value() = i;
}
} else {
_jointIndicesByName.insert(_joints[i].name, i);
}
}
// build mirror map.

19
libraries/avatars/src/AvatarData.cpp Normal file → Executable file
View file

@ -639,7 +639,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
// compute maxTranslationDimension before we send any joint data.
float maxTranslationDimension = 0.001f;
for (int i = sendStatus.rotationsSent; i < numJoints; ++i) {
for (int i = sendStatus.translationsSent; i < numJoints; ++i) {
const JointData& data = jointData[i];
if (!data.translationIsDefaultPose) {
maxTranslationDimension = glm::max(fabsf(data.translation.x), maxTranslationDimension);
@ -1172,6 +1172,9 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) {
sourceBuffer += sizeof(AvatarDataPacket::AdditionalFlags);
if (collideWithOtherAvatarsChanged) {
setCollisionWithOtherAvatarsFlags();
}
if (somethingChanged) {
_additionalFlagsChanged = now;
}
@ -2428,7 +2431,8 @@ static const QString JSON_AVATAR_VERSION = QStringLiteral("version");
enum class JsonAvatarFrameVersion : int {
JointRotationsInRelativeFrame = 0,
JointRotationsInAbsoluteFrame,
JointDefaultPoseBits
JointDefaultPoseBits,
JointUnscaledTranslations,
};
QJsonValue toJsonValue(const JointData& joint) {
@ -2445,7 +2449,16 @@ JointData jointDataFromJsonValue(int version, const QJsonValue& json) {
if (json.isArray()) {
QJsonArray array = json.toArray();
result.rotation = quatFromJsonValue(array[0]);
result.translation = vec3FromJsonValue(array[1]);
// In old recordings, translations are scaled by _geometryOffset. Undo that scaling.
if (version < (int)JsonAvatarFrameVersion::JointUnscaledTranslations) {
// because we don't have access to the actual _geometryOffset used. we have to guess.
// most avatar FBX files were authored in centimeters.
const float METERS_TO_CENTIMETERS = 100.0f;
result.translation *= METERS_TO_CENTIMETERS;
}
if (version >= (int)JsonAvatarFrameVersion::JointDefaultPoseBits) {
result.rotationIsDefaultPose = array[2].toBool();
result.translationIsDefaultPose = array[3].toBool();
@ -2464,7 +2477,7 @@ void AvatarData::avatarEntityDataToJson(QJsonObject& root) const {
QJsonObject AvatarData::toJson() const {
QJsonObject root;
root[JSON_AVATAR_VERSION] = (int)JsonAvatarFrameVersion::JointDefaultPoseBits;
root[JSON_AVATAR_VERSION] = (int)JsonAvatarFrameVersion::JointUnscaledTranslations;
if (!getSkeletonModelURL().isEmpty()) {
root[JSON_AVATAR_BODY_MODEL] = getSkeletonModelURL().toString();

2
libraries/avatars/src/AvatarData.h Normal file → Executable file
View file

@ -496,6 +496,8 @@ public:
/// \return number of bytes parsed
virtual int parseDataFromBuffer(const QByteArray& buffer);
virtual void setCollisionWithOtherAvatarsFlags() {};
// Body Rotation (degrees)
float getBodyYaw() const;
void setBodyYaw(float bodyYaw);

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

@ -159,20 +159,40 @@ Item::Bound EntityRenderer::getBound() {
return _bound;
}
ShapeKey EntityRenderer::getShapeKey() {
if (_primitiveMode == PrimitiveMode::LINES) {
return ShapeKey::Builder().withOwnPipeline().withWireframe();
}
return ShapeKey::Builder().withOwnPipeline();
}
render::hifi::Tag EntityRenderer::getTagMask() const {
return _isVisibleInSecondaryCamera ? render::hifi::TAG_ALL_VIEWS : render::hifi::TAG_MAIN_VIEW;
}
render::hifi::Layer EntityRenderer::getHifiRenderLayer() const {
switch (_renderLayer) {
case RenderLayer::WORLD:
return render::hifi::LAYER_3D;
case RenderLayer::FRONT:
return render::hifi::LAYER_3D_FRONT;
case RenderLayer::HUD:
return render::hifi::LAYER_3D_HUD;
default:
return render::hifi::LAYER_3D;
}
}
ItemKey EntityRenderer::getKey() {
if (isTransparent()) {
return ItemKey::Builder::transparentShape().withTypeMeta().withTagBits(getTagMask());
return ItemKey::Builder::transparentShape().withTypeMeta().withTagBits(getTagMask()).withLayer(getHifiRenderLayer());
}
// This allows shapes to cast shadows
if (_canCastShadow) {
return ItemKey::Builder::opaqueShape().withTypeMeta().withTagBits(getTagMask()).withShadowCaster();
return ItemKey::Builder::opaqueShape().withTypeMeta().withTagBits(getTagMask()).withShadowCaster().withLayer(getHifiRenderLayer());
} else {
return ItemKey::Builder::opaqueShape().withTypeMeta().withTagBits(getTagMask());
return ItemKey::Builder::opaqueShape().withTypeMeta().withTagBits(getTagMask()).withLayer(getHifiRenderLayer());
}
}
@ -411,6 +431,8 @@ void EntityRenderer::doRenderUpdateSynchronous(const ScenePointer& scene, Transa
_moving = entity->isMovingRelativeToParent();
_visible = entity->getVisible();
setIsVisibleInSecondaryCamera(entity->isVisibleInSecondaryCamera());
setRenderLayer(entity->getRenderLayer());
setPrimitiveMode(entity->getPrimitiveMode());
_canCastShadow = entity->getCanCastShadow();
_cauterized = entity->getCauterized();
_needsRenderUpdate = false;

View file

@ -72,11 +72,12 @@ protected:
// Implementing the PayloadProxyInterface methods
virtual ItemKey getKey() override;
virtual ShapeKey getShapeKey() override { return ShapeKey::Builder::ownPipeline(); }
virtual ShapeKey getShapeKey() override;
virtual Item::Bound getBound() override;
virtual void render(RenderArgs* args) override final;
virtual uint32_t metaFetchMetaSubItems(ItemIDs& subItems) override;
virtual render::hifi::Tag getTagMask() const;
virtual render::hifi::Layer getHifiRenderLayer() const;
// Returns true if the item in question needs to have updateInScene called because of internal rendering state changes
virtual bool needsRenderUpdate() const;
@ -103,6 +104,8 @@ protected:
inline bool isValidRenderItem() const { return _renderItemID != Item::INVALID_ITEM_ID; }
virtual void setIsVisibleInSecondaryCamera(bool value) { _isVisibleInSecondaryCamera = value; }
virtual void setRenderLayer(RenderLayer value) { _renderLayer = value; }
virtual void setPrimitiveMode(PrimitiveMode value) { _primitiveMode = value; }
template <typename F, typename T>
T withReadLockResult(const std::function<T()>& f) {
@ -136,6 +139,8 @@ protected:
bool _visible { false };
bool _isVisibleInSecondaryCamera { false };
bool _canCastShadow { false };
RenderLayer _renderLayer { RenderLayer::WORLD };
PrimitiveMode _primitiveMode { PrimitiveMode::SOLID };
bool _cauterized { false };
bool _moving { false };
bool _needsRenderUpdate { false };

View file

@ -97,6 +97,10 @@ ShapeKey GridEntityRenderer::getShapeKey() {
builder.withTranslucent();
}
if (_primitiveMode == PrimitiveMode::LINES) {
builder.withWireframe();
}
return builder.build();
}

View file

@ -123,6 +123,10 @@ ShapeKey ImageEntityRenderer::getShapeKey() {
if (_emissive) {
builder.withUnlit();
}
if (_primitiveMode == PrimitiveMode::LINES) {
builder.withWireframe();
}
});
return builder.build();

View file

@ -55,7 +55,7 @@ void MaterialEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer&
ItemKey MaterialEntityRenderer::getKey() {
ItemKey::Builder builder;
builder.withTypeShape().withTagBits(getTagMask());
builder.withTypeShape().withTagBits(getTagMask()).withLayer(getHifiRenderLayer());
if (!_visible) {
builder.withInvisible();
@ -98,6 +98,10 @@ ShapeKey MaterialEntityRenderer::getShapeKey() {
builder.withUnlit();
}
if (_primitiveMode == PrimitiveMode::LINES) {
builder.withWireframe();
}
return builder.build();
}

View file

@ -1074,10 +1074,16 @@ ModelEntityRenderer::ModelEntityRenderer(const EntityItemPointer& entity) : Pare
}
void ModelEntityRenderer::setKey(bool didVisualGeometryRequestSucceed) {
auto builder = ItemKey::Builder().withTypeMeta().withTagBits(getTagMask()).withLayer(getHifiRenderLayer());
if (_model && _model->isGroupCulled()) {
builder.withMetaCullGroup();
}
if (didVisualGeometryRequestSucceed) {
_itemKey = ItemKey::Builder().withTypeMeta().withTagBits(getTagMask());
_itemKey = builder.build();
} else {
_itemKey = ItemKey::Builder().withTypeMeta().withTypeShape().withTagBits(getTagMask());
_itemKey = builder.withTypeShape().build();
}
}
@ -1295,6 +1301,10 @@ bool ModelEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPoin
model->getRegistrationPoint() != entity->getRegistrationPoint()) {
return true;
}
if (model->isGroupCulled() != entity->getGroupCulled()) {
return true;
}
}
return false;
@ -1351,6 +1361,8 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce
connect(model.get(), &Model::requestRenderUpdate, this, &ModelEntityRenderer::requestRenderUpdate);
connect(model.get(), &Model::setURLFinished, this, [&](bool didVisualGeometryRequestSucceed) {
setKey(didVisualGeometryRequestSucceed);
_model->setTagMask(getTagMask());
_model->setHifiRenderLayer(getHifiRenderLayer());
emit requestRenderUpdate();
if(didVisualGeometryRequestSucceed) {
emit DependencyManager::get<scriptable::ModelProviderFactory>()->
@ -1437,6 +1449,14 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce
model->setCanCastShadow(_canCastShadow, scene);
}
{
bool groupCulled = entity->getGroupCulled();
if (model->isGroupCulled() != groupCulled) {
model->setGroupCulled(groupCulled);
setKey(_didLastVisualGeometryRequestSucceed);
}
}
{
DETAILED_PROFILE_RANGE(simulation_physics, "Fixup");
if (model->needsFixupInScene()) {
@ -1494,6 +1514,24 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce
void ModelEntityRenderer::setIsVisibleInSecondaryCamera(bool value) {
Parent::setIsVisibleInSecondaryCamera(value);
setKey(_didLastVisualGeometryRequestSucceed);
if (_model) {
_model->setTagMask(getTagMask());
}
}
void ModelEntityRenderer::setRenderLayer(RenderLayer value) {
Parent::setRenderLayer(value);
setKey(_didLastVisualGeometryRequestSucceed);
if (_model) {
_model->setHifiRenderLayer(getHifiRenderLayer());
}
}
void ModelEntityRenderer::setPrimitiveMode(PrimitiveMode value) {
Parent::setPrimitiveMode(value);
if (_model) {
_model->setPrimitiveMode(_primitiveMode);
}
}
// NOTE: this only renders the "meta" portion of the Model, namely it renders debugging items

View file

@ -169,6 +169,8 @@ protected:
render::hifi::Tag getTagMask() const override;
void setIsVisibleInSecondaryCamera(bool value) override;
void setRenderLayer(RenderLayer value) override;
void setPrimitiveMode(PrimitiveMode value) override;
private:
void animate(const TypedEntityPointer& entity);

View file

@ -159,14 +159,18 @@ void ParticleEffectEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEn
ItemKey ParticleEffectEntityRenderer::getKey() {
if (_visible) {
return ItemKey::Builder::transparentShape().withTagBits(getTagMask());
return ItemKey::Builder::transparentShape().withTagBits(getTagMask()).withLayer(getHifiRenderLayer());
} else {
return ItemKey::Builder().withInvisible().withTagBits(getTagMask()).build();
return ItemKey::Builder().withInvisible().withTagBits(getTagMask()).withLayer(getHifiRenderLayer()).build();
}
}
ShapeKey ParticleEffectEntityRenderer::getShapeKey() {
return ShapeKey::Builder().withCustom(CUSTOM_PIPELINE_NUMBER).withTranslucent().build();
auto builder = ShapeKey::Builder().withCustom(CUSTOM_PIPELINE_NUMBER).withTranslucent();
if (_primitiveMode == PrimitiveMode::LINES) {
builder.withWireframe();
}
return builder.build();
}
Item::Bound ParticleEffectEntityRenderer::getBound() {

View file

@ -55,11 +55,15 @@ void PolyLineEntityRenderer::buildPipeline() {
}
ItemKey PolyLineEntityRenderer::getKey() {
return ItemKey::Builder::transparentShape().withTypeMeta().withTagBits(getTagMask());
return ItemKey::Builder::transparentShape().withTypeMeta().withTagBits(getTagMask()).withLayer(getHifiRenderLayer());
}
ShapeKey PolyLineEntityRenderer::getShapeKey() {
return ShapeKey::Builder().withOwnPipeline().withTranslucent().withoutCullFace();
auto builder = ShapeKey::Builder().withOwnPipeline().withTranslucent().withoutCullFace();
if (_primitiveMode == PrimitiveMode::LINES) {
builder.withWireframe();
}
return builder.build();
}
bool PolyLineEntityRenderer::needsRenderUpdate() const {

View file

@ -1613,7 +1613,11 @@ PolyVoxEntityRenderer::PolyVoxEntityRenderer(const EntityItemPointer& entity) :
}
ShapeKey PolyVoxEntityRenderer::getShapeKey() {
return ShapeKey::Builder().withCustom(CUSTOM_PIPELINE_NUMBER).build();
auto builder = ShapeKey::Builder().withCustom(CUSTOM_PIPELINE_NUMBER);
if (_primitiveMode == PrimitiveMode::LINES) {
builder.withWireframe();
}
return builder.build();
}
bool PolyVoxEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const {

View file

@ -173,7 +173,7 @@ public:
}
protected:
virtual ItemKey getKey() override { return ItemKey::Builder::opaqueShape().withTagBits(getTagMask()); }
virtual ItemKey getKey() override { return ItemKey::Builder::opaqueShape().withTagBits(getTagMask()).withLayer(getHifiRenderLayer()); }
virtual ShapeKey getShapeKey() override;
virtual bool needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const override;
virtual void doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) override;

View file

@ -42,10 +42,23 @@ ShapeEntityRenderer::ShapeEntityRenderer(const EntityItemPointer& entity) : Pare
// TODO: move into Procedural.cpp
PrepareStencil::testMaskDrawShape(*_procedural._opaqueState);
PrepareStencil::testMask(*_procedural._transparentState);
addMaterial(graphics::MaterialLayer(_material, 0), "0");
}
bool ShapeEntityRenderer::needsRenderUpdate() const {
if (_procedural.isEnabled() && _procedural.isFading()) {
if (resultWithReadLock<bool>([&] {
if (_procedural.isEnabled() && _procedural.isFading()) {
return true;
}
auto mat = _materials.find("0");
if (mat != _materials.end() && mat->second.needsUpdate()) {
return true;
}
return false;
})) {
return true;
}
@ -56,7 +69,11 @@ bool ShapeEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPoin
if (_lastUserData != entity->getUserData()) {
return true;
}
if (_material != entity->getMaterial()) {
if (_color != entity->getColor()) {
return true;
}
if (_alpha != entity->getAlpha()) {
return true;
}
@ -79,10 +96,6 @@ void ShapeEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce
_procedural.setProceduralData(ProceduralData::parse(_lastUserData));
}
removeMaterial(_material, "0");
_material = entity->getMaterial();
addMaterial(graphics::MaterialLayer(_material, 0), "0");
_shape = entity->getShape();
});
@ -111,6 +124,20 @@ void ShapeEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPoint
_procedural.setIsFading(isFading);
}
});
glm::u8vec3 color = entity->getColor();
float alpha = entity->getAlpha();
if (_color != color || _alpha != alpha) {
_color = color;
_alpha = alpha;
_material->setAlbedo(toGlm(_color));
_material->setOpacity(_alpha);
auto materials = _materials.find("0");
if (materials != _materials.end()) {
materials->second.setNeedsUpdate(true);
}
}
}
bool ShapeEntityRenderer::isTransparent() const {
@ -120,18 +147,15 @@ bool ShapeEntityRenderer::isTransparent() const {
auto mat = _materials.find("0");
if (mat != _materials.end()) {
if (mat->second.top().material) {
auto matKey = mat->second.top().material->getKey();
if (matKey.isTranslucent()) {
return true;
}
if (mat->second.getMaterialKey().isTranslucent()) {
return true;
}
}
return Parent::isTransparent();
}
bool ShapeEntityRenderer::useMaterialPipeline() const {
bool ShapeEntityRenderer::useMaterialPipeline(const graphics::MultiMaterial& materials) const {
bool proceduralReady = resultWithReadLock<bool>([&] {
return _procedural.isReady();
});
@ -139,12 +163,7 @@ bool ShapeEntityRenderer::useMaterialPipeline() const {
return false;
}
graphics::MaterialKey drawMaterialKey;
auto mat = _materials.find("0");
if (mat != _materials.end() && mat->second.top().material) {
drawMaterialKey = mat->second.top().material->getKey();
}
graphics::MaterialKey drawMaterialKey = materials.getMaterialKey();
if (drawMaterialKey.isEmissive() || drawMaterialKey.isUnlit() || drawMaterialKey.isMetallic() || drawMaterialKey.isScattering()) {
return true;
}
@ -159,11 +178,13 @@ bool ShapeEntityRenderer::useMaterialPipeline() const {
}
ShapeKey ShapeEntityRenderer::getShapeKey() {
if (useMaterialPipeline()) {
graphics::MaterialKey drawMaterialKey;
if (_materials["0"].top().material) {
drawMaterialKey = _materials["0"].top().material->getKey();
}
auto mat = _materials.find("0");
if (mat != _materials.end() && mat->second.needsUpdate()) {
RenderPipelines::updateMultiMaterial(mat->second);
}
if (mat != _materials.end() && useMaterialPipeline(mat->second)) {
graphics::MaterialKey drawMaterialKey = mat->second.getMaterialKey();
bool isTranslucent = drawMaterialKey.isTranslucent();
bool hasTangents = drawMaterialKey.isNormalMap();
@ -186,6 +207,10 @@ ShapeKey ShapeEntityRenderer::getShapeKey() {
builder.withUnlit();
}
if (_primitiveMode == PrimitiveMode::LINES) {
builder.withWireframe();
}
return builder.build();
} else {
ShapeKey::Builder builder;
@ -198,6 +223,10 @@ ShapeKey ShapeEntityRenderer::getShapeKey() {
if (isTransparent()) {
builder.withTranslucent();
}
if (_primitiveMode == PrimitiveMode::LINES) {
builder.withWireframe();
}
return builder.build();
}
}
@ -208,7 +237,7 @@ void ShapeEntityRenderer::doRender(RenderArgs* args) {
gpu::Batch& batch = *args->_batch;
std::shared_ptr<graphics::Material> mat;
graphics::MultiMaterial materials;
auto geometryCache = DependencyManager::get<GeometryCache>();
GeometryCache::Shape geometryShape;
bool proceduralRender = false;
@ -216,40 +245,40 @@ void ShapeEntityRenderer::doRender(RenderArgs* args) {
withReadLock([&] {
geometryShape = geometryCache->getShapeForEntityShape(_shape);
batch.setModelTransform(_renderTransform); // use a transform with scale, rotation, registration point and translation
mat = _materials["0"].top().material;
if (mat) {
outColor = glm::vec4(mat->getAlbedo(), mat->getOpacity());
if (_procedural.isReady()) {
outColor = _procedural.getColor(outColor);
outColor.a *= _procedural.isFading() ? Interpolate::calculateFadeRatio(_procedural.getFadeStartTime()) : 1.0f;
_procedural.prepare(batch, _position, _dimensions, _orientation, ProceduralProgramKey(outColor.a < 1.0f));
proceduralRender = true;
}
materials = _materials["0"];
auto& schema = materials.getSchemaBuffer().get<graphics::MultiMaterial::Schema>();
outColor = glm::vec4(schema._albedo, schema._opacity);
if (_procedural.isReady()) {
outColor = _procedural.getColor(outColor);
outColor.a *= _procedural.isFading() ? Interpolate::calculateFadeRatio(_procedural.getFadeStartTime()) : 1.0f;
_procedural.prepare(batch, _position, _dimensions, _orientation, ProceduralProgramKey(outColor.a < 1.0f));
proceduralRender = true;
}
});
if (!mat) {
return;
}
if (proceduralRender) {
if (render::ShapeKey(args->_globalShapeKey).isWireframe()) {
geometryCache->renderWireShape(batch, geometryShape, outColor);
} else {
geometryCache->renderShape(batch, geometryShape, outColor);
}
} else if (!useMaterialPipeline()) {
} else if (!useMaterialPipeline(materials)) {
// FIXME, support instanced multi-shape rendering using multidraw indirect
outColor.a *= _isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) : 1.0f;
auto pipeline = outColor.a < 1.0f ? geometryCache->getTransparentShapePipeline() : geometryCache->getOpaqueShapePipeline();
if (render::ShapeKey(args->_globalShapeKey).isWireframe()) {
render::ShapePipelinePointer pipeline;
if (_renderLayer == RenderLayer::WORLD) {
pipeline = outColor.a < 1.0f ? geometryCache->getTransparentShapePipeline() : geometryCache->getOpaqueShapePipeline();
} else {
pipeline = outColor.a < 1.0f ? geometryCache->getForwardTransparentShapePipeline() : geometryCache->getForwardOpaqueShapePipeline();
}
if (render::ShapeKey(args->_globalShapeKey).isWireframe() || _primitiveMode == PrimitiveMode::LINES) {
geometryCache->renderWireShapeInstance(args, batch, geometryShape, outColor, pipeline);
} else {
geometryCache->renderSolidShapeInstance(args, batch, geometryShape, outColor, pipeline);
}
} else {
if (args->_renderMode != render::Args::RenderMode::SHADOW_RENDER_MODE) {
RenderPipelines::bindMaterial(mat, batch, args->_enableTexturing);
RenderPipelines::bindMaterials(materials, batch, args->_enableTexturing);
args->_details._materialSwitches++;
}
@ -268,8 +297,9 @@ scriptable::ScriptableModelBase ShapeEntityRenderer::getScriptableModel() {
{
std::lock_guard<std::mutex> lock(_materialsLock);
result.appendMaterials(_materials);
if (_materials["0"].top().material) {
vertexColor = _materials["0"].top().material->getAlbedo();
auto materials = _materials.find("0");
if (materials != _materials.end()) {
vertexColor = materials->second.getSchemaBuffer().get<graphics::MultiMaterial::Schema>()._albedo;
}
}
if (auto mesh = geometryCache->meshFromShape(geometryShape, vertexColor)) {

View file

@ -35,12 +35,14 @@ private:
virtual void doRender(RenderArgs* args) override;
virtual bool isTransparent() const override;
bool useMaterialPipeline() const;
bool useMaterialPipeline(const graphics::MultiMaterial& materials) const;
Procedural _procedural;
QString _lastUserData;
entity::Shape _shape { entity::Sphere };
std::shared_ptr<graphics::Material> _material;
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

@ -49,6 +49,9 @@ ShapeKey TextEntityRenderer::getShapeKey() {
if (isTransparent()) {
builder.withTranslucent();
}
if (_primitiveMode == PrimitiveMode::LINES) {
builder.withWireframe();
}
return builder.build();
}

View file

@ -30,22 +30,26 @@
using namespace render;
using namespace render::entities;
static const QString WEB_ENTITY_QML = "controls/WebEntityView.qml";
const QString WebEntityRenderer::QML = "Web3DSurface.qml";
const char* WebEntityRenderer::URL_PROPERTY = "url";
std::function<void(QString, bool, QSharedPointer<OffscreenQmlSurface>&, bool&)> WebEntityRenderer::_acquireWebSurfaceOperator = nullptr;
std::function<void(QSharedPointer<OffscreenQmlSurface>&, bool&, std::vector<QMetaObject::Connection>&)> WebEntityRenderer::_releaseWebSurfaceOperator = nullptr;
static int MAX_WINDOW_SIZE = 4096;
const float METERS_TO_INCHES = 39.3701f;
static uint32_t _currentWebCount{ 0 };
// Don't allow more than 20 concurrent web views
static const uint32_t MAX_CONCURRENT_WEB_VIEWS = 20;
static float OPAQUE_ALPHA_THRESHOLD = 0.99f;
// If a web-view hasn't been rendered for 30 seconds, de-allocate the framebuffer
static uint64_t MAX_NO_RENDER_INTERVAL = 30 * USECS_PER_SECOND;
static int MAX_WINDOW_SIZE = 4096;
static float OPAQUE_ALPHA_THRESHOLD = 0.99f;
static int DEFAULT_MAX_FPS = 10;
static int YOUTUBE_MAX_FPS = 30;
static uint8_t YOUTUBE_MAX_FPS = 30;
// Don't allow more than 20 concurrent web views
static uint32_t _currentWebCount { 0 };
static const uint32_t MAX_CONCURRENT_WEB_VIEWS = 20;
static QTouchDevice _touchDevice;
static const char* URL_PROPERTY = "url";
WebEntityRenderer::ContentType WebEntityRenderer::getContentType(const QString& urlString) {
if (urlString.isEmpty()) {
@ -71,13 +75,18 @@ WebEntityRenderer::WebEntityRenderer(const EntityItemPointer& entity) : Parent(e
_touchDevice.setMaximumTouchPoints(4);
});
_geometryId = DependencyManager::get<GeometryCache>()->allocateID();
_texture = gpu::Texture::createExternal(OffscreenQmlSurface::getDiscardLambda());
_texture->setSource(__FUNCTION__);
_contentType = ContentType::HtmlContent;
buildWebSurface(entity, "");
_timer.setInterval(MSECS_PER_SECOND);
connect(&_timer, &QTimer::timeout, this, &WebEntityRenderer::onTimeout);
}
void WebEntityRenderer::onRemoveFromSceneTyped(const TypedEntityPointer& entity) {
WebEntityRenderer::~WebEntityRenderer() {
destroyWebSurface();
auto geometryCache = DependencyManager::get<GeometryCache>();
@ -86,6 +95,11 @@ void WebEntityRenderer::onRemoveFromSceneTyped(const TypedEntityPointer& entity)
}
}
bool WebEntityRenderer::isTransparent() const {
float fadeRatio = _isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) : 1.0f;
return fadeRatio < OPAQUE_ALPHA_THRESHOLD || _alpha < 1.0f;
}
bool WebEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const {
if (_contextPosition != entity->getWorldPosition()) {
return true;
@ -101,11 +115,31 @@ bool WebEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPointe
}
}
if (_lastSourceUrl != entity->getSourceUrl()) {
if (_color != entity->getColor()) {
return true;
}
if (_lastDPI != entity->getDPI()) {
if (_alpha != entity->getAlpha()) {
return true;
}
if (_sourceURL != entity->getSourceUrl()) {
return true;
}
if (_dpi != entity->getDPI()) {
return true;
}
if (_scriptURL != entity->getScriptURL()) {
return true;
}
if (_maxFPS != entity->getMaxFPS()) {
return true;
}
if (_inputMode != entity->getInputMode()) {
return true;
}
@ -113,35 +147,25 @@ bool WebEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPointe
}
bool WebEntityRenderer::needsRenderUpdate() const {
{
QSharedPointer<OffscreenQmlSurface> webSurface;
withReadLock([&] {
webSurface = _webSurface;
});
if (!webSurface) {
// If we have rendered recently, and there is no web surface, we're going to create one
return true;
}
if (resultWithReadLock<bool>([this] {
return !_webSurface;
})) {
return true;
}
return Parent::needsRenderUpdate();
}
void WebEntityRenderer::onTimeout() {
bool needsCheck = resultWithReadLock<bool>([&] {
uint64_t lastRenderTime;
if (!resultWithReadLock<bool>([&] {
lastRenderTime = _lastRenderTime;
return (_lastRenderTime != 0 && (bool)_webSurface);
});
if (!needsCheck) {
})) {
return;
}
uint64_t interval;
withReadLock([&] {
interval = usecTimestampNow() - _lastRenderTime;
});
if (interval > MAX_NO_RENDER_INTERVAL) {
if (usecTimestampNow() - lastRenderTime > MAX_NO_RENDER_INTERVAL) {
destroyWebSurface();
}
}
@ -151,80 +175,100 @@ void WebEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene
// destroy the existing surface (because surfaces don't support changing the root
// object, so subsequent loads of content just overlap the existing content
bool urlChanged = false;
auto newSourceURL = entity->getSourceUrl();
{
auto newSourceUrl = entity->getSourceUrl();
auto newContentType = getContentType(newSourceUrl);
auto currentContentType = ContentType::NoContent;
auto newContentType = getContentType(newSourceURL);
ContentType currentContentType;
withReadLock([&] {
urlChanged = _lastSourceUrl != newSourceUrl;
urlChanged = _sourceURL != newSourceURL;
currentContentType = _contentType;
});
if (urlChanged) {
if (newContentType != ContentType::HtmlContent || currentContentType != ContentType::HtmlContent) {
destroyWebSurface();
// If we destroyed the surface, the URL change will be implicitly handled by the re-creation
urlChanged = false;
}
withWriteLock([&] {
_lastSourceUrl = newSourceUrl;
_contentType = newContentType;
});
if (newContentType != ContentType::HtmlContent || currentContentType != ContentType::HtmlContent) {
destroyWebSurface();
}
}
}
withWriteLock([&] {
_inputMode = entity->getInputMode();
_dpi = entity->getDPI();
_color = entity->getColor();
_alpha = entity->getAlpha();
if (_contentType == ContentType::NoContent) {
return;
}
// This work must be done on the main thread
// If we couldn't create a new web surface, exit
if (!hasWebSurface() && !buildWebSurface(entity)) {
return;
if (!_webSurface) {
buildWebSurface(entity, newSourceURL);
}
if (urlChanged && _contentType == ContentType::HtmlContent) {
_webSurface->getRootItem()->setProperty(URL_PROPERTY, _lastSourceUrl);
}
void* key = (void*)this;
AbstractViewStateInterface::instance()->pushPostUpdateLambda(key, [this, entity] () {
withWriteLock([&] {
if (_contextPosition != entity->getWorldPosition()) {
// update globalPosition
_contextPosition = entity->getWorldPosition();
_webSurface->getSurfaceContext()->setContextProperty("globalPosition", vec3toVariant(_contextPosition));
if (_webSurface && _webSurface->getRootItem()) {
if (_webSurface->getRootItem()) {
if (_contentType == ContentType::HtmlContent && urlChanged) {
_webSurface->getRootItem()->setProperty(URL_PROPERTY, newSourceURL);
_sourceURL = newSourceURL;
}
_lastDPI = entity->getDPI();
{
auto scriptURL = entity->getScriptURL();
if (_scriptURL != scriptURL) {
_webSurface->getRootItem()->setProperty("scriptURL", _scriptURL);
_scriptURL = scriptURL;
}
}
glm::vec2 windowSize = getWindowSize(entity);
_webSurface->resize(QSize(windowSize.x, windowSize.y));
updateModelTransformAndBound();
_renderTransform = getModelTransform();
_renderTransform.postScale(entity->getScaledDimensions());
{
auto maxFPS = entity->getMaxFPS();
if (_maxFPS != maxFPS) {
// We special case YouTube URLs since we know they are videos that we should play with at least 30 FPS.
// FIXME this doesn't handle redirects or shortened URLs, consider using a signaling method from the web entity
if (QUrl(_sourceURL).host().endsWith("youtube.com", Qt::CaseInsensitive)) {
_webSurface->setMaxFps(YOUTUBE_MAX_FPS);
} else {
_webSurface->setMaxFps(maxFPS);
}
_maxFPS = maxFPS;
}
}
{
auto contextPosition = entity->getWorldPosition();
if (_contextPosition != contextPosition) {
_webSurface->getSurfaceContext()->setContextProperty("globalPosition", vec3toVariant(contextPosition));
_contextPosition = contextPosition;
}
}
}
void* key = (void*)this;
AbstractViewStateInterface::instance()->pushPostUpdateLambda(key, [this, entity]() {
withWriteLock([&] {
glm::vec2 windowSize = getWindowSize(entity);
_webSurface->resize(QSize(windowSize.x, windowSize.y));
updateModelTransformAndBound();
_renderTransform = getModelTransform();
_renderTransform.postScale(entity->getScaledDimensions());
});
});
});
}
});
}
void WebEntityRenderer::doRender(RenderArgs* args) {
PerformanceTimer perfTimer("WebEntityRenderer::render");
withWriteLock([&] {
_lastRenderTime = usecTimestampNow();
});
#ifdef WANT_EXTRA_DEBUGGING
{
gpu::Batch& batch = *args->_batch;
batch.setModelTransform(getTransformToCenter()); // we want to include the scale as well
glm::vec4 cubeColor{ 1.0f, 0.0f, 0.0f, 1.0f };
DependencyManager::get<GeometryCache>()->renderWireCube(batch, 1.0f, cubeColor);
}
#endif
// Try to update the texture
{
QSharedPointer<OffscreenQmlSurface> webSurface;
@ -242,111 +286,61 @@ void WebEntityRenderer::doRender(RenderArgs* args) {
}
}
PerformanceTimer perfTimer("WebEntityRenderer::render");
static const glm::vec2 texMin(0.0f), texMax(1.0f), topLeft(-0.5f), bottomRight(0.5f);
gpu::Batch& batch = *args->_batch;
glm::vec4 color;
withReadLock([&] {
float fadeRatio = _isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) : 1.0f;
color = glm::vec4(toGlm(_color), _alpha * fadeRatio);
batch.setModelTransform(_renderTransform);
});
batch.setResourceTexture(0, _texture);
float fadeRatio = _isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) : 1.0f;
// Turn off jitter for these entities
batch.pushProjectionJitter();
DependencyManager::get<GeometryCache>()->bindWebBrowserProgram(batch, fadeRatio < OPAQUE_ALPHA_THRESHOLD);
DependencyManager::get<GeometryCache>()->renderQuad(batch, topLeft, bottomRight, texMin, texMax, glm::vec4(1.0f, 1.0f, 1.0f, fadeRatio), _geometryId);
DependencyManager::get<GeometryCache>()->bindWebBrowserProgram(batch, color.a < OPAQUE_ALPHA_THRESHOLD);
DependencyManager::get<GeometryCache>()->renderQuad(batch, topLeft, bottomRight, texMin, texMax, color, _geometryId);
batch.popProjectionJitter();
batch.setResourceTexture(0, nullptr);
}
bool WebEntityRenderer::hasWebSurface() {
return (bool)_webSurface && _webSurface->getRootItem();
}
static const auto WebSurfaceDeleter = [](OffscreenQmlSurface* webSurface) {
AbstractViewStateInterface::instance()->sendLambdaEvent([webSurface] {
// WebEngineView may run other threads (wasapi), so they must be deleted for a clean shutdown
// if the application has already stopped its event loop, delete must be explicit
delete webSurface;
});
};
bool WebEntityRenderer::buildWebSurface(const TypedEntityPointer& entity) {
void WebEntityRenderer::buildWebSurface(const EntityItemPointer& entity, const QString& newSourceURL) {
if (_currentWebCount >= MAX_CONCURRENT_WEB_VIEWS) {
qWarning() << "Too many concurrent web views to create new view";
return false;
return;
}
++_currentWebCount;
// FIXME use the surface cache instead of explicit creation
_webSurface = QSharedPointer<OffscreenQmlSurface>(new OffscreenQmlSurface(), WebSurfaceDeleter);
// FIXME, the max FPS could be better managed by being dynamic (based on the number of current surfaces
// and the current rendering load)
_webSurface->setMaxFps(DEFAULT_MAX_FPS);
QObject::connect(_webSurface.data(), &OffscreenQmlSurface::rootContextCreated, [](QQmlContext* surfaceContext) {
// FIXME - Keyboard HMD only: Possibly add "HMDinfo" object to context for WebView.qml.
surfaceContext->setContextProperty("desktop", QVariant());
// Let us interact with the keyboard
surfaceContext->setContextProperty("tabletInterface", DependencyManager::get<TabletScriptingInterface>().data());
});
// forward web events to EntityScriptingInterface
auto entities = DependencyManager::get<EntityScriptingInterface>();
const EntityItemID entityItemID = entity->getID();
QObject::connect(_webSurface.data(), &OffscreenQmlSurface::webEventReceived, [=](const QVariant& message) {
emit entities->webEventReceived(entityItemID, message);
});
if (_contentType == ContentType::HtmlContent) {
// We special case YouTube URLs since we know they are videos that we should play with at least 30 FPS.
// FIXME this doesn't handle redirects or shortened URLs, consider using a signaling method from the
// web entity
if (QUrl(_lastSourceUrl).host().endsWith("youtube.com", Qt::CaseInsensitive)) {
_webSurface->setMaxFps(YOUTUBE_MAX_FPS);
} else {
_webSurface->setMaxFps(DEFAULT_MAX_FPS);
}
_webSurface->load("controls/WebEntityView.qml", [this](QQmlContext* context, QObject* item) {
item->setProperty(URL_PROPERTY, _lastSourceUrl);
});
} else if (_contentType == ContentType::QmlContent) {
_webSurface->load(_lastSourceUrl);
}
WebEntityRenderer::acquireWebSurface(newSourceURL, _contentType == ContentType::HtmlContent, _webSurface, _cachedWebSurface);
_fadeStartTime = usecTimestampNow();
_webSurface->resume();
return _webSurface->getRootItem();
_connections.push_back(QObject::connect(this, &WebEntityRenderer::scriptEventReceived, _webSurface.data(), &OffscreenQmlSurface::emitScriptEvent));
_connections.push_back(QObject::connect(_webSurface.data(), &OffscreenQmlSurface::webEventReceived, this, &WebEntityRenderer::webEventReceived));
const EntityItemID entityItemID = entity->getID();
_connections.push_back(QObject::connect(_webSurface.data(), &OffscreenQmlSurface::webEventReceived, this, [entityItemID](const QVariant& message) {
emit DependencyManager::get<EntityScriptingInterface>()->webEventReceived(entityItemID, message);
}));
}
void WebEntityRenderer::destroyWebSurface() {
QSharedPointer<OffscreenQmlSurface> webSurface;
ContentType contentType{ ContentType::NoContent };
ContentType contentType = ContentType::NoContent;
withWriteLock([&] {
webSurface.swap(_webSurface);
std::swap(contentType, _contentType);
_contentType = contentType;
});
if (webSurface) {
--_currentWebCount;
QQuickItem* rootItem = webSurface->getRootItem();
// Fix for crash in QtWebEngineCore when rapidly switching domains
// Call stop on the QWebEngineView before destroying OffscreenQMLSurface.
if (rootItem && contentType == ContentType::HtmlContent) {
// stop loading
QMetaObject::invokeMethod(rootItem, "stop");
}
webSurface->pause();
webSurface.reset();
WebEntityRenderer::releaseWebSurface(webSurface, _cachedWebSurface, _connections);
}
}
glm::vec2 WebEntityRenderer::getWindowSize(const TypedEntityPointer& entity) const {
glm::vec2 dims = glm::vec2(entity->getScaledDimensions());
dims *= METERS_TO_INCHES * _lastDPI;
dims *= METERS_TO_INCHES * _dpi;
// ensure no side is never larger then MAX_WINDOW_SIZE
float max = (dims.x > dims.y) ? dims.x : dims.y;
@ -358,29 +352,84 @@ glm::vec2 WebEntityRenderer::getWindowSize(const TypedEntityPointer& entity) con
}
void WebEntityRenderer::hoverEnterEntity(const PointerEvent& event) {
if (_webSurface) {
if (_inputMode == WebInputMode::MOUSE) {
handlePointerEvent(event);
} else if (_webSurface) {
qDebug() << "boop5" << this << _webSurface << _webSurface->getRootItem();
PointerEvent webEvent = event;
webEvent.setPos2D(event.getPos2D() * (METERS_TO_INCHES * _lastDPI));
webEvent.setPos2D(event.getPos2D() * (METERS_TO_INCHES * _dpi));
_webSurface->hoverBeginEvent(webEvent, _touchDevice);
}
}
void WebEntityRenderer::hoverLeaveEntity(const PointerEvent& event) {
if (_webSurface) {
if (_inputMode == WebInputMode::MOUSE) {
PointerEvent endEvent(PointerEvent::Release, event.getID(), event.getPos2D(), event.getPos3D(), event.getNormal(), event.getDirection(),
event.getButton(), event.getButtons(), event.getKeyboardModifiers());
handlePointerEvent(endEvent);
// QML onReleased is only triggered if a click has happened first. We need to send this "fake" mouse move event to properly trigger an onExited.
PointerEvent endMoveEvent(PointerEvent::Move, event.getID());
handlePointerEvent(endMoveEvent);
} else if (_webSurface) {
PointerEvent webEvent = event;
webEvent.setPos2D(event.getPos2D() * (METERS_TO_INCHES * _lastDPI));
webEvent.setPos2D(event.getPos2D() * (METERS_TO_INCHES * _dpi));
_webSurface->hoverEndEvent(webEvent, _touchDevice);
}
}
void WebEntityRenderer::handlePointerEvent(const PointerEvent& event) {
if (_inputMode == WebInputMode::TOUCH) {
handlePointerEventAsTouch(event);
} else {
handlePointerEventAsMouse(event);
}
}
void WebEntityRenderer::handlePointerEventAsTouch(const PointerEvent& event) {
if (_webSurface) {
PointerEvent webEvent = event;
webEvent.setPos2D(event.getPos2D() * (METERS_TO_INCHES * _lastDPI));
webEvent.setPos2D(event.getPos2D() * (METERS_TO_INCHES * _dpi));
_webSurface->handlePointerEvent(webEvent, _touchDevice);
}
}
void WebEntityRenderer::handlePointerEventAsMouse(const PointerEvent& event) {
if (!_webSurface) {
return;
}
glm::vec2 windowPos = event.getPos2D() * (METERS_TO_INCHES * _dpi);
QPointF windowPoint(windowPos.x, windowPos.y);
Qt::MouseButtons buttons = Qt::NoButton;
if (event.getButtons() & PointerEvent::PrimaryButton) {
buttons |= Qt::LeftButton;
}
Qt::MouseButton button = Qt::NoButton;
if (event.getButton() == PointerEvent::PrimaryButton) {
button = Qt::LeftButton;
}
QEvent::Type type;
switch (event.getType()) {
case PointerEvent::Press:
type = QEvent::MouseButtonPress;
break;
case PointerEvent::Release:
type = QEvent::MouseButtonRelease;
break;
case PointerEvent::Move:
type = QEvent::MouseMove;
break;
default:
return;
}
QMouseEvent mouseEvent(type, windowPoint, windowPoint, windowPoint, button, buttons, event.getKeyboardModifiers());
QCoreApplication::sendEvent(_webSurface->getWindow(), &mouseEvent);
}
void WebEntityRenderer::setProxyWindow(QWindow* proxyWindow) {
if (_webSurface) {
_webSurface->setProxyWindow(proxyWindow);
@ -394,8 +443,6 @@ QObject* WebEntityRenderer::getEventHandler() {
return _webSurface->getEventHandler();
}
bool WebEntityRenderer::isTransparent() const {
float fadeRatio = _isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) : 1.0f;
return fadeRatio < OPAQUE_ALPHA_THRESHOLD;
}
void WebEntityRenderer::emitScriptEvent(const QVariant& message) {
QMetaObject::invokeMethod(this, "scriptEventReceived", Q_ARG(QVariant, message));
}

View file

@ -24,13 +24,30 @@ class WebEntityRenderer : public TypedEntityRenderer<WebEntityItem> {
public:
WebEntityRenderer(const EntityItemPointer& entity);
~WebEntityRenderer();
Q_INVOKABLE void hoverEnterEntity(const PointerEvent& event);
Q_INVOKABLE void hoverLeaveEntity(const PointerEvent& event);
Q_INVOKABLE void handlePointerEvent(const PointerEvent& event);
static const QString QML;
static const char* URL_PROPERTY;
static void setAcquireWebSurfaceOperator(std::function<void(const QString&, bool, QSharedPointer<OffscreenQmlSurface>&, bool&)> acquireWebSurfaceOperator) { _acquireWebSurfaceOperator = acquireWebSurfaceOperator; }
static void acquireWebSurface(const QString& url, bool htmlContent, QSharedPointer<OffscreenQmlSurface>& webSurface, bool& cachedWebSurface) {
if (_acquireWebSurfaceOperator) {
_acquireWebSurfaceOperator(url, htmlContent, webSurface, cachedWebSurface);
}
}
static void setReleaseWebSurfaceOperator(std::function<void(QSharedPointer<OffscreenQmlSurface>&, bool&, std::vector<QMetaObject::Connection>&)> releaseWebSurfaceOperator) { _releaseWebSurfaceOperator = releaseWebSurfaceOperator; }
static void releaseWebSurface(QSharedPointer<OffscreenQmlSurface>& webSurface, bool& cachedWebSurface, std::vector<QMetaObject::Connection>& connections) {
if (_releaseWebSurfaceOperator) {
_releaseWebSurfaceOperator(webSurface, cachedWebSurface, connections);
}
}
protected:
virtual void onRemoveFromSceneTyped(const TypedEntityPointer& entity) override;
virtual bool needsRenderUpdate() const override;
virtual bool needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const override;
virtual void doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) override;
@ -39,40 +56,59 @@ protected:
virtual bool wantsHandControllerPointerEvents() const override { return true; }
virtual bool wantsKeyboardFocus() const override { return true; }
virtual void setProxyWindow(QWindow* proxyWindow) override;
virtual QObject* getEventHandler() override;
void handlePointerEventAsTouch(const PointerEvent& event);
void handlePointerEventAsMouse(const PointerEvent& event);
private:
void onTimeout();
bool buildWebSurface(const TypedEntityPointer& entity);
void buildWebSurface(const EntityItemPointer& entity, const QString& newSourceURL);
void destroyWebSurface();
bool hasWebSurface();
glm::vec2 getWindowSize(const TypedEntityPointer& entity) const;
int _geometryId{ 0 };
enum class ContentType {
NoContent,
HtmlContent,
QmlContent
};
static ContentType getContentType(const QString& urlString);
ContentType _contentType { ContentType::NoContent };
ContentType _contentType{ ContentType::NoContent };
QSharedPointer<OffscreenQmlSurface> _webSurface;
glm::vec3 _contextPosition;
QSharedPointer<OffscreenQmlSurface> _webSurface { nullptr };
bool _cachedWebSurface { false };
gpu::TexturePointer _texture;
QString _lastSourceUrl;
uint16_t _lastDPI;
glm::u8vec3 _color;
float _alpha { 1.0f };
QString _sourceURL;
uint16_t _dpi;
QString _scriptURL;
uint8_t _maxFPS;
WebInputMode _inputMode;
glm::vec3 _contextPosition;
QTimer _timer;
uint64_t _lastRenderTime { 0 };
std::vector<QMetaObject::Connection> _connections;
static std::function<void(QString, bool, QSharedPointer<OffscreenQmlSurface>&, bool&)> _acquireWebSurfaceOperator;
static std::function<void(QSharedPointer<OffscreenQmlSurface>&, bool&, std::vector<QMetaObject::Connection>&)> _releaseWebSurfaceOperator;
public slots:
void emitScriptEvent(const QVariant& scriptMessage);
signals:
void scriptEventReceived(const QVariant& message);
void webEventReceived(const QVariant& message);
};
} } // namespace
#if 0
virtual void emitScriptEvent(const QVariant& message) override;
#endif
} }
#endif // hifi_RenderableWebEntityItem_h

View file

@ -95,6 +95,9 @@ EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& param
requestedProperties += PROP_QUERY_AA_CUBE;
requestedProperties += PROP_CAN_CAST_SHADOW;
// requestedProperties += PROP_VISIBLE_IN_SECONDARY_CAMERA; // not sent over the wire
requestedProperties += PROP_RENDER_LAYER;
requestedProperties += PROP_PRIMITIVE_MODE;
requestedProperties += PROP_IGNORE_PICK_INTERSECTION;
withReadLock([&] {
requestedProperties += _grabProperties.getEntityProperties(params);
});
@ -263,8 +266,8 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet
APPEND_ENTITY_PROPERTY(PROP_REGISTRATION_POINT, getRegistrationPoint());
APPEND_ENTITY_PROPERTY(PROP_CREATED, getCreated());
APPEND_ENTITY_PROPERTY(PROP_LAST_EDITED_BY, getLastEditedBy());
// APPEND_ENTITY_PROPERTY(PROP_ENTITY_HOST_TYPE, getEntityHostType()); // not sent over the wire
// APPEND_ENTITY_PROPERTY(PROP_OWNING_AVATAR_ID, getOwningAvatarID()); // not sent over the wire
// APPEND_ENTITY_PROPERTY(PROP_ENTITY_HOST_TYPE, (uint32_t)getEntityHostType()); // not sent over the wire
// APPEND_ENTITY_PROPERTY(PROP_OWNING_AVATAR_ID, getOwningAvatarID()); // not sent over the wire
// convert AVATAR_SELF_ID to actual sessionUUID.
QUuid actualParentID = getParentID();
if (actualParentID == AVATAR_SELF_ID) {
@ -276,6 +279,9 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet
APPEND_ENTITY_PROPERTY(PROP_QUERY_AA_CUBE, getQueryAACube());
APPEND_ENTITY_PROPERTY(PROP_CAN_CAST_SHADOW, getCanCastShadow());
// APPEND_ENTITY_PROPERTY(PROP_VISIBLE_IN_SECONDARY_CAMERA, getIsVisibleInSecondaryCamera()); // not sent over the wire
APPEND_ENTITY_PROPERTY(PROP_RENDER_LAYER, (uint32_t)getRenderLayer());
APPEND_ENTITY_PROPERTY(PROP_PRIMITIVE_MODE, (uint32_t)getPrimitiveMode());
APPEND_ENTITY_PROPERTY(PROP_IGNORE_PICK_INTERSECTION, getIgnorePickIntersection());
withReadLock([&] {
_grabProperties.appendSubclassData(packetData, params, entityTreeElementExtraEncodeData, requestedProperties,
propertyFlags, propertiesDidntFit, propertyCount, appendState);
@ -842,6 +848,9 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
}
READ_ENTITY_PROPERTY(PROP_CAN_CAST_SHADOW, bool, setCanCastShadow);
// READ_ENTITY_PROPERTY(PROP_VISIBLE_IN_SECONDARY_CAMERA, bool, setIsVisibleInSecondaryCamera); // not sent over the wire
READ_ENTITY_PROPERTY(PROP_RENDER_LAYER, RenderLayer, setRenderLayer);
READ_ENTITY_PROPERTY(PROP_PRIMITIVE_MODE, PrimitiveMode, setPrimitiveMode);
READ_ENTITY_PROPERTY(PROP_IGNORE_PICK_INTERSECTION, bool, setIgnorePickIntersection);
withWriteLock([&] {
int bytesFromGrab = _grabProperties.readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args,
propertyFlags, overwriteLocalData,
@ -1313,6 +1322,9 @@ EntityItemProperties EntityItem::getProperties(const EntityPropertyFlags& desire
COPY_ENTITY_PROPERTY_TO_PROPERTIES(queryAACube, getQueryAACube);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(canCastShadow, getCanCastShadow);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(isVisibleInSecondaryCamera, isVisibleInSecondaryCamera);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(renderLayer, getRenderLayer);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(primitiveMode, getPrimitiveMode);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(ignorePickIntersection, getIgnorePickIntersection);
withReadLock([&] {
_grabProperties.getProperties(properties);
});
@ -1457,6 +1469,9 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) {
SET_ENTITY_PROPERTY_FROM_PROPERTIES(queryAACube, setQueryAACube);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(canCastShadow, setCanCastShadow);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(isVisibleInSecondaryCamera, setIsVisibleInSecondaryCamera);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(renderLayer, setRenderLayer);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(primitiveMode, setPrimitiveMode);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(ignorePickIntersection, setIgnorePickIntersection);
withWriteLock([&] {
bool grabPropertiesChanged = _grabProperties.setProperties(properties);
somethingChanged |= grabPropertiesChanged;
@ -2934,6 +2949,58 @@ void EntityItem::setIsVisibleInSecondaryCamera(bool value) {
}
}
RenderLayer EntityItem::getRenderLayer() const {
return resultWithReadLock<RenderLayer>([&] {
return _renderLayer;
});
}
void EntityItem::setRenderLayer(RenderLayer value) {
bool changed = false;
withWriteLock([&] {
if (_renderLayer != value) {
changed = true;
_renderLayer = value;
}
});
if (changed) {
emit requestRenderUpdate();
}
}
PrimitiveMode EntityItem::getPrimitiveMode() const {
return resultWithReadLock<PrimitiveMode>([&] {
return _primitiveMode;
});
}
void EntityItem::setPrimitiveMode(PrimitiveMode value) {
bool changed = false;
withWriteLock([&] {
if (_primitiveMode != value) {
changed = true;
_primitiveMode = value;
}
});
if (changed) {
emit requestRenderUpdate();
}
}
bool EntityItem::getIgnorePickIntersection() const {
return resultWithReadLock<bool>([&] {
return _ignorePickIntersection;
});
}
void EntityItem::setIgnorePickIntersection(bool value) {
withWriteLock([&] {
_ignorePickIntersection = value;
});
}
bool EntityItem::getCanCastShadow() const {
bool result;
withReadLock([&] {

View file

@ -293,6 +293,15 @@ public:
bool isVisibleInSecondaryCamera() const;
void setIsVisibleInSecondaryCamera(bool value);
RenderLayer getRenderLayer() const;
void setRenderLayer(RenderLayer value);
PrimitiveMode getPrimitiveMode() const;
void setPrimitiveMode(PrimitiveMode value);
bool getIgnorePickIntersection() const;
void setIgnorePickIntersection(bool value);
bool getCanCastShadow() const;
void setCanCastShadow(bool value);
@ -621,7 +630,10 @@ protected:
float _angularDamping { ENTITY_ITEM_DEFAULT_ANGULAR_DAMPING };
bool _visible { ENTITY_ITEM_DEFAULT_VISIBLE };
bool _isVisibleInSecondaryCamera { ENTITY_ITEM_DEFAULT_VISIBLE_IN_SECONDARY_CAMERA };
RenderLayer _renderLayer { RenderLayer::WORLD };
PrimitiveMode _primitiveMode { PrimitiveMode::SOLID };
bool _canCastShadow{ ENTITY_ITEM_DEFAULT_CAN_CAST_SHADOW };
bool _ignorePickIntersection { false };
bool _collisionless { ENTITY_ITEM_DEFAULT_COLLISIONLESS };
uint16_t _collisionMask { ENTITY_COLLISION_MASK_DEFAULT };
bool _dynamic { ENTITY_ITEM_DEFAULT_DYNAMIC };

View file

@ -340,17 +340,96 @@ QString EntityItemProperties::getBillboardModeAsString() const {
return BillboardModeHelpers::getNameForBillboardMode(_billboardMode);
}
void EntityItemProperties::setBillboardModeFromString(const QString& materialMappingMode) {
void EntityItemProperties::setBillboardModeFromString(const QString& billboardMode) {
if (stringToBillboardModeLookup.empty()) {
buildStringToBillboardModeLookup();
}
auto billboardModeItr = stringToBillboardModeLookup.find(materialMappingMode.toLower());
auto billboardModeItr = stringToBillboardModeLookup.find(billboardMode.toLower());
if (billboardModeItr != stringToBillboardModeLookup.end()) {
_billboardMode = billboardModeItr.value();
_billboardModeChanged = true;
}
}
QHash<QString, RenderLayer> stringToRenderLayerLookup;
void addRenderLayer(RenderLayer mode) {
stringToRenderLayerLookup[RenderLayerHelpers::getNameForRenderLayer(mode)] = mode;
}
void buildStringToRenderLayerLookup() {
addRenderLayer(RenderLayer::WORLD);
addRenderLayer(RenderLayer::FRONT);
addRenderLayer(RenderLayer::HUD);
}
QString EntityItemProperties::getRenderLayerAsString() const {
return RenderLayerHelpers::getNameForRenderLayer(_renderLayer);
}
void EntityItemProperties::setRenderLayerFromString(const QString& renderLayer) {
if (stringToRenderLayerLookup.empty()) {
buildStringToRenderLayerLookup();
}
auto renderLayerItr = stringToRenderLayerLookup.find(renderLayer.toLower());
if (renderLayerItr != stringToRenderLayerLookup.end()) {
_renderLayer = renderLayerItr.value();
_renderLayerChanged = true;
}
}
QHash<QString, PrimitiveMode> stringToPrimitiveModeLookup;
void addPrimitiveMode(PrimitiveMode mode) {
stringToPrimitiveModeLookup[PrimitiveModeHelpers::getNameForPrimitiveMode(mode)] = mode;
}
void buildStringToPrimitiveModeLookup() {
addPrimitiveMode(PrimitiveMode::SOLID);
addPrimitiveMode(PrimitiveMode::LINES);
}
QString EntityItemProperties::getPrimitiveModeAsString() const {
return PrimitiveModeHelpers::getNameForPrimitiveMode(_primitiveMode);
}
void EntityItemProperties::setPrimitiveModeFromString(const QString& primitiveMode) {
if (stringToPrimitiveModeLookup.empty()) {
buildStringToPrimitiveModeLookup();
}
auto primitiveModeItr = stringToPrimitiveModeLookup.find(primitiveMode.toLower());
if (primitiveModeItr != stringToPrimitiveModeLookup.end()) {
_primitiveMode = primitiveModeItr.value();
_primitiveModeChanged = true;
}
}
QHash<QString, WebInputMode> stringToWebInputModeLookup;
void addWebInputMode(WebInputMode mode) {
stringToWebInputModeLookup[WebInputModeHelpers::getNameForWebInputMode(mode)] = mode;
}
void buildStringToWebInputModeLookup() {
addWebInputMode(WebInputMode::TOUCH);
addWebInputMode(WebInputMode::MOUSE);
}
QString EntityItemProperties::getInputModeAsString() const {
return WebInputModeHelpers::getNameForWebInputMode(_inputMode);
}
void EntityItemProperties::setInputModeFromString(const QString& webInputMode) {
if (stringToWebInputModeLookup.empty()) {
buildStringToWebInputModeLookup();
}
auto webInputModeItr = stringToWebInputModeLookup.find(webInputMode.toLower());
if (webInputModeItr != stringToWebInputModeLookup.end()) {
_inputMode = webInputModeItr.value();
_inputModeChanged = true;
}
}
EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
EntityPropertyFlags changedProperties;
@ -375,6 +454,9 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
CHECK_PROPERTY_CHANGE(PROP_QUERY_AA_CUBE, queryAACube);
CHECK_PROPERTY_CHANGE(PROP_CAN_CAST_SHADOW, canCastShadow);
CHECK_PROPERTY_CHANGE(PROP_VISIBLE_IN_SECONDARY_CAMERA, isVisibleInSecondaryCamera);
CHECK_PROPERTY_CHANGE(PROP_RENDER_LAYER, renderLayer);
CHECK_PROPERTY_CHANGE(PROP_PRIMITIVE_MODE, primitiveMode);
CHECK_PROPERTY_CHANGE(PROP_IGNORE_PICK_INTERSECTION, ignorePickIntersection);
changedProperties += _grab.getChangedProperties();
// Physics
@ -474,6 +556,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
CHECK_PROPERTY_CHANGE(PROP_JOINT_TRANSLATIONS_SET, jointTranslationsSet);
CHECK_PROPERTY_CHANGE(PROP_JOINT_TRANSLATIONS, jointTranslations);
CHECK_PROPERTY_CHANGE(PROP_RELAY_PARENT_JOINTS, relayParentJoints);
CHECK_PROPERTY_CHANGE(PROP_GROUP_CULLED, groupCulled);
changedProperties += _animation.getChangedProperties();
// Light
@ -528,6 +611,9 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
// Web
CHECK_PROPERTY_CHANGE(PROP_SOURCE_URL, sourceUrl);
CHECK_PROPERTY_CHANGE(PROP_DPI, dpi);
CHECK_PROPERTY_CHANGE(PROP_SCRIPT_URL, scriptURL);
CHECK_PROPERTY_CHANGE(PROP_MAX_FPS, maxFPS);
CHECK_PROPERTY_CHANGE(PROP_INPUT_MODE, inputMode);
// Polyline
CHECK_PROPERTY_CHANGE(PROP_LINE_POINTS, linePoints);
@ -579,15 +665,15 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
* the <code>shape</code> property set for entities of these types.) <em>Read-only.</em>
* @property {EntityHostType} entityHostType="domain" - How this entity will behave, including if and how it is sent to other people.
* The value can only be set at entity creation by using the <code>entityHostType</code> parameter in
* {@link Entities.addEntity}.
* {@link Entities.addEntity}. Read-only.
* @property {boolean} avatarEntity=false - If <code>true</code> then the entity is an avatar entity; An avatar entity follows you to each domain you visit,
* rendering at the same world coordinates unless it's parented to your avatar. <em>Value cannot be changed after the entity is created.</em><br />
* The value can only be set at entity creation by using the <code>entityHostType</code> parameter in
* {@link Entities.addEntity}. <code>clientOnly</code> is an alias.
* {@link Entities.addEntity}. <code>clientOnly</code> is an alias. Read-only.
* @property {boolean} localEntity=false - If <code>true</code> then the entity is a local entity; Local entities only render for you and are not sent over the wire.
* <em>Value cannot be changed after the entity is created.</em><br />
* The value can only be set at entity creation by using the <code>entityHostType</code> parameter in
* {@link Entities.addEntity}.
* {@link Entities.addEntity}. Read-only.
* @property {Uuid} owningAvatarID=Uuid.NULL - The session ID of the owning avatar if <code>avatarEntity</code> is
* <code>true</code>, otherwise {@link Uuid|Uuid.NULL}. <em>Read-only.</em>
*
@ -611,6 +697,9 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
* {@link Entities.EntityType|Zone} entity with <code>castShadows</code> enabled in its
* {@link Entities.EntityProperties-Zone|keyLight} property.
* @property {boolean} isVisibleInSecondaryCamera=true - Whether or not the entity is rendered in the secondary camera. If <code>true</code> then the entity is rendered.
* @property {RenderLayer} renderLayer="world" - In which layer this entity renders.
* @property {PrimitiveMode} primitiveMode="solid" - How this entity's geometry is rendered.
* @property {boolean} ignorePickIntersection=false - If <code>true</code>, picks ignore the entity.
*
* @property {Vec3} position=0,0,0 - The position of the entity.
* @property {Quat} rotation=0,0,0,1 - The orientation of the entity with respect to world coordinates.
@ -904,7 +993,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
* parse the JSON string into a JavaScript object of name, URL pairs. <em>Read-only.</em>
*
* @property {ShapeType} shapeType="none" - The shape of the collision hull used if collisions are enabled.
* @property {string} compoundShapeURL="" - The OBJ file to use for the compound shape if <code>shapeType</code> is
* @property {string} compoundShapeURL="" - The model file to use for the compound shape if <code>shapeType</code> is
* <code>"compound"</code>.
*
* @property {Entities.AnimationProperties} animation - An animation to play on the model.
@ -929,6 +1018,8 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
* {@link Entities.getJointIndex|getJointIndex}.
* @property {boolean} relayParentJoints=false - If <code>true</code> and the entity is parented to an avatar, then the
* avatar's joint rotations are applied to the entity's joints.
* @property {boolean} groupCulled=false - If <code>true</code>, the mesh parts of the model are LOD culled as a group.
* If <code>false</code>, separate mesh parts will be LOD culled individually.
*
* @example <caption>Rez a Vive tracker puck.</caption>
* var entity = Entities.addEntity({
@ -1209,11 +1300,16 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
* The entity has properties in addition to the common {@link Entities.EntityProperties|EntityProperties}.
* @typedef {object} Entities.EntityProperties-Web
* @property {Vec3} dimensions=0.1,0.1,0.01 - The dimensions of the entity.
* @property {Color} color=255,255,255 - The color of the web surface.
* @property {number} alpha=1 - The alpha of the web surface.
* @property {string} sourceUrl="" - The URL of the Web page to display. This value does not change as you or others navigate
* on the Web entity.
* @property {number} dpi=30 - The resolution to display the page at, in dots per inch. If you convert this to dots per meter
* (multiply by 1 / 0.0254 = 39.3701) then multiply <code>dimensions.x</code> and <code>dimensions.y</code> by that value
* you get the resolution in pixels.
* @property {string} scriptURL="" - The URL of a JavaScript file to inject into the Web page.
* @property {number} maxFPS=10 - The maximum update rate for the Web content, in frames/second.
* @property {WebInputMode} inputMode="touch" - The user input mode to use.
* @example <caption>Create a Web entity displaying at 1920 x 1080 resolution.</caption>
* var METERS_TO_INCHES = 39.3701;
* var entity = Entities.addEntity({
@ -1242,7 +1338,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
* @property {ShapeType} shapeType="box" - The shape of the volume in which the zone's lighting effects and avatar
* permissions have effect. Reverts to the default value if set to <code>"none"</code>, or set to <code>"compound"</code>
* and <code>compoundShapeURL</code> is <code>""</code>.
* @property {string} compoundShapeURL="" - The OBJ file to use for the compound shape if <code>shapeType</code> is
* @property {string} compoundShapeURL="" - The model file to use for the compound shape if <code>shapeType</code> is
* <code>"compound"</code>.
*
* @property {string} keyLightMode="inherit" - Configures the key light in the zone. Possible values:<br />
@ -1420,6 +1516,9 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_QUERY_AA_CUBE, queryAACube);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_CAN_CAST_SHADOW, canCastShadow);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_VISIBLE_IN_SECONDARY_CAMERA, isVisibleInSecondaryCamera);
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_RENDER_LAYER, renderLayer, getRenderLayerAsString());
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_PRIMITIVE_MODE, primitiveMode, getPrimitiveModeAsString());
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_IGNORE_PICK_INTERSECTION, ignorePickIntersection);
_grab.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties);
// Physics
@ -1536,6 +1635,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_JOINT_TRANSLATIONS_SET, jointTranslationsSet);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_JOINT_TRANSLATIONS, jointTranslations);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_RELAY_PARENT_JOINTS, relayParentJoints);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_GROUP_CULLED, groupCulled);
if (!psuedoPropertyFlagsButDesiredEmpty) {
_animation.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties);
}
@ -1606,8 +1706,14 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool
// Web only
if (_type == EntityTypes::Web) {
COPY_PROPERTY_TO_QSCRIPTVALUE_TYPED(PROP_COLOR, color, u8vec3Color);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ALPHA, alpha);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_SOURCE_URL, sourceUrl);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_DPI, dpi);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_SCRIPT_URL, scriptURL);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_MAX_FPS, maxFPS);
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_INPUT_MODE, inputMode, getInputModeAsString());
}
// PolyVoxel only
@ -1801,6 +1907,9 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool
COPY_PROPERTY_FROM_QSCRIPTVALUE(queryAACube, AACube, setQueryAACube); // TODO: should scripts be able to set this?
COPY_PROPERTY_FROM_QSCRIPTVALUE(canCastShadow, bool, setCanCastShadow);
COPY_PROPERTY_FROM_QSCRIPTVALUE(isVisibleInSecondaryCamera, bool, setIsVisibleInSecondaryCamera);
COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(renderLayer, RenderLayer);
COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(primitiveMode, PrimitiveMode);
COPY_PROPERTY_FROM_QSCRIPTVALUE(ignorePickIntersection, bool, setIgnorePickIntersection);
_grab.copyFromScriptValue(object, _defaultSettings);
// Physics
@ -1905,6 +2014,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool
COPY_PROPERTY_FROM_QSCRIPTVALUE(jointTranslationsSet, qVectorBool, setJointTranslationsSet);
COPY_PROPERTY_FROM_QSCRIPTVALUE(jointTranslations, qVectorVec3, setJointTranslations);
COPY_PROPERTY_FROM_QSCRIPTVALUE(relayParentJoints, bool, setRelayParentJoints);
COPY_PROPERTY_FROM_QSCRIPTVALUE(groupCulled, bool, setGroupCulled);
_animation.copyFromScriptValue(object, _defaultSettings);
// Light
@ -1959,6 +2069,9 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool
// Web
COPY_PROPERTY_FROM_QSCRIPTVALUE(sourceUrl, QString, setSourceUrl);
COPY_PROPERTY_FROM_QSCRIPTVALUE(dpi, uint16_t, setDPI);
COPY_PROPERTY_FROM_QSCRIPTVALUE(scriptURL, QString, setScriptURL);
COPY_PROPERTY_FROM_QSCRIPTVALUE(maxFPS, uint8_t, setMaxFPS);
COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(inputMode, InputMode);
// Polyline
COPY_PROPERTY_FROM_QSCRIPTVALUE(linePoints, qVectorVec3, setLinePoints);
@ -2072,6 +2185,9 @@ void EntityItemProperties::merge(const EntityItemProperties& other) {
COPY_PROPERTY_IF_CHANGED(queryAACube);
COPY_PROPERTY_IF_CHANGED(canCastShadow);
COPY_PROPERTY_IF_CHANGED(isVisibleInSecondaryCamera);
COPY_PROPERTY_IF_CHANGED(renderLayer);
COPY_PROPERTY_IF_CHANGED(primitiveMode);
COPY_PROPERTY_IF_CHANGED(ignorePickIntersection);
_grab.merge(other._grab);
// Physics
@ -2171,6 +2287,7 @@ void EntityItemProperties::merge(const EntityItemProperties& other) {
COPY_PROPERTY_IF_CHANGED(jointTranslationsSet);
COPY_PROPERTY_IF_CHANGED(jointTranslations);
COPY_PROPERTY_IF_CHANGED(relayParentJoints);
COPY_PROPERTY_IF_CHANGED(groupCulled);
_animation.merge(other._animation);
// Light
@ -2225,6 +2342,9 @@ void EntityItemProperties::merge(const EntityItemProperties& other) {
// Web
COPY_PROPERTY_IF_CHANGED(sourceUrl);
COPY_PROPERTY_IF_CHANGED(dpi);
COPY_PROPERTY_IF_CHANGED(scriptURL);
COPY_PROPERTY_IF_CHANGED(maxFPS);
COPY_PROPERTY_IF_CHANGED(inputMode);
// Polyline
COPY_PROPERTY_IF_CHANGED(linePoints);
@ -2342,6 +2462,9 @@ bool EntityItemProperties::getPropertyInfo(const QString& propertyName, EntityPr
ADD_PROPERTY_TO_MAP(PROP_QUERY_AA_CUBE, QueryAACube, queryAACube, AACube);
ADD_PROPERTY_TO_MAP(PROP_CAN_CAST_SHADOW, CanCastShadow, canCastShadow, bool);
ADD_PROPERTY_TO_MAP(PROP_VISIBLE_IN_SECONDARY_CAMERA, IsVisibleInSecondaryCamera, isVisibleInSecondaryCamera, bool);
ADD_PROPERTY_TO_MAP(PROP_RENDER_LAYER, RenderLayer, renderLayer, RenderLayer);
ADD_PROPERTY_TO_MAP(PROP_PRIMITIVE_MODE, PrimitiveMode, primitiveMode, PrimitiveMode);
ADD_PROPERTY_TO_MAP(PROP_IGNORE_PICK_INTERSECTION, IgnorePickIntersection, ignorePickIntersection, bool);
{ // Grab
ADD_GROUP_PROPERTY_TO_MAP(PROP_GRAB_GRABBABLE, Grab, grab, Grabbable, grabbable);
ADD_GROUP_PROPERTY_TO_MAP(PROP_GRAB_KINEMATIC, Grab, grab, GrabKinematic, grabKinematic);
@ -2495,6 +2618,7 @@ bool EntityItemProperties::getPropertyInfo(const QString& propertyName, EntityPr
ADD_PROPERTY_TO_MAP(PROP_JOINT_TRANSLATIONS_SET, JointTranslationsSet, jointTranslationsSet, QVector<bool>);
ADD_PROPERTY_TO_MAP(PROP_JOINT_TRANSLATIONS, JointTranslations, jointTranslations, QVector<vec3>);
ADD_PROPERTY_TO_MAP(PROP_RELAY_PARENT_JOINTS, RelayParentJoints, relayParentJoints, bool);
ADD_PROPERTY_TO_MAP(PROP_GROUP_CULLED, GroupCulled, groupCulled, bool);
{ // Animation
ADD_GROUP_PROPERTY_TO_MAP(PROP_ANIMATION_URL, Animation, animation, URL, url);
ADD_GROUP_PROPERTY_TO_MAP(PROP_ANIMATION_ALLOW_TRANSLATION, Animation, animation, AllowTranslation, allowTranslation);
@ -2591,6 +2715,9 @@ bool EntityItemProperties::getPropertyInfo(const QString& propertyName, EntityPr
// Web
ADD_PROPERTY_TO_MAP(PROP_SOURCE_URL, SourceUrl, sourceUrl, QString);
ADD_PROPERTY_TO_MAP(PROP_DPI, DPI, dpi, uint16_t);
ADD_PROPERTY_TO_MAP(PROP_SCRIPT_URL, ScriptURL, scriptURL, QString);
ADD_PROPERTY_TO_MAP(PROP_MAX_FPS, MaxFPS, maxFPS, uint8_t);
ADD_PROPERTY_TO_MAP(PROP_INPUT_MODE, InputMode, inputMode, WebInputMode);
// Polyline
ADD_PROPERTY_TO_MAP(PROP_LINE_POINTS, LinePoints, linePoints, QVector<vec3>);
@ -2771,6 +2898,9 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy
APPEND_ENTITY_PROPERTY(PROP_QUERY_AA_CUBE, properties.getQueryAACube());
APPEND_ENTITY_PROPERTY(PROP_CAN_CAST_SHADOW, properties.getCanCastShadow());
// APPEND_ENTITY_PROPERTY(PROP_VISIBLE_IN_SECONDARY_CAMERA, properties.getIsVisibleInSecondaryCamera()); // not sent over the wire
APPEND_ENTITY_PROPERTY(PROP_RENDER_LAYER, (uint32_t)properties.getRenderLayer());
APPEND_ENTITY_PROPERTY(PROP_PRIMITIVE_MODE, (uint32_t)properties.getPrimitiveMode());
APPEND_ENTITY_PROPERTY(PROP_IGNORE_PICK_INTERSECTION, properties.getIgnorePickIntersection());
_staticGrab.setProperties(properties);
_staticGrab.appendToEditPacket(packetData, requestedProperties, propertyFlags,
propertiesDidntFit, propertyCount, appendState);
@ -2877,6 +3007,7 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy
APPEND_ENTITY_PROPERTY(PROP_JOINT_TRANSLATIONS_SET, properties.getJointTranslationsSet());
APPEND_ENTITY_PROPERTY(PROP_JOINT_TRANSLATIONS, properties.getJointTranslations());
APPEND_ENTITY_PROPERTY(PROP_RELAY_PARENT_JOINTS, properties.getRelayParentJoints());
APPEND_ENTITY_PROPERTY(PROP_GROUP_CULLED, properties.getGroupCulled());
_staticAnimation.setProperties(properties);
_staticAnimation.appendToEditPacket(packetData, requestedProperties, propertyFlags, propertiesDidntFit, propertyCount, appendState);
@ -2951,8 +3082,14 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy
}
if (properties.getType() == EntityTypes::Web) {
APPEND_ENTITY_PROPERTY(PROP_COLOR, properties.getColor());
APPEND_ENTITY_PROPERTY(PROP_ALPHA, properties.getAlpha());
APPEND_ENTITY_PROPERTY(PROP_SOURCE_URL, properties.getSourceUrl());
APPEND_ENTITY_PROPERTY(PROP_DPI, properties.getDPI());
APPEND_ENTITY_PROPERTY(PROP_SCRIPT_URL, properties.getScriptURL());
APPEND_ENTITY_PROPERTY(PROP_MAX_FPS, properties.getMaxFPS());
APPEND_ENTITY_PROPERTY(PROP_INPUT_MODE, (uint32_t)properties.getInputMode());
}
if (properties.getType() == EntityTypes::Line) {
@ -3212,6 +3349,9 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_QUERY_AA_CUBE, AACube, setQueryAACube);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_CAN_CAST_SHADOW, bool, setCanCastShadow);
// READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_VISIBLE_IN_SECONDARY_CAMERA, bool, setIsVisibleInSecondaryCamera); // not sent over the wire
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_RENDER_LAYER, RenderLayer, setRenderLayer);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_PRIMITIVE_MODE, PrimitiveMode, setPrimitiveMode);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_IGNORE_PICK_INTERSECTION, bool, setIgnorePickIntersection);
properties.getGrab().decodeFromEditPacket(propertyFlags, dataAt, processedBytes);
// Physics
@ -3316,6 +3456,7 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_JOINT_TRANSLATIONS_SET, QVector<bool>, setJointTranslationsSet);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_JOINT_TRANSLATIONS, QVector<vec3>, setJointTranslations);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_RELAY_PARENT_JOINTS, bool, setRelayParentJoints);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_GROUP_CULLED, bool, setGroupCulled);
properties.getAnimation().decodeFromEditPacket(propertyFlags, dataAt, processedBytes);
}
@ -3381,8 +3522,14 @@ 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);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SOURCE_URL, QString, setSourceUrl);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_DPI, uint16_t, setDPI);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SCRIPT_URL, QString, setScriptURL);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MAX_FPS, uint8_t, setMaxFPS);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_INPUT_MODE, WebInputMode, setInputMode);
}
if (properties.getType() == EntityTypes::Line) {
@ -3594,6 +3741,9 @@ void EntityItemProperties::markAllChanged() {
_queryAACubeChanged = true;
_canCastShadowChanged = true;
_isVisibleInSecondaryCameraChanged = true;
_renderLayerChanged = true;
_primitiveModeChanged = true;
_ignorePickIntersectionChanged = true;
_grab.markAllChanged();
// Physics
@ -3686,6 +3836,7 @@ void EntityItemProperties::markAllChanged() {
_jointTranslationsSetChanged = true;
_jointTranslationsChanged = true;
_relayParentJointsChanged = true;
_groupCulledChanged = true;
_animation.markAllChanged();
// Light
@ -3740,6 +3891,9 @@ void EntityItemProperties::markAllChanged() {
// Web
_sourceUrlChanged = true;
_dpiChanged = true;
_scriptURLChanged = true;
_maxFPSChanged = true;
_inputModeChanged = true;
// Polyline
_linePointsChanged = true;
@ -3966,6 +4120,15 @@ QList<QString> EntityItemProperties::listChangedProperties() {
if (isVisibleInSecondaryCameraChanged()) {
out += "isVisibleInSecondaryCamera";
}
if (renderLayerChanged()) {
out += "renderLayer";
}
if (primitiveModeChanged()) {
out += "primitiveMode";
}
if (ignorePickIntersectionChanged()) {
out += "ignorePickIntersection";
}
getGrab().listChangedProperties(out);
// Physics
@ -4212,6 +4375,9 @@ QList<QString> EntityItemProperties::listChangedProperties() {
if (relayParentJointsChanged()) {
out += "relayParentJoints";
}
if (groupCulledChanged()) {
out += "groupCulled";
}
getAnimation().listChangedProperties(out);
// Light
@ -4342,6 +4508,15 @@ QList<QString> EntityItemProperties::listChangedProperties() {
if (dpiChanged()) {
out += "dpi";
}
if (scriptURLChanged()) {
out += "scriptURL";
}
if (maxFPSChanged()) {
out += "maxFPS";
}
if (inputModeChanged()) {
out += "inputMode";
}
// Polyline
if (linePointsChanged()) {

View file

@ -30,27 +30,33 @@
#include <ShapeInfo.h>
#include <ColorUtils.h>
#include "AnimationPropertyGroup.h"
#include "EntityItemID.h"
#include "EntityItemPropertiesDefaults.h"
#include "EntityItemPropertiesMacros.h"
#include "EntityTypes.h"
#include "EntityPropertyFlags.h"
#include "EntityPsuedoPropertyFlags.h"
#include "LightEntityItem.h"
#include "LineEntityItem.h"
#include "ParticleEffectEntityItem.h"
#include "PolyVoxEntityItem.h"
#include "SimulationOwner.h"
#include "TextEntityItem.h"
#include "WebEntityItem.h"
#include "ParticleEffectEntityItem.h"
#include "LineEntityItem.h"
#include "PolyVoxEntityItem.h"
#include "GridEntityItem.h"
#include "LightEntityItem.h"
#include "ZoneEntityItem.h"
#include "AnimationPropertyGroup.h"
#include "SkyboxPropertyGroup.h"
#include "HazePropertyGroup.h"
#include "BloomPropertyGroup.h"
#include "TextEntityItem.h"
#include "ZoneEntityItem.h"
#include "GridEntityItem.h"
#include "MaterialMappingMode.h"
#include "BillboardMode.h"
#include "RenderLayer.h"
#include "PrimitiveMode.h"
#include "WebInputMode.h"
const quint64 UNKNOWN_CREATED_TIME = 0;
@ -169,6 +175,9 @@ public:
DEFINE_PROPERTY_REF(PROP_QUERY_AA_CUBE, QueryAACube, queryAACube, AACube, AACube());
DEFINE_PROPERTY(PROP_CAN_CAST_SHADOW, CanCastShadow, canCastShadow, bool, ENTITY_ITEM_DEFAULT_CAN_CAST_SHADOW);
DEFINE_PROPERTY(PROP_VISIBLE_IN_SECONDARY_CAMERA, IsVisibleInSecondaryCamera, isVisibleInSecondaryCamera, bool, ENTITY_ITEM_DEFAULT_VISIBLE_IN_SECONDARY_CAMERA);
DEFINE_PROPERTY_REF_ENUM(PROP_RENDER_LAYER, RenderLayer, renderLayer, RenderLayer, RenderLayer::WORLD);
DEFINE_PROPERTY_REF_ENUM(PROP_PRIMITIVE_MODE, PrimitiveMode, primitiveMode, PrimitiveMode, PrimitiveMode::SOLID);
DEFINE_PROPERTY(PROP_IGNORE_PICK_INTERSECTION, IgnorePickIntersection, ignorePickIntersection, bool, false);
DEFINE_PROPERTY_GROUP(Grab, grab, GrabPropertyGroup);
// Physics
@ -268,6 +277,7 @@ public:
DEFINE_PROPERTY_REF(PROP_JOINT_TRANSLATIONS_SET, JointTranslationsSet, jointTranslationsSet, QVector<bool>, QVector<bool>());
DEFINE_PROPERTY_REF(PROP_JOINT_TRANSLATIONS, JointTranslations, jointTranslations, QVector<glm::vec3>, ENTITY_ITEM_DEFAULT_EMPTY_VEC3_QVEC);
DEFINE_PROPERTY(PROP_RELAY_PARENT_JOINTS, RelayParentJoints, relayParentJoints, bool, ENTITY_ITEM_DEFAULT_RELAY_PARENT_JOINTS);
DEFINE_PROPERTY_REF(PROP_GROUP_CULLED, GroupCulled, groupCulled, bool, false);
DEFINE_PROPERTY_GROUP(Animation, animation, AnimationPropertyGroup);
// Light
@ -320,8 +330,11 @@ public:
DEFINE_PROPERTY_REF(PROP_Z_P_NEIGHBOR_ID, ZPNeighborID, zPNeighborID, EntityItemID, UNKNOWN_ENTITY_ID);
// Web
DEFINE_PROPERTY_REF(PROP_SOURCE_URL, SourceUrl, sourceUrl, QString, "");
DEFINE_PROPERTY_REF(PROP_SOURCE_URL, SourceUrl, sourceUrl, QString, WebEntityItem::DEFAULT_SOURCE_URL);
DEFINE_PROPERTY_REF(PROP_DPI, DPI, dpi, uint16_t, ENTITY_ITEM_DEFAULT_DPI);
DEFINE_PROPERTY_REF(PROP_SCRIPT_URL, ScriptURL, scriptURL, QString, "");
DEFINE_PROPERTY_REF(PROP_MAX_FPS, MaxFPS, maxFPS, uint8_t, WebEntityItem::DEFAULT_MAX_FPS);
DEFINE_PROPERTY_REF_ENUM(PROP_INPUT_MODE, InputMode, inputMode, WebInputMode, WebInputMode::TOUCH);
// Polyline
DEFINE_PROPERTY_REF(PROP_LINE_POINTS, LinePoints, linePoints, QVector<glm::vec3>, ENTITY_ITEM_DEFAULT_EMPTY_VEC3_QVEC);

View file

@ -39,6 +39,9 @@ enum EntityPropertyList {
PROP_QUERY_AA_CUBE,
PROP_CAN_CAST_SHADOW,
PROP_VISIBLE_IN_SECONDARY_CAMERA, // not sent over the wire
PROP_RENDER_LAYER,
PROP_PRIMITIVE_MODE,
PROP_IGNORE_PICK_INTERSECTION,
// Grab
PROP_GRAB_GRABBABLE,
PROP_GRAB_KINEMATIC,
@ -198,16 +201,17 @@ enum EntityPropertyList {
PROP_JOINT_TRANSLATIONS_SET = PROP_DERIVED_3,
PROP_JOINT_TRANSLATIONS = PROP_DERIVED_4,
PROP_RELAY_PARENT_JOINTS = PROP_DERIVED_5,
PROP_GROUP_CULLED = PROP_DERIVED_6,
// Animation
PROP_ANIMATION_URL = PROP_DERIVED_6,
PROP_ANIMATION_ALLOW_TRANSLATION = PROP_DERIVED_7,
PROP_ANIMATION_FPS = PROP_DERIVED_8,
PROP_ANIMATION_FRAME_INDEX = PROP_DERIVED_9,
PROP_ANIMATION_PLAYING = PROP_DERIVED_10,
PROP_ANIMATION_LOOP = PROP_DERIVED_11,
PROP_ANIMATION_FIRST_FRAME = PROP_DERIVED_12,
PROP_ANIMATION_LAST_FRAME = PROP_DERIVED_13,
PROP_ANIMATION_HOLD = PROP_DERIVED_14,
PROP_ANIMATION_URL = PROP_DERIVED_7,
PROP_ANIMATION_ALLOW_TRANSLATION = PROP_DERIVED_8,
PROP_ANIMATION_FPS = PROP_DERIVED_9,
PROP_ANIMATION_FRAME_INDEX = PROP_DERIVED_10,
PROP_ANIMATION_PLAYING = PROP_DERIVED_11,
PROP_ANIMATION_LOOP = PROP_DERIVED_12,
PROP_ANIMATION_FIRST_FRAME = PROP_DERIVED_13,
PROP_ANIMATION_LAST_FRAME = PROP_DERIVED_14,
PROP_ANIMATION_HOLD = PROP_DERIVED_15,
// Light
PROP_IS_SPOTLIGHT = PROP_DERIVED_0,
@ -284,6 +288,9 @@ enum EntityPropertyList {
// Web
PROP_SOURCE_URL = PROP_DERIVED_0,
PROP_DPI = PROP_DERIVED_1,
PROP_SCRIPT_URL = PROP_DERIVED_2,
PROP_MAX_FPS = PROP_DERIVED_3,
PROP_INPUT_MODE = PROP_DERIVED_4,
// Polyline
PROP_LINE_POINTS = PROP_DERIVED_0,

View file

@ -2707,7 +2707,6 @@ void convertGrabUserDataToProperties(EntityItemProperties& properties) {
bool EntityTree::readFromMap(QVariantMap& map) {
// These are needed to deal with older content (before adding inheritance modes)
int contentVersion = map["Version"].toInt();
bool needsConversion = (contentVersion < (int)EntityVersion::ZoneLightInheritModes);
if (map.contains("Id")) {
_persistID = map["Id"].toUuid();
@ -2782,7 +2781,7 @@ bool EntityTree::readFromMap(QVariantMap& map) {
}
// Fix for older content not containing mode fields in the zones
if (needsConversion && (properties.getType() == EntityTypes::EntityType::Zone)) {
if (contentVersion < (int)EntityVersion::ZoneLightInheritModes && (properties.getType() == EntityTypes::EntityType::Zone)) {
// The legacy version had no keylight mode - this is set to on
properties.setKeyLightMode(COMPONENT_MODE_ENABLED);

View file

@ -187,6 +187,10 @@ EntityItemID EntityTreeElement::evalDetailedRayIntersection(const glm::vec3& ori
// only called if we do intersect our bounding cube, but find if we actually intersect with entities...
EntityItemID entityID;
forEachEntity([&](EntityItemPointer entity) {
if (entity->getIgnorePickIntersection()) {
return;
}
// use simple line-sphere for broadphase check
// (this is faster and more likely to cull results than the filter check below so we do it first)
bool success;
@ -327,6 +331,10 @@ EntityItemID EntityTreeElement::evalDetailedParabolaIntersection(const glm::vec3
// only called if we do intersect our bounding cube, but find if we actually intersect with entities...
EntityItemID entityID;
forEachEntity([&](EntityItemPointer entity) {
if (entity->getIgnorePickIntersection()) {
return;
}
// use simple line-sphere for broadphase check
// (this is faster and more likely to cull results than the filter check below so we do it first)
bool success;

View file

@ -67,6 +67,7 @@ EntityItemProperties ModelEntityItem::getProperties(const EntityPropertyFlags& d
COPY_ENTITY_PROPERTY_TO_PROPERTIES(jointTranslationsSet, getJointTranslationsSet);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(jointTranslations, getJointTranslations);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(relayParentJoints, getRelayParentJoints);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(groupCulled, getGroupCulled);
withReadLock([&] {
_animationProperties.getProperties(properties);
});
@ -88,6 +89,7 @@ bool ModelEntityItem::setProperties(const EntityItemProperties& properties) {
SET_ENTITY_PROPERTY_FROM_PROPERTIES(jointTranslationsSet, setJointTranslationsSet);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(jointTranslations, setJointTranslations);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(relayParentJoints, setRelayParentJoints);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(groupCulled, setGroupCulled);
withWriteLock([&] {
AnimationPropertyGroup animationProperties = _animationProperties;
@ -130,6 +132,7 @@ int ModelEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data,
READ_ENTITY_PROPERTY(PROP_JOINT_TRANSLATIONS_SET, QVector<bool>, setJointTranslationsSet);
READ_ENTITY_PROPERTY(PROP_JOINT_TRANSLATIONS, QVector<glm::vec3>, setJointTranslations);
READ_ENTITY_PROPERTY(PROP_RELAY_PARENT_JOINTS, bool, setRelayParentJoints);
READ_ENTITY_PROPERTY(PROP_GROUP_CULLED, bool, setGroupCulled);
// grab a local copy of _animationProperties to avoid multiple locks
int bytesFromAnimation;
@ -166,6 +169,7 @@ EntityPropertyFlags ModelEntityItem::getEntityProperties(EncodeBitstreamParams&
requestedProperties += PROP_JOINT_TRANSLATIONS_SET;
requestedProperties += PROP_JOINT_TRANSLATIONS;
requestedProperties += PROP_RELAY_PARENT_JOINTS;
requestedProperties += PROP_GROUP_CULLED;
requestedProperties += _animationProperties.getEntityProperties(params);
return requestedProperties;
@ -192,6 +196,7 @@ void ModelEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBit
APPEND_ENTITY_PROPERTY(PROP_JOINT_TRANSLATIONS_SET, getJointTranslationsSet());
APPEND_ENTITY_PROPERTY(PROP_JOINT_TRANSLATIONS, getJointTranslations());
APPEND_ENTITY_PROPERTY(PROP_RELAY_PARENT_JOINTS, getRelayParentJoints());
APPEND_ENTITY_PROPERTY(PROP_GROUP_CULLED, getGroupCulled());
withReadLock([&] {
_animationProperties.appendSubclassData(packetData, params, entityTreeElementExtraEncodeData, requestedProperties,
@ -548,6 +553,18 @@ bool ModelEntityItem::getRelayParentJoints() const {
});
}
void ModelEntityItem::setGroupCulled(bool value) {
withWriteLock([&] {
_groupCulled = value;
});
}
bool ModelEntityItem::getGroupCulled() const {
return resultWithReadLock<bool>([&] {
return _groupCulled;
});
}
QString ModelEntityItem::getCompoundShapeURL() const {
return _compoundShapeURL.get();
}

View file

@ -100,6 +100,9 @@ public:
void setRelayParentJoints(bool relayJoints);
bool getRelayParentJoints() const;
void setGroupCulled(bool value);
bool getGroupCulled() const;
bool getAnimationIsPlaying() const;
float getAnimationCurrentFrame() const;
float getAnimationFPS() const;
@ -154,6 +157,7 @@ protected:
glm::u8vec3 _color;
QString _modelURL;
bool _relayParentJoints;
bool _groupCulled { false };
ThreadSafeValueCache<QString> _compoundShapeURL;

View file

@ -112,7 +112,6 @@ EntityItemPointer ShapeEntityItem::sphereFactory(const EntityItemID& entityID, c
ShapeEntityItem::ShapeEntityItem(const EntityItemID& entityItemID) : EntityItem(entityItemID) {
_type = EntityTypes::Shape;
_volumeMultiplier *= PI / 6.0f;
_material = std::make_shared<graphics::Material>();
}
EntityItemProperties ShapeEntityItem::getProperties(const EntityPropertyFlags& desiredProperties, bool allowEmptyDesiredProperties) const {
@ -215,7 +214,6 @@ void ShapeEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBit
void ShapeEntityItem::setColor(const glm::u8vec3& value) {
withWriteLock([&] {
_color = value;
_material->setAlbedo(toGlm(_color));
});
}
@ -228,7 +226,12 @@ glm::u8vec3 ShapeEntityItem::getColor() const {
void ShapeEntityItem::setAlpha(float alpha) {
withWriteLock([&] {
_alpha = alpha;
_material->setOpacity(alpha);
});
}
float ShapeEntityItem::getAlpha() const {
return resultWithReadLock<float>([&] {
return _alpha;
});
}

View file

@ -74,7 +74,7 @@ public:
void setShape(const entity::Shape& shape);
void setShape(const QString& shape) { setShape(entity::shapeFromString(shape)); }
float getAlpha() const { return _alpha; };
float getAlpha() const;
void setAlpha(float alpha);
glm::u8vec3 getColor() const;
@ -99,20 +99,15 @@ public:
virtual void computeShapeInfo(ShapeInfo& info) override;
virtual ShapeType getShapeType() const override;
std::shared_ptr<graphics::Material> getMaterial() { return _material; }
protected:
float _alpha { 1.0f };
glm::u8vec3 _color;
float _alpha { 1.0f };
entity::Shape _shape { entity::Shape::Sphere };
//! This is SHAPE_TYPE_ELLIPSOID rather than SHAPE_TYPE_NONE to maintain
//! prior functionality where new or unsupported shapes are treated as
//! ellipsoids.
ShapeType _collisionShapeType{ ShapeType::SHAPE_TYPE_ELLIPSOID };
std::shared_ptr<graphics::Material> _material;
};
#endif // hifi_ShapeEntityItem_h

View file

@ -21,7 +21,8 @@
#include "EntityTree.h"
#include "EntityTreeElement.h"
const QString WebEntityItem::DEFAULT_SOURCE_URL("http://www.google.com");
const QString WebEntityItem::DEFAULT_SOURCE_URL = "http://www.google.com";
const uint8_t WebEntityItem::DEFAULT_MAX_FPS = 10;
EntityItemPointer WebEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) {
EntityItemPointer entity(new WebEntityItem(entityID), [](EntityItem* ptr) { ptr->deleteLater(); });
@ -31,20 +32,25 @@ EntityItemPointer WebEntityItem::factory(const EntityItemID& entityID, const Ent
WebEntityItem::WebEntityItem(const EntityItemID& entityItemID) : EntityItem(entityItemID) {
_type = EntityTypes::Web;
_dpi = ENTITY_ITEM_DEFAULT_DPI;
}
const float WEB_ENTITY_ITEM_FIXED_DEPTH = 0.01f;
void WebEntityItem::setUnscaledDimensions(const glm::vec3& value) {
// NOTE: Web Entities always have a "depth" of 1cm.
const float WEB_ENTITY_ITEM_FIXED_DEPTH = 0.01f;
EntityItem::setUnscaledDimensions(glm::vec3(value.x, value.y, WEB_ENTITY_ITEM_FIXED_DEPTH));
}
EntityItemProperties WebEntityItem::getProperties(const EntityPropertyFlags& desiredProperties, bool allowEmptyDesiredProperties) const {
EntityItemProperties properties = EntityItem::getProperties(desiredProperties, allowEmptyDesiredProperties); // get the properties from our base class
COPY_ENTITY_PROPERTY_TO_PROPERTIES(color, getColor);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(alpha, getAlpha);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(sourceUrl, getSourceUrl);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(dpi, getDPI);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(scriptURL, getScriptURL);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(maxFPS, getMaxFPS);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(inputMode, getInputMode);
return properties;
}
@ -52,8 +58,14 @@ bool WebEntityItem::setProperties(const EntityItemProperties& properties) {
bool somethingChanged = false;
somethingChanged = EntityItem::setProperties(properties); // set the properties in our base class
SET_ENTITY_PROPERTY_FROM_PROPERTIES(color, setColor);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(alpha, setAlpha);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(sourceUrl, setSourceUrl);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(dpi, setDPI);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(scriptURL, setScriptURL);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(maxFPS, setMaxFPS);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(inputMode, setInputMode);
if (somethingChanged) {
bool wantDebug = false;
@ -77,16 +89,28 @@ int WebEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, i
int bytesRead = 0;
const unsigned char* dataAt = data;
READ_ENTITY_PROPERTY(PROP_COLOR, glm::u8vec3, setColor);
READ_ENTITY_PROPERTY(PROP_ALPHA, float, setAlpha);
READ_ENTITY_PROPERTY(PROP_SOURCE_URL, QString, setSourceUrl);
READ_ENTITY_PROPERTY(PROP_DPI, uint16_t, setDPI);
READ_ENTITY_PROPERTY(PROP_SCRIPT_URL, QString, setScriptURL);
READ_ENTITY_PROPERTY(PROP_MAX_FPS, uint8_t, setMaxFPS);
READ_ENTITY_PROPERTY(PROP_INPUT_MODE, WebInputMode, setInputMode);
return bytesRead;
}
EntityPropertyFlags WebEntityItem::getEntityProperties(EncodeBitstreamParams& params) const {
EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params);
requestedProperties += PROP_COLOR;
requestedProperties += PROP_ALPHA;
requestedProperties += PROP_SOURCE_URL;
requestedProperties += PROP_DPI;
requestedProperties += PROP_SCRIPT_URL;
requestedProperties += PROP_MAX_FPS;
requestedProperties += PROP_INPUT_MODE;
return requestedProperties;
}
@ -99,8 +123,14 @@ void WebEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBitst
OctreeElement::AppendState& appendState) const {
bool successPropertyFits = true;
APPEND_ENTITY_PROPERTY(PROP_SOURCE_URL, _sourceUrl);
APPEND_ENTITY_PROPERTY(PROP_DPI, _dpi);
APPEND_ENTITY_PROPERTY(PROP_COLOR, getColor());
APPEND_ENTITY_PROPERTY(PROP_ALPHA, getAlpha());
APPEND_ENTITY_PROPERTY(PROP_SOURCE_URL, getSourceUrl());
APPEND_ENTITY_PROPERTY(PROP_DPI, getDPI());
APPEND_ENTITY_PROPERTY(PROP_SCRIPT_URL, getScriptURL());
APPEND_ENTITY_PROPERTY(PROP_MAX_FPS, getMaxFPS());
APPEND_ENTITY_PROPERTY(PROP_INPUT_MODE, (uint32_t)getInputMode());
}
bool WebEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
@ -157,6 +187,30 @@ bool WebEntityItem::findDetailedParabolaIntersection(const glm::vec3& origin, co
}
}
void WebEntityItem::setColor(const glm::u8vec3& value) {
withWriteLock([&] {
_color = value;
});
}
glm::u8vec3 WebEntityItem::getColor() const {
return resultWithReadLock<glm::u8vec3>([&] {
return _color;
});
}
void WebEntityItem::setAlpha(float alpha) {
withWriteLock([&] {
_alpha = alpha;
});
}
float WebEntityItem::getAlpha() const {
return resultWithReadLock<float>([&] {
return _alpha;
});
}
void WebEntityItem::setSourceUrl(const QString& value) {
withWriteLock([&] {
if (_sourceUrl != value) {
@ -172,17 +226,63 @@ void WebEntityItem::setSourceUrl(const QString& value) {
}
QString WebEntityItem::getSourceUrl() const {
QString result;
withReadLock([&] {
result = _sourceUrl;
return resultWithReadLock<QString>([&] {
return _sourceUrl;
});
return result;
}
void WebEntityItem::setDPI(uint16_t value) {
_dpi = value;
withWriteLock([&] {
_dpi = value;
});
}
uint16_t WebEntityItem::getDPI() const {
return _dpi;
return resultWithReadLock<uint16_t>([&] {
return _dpi;
});
}
void WebEntityItem::setScriptURL(const QString& value) {
withWriteLock([&] {
if (_scriptURL != value) {
auto newURL = QUrl::fromUserInput(value);
if (newURL.isValid()) {
_scriptURL = newURL.toDisplayString();
} else {
qCDebug(entities) << "Clearing web entity source URL since" << value << "cannot be parsed to a valid URL.";
}
}
});
}
QString WebEntityItem::getScriptURL() const {
return resultWithReadLock<QString>([&] {
return _scriptURL;
});
}
void WebEntityItem::setMaxFPS(uint8_t value) {
withWriteLock([&] {
_maxFPS = value;
});
}
uint8_t WebEntityItem::getMaxFPS() const {
return resultWithReadLock<uint8_t>([&] {
return _maxFPS;
});
}
void WebEntityItem::setInputMode(const WebInputMode& value) {
withWriteLock([&] {
_inputMode = value;
});
}
WebInputMode WebEntityItem::getInputMode() const {
return resultWithReadLock<WebInputMode>([&] {
return _inputMode;
});
}

View file

@ -13,8 +13,6 @@
class WebEntityItem : public EntityItem {
public:
static const QString DEFAULT_SOURCE_URL;
static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties);
WebEntityItem(const EntityItemID& entityItemID);
@ -54,15 +52,38 @@ public:
BoxFace& face, glm::vec3& surfaceNormal,
QVariantMap& extraInfo, bool precisionPicking) const override;
virtual void setSourceUrl(const QString& value);
glm::u8vec3 getColor() const;
void setColor(const glm::u8vec3& value);
float getAlpha() const;
void setAlpha(float alpha);
static const QString DEFAULT_SOURCE_URL;
void setSourceUrl(const QString& value);
QString getSourceUrl() const;
void setDPI(uint16_t value);
uint16_t getDPI() const;
void setScriptURL(const QString& value);
QString getScriptURL() const;
static const uint8_t DEFAULT_MAX_FPS;
void setMaxFPS(uint8_t value);
uint8_t getMaxFPS() const;
void setInputMode(const WebInputMode& value);
WebInputMode getInputMode() const;
protected:
glm::u8vec3 _color;
float _alpha { 1.0f };
QString _sourceUrl;
uint16_t _dpi;
QString _scriptURL;
uint8_t _maxFPS;
WebInputMode _inputMode;
};
#endif // hifi_WebEntityItem_h

View file

@ -131,6 +131,7 @@ public:
glm::vec3 geometricTranslation;
glm::quat geometricRotation;
glm::vec3 geometricScaling;
bool isLimbNode; // is this FBXModel transform is a "LimbNode" i.e. a joint
};
glm::mat4 getGlobalTransform(const QMultiMap<QString, QString>& _connectionParentMap,
@ -559,9 +560,11 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr
glm::vec3 geometricRotation;
glm::vec3 rotationMin, rotationMax;
bool isLimbNode = object.properties.size() >= 3 && object.properties.at(2) == "LimbNode";
FBXModel fbxModel = { name, -1, glm::vec3(), glm::mat4(), glm::quat(), glm::quat(), glm::quat(),
glm::mat4(), glm::vec3(), glm::vec3(),
false, glm::vec3(), glm::quat(), glm::vec3(1.0f) };
glm::mat4(), glm::vec3(), glm::vec3(),
false, glm::vec3(), glm::quat(), glm::vec3(1.0f), isLimbNode };
ExtractedMesh* mesh = NULL;
QVector<ExtractedBlendshape> blendshapes;
foreach (const FBXNode& subobject, object.children) {
@ -752,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>(),
@ -790,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 {
@ -848,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;
@ -892,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;
@ -980,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());
@ -1001,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") {
@ -1258,6 +1289,7 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr
// convert the models to joints
QVariantList freeJoints = mapping.values("freeJoint");
hfmModel.hasSkeletonJoints = false;
foreach (const QString& modelID, modelIDs) {
const FBXModel& fbxModel = fbxModels[modelID];
HFMJoint joint;
@ -1288,6 +1320,8 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr
joint.geometricTranslation = fbxModel.geometricTranslation;
joint.geometricRotation = fbxModel.geometricRotation;
joint.geometricScaling = fbxModel.geometricScaling;
joint.isSkeletonJoint = fbxModel.isLimbNode;
hfmModel.hasSkeletonJoints = (hfmModel.hasSkeletonJoints || joint.isSkeletonJoint);
glm::quat combinedRotation = joint.preRotation * joint.rotation * joint.postRotation;
@ -1311,14 +1345,6 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr
joint.name = hfmModel.hfmToHifiJointNameMapping.key(fbxModel.name);
}
foreach (const QString& childID, _connectionChildMap.values(modelID)) {
QString type = typeFlags.value(childID);
if (!type.isEmpty()) {
hfmModel.hasSkeletonJoints |= (joint.isSkeletonJoint = type.toLower().contains("Skeleton"));
break;
}
}
joint.bindTransformFoundInCluster = false;
hfmModel.joints.append(joint);
@ -1439,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

@ -40,13 +40,13 @@ namespace scriptable {
* @typedef {object} Graphics.Material
* @property {string} name
* @property {string} model
* @property {number} opacity
* @property {number} roughness
* @property {number} metallic
* @property {number} scattering
* @property {boolean} unlit
* @propety {Vec3} emissive
* @propety {Vec3} albedo
* @property {number|string} opacity
* @property {number|string} roughness
* @property {number|string} metallic
* @property {number|string} scattering
* @property {boolean|string} unlit
* @propety {Vec3|string} emissive
* @propety {Vec3|string} albedo
* @property {string} emissiveMap
* @property {string} albedoMap
* @property {string} opacityMap
@ -59,6 +59,11 @@ namespace scriptable {
* @property {string} occlusionMap
* @property {string} lightmapMap
* @property {string} scatteringMap
* @property {string} texCoordTransform0
* @property {string} texCoordTransform1
* @property {string} lightmapParams
* @property {string} materialParams
* @property {boolean} defaultFallthrough
*/
class ScriptableMaterial {
public:
@ -88,6 +93,9 @@ namespace scriptable {
QString occlusionMap;
QString lightmapMap;
QString scatteringMap;
bool defaultFallthrough;
std::unordered_map<uint, bool> propertyFallthroughs; // not actually exposed to script
};
/**jsdoc

View file

@ -362,25 +362,64 @@ namespace scriptable {
QScriptValue obj = engine->newObject();
obj.setProperty("name", material.name);
obj.setProperty("model", material.model);
obj.setProperty("opacity", material.opacity);
obj.setProperty("roughness", material.roughness);
obj.setProperty("metallic", material.metallic);
obj.setProperty("scattering", material.scattering);
obj.setProperty("unlit", material.unlit);
obj.setProperty("emissive", vec3ColorToScriptValue(engine, material.emissive));
obj.setProperty("albedo", vec3ColorToScriptValue(engine, material.albedo));
obj.setProperty("emissiveMap", material.emissiveMap);
obj.setProperty("albedoMap", material.albedoMap);
const QScriptValue FALLTHROUGH("fallthrough");
obj.setProperty("opacity", material.propertyFallthroughs.at(graphics::MaterialKey::OPACITY_VAL_BIT) ? FALLTHROUGH : material.opacity);
obj.setProperty("roughness", material.propertyFallthroughs.at(graphics::MaterialKey::GLOSSY_VAL_BIT) ? FALLTHROUGH : material.roughness);
obj.setProperty("metallic", material.propertyFallthroughs.at(graphics::MaterialKey::METALLIC_VAL_BIT) ? FALLTHROUGH : material.metallic);
obj.setProperty("scattering", material.propertyFallthroughs.at(graphics::MaterialKey::SCATTERING_VAL_BIT) ? FALLTHROUGH : material.scattering);
obj.setProperty("unlit", material.propertyFallthroughs.at(graphics::MaterialKey::UNLIT_VAL_BIT) ? FALLTHROUGH : material.unlit);
obj.setProperty("emissive", material.propertyFallthroughs.at(graphics::MaterialKey::EMISSIVE_VAL_BIT) ? FALLTHROUGH : vec3ColorToScriptValue(engine, material.emissive));
obj.setProperty("albedo", material.propertyFallthroughs.at(graphics::MaterialKey::ALBEDO_VAL_BIT) ? FALLTHROUGH : vec3ColorToScriptValue(engine, material.albedo));
obj.setProperty("emissiveMap", material.propertyFallthroughs.at(graphics::MaterialKey::EMISSIVE_MAP_BIT) ? FALLTHROUGH : material.emissiveMap);
obj.setProperty("albedoMap", material.propertyFallthroughs.at(graphics::MaterialKey::ALBEDO_MAP_BIT) ? FALLTHROUGH : material.albedoMap);
obj.setProperty("opacityMap", material.opacityMap);
obj.setProperty("metallicMap", material.metallicMap);
obj.setProperty("specularMap", material.specularMap);
obj.setProperty("roughnessMap", material.roughnessMap);
obj.setProperty("glossMap", material.glossMap);
obj.setProperty("normalMap", material.normalMap);
obj.setProperty("bumpMap", material.bumpMap);
obj.setProperty("occlusionMap", material.occlusionMap);
obj.setProperty("lightmapMap", material.lightmapMap);
obj.setProperty("scatteringMap", material.scatteringMap);
obj.setProperty("occlusionMap", material.propertyFallthroughs.at(graphics::MaterialKey::OCCLUSION_MAP_BIT) ? FALLTHROUGH : material.occlusionMap);
obj.setProperty("lightmapMap", material.propertyFallthroughs.at(graphics::MaterialKey::LIGHTMAP_MAP_BIT) ? FALLTHROUGH : material.lightmapMap);
obj.setProperty("scatteringMap", material.propertyFallthroughs.at(graphics::MaterialKey::SCATTERING_MAP_BIT) ? FALLTHROUGH : material.scatteringMap);
// Only set one of each of these
if (material.propertyFallthroughs.at(graphics::MaterialKey::METALLIC_MAP_BIT)) {
obj.setProperty("metallicMap", FALLTHROUGH);
} else if (!material.metallicMap.isEmpty()) {
obj.setProperty("metallicMap", material.metallicMap);
} else if (!material.specularMap.isEmpty()) {
obj.setProperty("specularMap", material.specularMap);
}
if (material.propertyFallthroughs.at(graphics::MaterialKey::ROUGHNESS_MAP_BIT)) {
obj.setProperty("roughnessMap", FALLTHROUGH);
} else if (!material.roughnessMap.isEmpty()) {
obj.setProperty("roughnessMap", material.roughnessMap);
} else if (!material.glossMap.isEmpty()) {
obj.setProperty("glossMap", material.glossMap);
}
if (material.propertyFallthroughs.at(graphics::MaterialKey::NORMAL_MAP_BIT)) {
obj.setProperty("normalMap", FALLTHROUGH);
} else if (!material.normalMap.isEmpty()) {
obj.setProperty("normalMap", material.normalMap);
} else if (!material.bumpMap.isEmpty()) {
obj.setProperty("bumpMap", material.bumpMap);
}
// These need to be implemented, but set the fallthrough for now
if (material.propertyFallthroughs.at(graphics::Material::TEXCOORDTRANSFORM0)) {
obj.setProperty("texCoordTransform0", FALLTHROUGH);
}
if (material.propertyFallthroughs.at(graphics::Material::TEXCOORDTRANSFORM1)) {
obj.setProperty("texCoordTransform1", FALLTHROUGH);
}
if (material.propertyFallthroughs.at(graphics::Material::LIGHTMAP_PARAMS)) {
obj.setProperty("lightmapParams", FALLTHROUGH);
}
if (material.propertyFallthroughs.at(graphics::Material::MATERIAL_PARAMS)) {
obj.setProperty("materialParams", FALLTHROUGH);
}
obj.setProperty("defaultFallthrough", material.defaultFallthrough);
return obj;
}

View file

@ -42,6 +42,9 @@ scriptable::ScriptableMaterial& scriptable::ScriptableMaterial::operator=(const
lightmapMap = material.lightmapMap;
scatteringMap = material.scatteringMap;
defaultFallthrough = material.defaultFallthrough;
propertyFallthroughs = material.propertyFallthroughs;
return *this;
}
@ -54,7 +57,9 @@ scriptable::ScriptableMaterial::ScriptableMaterial(const graphics::MaterialPoint
scattering(material->getScattering()),
unlit(material->isUnlit()),
emissive(material->getEmissive()),
albedo(material->getAlbedo())
albedo(material->getAlbedo()),
defaultFallthrough(material->getDefaultFallthrough()),
propertyFallthroughs(material->getPropertyFallthroughs())
{
auto map = material->getTextureMap(graphics::Material::MapChannel::EMISSIVE_MAP);
if (map && map->getTextureSource()) {

View file

@ -17,125 +17,125 @@
using namespace graphics;
using namespace gpu;
Material::Material() :
_key(0),
_schemaBuffer(),
_textureMaps()
{
// created from nothing: create the Buffer to store the properties
Schema schema;
_schemaBuffer = gpu::BufferView(std::make_shared<gpu::Buffer>(sizeof(Schema), (const gpu::Byte*) &schema, sizeof(Schema)));
const float Material::DEFAULT_EMISSIVE { 0.0f };
const float Material::DEFAULT_OPACITY { 1.0f };
const float Material::DEFAULT_ALBEDO { 0.5f };
const float Material::DEFAULT_METALLIC { 0.0f };
const float Material::DEFAULT_ROUGHNESS { 1.0f };
const float Material::DEFAULT_SCATTERING { 0.0f };
Material::Material() {
for (int i = 0; i < NUM_TOTAL_FLAGS; i++) {
_propertyFallthroughs[i] = false;
}
}
Material::Material(const Material& material) :
_name(material._name),
_model(material._model),
_key(material._key),
_textureMaps(material._textureMaps)
_emissive(material._emissive),
_opacity(material._opacity),
_albedo(material._albedo),
_roughness(material._roughness),
_metallic(material._metallic),
_scattering(material._scattering),
_texcoordTransforms(material._texcoordTransforms),
_lightmapParams(material._lightmapParams),
_materialParams(material._materialParams),
_textureMaps(material._textureMaps),
_defaultFallthrough(material._defaultFallthrough),
_propertyFallthroughs(material._propertyFallthroughs)
{
// copied: create the Buffer to store the properties, avoid holding a ref to the old Buffer
Schema schema;
_schemaBuffer = gpu::BufferView(std::make_shared<gpu::Buffer>(sizeof(Schema), (const gpu::Byte*) &schema, sizeof(Schema)));
_schemaBuffer.edit<Schema>() = material._schemaBuffer.get<Schema>();
}
Material& Material::operator= (const Material& material) {
Material& Material::operator=(const Material& material) {
QMutexLocker locker(&_textureMapsMutex);
_name = material._name;
_model = material._model;
_key = material._key;
_emissive = material._emissive;
_opacity = material._opacity;
_albedo = material._albedo;
_roughness = material._roughness;
_metallic = material._metallic;
_scattering = material._scattering;
_texcoordTransforms = material._texcoordTransforms;
_lightmapParams = material._lightmapParams;
_materialParams = material._materialParams;
_textureMaps = material._textureMaps;
_key = (material._key);
_textureMaps = (material._textureMaps);
_hasCalculatedTextureInfo = false;
// copied: create the Buffer to store the properties, avoid holding a ref to the old Buffer
Schema schema;
_schemaBuffer = gpu::BufferView(std::make_shared<gpu::Buffer>(sizeof(Schema), (const gpu::Byte*) &schema, sizeof(Schema)));
_schemaBuffer.edit<Schema>() = material._schemaBuffer.get<Schema>();
_defaultFallthrough = material._defaultFallthrough;
_propertyFallthroughs = material._propertyFallthroughs;
return (*this);
}
Material::~Material() {
}
void Material::setEmissive(const Color& emissive, bool isSRGB) {
_key.setEmissive(glm::any(glm::greaterThan(emissive, Color(0.0f))));
_schemaBuffer.edit<Schema>()._key = (uint32) _key._flags.to_ulong();
_schemaBuffer.edit<Schema>()._emissive = (isSRGB ? ColorUtils::sRGBToLinearVec3(emissive) : emissive);
void Material::setEmissive(const glm::vec3& emissive, bool isSRGB) {
_key.setEmissive(glm::any(glm::greaterThan(emissive, glm::vec3(0.0f))));
_emissive = (isSRGB ? ColorUtils::sRGBToLinearVec3(emissive) : emissive);
}
void Material::setOpacity(float opacity) {
_key.setTranslucentFactor((opacity < 1.0f));
_schemaBuffer.edit<Schema>()._key = (uint32)_key._flags.to_ulong();
_schemaBuffer.edit<Schema>()._opacity = opacity;
_opacity = opacity;
}
void Material::setUnlit(bool value) {
_key.setUnlit(value);
_schemaBuffer.edit<Schema>()._key = (uint32)_key._flags.to_ulong();
}
void Material::setAlbedo(const Color& albedo, bool isSRGB) {
_key.setAlbedo(glm::any(glm::greaterThan(albedo, Color(0.0f))));
_schemaBuffer.edit<Schema>()._key = (uint32)_key._flags.to_ulong();
_schemaBuffer.edit<Schema>()._albedo = (isSRGB ? ColorUtils::sRGBToLinearVec3(albedo) : albedo);
void Material::setAlbedo(const glm::vec3& albedo, bool isSRGB) {
_key.setAlbedo(glm::any(glm::greaterThan(albedo, glm::vec3(0.0f))));
_albedo = (isSRGB ? ColorUtils::sRGBToLinearVec3(albedo) : albedo);
}
void Material::setRoughness(float roughness) {
roughness = std::min(1.0f, std::max(roughness, 0.0f));
_key.setGlossy((roughness < 1.0f));
_schemaBuffer.edit<Schema>()._key = (uint32)_key._flags.to_ulong();
_schemaBuffer.edit<Schema>()._roughness = roughness;
_key.setGlossy(roughness < 1.0f);
_roughness = roughness;
}
void Material::setMetallic(float metallic) {
metallic = glm::clamp(metallic, 0.0f, 1.0f);
_key.setMetallic(metallic > 0.0f);
_schemaBuffer.edit<Schema>()._key = (uint32)_key._flags.to_ulong();
_schemaBuffer.edit<Schema>()._metallic = metallic;
_metallic = metallic;
}
void Material::setScattering(float scattering) {
scattering = glm::clamp(scattering, 0.0f, 1.0f);
_key.setMetallic(scattering > 0.0f);
_schemaBuffer.edit<Schema>()._key = (uint32)_key._flags.to_ulong();
_schemaBuffer.edit<Schema>()._scattering = scattering;
_scattering = scattering;
}
void Material::setTextureMap(MapChannel channel, const TextureMapPointer& textureMap) {
QMutexLocker locker(&_textureMapsMutex);
if (textureMap) {
_key.setMapChannel(channel, (true));
_key.setMapChannel(channel, true);
_textureMaps[channel] = textureMap;
} else {
_key.setMapChannel(channel, (false));
_key.setMapChannel(channel, false);
_textureMaps.erase(channel);
}
_hasCalculatedTextureInfo = false;
_schemaBuffer.edit<Schema>()._key = (uint32)_key._flags.to_ulong();
if (channel == MaterialKey::ALBEDO_MAP) {
resetOpacityMap();
// update the texcoord0 with albedo
_schemaBuffer.edit<Schema>()._texcoordTransforms[0] = (textureMap ? textureMap->getTextureTransform().getMatrix() : glm::mat4());
_texcoordTransforms[0] = (textureMap ? textureMap->getTextureTransform().getMatrix() : glm::mat4());
}
if (channel == MaterialKey::OCCLUSION_MAP) {
_schemaBuffer.edit<Schema>()._texcoordTransforms[1] = (textureMap ? textureMap->getTextureTransform().getMatrix() : glm::mat4());
_texcoordTransforms[1] = (textureMap ? textureMap->getTextureTransform().getMatrix() : glm::mat4());
}
if (channel == MaterialKey::LIGHTMAP_MAP) {
// update the texcoord1 with lightmap
_schemaBuffer.edit<Schema>()._texcoordTransforms[1] = (textureMap ? textureMap->getTextureTransform().getMatrix() : glm::mat4());
_schemaBuffer.edit<Schema>()._lightmapParams = (textureMap ? glm::vec2(textureMap->getLightmapOffsetScale()) : glm::vec2(0.0, 1.0));
_texcoordTransforms[1] = (textureMap ? textureMap->getTextureTransform().getMatrix() : glm::mat4());
_lightmapParams = (textureMap ? glm::vec2(textureMap->getLightmapOffsetScale()) : glm::vec2(0.0, 1.0));
}
_schemaBuffer.edit<Schema>()._materialParams = (textureMap ? glm::vec2(textureMap->getMappingMode(), textureMap->getRepeat()) : glm::vec2(MaterialMappingMode::UV, 1.0));
_schemaBuffer.edit<Schema>()._key = (uint32)_key._flags.to_ulong();
_materialParams = (textureMap ? glm::vec2(textureMap->getMappingMode(), textureMap->getRepeat()) : glm::vec2(MaterialMappingMode::UV, 1.0));
}
@ -163,11 +163,8 @@ void Material::resetOpacityMap() const {
}
}
}
_schemaBuffer.edit<Schema>()._key = (uint32)_key._flags.to_ulong();
}
const TextureMapPointer Material::getTextureMap(MapChannel channel) const {
QMutexLocker locker(&_textureMapsMutex);
@ -179,40 +176,6 @@ const TextureMapPointer Material::getTextureMap(MapChannel channel) const {
}
}
bool Material::calculateMaterialInfo() const {
if (!_hasCalculatedTextureInfo) {
QMutexLocker locker(&_textureMapsMutex);
bool allTextures = true; // assume we got this...
_textureSize = 0;
_textureCount = 0;
for (auto const &textureMapItem : _textureMaps) {
auto textureMap = textureMapItem.second;
if (textureMap) {
auto textureSoure = textureMap->getTextureSource();
if (textureSoure) {
auto texture = textureSoure->getGPUTexture();
if (texture) {
auto size = texture->getSize();
_textureSize += size;
_textureCount++;
} else {
allTextures = false;
}
} else {
allTextures = false;
}
} else {
allTextures = false;
}
}
_hasCalculatedTextureInfo = allTextures;
}
return _hasCalculatedTextureInfo;
}
void Material::setTextureTransforms(const Transform& transform, MaterialMappingMode mode, bool repeat) {
for (auto &textureMapItem : _textureMaps) {
if (textureMapItem.second) {
@ -222,7 +185,32 @@ void Material::setTextureTransforms(const Transform& transform, MaterialMappingM
}
}
for (int i = 0; i < NUM_TEXCOORD_TRANSFORMS; i++) {
_schemaBuffer.edit<Schema>()._texcoordTransforms[i] = transform.getMatrix();
_texcoordTransforms[i] = transform.getMatrix();
}
_schemaBuffer.edit<Schema>()._materialParams = glm::vec2(mode, repeat);
_materialParams = glm::vec2(mode, repeat);
}
MultiMaterial::MultiMaterial() {
Schema schema;
_schemaBuffer = gpu::BufferView(std::make_shared<gpu::Buffer>(sizeof(Schema), (const gpu::Byte*) &schema, sizeof(Schema)));
}
void MultiMaterial::calculateMaterialInfo() const {
if (!_hasCalculatedTextureInfo) {
bool allTextures = true; // assume we got this...
_textureSize = 0;
_textureCount = 0;
auto textures = _textureTable->getTextures();
for (auto const &texture : textures) {
if (texture && texture->isDefined()) {
auto size = texture->getSize();
_textureSize += size;
_textureCount++;
} else {
allTextures = false;
}
}
_hasCalculatedTextureInfo = allTextures;
}
}

View file

@ -15,6 +15,7 @@
#include <bitset>
#include <map>
#include <unordered_map>
#include <queue>
#include <ColorUtils.h>
@ -176,7 +177,6 @@ public:
bool isTexelOpaque() const { return isOpaque() && isOpacityMaskMap(); }
};
class MaterialFilter {
public:
MaterialKey::Flags _value{ 0 };
@ -266,84 +266,44 @@ public:
class Material {
public:
typedef gpu::BufferView UniformBufferView;
typedef glm::vec3 Color;
// Texture Map Array Schema
static const int NUM_TEXCOORD_TRANSFORMS{ 2 };
typedef MaterialKey::MapChannel MapChannel;
typedef std::map<MapChannel, TextureMapPointer> TextureMaps;
typedef std::bitset<MaterialKey::NUM_MAP_CHANNELS> MapFlags;
Material();
Material(const Material& material);
Material& operator= (const Material& material);
virtual ~Material();
const MaterialKey& getKey() const { return _key; }
void setEmissive(const Color& emissive, bool isSRGB = true);
Color getEmissive(bool SRGB = true) const { return (SRGB ? ColorUtils::tosRGBVec3(_schemaBuffer.get<Schema>()._emissive) : _schemaBuffer.get<Schema>()._emissive); }
static const float DEFAULT_EMISSIVE;
void setEmissive(const glm::vec3& emissive, bool isSRGB = true);
glm::vec3 getEmissive(bool SRGB = true) const { return (SRGB ? ColorUtils::tosRGBVec3(_emissive) : _emissive); }
static const float DEFAULT_OPACITY;
void setOpacity(float opacity);
float getOpacity() const { return _schemaBuffer.get<Schema>()._opacity; }
float getOpacity() const { return _opacity; }
void setUnlit(bool value);
bool isUnlit() const { return _key.isUnlit(); }
void setAlbedo(const Color& albedo, bool isSRGB = true);
Color getAlbedo(bool SRGB = true) const { return (SRGB ? ColorUtils::tosRGBVec3(_schemaBuffer.get<Schema>()._albedo) : _schemaBuffer.get<Schema>()._albedo); }
static const float DEFAULT_ALBEDO;
void setAlbedo(const glm::vec3& albedo, bool isSRGB = true);
glm::vec3 getAlbedo(bool SRGB = true) const { return (SRGB ? ColorUtils::tosRGBVec3(_albedo) : _albedo); }
static const float DEFAULT_METALLIC;
void setMetallic(float metallic);
float getMetallic() const { return _schemaBuffer.get<Schema>()._metallic; }
float getMetallic() const { return _metallic; }
static const float DEFAULT_ROUGHNESS;
void setRoughness(float roughness);
float getRoughness() const { return _schemaBuffer.get<Schema>()._roughness; }
float getRoughness() const { return _roughness; }
static const float DEFAULT_SCATTERING;
void setScattering(float scattering);
float getScattering() const { return _schemaBuffer.get<Schema>()._scattering; }
// Schema to access the attribute values of the material
class Schema {
public:
glm::vec3 _emissive { 0.0f }; // No Emissive
float _opacity { 1.0f }; // Opacity = 1 => Not Transparent
glm::vec3 _albedo { 0.5f }; // Grey albedo => isAlbedo
float _roughness { 1.0f }; // Roughness = 1 => Not Glossy
float _metallic { 0.0f }; // Not Metallic
float _scattering { 0.0f }; // Scattering info
#if defined(__clang__)
__attribute__((unused))
#endif
glm::vec2 _spare { 0.0f }; // Padding
uint32_t _key { 0 }; // a copy of the materialKey
#if defined(__clang__)
__attribute__((unused))
#endif
glm::vec3 _spare2 { 0.0f };
// for alignment beauty, Material size == Mat4x4
// Texture Coord Transform Array
glm::mat4 _texcoordTransforms[NUM_TEXCOORD_TRANSFORMS];
glm::vec2 _lightmapParams { 0.0, 1.0 };
// x: material mode (0 for UV, 1 for PROJECTED)
// y: 1 for texture repeat, 0 for discard outside of 0 - 1
glm::vec2 _materialParams { 0.0, 1.0 };
Schema() {}
};
const UniformBufferView& getSchemaBuffer() const { return _schemaBuffer; }
float getScattering() const { return _scattering; }
// The texture map to channel association
static const int NUM_TEXCOORD_TRANSFORMS { 2 };
void setTextureMap(MapChannel channel, const TextureMapPointer& textureMap);
const TextureMaps& getTextureMaps() const { return _textureMaps; } // FIXME - not thread safe...
const TextureMapPointer getTextureMap(MapChannel channel) const;
@ -355,10 +315,6 @@ public:
// conversion from legacy material properties to PBR equivalent
static float shininessToRoughness(float shininess) { return 1.0f - shininess / 100.0f; }
int getTextureCount() const { calculateMaterialInfo(); return _textureCount; }
size_t getTextureSize() const { calculateMaterialInfo(); return _textureSize; }
bool hasTextureInfo() const { return _hasCalculatedTextureInfo; }
void setTextureTransforms(const Transform& transform, MaterialMappingMode mode, bool repeat);
const std::string& getName() const { return _name; }
@ -366,28 +322,50 @@ public:
const std::string& getModel() const { return _model; }
void setModel(const std::string& model) { _model = model; }
const gpu::TextureTablePointer& getTextureTable() const { return _textureTable; }
glm::mat4 getTexCoordTransform(uint i) const { return _texcoordTransforms[i]; }
glm::vec2 getLightmapParams() const { return _lightmapParams; }
glm::vec2 getMaterialParams() const { return _materialParams; }
bool getDefaultFallthrough() const { return _defaultFallthrough; }
void setDefaultFallthrough(bool defaultFallthrough) { _defaultFallthrough = defaultFallthrough; }
enum ExtraFlagBit {
TEXCOORDTRANSFORM0 = MaterialKey::NUM_FLAGS,
TEXCOORDTRANSFORM1,
LIGHTMAP_PARAMS,
MATERIAL_PARAMS,
NUM_TOTAL_FLAGS
};
std::unordered_map<uint, bool> getPropertyFallthroughs() { return _propertyFallthroughs; }
bool getPropertyFallthrough(uint property) { return _propertyFallthroughs[property]; }
void setPropertyDoesFallthrough(uint property) { _propertyFallthroughs[property] = true; }
protected:
std::string _name { "" };
private:
mutable MaterialKey _key;
mutable UniformBufferView _schemaBuffer;
mutable gpu::TextureTablePointer _textureTable{ std::make_shared<gpu::TextureTable>() };
std::string _model { "hifi_pbr" };
mutable MaterialKey _key { 0 };
// Material properties
glm::vec3 _emissive { DEFAULT_EMISSIVE };
float _opacity { DEFAULT_OPACITY };
glm::vec3 _albedo { DEFAULT_ALBEDO };
float _roughness { DEFAULT_ROUGHNESS };
float _metallic { DEFAULT_METALLIC };
float _scattering { DEFAULT_SCATTERING };
std::array<glm::mat4, NUM_TEXCOORD_TRANSFORMS> _texcoordTransforms;
glm::vec2 _lightmapParams { 0.0, 1.0 };
glm::vec2 _materialParams { 0.0, 1.0 };
TextureMaps _textureMaps;
bool _defaultFallthrough { false };
std::unordered_map<uint, bool> _propertyFallthroughs { NUM_TOTAL_FLAGS };
mutable QMutex _textureMapsMutex { QMutex::Recursive };
mutable size_t _textureSize { 0 };
mutable int _textureCount { 0 };
mutable bool _hasCalculatedTextureInfo { false };
bool calculateMaterialInfo() const;
std::string _model { "hifi_pbr" };
};
typedef std::shared_ptr< Material > MaterialPointer;
typedef std::shared_ptr<Material> MaterialPointer;
class MaterialLayer {
public:
@ -403,9 +381,18 @@ public:
return left.priority < right.priority;
}
};
typedef std::priority_queue<MaterialLayer, std::vector<MaterialLayer>, MaterialLayerCompare> MaterialLayerQueue;
class MultiMaterial : public std::priority_queue<MaterialLayer, std::vector<MaterialLayer>, MaterialLayerCompare> {
class MultiMaterial : public MaterialLayerQueue {
public:
MultiMaterial();
void push(const MaterialLayer& value) {
MaterialLayerQueue::push(value);
_hasCalculatedTextureInfo = false;
_needsUpdate = true;
}
bool remove(const MaterialPointer& value) {
auto it = c.begin();
while (it != c.end()) {
@ -417,11 +404,78 @@ public:
if (it != c.end()) {
c.erase(it);
std::make_heap(c.begin(), c.end(), comp);
_hasCalculatedTextureInfo = false;
_needsUpdate = true;
return true;
} else {
return false;
}
}
// Schema to access the attribute values of the material
class Schema {
public:
glm::vec3 _emissive { Material::DEFAULT_EMISSIVE }; // No Emissive
float _opacity { Material::DEFAULT_OPACITY }; // Opacity = 1 => Not Transparent
glm::vec3 _albedo { Material::DEFAULT_ALBEDO }; // Grey albedo => isAlbedo
float _roughness { Material::DEFAULT_ROUGHNESS }; // Roughness = 1 => Not Glossy
float _metallic { Material::DEFAULT_METALLIC }; // Not Metallic
float _scattering { Material::DEFAULT_SCATTERING }; // Scattering info
#if defined(__clang__)
__attribute__((unused))
#endif
glm::vec2 _spare { 0.0f }; // Padding
uint32_t _key { 0 }; // a copy of the materialKey
#if defined(__clang__)
__attribute__((unused))
#endif
glm::vec3 _spare2 { 0.0f };
// for alignment beauty, Material size == Mat4x4
// Texture Coord Transform Array
glm::mat4 _texcoordTransforms[Material::NUM_TEXCOORD_TRANSFORMS];
glm::vec2 _lightmapParams { 0.0, 1.0 };
// x: material mode (0 for UV, 1 for PROJECTED)
// y: 1 for texture repeat, 0 for discard outside of 0 - 1
glm::vec2 _materialParams { 0.0, 1.0 };
Schema() {
for (auto& transform : _texcoordTransforms) {
transform = glm::mat4();
}
}
};
gpu::BufferView& getSchemaBuffer() { return _schemaBuffer; }
graphics::MaterialKey getMaterialKey() const { return graphics::MaterialKey(_schemaBuffer.get<graphics::MultiMaterial::Schema>()._key); }
const gpu::TextureTablePointer& getTextureTable() const { return _textureTable; }
bool needsUpdate() const { return _needsUpdate; }
void setNeedsUpdate(bool needsUpdate) { _needsUpdate = needsUpdate; }
void setTexturesLoading(bool value) { _texturesLoading = value; }
bool areTexturesLoading() const { return _texturesLoading; }
int getTextureCount() const { calculateMaterialInfo(); return _textureCount; }
size_t getTextureSize() const { calculateMaterialInfo(); return _textureSize; }
bool hasTextureInfo() const { return _hasCalculatedTextureInfo; }
private:
gpu::BufferView _schemaBuffer;
gpu::TextureTablePointer _textureTable { std::make_shared<gpu::TextureTable>() };
bool _needsUpdate { false };
bool _texturesLoading { false };
mutable size_t _textureSize { 0 };
mutable int _textureCount { 0 };
mutable bool _hasCalculatedTextureInfo { false };
void calculateMaterialInfo() const;
};
};

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

@ -111,146 +111,300 @@ NetworkMaterialResource::ParsedMaterials NetworkMaterialResource::parseJSONMater
/**jsdoc
* A material such as may be used by a {@link Entities.EntityType|Material} entity.
* @typedef {object} Material
* @property {string} name="" - A name for the material.
* @property {string} model="hifi_pbr" - <em>Currently not used.</em>
* @property {Color|RGBS} emissive - The emissive color, i.e., the color that the material emits. A {@link Color} value
* is treated as sRGB. A {@link RGBS} value can be either RGB or sRGB.
* @property {number} opacity=1.0 - The opacity, <code>0.0</code> &ndash; <code>1.0</code>.
* @property {boolean} unlit=false - If <code>true</code>, the material is not lit.
* @property {Color|RGBS} albedo - The albedo color. A {@link Color} value is treated as sRGB. A {@link RGBS} value can
* be either RGB or sRGB.
* @property {number} roughness - The roughness, <code>0.0</code> &ndash; <code>1.0</code>.
* @property {number} metallic - The metallicness, <code>0.0</code> &ndash; <code>1.0</code>.
* @property {number} scattering - The scattering, <code>0.0</code> &ndash; <code>1.0</code>.
* @property {string} emissiveMap - URL of emissive texture image.
* @property {string} albedoMap - URL of albedo texture image.
* @property {string} model="hifi_pbr" - Different material models support different properties and rendering modes.
* Supported models are: "hifi_pbr"
* @property {string} name="" - A name for the material. Supported by all material models.
* @property {Color|RGBS|string} emissive - The emissive color, i.e., the color that the material emits. A {@link Color} value
* is treated as sRGB. A {@link RGBS} value can be either RGB or sRGB. Set to <code>"fallthrough"</code> to fallthrough to
* the material below. "hifi_pbr" model only.
* @property {number|string} opacity=1.0 - The opacity, <code>0.0</code> &ndash; <code>1.0</code>. Set to <code>"fallthrough"</code> to fallthrough to
* the material below. "hifi_pbr" model only.
* @property {boolean|string} unlit=false - If <code>true</code>, the material is not lit. Set to <code>"fallthrough"</code> to fallthrough to
* the material below. "hifi_pbr" model only.
* @property {Color|RGBS|string} albedo - The albedo color. A {@link Color} value is treated as sRGB. A {@link RGBS} value can
* be either RGB or sRGB. Set to <code>"fallthrough"</code> to fallthrough to the material below. Set to <code>"fallthrough"</code> to fallthrough to
* the material below. "hifi_pbr" model only.
* @property {number|string} roughness - The roughness, <code>0.0</code> &ndash; <code>1.0</code>. Set to <code>"fallthrough"</code> to fallthrough to
* the material below. "hifi_pbr" model only.
* @property {number|string} metallic - The metallicness, <code>0.0</code> &ndash; <code>1.0</code>. Set to <code>"fallthrough"</code> to fallthrough to
* the material below. "hifi_pbr" model only.
* @property {number|string} scattering - The scattering, <code>0.0</code> &ndash; <code>1.0</code>. Set to <code>"fallthrough"</code> to fallthrough to
* the material below. "hifi_pbr" model only.
* @property {string} emissiveMap - URL of emissive texture image. Set to <code>"fallthrough"</code> to fallthrough to
* the material below. "hifi_pbr" model only.
* @property {string} albedoMap - URL of albedo texture image. Set to <code>"fallthrough"</code> to fallthrough to
* the material below. "hifi_pbr" model only.
* @property {string} opacityMap - URL of opacity texture image. Set value the same as the <code>albedoMap</code> value for
* transparency.
* @property {string} roughnessMap - URL of roughness texture image. Can use this or <code>glossMap</code>, but not both.
* @property {string} glossMap - URL of gloss texture image. Can use this or <code>roughnessMap</code>, but not both.
* @property {string} metallicMap - URL of metallic texture image. Can use this or <code>specularMap</code>, but not both.
* @property {string} specularMap - URL of specular texture image. Can use this or <code>metallicMap</code>, but not both.
* @property {string} normalMap - URL of normal texture image. Can use this or <code>bumpMap</code>, but not both.
* @property {string} bumpMap - URL of bump texture image. Can use this or <code>normalMap</code>, but not both.
* @property {string} occlusionMap - URL of occlusion texture image.
* transparency. "hifi_pbr" model only.
* @property {string} roughnessMap - URL of roughness texture image. Can use this or <code>glossMap</code>, but not both. Set to <code>"fallthrough"</code>
* to fallthrough to the material below. "hifi_pbr" model only.
* @property {string} glossMap - URL of gloss texture image. Can use this or <code>roughnessMap</code>, but not both. Set to <code>"fallthrough"</code>
* to fallthrough to the material below. "hifi_pbr" model only.
* @property {string} metallicMap - URL of metallic texture image. Can use this or <code>specularMap</code>, but not both. Set to <code>"fallthrough"</code>
* to fallthrough to the material below. "hifi_pbr" model only.
* @property {string} specularMap - URL of specular texture image. Can use this or <code>metallicMap</code>, but not both. Set to <code>"fallthrough"</code>
* to fallthrough to the material below. "hifi_pbr" model only.
* @property {string} normalMap - URL of normal texture image. Can use this or <code>bumpMap</code>, but not both. Set to <code>"fallthrough"</code>
* to fallthrough to the material below. "hifi_pbr" model only.
* @property {string} bumpMap - URL of bump texture image. Can use this or <code>normalMap</code>, but not both. Set to <code>"fallthrough"</code>
* to fallthrough to the material below. "hifi_pbr" model only.
* @property {string} occlusionMap - URL of occlusion texture image. Set to <code>"fallthrough"</code> to fallthrough to the material below. "hifi_pbr" model only.
* @property {string} scatteringMap - URL of scattering texture image. Only used if <code>normalMap</code> or
* <code>bumpMap</code> is specified.
* @property {string} lightMap - URL of light map texture image. <em>Currently not used.</em>
* <code>bumpMap</code> is specified. Set to <code>"fallthrough"</code> to fallthrough to the material below. "hifi_pbr" model only.
* @property {string} lightMap - URL of light map texture image. <em>Currently not used.</em>. Set to <code>"fallthrough"</code>
* to fallthrough to the material below. "hifi_pbr" model only.
* @property {string} texCoordTransform0 - The transform to use for all of the maps besides occlusionMap and lightMap. Currently unused. Set to
* <code>"fallthrough"</code> to fallthrough to the material below. "hifi_pbr" model only.
* @property {string} texCoordTransform1 - The transform to use for occlusionMap and lightMap. Currently unused. Set to <code>"fallthrough"</code>
* to fallthrough to the material below. "hifi_pbr" model only.
* @property {string} lightmapParams - Parameters for controlling how lightMap is used. Currently unused. Set to <code>"fallthrough"</code>
* to fallthrough to the material below. "hifi_pbr" model only.
* @property {string} materialParams - Parameters for controlling the material projection and repition. Currently unused. Set to <code>"fallthrough"</code>
* to fallthrough to the material below. "hifi_pbr" model only.
* @property {bool} defaultFallthrough=false - If <code>true</code>, all properties will fallthrough to the material below unless they are set. If
* <code>false</code>, they will respect the individual properties' fallthrough state. "hifi_pbr" model only.
*/
// Note: See MaterialEntityItem.h for default values used in practice.
std::pair<std::string, std::shared_ptr<NetworkMaterial>> NetworkMaterialResource::parseJSONMaterial(const QJsonObject& materialJSON, const QUrl& baseUrl) {
std::string name = "";
std::shared_ptr<NetworkMaterial> material = std::make_shared<NetworkMaterial>();
for (auto& key : materialJSON.keys()) {
if (key == "name") {
auto nameJSON = materialJSON.value(key);
if (nameJSON.isString()) {
name = nameJSON.toString().toStdString();
}
} else if (key == "model") {
auto modelJSON = materialJSON.value(key);
if (modelJSON.isString()) {
material->setModel(modelJSON.toString().toStdString());
}
} else if (key == "emissive") {
glm::vec3 color;
bool isSRGB;
bool valid = parseJSONColor(materialJSON.value(key), color, isSRGB);
if (valid) {
material->setEmissive(color, isSRGB);
}
} else if (key == "opacity") {
auto value = materialJSON.value(key);
if (value.isDouble()) {
material->setOpacity(value.toDouble());
}
} else if (key == "unlit") {
auto value = materialJSON.value(key);
if (value.isBool()) {
material->setUnlit(value.toBool());
}
} else if (key == "albedo") {
glm::vec3 color;
bool isSRGB;
bool valid = parseJSONColor(materialJSON.value(key), color, isSRGB);
if (valid) {
material->setAlbedo(color, isSRGB);
}
} else if (key == "roughness") {
auto value = materialJSON.value(key);
if (value.isDouble()) {
material->setRoughness(value.toDouble());
}
} else if (key == "metallic") {
auto value = materialJSON.value(key);
if (value.isDouble()) {
material->setMetallic(value.toDouble());
}
} else if (key == "scattering") {
auto value = materialJSON.value(key);
if (value.isDouble()) {
material->setScattering(value.toDouble());
}
} else if (key == "emissiveMap") {
auto value = materialJSON.value(key);
if (value.isString()) {
material->setEmissiveMap(baseUrl.resolved(value.toString()));
}
} else if (key == "albedoMap") {
auto value = materialJSON.value(key);
if (value.isString()) {
QString urlString = value.toString();
bool useAlphaChannel = false;
auto opacityMap = materialJSON.find("opacityMap");
if (opacityMap != materialJSON.end() && opacityMap->isString() && opacityMap->toString() == urlString) {
useAlphaChannel = true;
const std::string HIFI_PBR = "hifi_pbr";
std::string modelString = HIFI_PBR;
auto modelJSONIter = materialJSON.find("model");
if (modelJSONIter != materialJSON.end() && modelJSONIter.value().isString()) {
modelString = modelJSONIter.value().toString().toStdString();
material->setModel(modelString);
}
if (modelString == HIFI_PBR) {
const QString FALLTHROUGH("fallthrough");
for (auto& key : materialJSON.keys()) {
if (key == "name") {
auto nameJSON = materialJSON.value(key);
if (nameJSON.isString()) {
name = nameJSON.toString().toStdString();
}
} else if (key == "model") {
auto modelJSON = materialJSON.value(key);
if (modelJSON.isString()) {
material->setModel(modelJSON.toString().toStdString());
}
} else if (key == "emissive") {
auto value = materialJSON.value(key);
if (value.isString() && value.toString() == FALLTHROUGH) {
material->setPropertyDoesFallthrough(graphics::MaterialKey::FlagBit::EMISSIVE_VAL_BIT);
} else {
glm::vec3 color;
bool isSRGB;
bool valid = parseJSONColor(value, color, isSRGB);
if (valid) {
material->setEmissive(color, isSRGB);
}
}
} else if (key == "opacity") {
auto value = materialJSON.value(key);
if (value.isString() && value.toString() == FALLTHROUGH) {
material->setPropertyDoesFallthrough(graphics::MaterialKey::FlagBit::OPACITY_VAL_BIT);
} else if (value.isDouble()) {
material->setOpacity(value.toDouble());
}
} else if (key == "unlit") {
auto value = materialJSON.value(key);
if (value.isString() && value.toString() == FALLTHROUGH) {
material->setPropertyDoesFallthrough(graphics::MaterialKey::FlagBit::UNLIT_VAL_BIT);
} else if (value.isBool()) {
material->setUnlit(value.toBool());
}
} else if (key == "albedo") {
auto value = materialJSON.value(key);
if (value.isString() && value.toString() == FALLTHROUGH) {
material->setPropertyDoesFallthrough(graphics::MaterialKey::FlagBit::ALBEDO_VAL_BIT);
} else {
glm::vec3 color;
bool isSRGB;
bool valid = parseJSONColor(value, color, isSRGB);
if (valid) {
material->setAlbedo(color, isSRGB);
}
}
} else if (key == "roughness") {
auto value = materialJSON.value(key);
if (value.isString() && value.toString() == FALLTHROUGH) {
material->setPropertyDoesFallthrough(graphics::MaterialKey::FlagBit::GLOSSY_VAL_BIT);
} else if (value.isDouble()) {
material->setRoughness(value.toDouble());
}
} else if (key == "metallic") {
auto value = materialJSON.value(key);
if (value.isString() && value.toString() == FALLTHROUGH) {
material->setPropertyDoesFallthrough(graphics::MaterialKey::FlagBit::METALLIC_VAL_BIT);
} else if (value.isDouble()) {
material->setMetallic(value.toDouble());
}
} else if (key == "scattering") {
auto value = materialJSON.value(key);
if (value.isString() && value.toString() == FALLTHROUGH) {
material->setPropertyDoesFallthrough(graphics::MaterialKey::FlagBit::SCATTERING_VAL_BIT);
} else if (value.isDouble()) {
material->setScattering(value.toDouble());
}
} else if (key == "emissiveMap") {
auto value = materialJSON.value(key);
if (value.isString()) {
auto valueString = value.toString();
if (valueString == FALLTHROUGH) {
material->setPropertyDoesFallthrough(graphics::MaterialKey::FlagBit::EMISSIVE_MAP_BIT);
} else {
material->setEmissiveMap(baseUrl.resolved(valueString));
}
}
} else if (key == "albedoMap") {
auto value = materialJSON.value(key);
if (value.isString()) {
QString valueString = value.toString();
if (valueString == FALLTHROUGH) {
material->setPropertyDoesFallthrough(graphics::MaterialKey::FlagBit::ALBEDO_MAP_BIT);
} else {
bool useAlphaChannel = false;
auto opacityMap = materialJSON.find("opacityMap");
if (opacityMap != materialJSON.end() && opacityMap->isString() && opacityMap->toString() == valueString) {
useAlphaChannel = true;
}
material->setAlbedoMap(baseUrl.resolved(valueString), useAlphaChannel);
}
}
} else if (key == "roughnessMap") {
auto value = materialJSON.value(key);
if (value.isString()) {
auto valueString = value.toString();
if (valueString == FALLTHROUGH) {
material->setPropertyDoesFallthrough(graphics::MaterialKey::FlagBit::ROUGHNESS_MAP_BIT);
} else {
material->setRoughnessMap(baseUrl.resolved(valueString), false);
}
}
} else if (key == "glossMap") {
auto value = materialJSON.value(key);
if (value.isString()) {
auto valueString = value.toString();
if (valueString == FALLTHROUGH) {
material->setPropertyDoesFallthrough(graphics::MaterialKey::FlagBit::ROUGHNESS_MAP_BIT);
} else {
material->setRoughnessMap(baseUrl.resolved(valueString), true);
}
}
} else if (key == "metallicMap") {
auto value = materialJSON.value(key);
if (value.isString()) {
auto valueString = value.toString();
if (valueString == FALLTHROUGH) {
material->setPropertyDoesFallthrough(graphics::MaterialKey::FlagBit::METALLIC_MAP_BIT);
} else {
material->setMetallicMap(baseUrl.resolved(valueString), false);
}
}
} else if (key == "specularMap") {
auto value = materialJSON.value(key);
if (value.isString()) {
auto valueString = value.toString();
if (valueString == FALLTHROUGH) {
material->setPropertyDoesFallthrough(graphics::MaterialKey::FlagBit::METALLIC_MAP_BIT);
} else {
material->setMetallicMap(baseUrl.resolved(valueString), true);
}
}
} else if (key == "normalMap") {
auto value = materialJSON.value(key);
if (value.isString()) {
auto valueString = value.toString();
if (valueString == FALLTHROUGH) {
material->setPropertyDoesFallthrough(graphics::MaterialKey::FlagBit::NORMAL_MAP_BIT);
} else {
material->setNormalMap(baseUrl.resolved(valueString), false);
}
}
} else if (key == "bumpMap") {
auto value = materialJSON.value(key);
if (value.isString()) {
auto valueString = value.toString();
if (valueString == FALLTHROUGH) {
material->setPropertyDoesFallthrough(graphics::MaterialKey::FlagBit::NORMAL_MAP_BIT);
} else {
material->setNormalMap(baseUrl.resolved(valueString), true);
}
}
} else if (key == "occlusionMap") {
auto value = materialJSON.value(key);
if (value.isString()) {
auto valueString = value.toString();
if (valueString == FALLTHROUGH) {
material->setPropertyDoesFallthrough(graphics::MaterialKey::FlagBit::OCCLUSION_MAP_BIT);
} else {
material->setOcclusionMap(baseUrl.resolved(valueString));
}
}
} else if (key == "scatteringMap") {
auto value = materialJSON.value(key);
if (value.isString()) {
auto valueString = value.toString();
if (valueString == FALLTHROUGH) {
material->setPropertyDoesFallthrough(graphics::MaterialKey::FlagBit::SCATTERING_MAP_BIT);
} else {
material->setScatteringMap(baseUrl.resolved(valueString));
}
}
} else if (key == "lightMap") {
auto value = materialJSON.value(key);
if (value.isString()) {
auto valueString = value.toString();
if (valueString == FALLTHROUGH) {
material->setPropertyDoesFallthrough(graphics::MaterialKey::FlagBit::LIGHTMAP_MAP_BIT);
} else {
material->setLightmapMap(baseUrl.resolved(valueString));
}
}
} else if (key == "texCoordTransform0") {
auto value = materialJSON.value(key);
if (value.isString()) {
auto valueString = value.toString();
if (valueString == FALLTHROUGH) {
material->setPropertyDoesFallthrough(graphics::Material::ExtraFlagBit::TEXCOORDTRANSFORM0);
}
}
// TODO: implement texCoordTransform0
} else if (key == "texCoordTransform1") {
auto value = materialJSON.value(key);
if (value.isString()) {
auto valueString = value.toString();
if (valueString == FALLTHROUGH) {
material->setPropertyDoesFallthrough(graphics::Material::ExtraFlagBit::TEXCOORDTRANSFORM1);
}
}
// TODO: implement texCoordTransform1
} else if (key == "lightmapParams") {
auto value = materialJSON.value(key);
if (value.isString()) {
auto valueString = value.toString();
if (valueString == FALLTHROUGH) {
material->setPropertyDoesFallthrough(graphics::Material::ExtraFlagBit::LIGHTMAP_PARAMS);
}
}
// TODO: implement lightmapParams
} else if (key == "materialParams") {
auto value = materialJSON.value(key);
if (value.isString()) {
auto valueString = value.toString();
if (valueString == FALLTHROUGH) {
material->setPropertyDoesFallthrough(graphics::Material::ExtraFlagBit::MATERIAL_PARAMS);
}
}
// TODO: implement materialParams
} else if (key == "defaultFallthrough") {
auto value = materialJSON.value(key);
if (value.isBool()) {
material->setDefaultFallthrough(value.toBool());
}
material->setAlbedoMap(baseUrl.resolved(urlString), useAlphaChannel);
}
} else if (key == "roughnessMap") {
auto value = materialJSON.value(key);
if (value.isString()) {
material->setRoughnessMap(baseUrl.resolved(value.toString()), false);
}
} else if (key == "glossMap") {
auto value = materialJSON.value(key);
if (value.isString()) {
material->setRoughnessMap(baseUrl.resolved(value.toString()), true);
}
} else if (key == "metallicMap") {
auto value = materialJSON.value(key);
if (value.isString()) {
material->setMetallicMap(baseUrl.resolved(value.toString()), false);
}
} else if (key == "specularMap") {
auto value = materialJSON.value(key);
if (value.isString()) {
material->setMetallicMap(baseUrl.resolved(value.toString()), true);
}
} else if (key == "normalMap") {
auto value = materialJSON.value(key);
if (value.isString()) {
material->setNormalMap(baseUrl.resolved(value.toString()), false);
}
} else if (key == "bumpMap") {
auto value = materialJSON.value(key);
if (value.isString()) {
material->setNormalMap(baseUrl.resolved(value.toString()), true);
}
} else if (key == "occlusionMap") {
auto value = materialJSON.value(key);
if (value.isString()) {
material->setOcclusionMap(baseUrl.resolved(value.toString()));
}
} else if (key == "scatteringMap") {
auto value = materialJSON.value(key);
if (value.isString()) {
material->setScatteringMap(baseUrl.resolved(value.toString()));
}
} else if (key == "lightMap") {
auto value = materialJSON.value(key);
if (value.isString()) {
material->setLightmapMap(baseUrl.resolved(value.toString()));
}
}
}

View file

@ -588,6 +588,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

@ -33,7 +33,7 @@ PacketVersion versionForPacketType(PacketType packetType) {
case PacketType::EntityEdit:
case PacketType::EntityData:
case PacketType::EntityPhysics:
return static_cast<PacketVersion>(EntityVersion::FixProtocolVersionBumpMismatch);
return static_cast<PacketVersion>(EntityVersion::LAST_PACKET_TYPE);
case PacketType::EntityQuery:
return static_cast<PacketVersion>(EntityQueryPacketVersion::ConicalFrustums);
case PacketType::AvatarIdentity:

View file

@ -255,7 +255,13 @@ enum class EntityVersion : PacketVersion {
MorePropertiesCleanup,
FixPropertiesFromCleanup,
UpdatedPolyLines,
FixProtocolVersionBumpMismatch
FixProtocolVersionBumpMismatch,
MigrateOverlayRenderProperties,
MissingWebEntityProperties,
// Add new versions above here
NUM_PACKET_TYPE,
LAST_PACKET_TYPE = NUM_PACKET_TYPE - 1
};
enum class EntityScriptCallMethodVersion : PacketVersion {

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

@ -35,6 +35,9 @@
#include "MaterialMappingMode.h"
#include "BillboardMode.h"
#include "RenderLayer.h"
#include "PrimitiveMode.h"
#include "WebInputMode.h"
#include "OctreeConstants.h"
#include "OctreeElement.h"
@ -263,6 +266,9 @@ public:
static int unpackDataFromBytes(const unsigned char* dataBytes, ShapeType& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); }
static int unpackDataFromBytes(const unsigned char* dataBytes, MaterialMappingMode& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); }
static int unpackDataFromBytes(const unsigned char* dataBytes, BillboardMode& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); }
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, 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

@ -109,7 +109,8 @@ void CharacterController::setDynamicsWorld(btDynamicsWorld* world) {
}
_dynamicsWorld = nullptr;
}
int32_t collisionGroup = computeCollisionGroup();
int32_t collisionMask = computeCollisionMask();
int32_t collisionGroup = BULLET_COLLISION_GROUP_MY_AVATAR;
if (_rigidBody) {
updateMassProperties();
}
@ -117,7 +118,7 @@ void CharacterController::setDynamicsWorld(btDynamicsWorld* world) {
// add to new world
_dynamicsWorld = world;
_pendingFlags &= ~PENDING_FLAG_JUMP;
_dynamicsWorld->addRigidBody(_rigidBody, collisionGroup, BULLET_COLLISION_MASK_MY_AVATAR);
_dynamicsWorld->addRigidBody(_rigidBody, collisionGroup, collisionMask);
_dynamicsWorld->addAction(this);
// restore gravity settings because adding an object to the world overwrites its gravity setting
_rigidBody->setGravity(_currentGravity * _currentUp);
@ -127,7 +128,7 @@ void CharacterController::setDynamicsWorld(btDynamicsWorld* world) {
assert(shape && shape->getShapeType() == CONVEX_HULL_SHAPE_PROXYTYPE);
_ghost.setCharacterShape(static_cast<btConvexHullShape*>(shape));
}
_ghost.setCollisionGroupAndMask(collisionGroup, BULLET_COLLISION_MASK_MY_AVATAR & (~ collisionGroup));
_ghost.setCollisionGroupAndMask(collisionGroup, collisionMask & (~ collisionGroup));
_ghost.setCollisionWorld(_dynamicsWorld);
_ghost.setRadiusAndHalfHeight(_radius, _halfHeight);
if (_rigidBody) {
@ -384,8 +385,8 @@ static const char* stateToStr(CharacterController::State state) {
#endif // #ifdef DEBUG_STATE_CHANGE
void CharacterController::updateCurrentGravity() {
int32_t collisionGroup = computeCollisionGroup();
if (_state == State::Hover || collisionGroup == BULLET_COLLISION_GROUP_COLLISIONLESS) {
int32_t collisionMask = computeCollisionMask();
if (_state == State::Hover || collisionMask == BULLET_COLLISION_MASK_COLLISIONLESS) {
_currentGravity = 0.0f;
} else {
_currentGravity = _gravity;
@ -458,28 +459,7 @@ void CharacterController::setLocalBoundingBox(const glm::vec3& minCorner, const
void CharacterController::setCollisionless(bool collisionless) {
if (collisionless != _collisionless) {
_collisionless = collisionless;
_pendingFlags |= PENDING_FLAG_UPDATE_COLLISION_GROUP;
}
}
int32_t CharacterController::computeCollisionGroup() const {
if (_collisionless) {
return _collisionlessAllowed ? BULLET_COLLISION_GROUP_COLLISIONLESS : BULLET_COLLISION_GROUP_MY_AVATAR;
} else {
return BULLET_COLLISION_GROUP_MY_AVATAR;
}
}
void CharacterController::handleChangedCollisionGroup() {
if (_pendingFlags & PENDING_FLAG_UPDATE_COLLISION_GROUP) {
// ATM the easiest way to update collision groups is to remove/re-add the RigidBody
if (_dynamicsWorld) {
_dynamicsWorld->removeRigidBody(_rigidBody);
int32_t collisionGroup = computeCollisionGroup();
_dynamicsWorld->addRigidBody(_rigidBody, collisionGroup, BULLET_COLLISION_MASK_MY_AVATAR);
}
_pendingFlags &= ~PENDING_FLAG_UPDATE_COLLISION_GROUP;
updateCurrentGravity();
_pendingFlags |= PENDING_FLAG_UPDATE_COLLISION_MASK;
}
}
@ -567,8 +547,8 @@ void CharacterController::applyMotor(int index, btScalar dt, btVector3& worldVel
btScalar angle = motor.rotation.getAngle();
btVector3 velocity = worldVelocity.rotate(axis, -angle);
int32_t collisionGroup = computeCollisionGroup();
if (collisionGroup == BULLET_COLLISION_GROUP_COLLISIONLESS ||
int32_t collisionMask = computeCollisionMask();
if (collisionMask == BULLET_COLLISION_MASK_COLLISIONLESS ||
_state == State::Hover || motor.hTimescale == motor.vTimescale) {
// modify velocity
btScalar tau = dt / motor.hTimescale;
@ -708,11 +688,11 @@ void CharacterController::updateState() {
btVector3 rayStart = _position;
btScalar rayLength = _radius;
int32_t collisionGroup = computeCollisionGroup();
if (collisionGroup == BULLET_COLLISION_GROUP_MY_AVATAR) {
rayLength += _scaleFactor * DEFAULT_AVATAR_FALL_HEIGHT;
} else {
int32_t collisionMask = computeCollisionMask();
if (collisionMask == BULLET_COLLISION_MASK_COLLISIONLESS) {
rayLength += MIN_HOVER_HEIGHT;
} else {
rayLength += _scaleFactor * DEFAULT_AVATAR_FALL_HEIGHT;
}
btVector3 rayEnd = rayStart - rayLength * _currentUp;
@ -746,69 +726,7 @@ void CharacterController::updateState() {
// disable normal state transitions while collisionless
const btScalar MAX_WALKING_SPEED = 2.65f;
if (collisionGroup == BULLET_COLLISION_GROUP_MY_AVATAR) {
switch (_state) {
case State::Ground:
if (!rayHasHit && !_hasSupport) {
SET_STATE(State::Hover, "no ground detected");
} else if (_pendingFlags & PENDING_FLAG_JUMP && _jumpButtonDownCount != _takeoffJumpButtonID) {
_takeoffJumpButtonID = _jumpButtonDownCount;
_takeoffToInAirStartTime = now;
SET_STATE(State::Takeoff, "jump pressed");
} else if (rayHasHit && !_hasSupport && _floorDistance > GROUND_TO_FLY_THRESHOLD) {
SET_STATE(State::InAir, "falling");
}
break;
case State::Takeoff:
if (!rayHasHit && !_hasSupport) {
SET_STATE(State::Hover, "no ground");
} else if ((now - _takeoffToInAirStartTime) > TAKE_OFF_TO_IN_AIR_PERIOD) {
SET_STATE(State::InAir, "takeoff done");
// compute jumpSpeed based on the scaled jump height for the default avatar in default gravity.
const float jumpHeight = std::max(_scaleFactor * DEFAULT_AVATAR_JUMP_HEIGHT, DEFAULT_AVATAR_MIN_JUMP_HEIGHT);
const float jumpSpeed = sqrtf(2.0f * -DEFAULT_AVATAR_GRAVITY * jumpHeight);
velocity += jumpSpeed * _currentUp;
_rigidBody->setLinearVelocity(velocity);
}
break;
case State::InAir: {
const float jumpHeight = std::max(_scaleFactor * DEFAULT_AVATAR_JUMP_HEIGHT, DEFAULT_AVATAR_MIN_JUMP_HEIGHT);
const float jumpSpeed = sqrtf(2.0f * -DEFAULT_AVATAR_GRAVITY * jumpHeight);
if ((velocity.dot(_currentUp) <= (jumpSpeed / 2.0f)) && ((_floorDistance < FLY_TO_GROUND_THRESHOLD) || _hasSupport)) {
SET_STATE(State::Ground, "hit ground");
} else if (_flyingAllowed) {
btVector3 desiredVelocity = _targetVelocity;
if (desiredVelocity.length2() < MIN_TARGET_SPEED_SQUARED) {
desiredVelocity = btVector3(0.0f, 0.0f, 0.0f);
}
bool vertTargetSpeedIsNonZero = desiredVelocity.dot(_currentUp) > MIN_TARGET_SPEED;
if ((jumpButtonHeld || vertTargetSpeedIsNonZero) && (_takeoffJumpButtonID != _jumpButtonDownCount)) {
SET_STATE(State::Hover, "double jump button");
} else if ((jumpButtonHeld || vertTargetSpeedIsNonZero) && (now - _jumpButtonDownStartTime) > JUMP_TO_HOVER_PERIOD) {
SET_STATE(State::Hover, "jump button held");
} else if (_floorDistance > _scaleFactor * DEFAULT_AVATAR_FALL_HEIGHT) {
// Transition to hover if we are above the fall threshold
SET_STATE(State::Hover, "above fall threshold");
}
} else if (!rayHasHit && !_hasSupport) {
SET_STATE(State::Hover, "no ground detected");
}
break;
}
case State::Hover:
btScalar horizontalSpeed = (velocity - velocity.dot(_currentUp) * _currentUp).length();
bool flyingFast = horizontalSpeed > (MAX_WALKING_SPEED * 0.75f);
if (!_flyingAllowed && rayHasHit) {
SET_STATE(State::InAir, "flying not allowed");
} else if ((_floorDistance < MIN_HOVER_HEIGHT) && !jumpButtonHeld && !flyingFast) {
SET_STATE(State::InAir, "near ground");
} else if (((_floorDistance < FLY_TO_GROUND_THRESHOLD) || _hasSupport) && !flyingFast) {
SET_STATE(State::Ground, "touching ground");
}
break;
}
} else {
if (collisionMask == BULLET_COLLISION_MASK_COLLISIONLESS) {
// when collisionless: only switch between State::Ground and State::Hover
// and bypass state debugging
if (rayHasHit) {
@ -820,6 +738,68 @@ void CharacterController::updateState() {
} else {
_state = State::Hover;
}
} else {
switch (_state) {
case State::Ground:
if (!rayHasHit && !_hasSupport) {
SET_STATE(State::Hover, "no ground detected");
} else if (_pendingFlags & PENDING_FLAG_JUMP && _jumpButtonDownCount != _takeoffJumpButtonID) {
_takeoffJumpButtonID = _jumpButtonDownCount;
_takeoffToInAirStartTime = now;
SET_STATE(State::Takeoff, "jump pressed");
} else if (rayHasHit && !_hasSupport && _floorDistance > GROUND_TO_FLY_THRESHOLD) {
SET_STATE(State::InAir, "falling");
}
break;
case State::Takeoff:
if (!rayHasHit && !_hasSupport) {
SET_STATE(State::Hover, "no ground");
} else if ((now - _takeoffToInAirStartTime) > TAKE_OFF_TO_IN_AIR_PERIOD) {
SET_STATE(State::InAir, "takeoff done");
// compute jumpSpeed based on the scaled jump height for the default avatar in default gravity.
const float jumpHeight = std::max(_scaleFactor * DEFAULT_AVATAR_JUMP_HEIGHT, DEFAULT_AVATAR_MIN_JUMP_HEIGHT);
const float jumpSpeed = sqrtf(2.0f * -DEFAULT_AVATAR_GRAVITY * jumpHeight);
velocity += jumpSpeed * _currentUp;
_rigidBody->setLinearVelocity(velocity);
}
break;
case State::InAir: {
const float jumpHeight = std::max(_scaleFactor * DEFAULT_AVATAR_JUMP_HEIGHT, DEFAULT_AVATAR_MIN_JUMP_HEIGHT);
const float jumpSpeed = sqrtf(2.0f * -DEFAULT_AVATAR_GRAVITY * jumpHeight);
if ((velocity.dot(_currentUp) <= (jumpSpeed / 2.0f)) && ((_floorDistance < FLY_TO_GROUND_THRESHOLD) || _hasSupport)) {
SET_STATE(State::Ground, "hit ground");
} else if (_flyingAllowed) {
btVector3 desiredVelocity = _targetVelocity;
if (desiredVelocity.length2() < MIN_TARGET_SPEED_SQUARED) {
desiredVelocity = btVector3(0.0f, 0.0f, 0.0f);
}
bool vertTargetSpeedIsNonZero = desiredVelocity.dot(_currentUp) > MIN_TARGET_SPEED;
if ((jumpButtonHeld || vertTargetSpeedIsNonZero) && (_takeoffJumpButtonID != _jumpButtonDownCount)) {
SET_STATE(State::Hover, "double jump button");
} else if ((jumpButtonHeld || vertTargetSpeedIsNonZero) && (now - _jumpButtonDownStartTime) > JUMP_TO_HOVER_PERIOD) {
SET_STATE(State::Hover, "jump button held");
} else if (_floorDistance > _scaleFactor * DEFAULT_AVATAR_FALL_HEIGHT) {
// Transition to hover if we are above the fall threshold
SET_STATE(State::Hover, "above fall threshold");
}
} else if (!rayHasHit && !_hasSupport) {
SET_STATE(State::Hover, "no ground detected");
}
break;
}
case State::Hover:
btScalar horizontalSpeed = (velocity - velocity.dot(_currentUp) * _currentUp).length();
bool flyingFast = horizontalSpeed > (MAX_WALKING_SPEED * 0.75f);
if (!_flyingAllowed && rayHasHit) {
SET_STATE(State::InAir, "flying not allowed");
} else if ((_floorDistance < MIN_HOVER_HEIGHT) && !jumpButtonHeld && !flyingFast) {
SET_STATE(State::InAir, "near ground");
} else if (((_floorDistance < FLY_TO_GROUND_THRESHOLD) || _hasSupport) && !flyingFast) {
SET_STATE(State::Ground, "touching ground");
}
break;
}
}
}
@ -866,6 +846,6 @@ void CharacterController::setFlyingAllowed(bool value) {
void CharacterController::setCollisionlessAllowed(bool value) {
if (value != _collisionlessAllowed) {
_collisionlessAllowed = value;
_pendingFlags |= PENDING_FLAG_UPDATE_COLLISION_GROUP;
_pendingFlags |= PENDING_FLAG_UPDATE_COLLISION_MASK;
}
}

8
libraries/physics/src/CharacterController.h Normal file → Executable file
View file

@ -30,7 +30,7 @@ const uint32_t PENDING_FLAG_ADD_TO_SIMULATION = 1U << 0;
const uint32_t PENDING_FLAG_REMOVE_FROM_SIMULATION = 1U << 1;
const uint32_t PENDING_FLAG_UPDATE_SHAPE = 1U << 2;
const uint32_t PENDING_FLAG_JUMP = 1U << 3;
const uint32_t PENDING_FLAG_UPDATE_COLLISION_GROUP = 1U << 4;
const uint32_t PENDING_FLAG_UPDATE_COLLISION_MASK = 1U << 4;
const uint32_t PENDING_FLAG_RECOMPUTE_FLYING = 1U << 5;
const float DEFAULT_MIN_FLOOR_NORMAL_DOT_UP = cosf(PI / 3.0f);
@ -120,14 +120,16 @@ public:
bool isStuck() const { return _isStuck; }
void setCollisionless(bool collisionless);
int32_t computeCollisionGroup() const;
void handleChangedCollisionGroup();
virtual int32_t computeCollisionMask() const = 0;
virtual void handleChangedCollisionMask() = 0;
bool getRigidBodyLocation(glm::vec3& avatarRigidBodyPosition, glm::quat& avatarRigidBodyRotation);
void setFlyingAllowed(bool value);
void setCollisionlessAllowed(bool value);
void setPendingFlagsUpdateCollisionMask(){ _pendingFlags |= PENDING_FLAG_UPDATE_COLLISION_MASK; }
protected:
#ifdef DEBUG_STATE_CHANGE

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