mirror of
https://github.com/lubosz/overte.git
synced 2025-04-24 09:23:17 +02:00
Merge remote-tracking branch 'upstream/master' into scriptableMaterials
This commit is contained in:
commit
cc53b338db
158 changed files with 2900 additions and 1478 deletions
|
@ -9,6 +9,8 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "AudioMixer.h"
|
||||
|
||||
#include <thread>
|
||||
|
||||
#include <QtCore/QJsonArray>
|
||||
|
@ -36,8 +38,6 @@
|
|||
#include "AvatarAudioStream.h"
|
||||
#include "InjectedAudioStream.h"
|
||||
|
||||
#include "AudioMixer.h"
|
||||
|
||||
static const float DEFAULT_ATTENUATION_PER_DOUBLING_IN_DISTANCE = 0.5f; // attenuation = -6dB * log2(distance)
|
||||
static const int DISABLE_STATIC_JITTER_FRAMES = -1;
|
||||
static const float DEFAULT_NOISE_MUTING_THRESHOLD = 1.0f;
|
||||
|
|
|
@ -18,6 +18,8 @@
|
|||
#include <ThreadedAssignment.h>
|
||||
#include <UUIDHasher.h>
|
||||
|
||||
#include <plugins/Forward.h>
|
||||
|
||||
#include "AudioMixerStats.h"
|
||||
#include "AudioMixerSlavePool.h"
|
||||
|
||||
|
|
|
@ -9,6 +9,8 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "AudioMixerClientData.h"
|
||||
|
||||
#include <random>
|
||||
|
||||
#include <QtCore/QDebug>
|
||||
|
@ -22,8 +24,6 @@
|
|||
#include "AudioLogging.h"
|
||||
#include "AudioHelpers.h"
|
||||
#include "AudioMixer.h"
|
||||
#include "AudioMixerClientData.h"
|
||||
|
||||
|
||||
AudioMixerClientData::AudioMixerClientData(const QUuid& nodeID) :
|
||||
NodeData(nodeID),
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
#include <AudioLimiter.h>
|
||||
#include <UUIDHasher.h>
|
||||
|
||||
#include <plugins/Forward.h>
|
||||
#include <plugins/CodecPlugin.h>
|
||||
|
||||
#include "PositionalAudioStream.h"
|
||||
|
|
|
@ -9,6 +9,8 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "AudioMixerSlave.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
|
@ -34,8 +36,6 @@
|
|||
#include "InjectedAudioStream.h"
|
||||
#include "AudioHelpers.h"
|
||||
|
||||
#include "AudioMixerSlave.h"
|
||||
|
||||
using AudioStreamMap = AudioMixerClientData::AudioStreamMap;
|
||||
|
||||
// packet helpers
|
||||
|
|
|
@ -9,22 +9,13 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <LogHandler.h>
|
||||
#include <BuildInfo.h>
|
||||
#include <SharedUtil.h>
|
||||
|
||||
#include "AssignmentClientApp.h"
|
||||
#include <BuildInfo.h>
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
disableQtBearerPoll(); // Fixes wifi ping spikes
|
||||
|
||||
QCoreApplication::setApplicationName(BuildInfo::ASSIGNMENT_CLIENT_NAME);
|
||||
QCoreApplication::setOrganizationName(BuildInfo::MODIFIED_ORGANIZATION);
|
||||
QCoreApplication::setOrganizationDomain(BuildInfo::ORGANIZATION_DOMAIN);
|
||||
QCoreApplication::setApplicationVersion(BuildInfo::VERSION);
|
||||
|
||||
qInstallMessageHandler(LogHandler::verboseMessageHandler);
|
||||
qInfo() << "Starting.";
|
||||
setupHifiApplication(BuildInfo::ASSIGNMENT_CLIENT_NAME);
|
||||
|
||||
AssignmentClientApp app(argc, argv);
|
||||
|
||||
|
|
|
@ -22,22 +22,10 @@
|
|||
#include "DomainServer.h"
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
disableQtBearerPoll(); // Fixes wifi ping spikes
|
||||
|
||||
QCoreApplication::setApplicationName(BuildInfo::DOMAIN_SERVER_NAME);
|
||||
QCoreApplication::setOrganizationName(BuildInfo::MODIFIED_ORGANIZATION);
|
||||
QCoreApplication::setOrganizationDomain(BuildInfo::ORGANIZATION_DOMAIN);
|
||||
QCoreApplication::setApplicationVersion(BuildInfo::VERSION);
|
||||
setupHifiApplication(BuildInfo::DOMAIN_SERVER_NAME);
|
||||
|
||||
Setting::init();
|
||||
|
||||
#ifndef WIN32
|
||||
setvbuf(stdout, NULL, _IOLBF, 0);
|
||||
#endif
|
||||
|
||||
qInstallMessageHandler(LogHandler::verboseMessageHandler);
|
||||
qInfo() << "Starting.";
|
||||
|
||||
int currentExitCode = 0;
|
||||
|
||||
// use a do-while to handle domain-server restart
|
||||
|
|
|
@ -11,18 +11,13 @@
|
|||
|
||||
#include <QtCore/QCoreApplication>
|
||||
|
||||
#include <LogHandler.h>
|
||||
#include <SharedUtil.h>
|
||||
|
||||
#include "IceServer.h"
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
#ifndef WIN32
|
||||
setvbuf(stdout, NULL, _IOLBF, 0);
|
||||
#endif
|
||||
|
||||
qInstallMessageHandler(LogHandler::verboseMessageHandler);
|
||||
qInfo() << "Starting.";
|
||||
setupHifiApplication("Ice Server");
|
||||
|
||||
IceServer iceServer(argc, argv);
|
||||
return iceServer.exec();
|
||||
}
|
||||
}
|
||||
|
|
Binary file not shown.
|
@ -14,8 +14,8 @@ import "../styles-uit"
|
|||
Item {
|
||||
property int colorScheme: 0;
|
||||
|
||||
readonly property var topColor: [ hifi.colors.baseGrayShadow, hifi.colors.faintGray ];
|
||||
readonly property var bottomColor: [ hifi.colors.baseGrayHighlight, hifi.colors.faintGray ];
|
||||
readonly property var topColor: [ hifi.colors.baseGrayShadow, hifi.colors.faintGray, "#89858C" ];
|
||||
readonly property var bottomColor: [ hifi.colors.baseGrayHighlight, hifi.colors.faintGray, "#89858C" ];
|
||||
|
||||
// Size
|
||||
height: colorScheme === 0 ? 2 : 1;
|
||||
|
|
|
@ -22,6 +22,10 @@ TableView {
|
|||
readonly property bool isLightColorScheme: colorScheme == hifi.colorSchemes.light
|
||||
property bool expandSelectedRow: false
|
||||
property bool centerHeaderText: false
|
||||
readonly property real headerSpacing: 3 //spacing between sort indicator and table header title
|
||||
property var titlePaintedPos: [] // storing extra data position behind painted
|
||||
// title text and sort indicatorin table's header
|
||||
signal titlePaintedPosSignal(int column) //signal that extradata position gets changed
|
||||
|
||||
model: ListModel { }
|
||||
|
||||
|
@ -69,36 +73,39 @@ TableView {
|
|||
height: hifi.dimensions.tableHeaderHeight
|
||||
color: isLightColorScheme ? hifi.colors.tableBackgroundLight : hifi.colors.tableBackgroundDark
|
||||
|
||||
|
||||
RalewayRegular {
|
||||
id: titleText
|
||||
x: centerHeaderText ? (parent.width - paintedWidth -
|
||||
((sortIndicatorVisible &&
|
||||
sortIndicatorColumn === styleData.column) ?
|
||||
(titleSort.paintedWidth / 5 + tableView.headerSpacing) : 0)) / 2 :
|
||||
hifi.dimensions.tablePadding
|
||||
text: styleData.value
|
||||
size: hifi.fontSizes.tableHeading
|
||||
font.capitalization: Font.AllUppercase
|
||||
color: hifi.colors.baseGrayHighlight
|
||||
horizontalAlignment: (centerHeaderText ? Text.AlignHCenter : Text.AlignLeft)
|
||||
anchors {
|
||||
left: parent.left
|
||||
leftMargin: hifi.dimensions.tablePadding
|
||||
right: parent.right
|
||||
rightMargin: hifi.dimensions.tablePadding
|
||||
verticalCenter: parent.verticalCenter
|
||||
}
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
//actual image of sort indicator in glyph font only 20% of real font size
|
||||
//i.e. if the charachter size set to 60 pixels, actual image is 12 pixels
|
||||
HiFiGlyphs {
|
||||
id: titleSort
|
||||
text: sortIndicatorOrder == Qt.AscendingOrder ? hifi.glyphs.caratUp : hifi.glyphs.caratDn
|
||||
color: hifi.colors.darkGray
|
||||
opacity: 0.6;
|
||||
size: hifi.fontSizes.tableHeadingIcon
|
||||
anchors {
|
||||
left: titleText.right
|
||||
leftMargin: -hifi.fontSizes.tableHeadingIcon / 3 - (centerHeaderText ? 15 : 10)
|
||||
right: parent.right
|
||||
rightMargin: hifi.dimensions.tablePadding
|
||||
verticalCenter: titleText.verticalCenter
|
||||
}
|
||||
anchors.verticalCenter: titleText.verticalCenter
|
||||
anchors.left: titleText.right
|
||||
anchors.leftMargin: -(hifi.fontSizes.tableHeadingIcon / 2.5) + tableView.headerSpacing
|
||||
visible: sortIndicatorVisible && sortIndicatorColumn === styleData.column
|
||||
onXChanged: {
|
||||
titlePaintedPos[styleData.column] = titleText.x + titleText.paintedWidth +
|
||||
paintedWidth / 5 + tableView.headerSpacing*2
|
||||
titlePaintedPosSignal(styleData.column)
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
|
@ -152,7 +159,7 @@ TableView {
|
|||
color: styleData.selected
|
||||
? hifi.colors.primaryHighlight
|
||||
: tableView.isLightColorScheme
|
||||
? (styleData.alternate ? hifi.colors.tableRowLightEven : hifi.colors.tableRowLightOdd)
|
||||
: (styleData.alternate ? hifi.colors.tableRowDarkEven : hifi.colors.tableRowDarkOdd)
|
||||
? (styleData.alternate ? hifi.colors.tableRowLightEven : hifi.colors.tableRowLightOdd)
|
||||
: (styleData.alternate ? hifi.colors.tableRowDarkEven : hifi.colors.tableRowDarkOdd)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,11 +34,10 @@ TextField {
|
|||
|
||||
placeholderText: textField.placeholderText
|
||||
|
||||
FontLoader { id: firaSansSemiBold; source: "../../fonts/FiraSans-SemiBold.ttf"; }
|
||||
FontLoader { id: firaSansRegular; source: "../../fonts/FiraSans-Regular.ttf"; }
|
||||
FontLoader { id: hifiGlyphs; source: "../../fonts/hifi-glyphs.ttf"; }
|
||||
font.family: firaSansSemiBold.name
|
||||
font.family: firaSansRegular.name
|
||||
font.pixelSize: hifi.fontSizes.textFieldInput
|
||||
font.italic: textField.text == ""
|
||||
height: implicitHeight + 3 // Make surrounding box higher so that highlight is vertically centered.
|
||||
property alias textFieldLabel: textFieldLabel
|
||||
|
||||
|
|
|
@ -206,7 +206,7 @@ Windows.ScrollingWindow {
|
|||
SHAPE_TYPES[SHAPE_TYPE_BOX] = "Box";
|
||||
SHAPE_TYPES[SHAPE_TYPE_SPHERE] = "Sphere";
|
||||
|
||||
var SHAPE_TYPE_DEFAULT = SHAPE_TYPE_STATIC_MESH;
|
||||
var SHAPE_TYPE_DEFAULT = SHAPE_TYPE_SIMPLE_COMPOUND;
|
||||
var DYNAMIC_DEFAULT = false;
|
||||
var prompt = desktop.customInputDialog({
|
||||
textInput: {
|
||||
|
|
|
@ -30,7 +30,7 @@ Rectangle {
|
|||
property int myCardWidth: width - upperRightInfoContainer.width;
|
||||
property int myCardHeight: 100;
|
||||
property int rowHeight: 60;
|
||||
property int actionButtonWidth: 55;
|
||||
property int actionButtonWidth: 65;
|
||||
property int locationColumnWidth: 170;
|
||||
property int nearbyNameCardWidth: nearbyTable.width - (iAmAdmin ? (actionButtonWidth * 4) : (actionButtonWidth * 2)) - 4 - hifi.dimensions.scrollbarBackgroundWidth;
|
||||
property int connectionsNameCardWidth: connectionsTable.width - locationColumnWidth - actionButtonWidth - 4 - hifi.dimensions.scrollbarBackgroundWidth;
|
||||
|
@ -415,6 +415,7 @@ Rectangle {
|
|||
movable: false;
|
||||
resizable: false;
|
||||
}
|
||||
|
||||
TableViewColumn {
|
||||
role: "ignore";
|
||||
title: "IGNORE";
|
||||
|
@ -599,13 +600,23 @@ Rectangle {
|
|||
}
|
||||
// This Rectangle refers to the [?] popup button next to "NAMES"
|
||||
Rectangle {
|
||||
id: questionRect
|
||||
color: hifi.colors.tableBackgroundLight;
|
||||
width: 20;
|
||||
height: hifi.dimensions.tableHeaderHeight - 2;
|
||||
anchors.left: nearbyTable.left;
|
||||
anchors.top: nearbyTable.top;
|
||||
anchors.topMargin: 1;
|
||||
anchors.leftMargin: actionButtonWidth + nearbyNameCardWidth/2 + displayNameHeaderMetrics.width/2 + 6;
|
||||
|
||||
Connections {
|
||||
target: nearbyTable
|
||||
onTitlePaintedPosSignal: {
|
||||
if (column === 1) { // name column
|
||||
questionRect.anchors.leftMargin = actionButtonWidth + nearbyTable.titlePaintedPos[column]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RalewayRegular {
|
||||
id: helpText;
|
||||
text: "[?]";
|
||||
|
|
|
@ -28,7 +28,7 @@ Rectangle {
|
|||
id: root;
|
||||
objectName: "checkout"
|
||||
property string activeView: "initialize";
|
||||
property bool purchasesReceived: false;
|
||||
property bool ownershipStatusReceived: false;
|
||||
property bool balanceReceived: false;
|
||||
property string itemName;
|
||||
property string itemId;
|
||||
|
@ -37,12 +37,18 @@ Rectangle {
|
|||
property double balanceAfterPurchase;
|
||||
property bool alreadyOwned: false;
|
||||
property int itemPrice: -1;
|
||||
property bool itemIsJson: true;
|
||||
property bool isCertified;
|
||||
property string itemType;
|
||||
property var itemTypesArray: ["entity", "wearable", "contentSet", "app", "avatar"];
|
||||
property var itemTypesText: ["entity", "wearable", "content set", "app", "avatar"];
|
||||
property var buttonTextNormal: ["REZ", "WEAR", "REPLACE CONTENT SET", "INSTALL", "WEAR"];
|
||||
property var buttonTextClicked: ["REZZED!", "WORN!", "CONTENT SET REPLACED!", "INSTALLED!", "AVATAR CHANGED!"]
|
||||
property var buttonGlyph: [hifi.glyphs.wand, hifi.glyphs.hat, hifi.glyphs.globe, hifi.glyphs.install, hifi.glyphs.avatar];
|
||||
property bool shouldBuyWithControlledFailure: false;
|
||||
property bool debugCheckoutSuccess: false;
|
||||
property bool canRezCertifiedItems: Entities.canRezCertified() || Entities.canRezTmpCertified();
|
||||
property bool isWearable;
|
||||
property string referrer;
|
||||
property bool isInstalled;
|
||||
// Style
|
||||
color: hifi.colors.white;
|
||||
Connections {
|
||||
|
@ -53,17 +59,17 @@ Rectangle {
|
|||
if (root.activeView !== "needsLogIn") {
|
||||
root.activeView = "needsLogIn";
|
||||
}
|
||||
} else if (walletStatus === 1) {
|
||||
} else if ((walletStatus === 1) || (walletStatus === 2) || (walletStatus === 3)) {
|
||||
if (root.activeView !== "notSetUp") {
|
||||
root.activeView = "notSetUp";
|
||||
notSetUpTimer.start();
|
||||
}
|
||||
} else if (walletStatus === 2) {
|
||||
} else if (walletStatus === 4) {
|
||||
if (root.activeView !== "passphraseModal") {
|
||||
root.activeView = "passphraseModal";
|
||||
UserActivityLogger.commercePassphraseEntry("marketplace checkout");
|
||||
}
|
||||
} else if (walletStatus === 3) {
|
||||
} else if (walletStatus === 5) {
|
||||
authSuccessStep();
|
||||
} else {
|
||||
console.log("ERROR in Checkout.qml: Unknown wallet status: " + walletStatus);
|
||||
|
@ -85,7 +91,9 @@ Rectangle {
|
|||
UserActivityLogger.commercePurchaseFailure(root.itemId, root.itemAuthor, root.itemPrice, !root.alreadyOwned, result.message);
|
||||
} else {
|
||||
root.itemHref = result.data.download_url;
|
||||
root.isWearable = result.data.categories.indexOf("Wearables") > -1;
|
||||
if (result.data.categories.indexOf("Wearables") > -1) {
|
||||
root.itemType = "wearable";
|
||||
}
|
||||
root.activeView = "checkoutSuccess";
|
||||
UserActivityLogger.commercePurchaseSuccess(root.itemId, root.itemAuthor, root.itemPrice, !root.alreadyOwned);
|
||||
}
|
||||
|
@ -97,32 +105,60 @@ Rectangle {
|
|||
} else {
|
||||
root.balanceReceived = true;
|
||||
root.balanceAfterPurchase = result.data.balance - root.itemPrice;
|
||||
root.setBuyText();
|
||||
root.refreshBuyUI();
|
||||
}
|
||||
}
|
||||
|
||||
onInventoryResult: {
|
||||
onAlreadyOwnedResult: {
|
||||
if (result.status !== 'success') {
|
||||
console.log("Failed to get purchases", result.data.message);
|
||||
console.log("Failed to get Already Owned status", result.data.message);
|
||||
} else {
|
||||
root.purchasesReceived = true;
|
||||
if (purchasesContains(result.data.assets, itemId)) {
|
||||
root.alreadyOwned = true;
|
||||
root.ownershipStatusReceived = true;
|
||||
if (result.data.marketplace_item_id === root.itemId) {
|
||||
root.alreadyOwned = result.data.already_owned;
|
||||
} else {
|
||||
console.log("WARNING - Received 'Already Owned' status about different Marketplace ID!");
|
||||
root.alreadyOwned = false;
|
||||
}
|
||||
root.setBuyText();
|
||||
root.refreshBuyUI();
|
||||
}
|
||||
}
|
||||
|
||||
onAppInstalled: {
|
||||
if (appHref === root.itemHref) {
|
||||
root.isInstalled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onItemIdChanged: {
|
||||
Commerce.inventory();
|
||||
root.ownershipStatusReceived = false;
|
||||
Commerce.alreadyOwned(root.itemId);
|
||||
itemPreviewImage.source = "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/" + itemId + "/thumbnail/hifi-mp-" + itemId + ".jpg";
|
||||
}
|
||||
|
||||
onItemHrefChanged: {
|
||||
itemIsJson = root.itemHref.endsWith('.json');
|
||||
if (root.itemHref.indexOf(".fst") > -1) {
|
||||
root.itemType = "avatar";
|
||||
} else if (root.itemHref.indexOf('.json.gz') > -1) {
|
||||
root.itemType = "contentSet";
|
||||
} else if (root.itemHref.indexOf('.app.json') > -1) {
|
||||
root.itemType = "app";
|
||||
} else if (root.itemHref.indexOf('.json') > -1) {
|
||||
root.itemType = "entity"; // "wearable" type handled later
|
||||
} else {
|
||||
console.log("WARNING - Item type is UNKNOWN!");
|
||||
root.itemType = "entity";
|
||||
}
|
||||
}
|
||||
|
||||
onItemTypeChanged: {
|
||||
if (root.itemType === "entity" || root.itemType === "wearable" ||
|
||||
root.itemType === "contentSet" || root.itemType === "avatar" || root.itemType === "app") {
|
||||
root.isCertified = true;
|
||||
} else {
|
||||
root.isCertified = false;
|
||||
}
|
||||
}
|
||||
|
||||
onItemPriceChanged: {
|
||||
|
@ -203,7 +239,7 @@ Rectangle {
|
|||
color: hifi.colors.white;
|
||||
|
||||
Component.onCompleted: {
|
||||
purchasesReceived = false;
|
||||
ownershipStatusReceived = false;
|
||||
balanceReceived = false;
|
||||
Commerce.getWalletStatus();
|
||||
}
|
||||
|
@ -278,6 +314,32 @@ Rectangle {
|
|||
anchors.left: parent.left;
|
||||
anchors.right: parent.right;
|
||||
|
||||
Rectangle {
|
||||
id: loading;
|
||||
z: 997;
|
||||
visible: !root.ownershipStatusReceived || !root.balanceReceived;
|
||||
anchors.fill: parent;
|
||||
color: hifi.colors.white;
|
||||
|
||||
// This object is always used in a popup.
|
||||
// This MouseArea is used to prevent a user from being
|
||||
// able to click on a button/mouseArea underneath the popup/section.
|
||||
MouseArea {
|
||||
anchors.fill: parent;
|
||||
hoverEnabled: true;
|
||||
propagateComposedEvents: false;
|
||||
}
|
||||
|
||||
AnimatedImage {
|
||||
id: loadingImage;
|
||||
source: "../common/images/loader-blue.gif"
|
||||
width: 74;
|
||||
height: width;
|
||||
anchors.verticalCenter: parent.verticalCenter;
|
||||
anchors.horizontalCenter: parent.horizontalCenter;
|
||||
}
|
||||
}
|
||||
|
||||
RalewayRegular {
|
||||
id: confirmPurchaseText;
|
||||
anchors.top: parent.top;
|
||||
|
@ -286,8 +348,8 @@ Rectangle {
|
|||
anchors.leftMargin: 16;
|
||||
width: paintedWidth;
|
||||
height: paintedHeight;
|
||||
text: "Confirm Purchase:";
|
||||
color: hifi.colors.baseGray;
|
||||
text: "Review Purchase:";
|
||||
color: hifi.colors.black;
|
||||
size: 28;
|
||||
}
|
||||
|
||||
|
@ -400,7 +462,7 @@ Rectangle {
|
|||
width: root.width;
|
||||
// Anchors
|
||||
anchors.top: separator2.bottom;
|
||||
anchors.topMargin: 16;
|
||||
anchors.topMargin: 0;
|
||||
anchors.left: parent.left;
|
||||
anchors.leftMargin: 16;
|
||||
anchors.right: parent.right;
|
||||
|
@ -411,8 +473,8 @@ Rectangle {
|
|||
Rectangle {
|
||||
id: buyTextContainer;
|
||||
visible: buyText.text !== "";
|
||||
anchors.top: cancelPurchaseButton.bottom;
|
||||
anchors.topMargin: 16;
|
||||
anchors.top: parent.top;
|
||||
anchors.topMargin: 10;
|
||||
anchors.left: parent.left;
|
||||
anchors.right: parent.right;
|
||||
height: buyText.height + 30;
|
||||
|
@ -454,32 +516,63 @@ Rectangle {
|
|||
// Alignment
|
||||
horizontalAlignment: Text.AlignLeft;
|
||||
verticalAlignment: Text.AlignVCenter;
|
||||
}
|
||||
}
|
||||
|
||||
onLinkActivated: {
|
||||
sendToScript({method: 'checkout_goToPurchases', filterText: root.itemName});
|
||||
}
|
||||
// "View in My Purchases" button
|
||||
HifiControlsUit.Button {
|
||||
id: viewInMyPurchasesButton;
|
||||
visible: false;
|
||||
color: hifi.buttons.blue;
|
||||
colorScheme: hifi.colorSchemes.light;
|
||||
anchors.top: buyTextContainer.visible ? buyTextContainer.bottom : checkoutActionButtonsContainer.top;
|
||||
anchors.topMargin: 10;
|
||||
height: 50;
|
||||
anchors.left: parent.left;
|
||||
anchors.right: parent.right;
|
||||
text: "VIEW THIS ITEM IN MY PURCHASES";
|
||||
onClicked: {
|
||||
sendToScript({method: 'checkout_goToPurchases', filterText: root.itemName});
|
||||
}
|
||||
}
|
||||
|
||||
// "Buy" button
|
||||
HifiControlsUit.Button {
|
||||
id: buyButton;
|
||||
enabled: (root.balanceAfterPurchase >= 0 && purchasesReceived && balanceReceived) || !itemIsJson;
|
||||
color: hifi.buttons.blue;
|
||||
visible: !((root.itemType === "avatar" || root.itemType === "app") && viewInMyPurchasesButton.visible)
|
||||
enabled: (root.balanceAfterPurchase >= 0 && ownershipStatusReceived && balanceReceived) || (!root.isCertified);
|
||||
color: viewInMyPurchasesButton.visible ? hifi.buttons.white : hifi.buttons.blue;
|
||||
colorScheme: hifi.colorSchemes.light;
|
||||
anchors.top: checkoutActionButtonsContainer.top;
|
||||
anchors.topMargin: 16;
|
||||
height: 40;
|
||||
anchors.top: viewInMyPurchasesButton.visible ? viewInMyPurchasesButton.bottom :
|
||||
(buyTextContainer.visible ? buyTextContainer.bottom : checkoutActionButtonsContainer.top);
|
||||
anchors.topMargin: 10;
|
||||
height: 50;
|
||||
anchors.left: parent.left;
|
||||
anchors.right: parent.right;
|
||||
text: (itemIsJson ? ((purchasesReceived && balanceReceived) ? "Confirm Purchase" : "--") : "Get Item");
|
||||
text: ((root.isCertified) ? ((ownershipStatusReceived && balanceReceived) ?
|
||||
(viewInMyPurchasesButton.visible ? "Buy It Again" : "Confirm Purchase") : "--") : "Get Item");
|
||||
onClicked: {
|
||||
if (itemIsJson) {
|
||||
buyButton.enabled = false;
|
||||
if (root.isCertified) {
|
||||
if (!root.shouldBuyWithControlledFailure) {
|
||||
Commerce.buy(itemId, itemPrice);
|
||||
if (root.itemType === "contentSet" && !Entities.canReplaceContent()) {
|
||||
lightboxPopup.titleText = "Purchase Content Set";
|
||||
lightboxPopup.bodyText = "You will not be able to replace this domain's content with <b>" + root.itemName +
|
||||
" </b>until the server owner gives you 'Replace Content' permissions.<br><br>Are you sure you want to purchase this content set?";
|
||||
lightboxPopup.button1text = "CANCEL";
|
||||
lightboxPopup.button1method = "root.visible = false;"
|
||||
lightboxPopup.button2text = "CONFIRM";
|
||||
lightboxPopup.button2method = "Commerce.buy('" + root.itemId + "', " + root.itemPrice + ");" +
|
||||
"root.visible = false; buyButton.enabled = false; loading.visible = true;";
|
||||
lightboxPopup.visible = true;
|
||||
} else {
|
||||
buyButton.enabled = false;
|
||||
loading.visible = true;
|
||||
Commerce.buy(root.itemId, root.itemPrice);
|
||||
}
|
||||
} else {
|
||||
Commerce.buy(itemId, itemPrice, true);
|
||||
buyButton.enabled = false;
|
||||
loading.visible = true;
|
||||
Commerce.buy(root.itemId, root.itemPrice, true);
|
||||
}
|
||||
} else {
|
||||
if (urlHandler.canHandleUrl(itemHref)) {
|
||||
|
@ -494,9 +587,9 @@ Rectangle {
|
|||
id: cancelPurchaseButton;
|
||||
color: hifi.buttons.noneBorderlessGray;
|
||||
colorScheme: hifi.colorSchemes.light;
|
||||
anchors.top: buyButton.bottom;
|
||||
anchors.topMargin: 16;
|
||||
height: 40;
|
||||
anchors.top: buyButton.visible ? buyButton.bottom : viewInMyPurchasesButton.bottom;
|
||||
anchors.topMargin: 10;
|
||||
height: 50;
|
||||
anchors.left: parent.left;
|
||||
anchors.right: parent.right;
|
||||
text: "Cancel"
|
||||
|
@ -522,31 +615,32 @@ Rectangle {
|
|||
anchors.top: titleBarContainer.bottom;
|
||||
anchors.bottom: root.bottom;
|
||||
anchors.left: parent.left;
|
||||
anchors.leftMargin: 16;
|
||||
anchors.leftMargin: 20;
|
||||
anchors.right: parent.right;
|
||||
anchors.rightMargin: 16;
|
||||
anchors.rightMargin: 20;
|
||||
|
||||
RalewayRegular {
|
||||
id: completeText;
|
||||
anchors.top: parent.top;
|
||||
anchors.topMargin: 30;
|
||||
anchors.topMargin: 18;
|
||||
anchors.left: parent.left;
|
||||
width: paintedWidth;
|
||||
height: paintedHeight;
|
||||
text: "Thank you for your order!";
|
||||
color: hifi.colors.baseGray;
|
||||
size: 28;
|
||||
size: 36;
|
||||
}
|
||||
|
||||
RalewaySemiBold {
|
||||
id: completeText2;
|
||||
text: "The item " + '<font color="' + hifi.colors.blueAccent + '"><a href="#">' + root.itemName + '</a></font>' +
|
||||
" has been added to your Purchases and a receipt will appear in your Wallet's transaction history.";
|
||||
text: "The " + (root.itemTypesText)[itemTypesArray.indexOf(root.itemType)] +
|
||||
' <font color="' + hifi.colors.blueAccent + '"><a href="#">' + root.itemName + '</a></font>' +
|
||||
" has been added to your Purchases and a receipt will appear in your Wallet's transaction history.";
|
||||
// Text size
|
||||
size: 20;
|
||||
size: 18;
|
||||
// Anchors
|
||||
anchors.top: completeText.bottom;
|
||||
anchors.topMargin: 10;
|
||||
anchors.topMargin: 15;
|
||||
height: paintedHeight;
|
||||
anchors.left: parent.left;
|
||||
anchors.right: parent.right;
|
||||
|
@ -576,7 +670,7 @@ Rectangle {
|
|||
|
||||
RalewayBold {
|
||||
anchors.fill: parent;
|
||||
text: "REZZED";
|
||||
text: (root.buttonTextClicked)[itemTypesArray.indexOf(root.itemType)];
|
||||
size: 18;
|
||||
color: hifi.colors.white;
|
||||
verticalAlignment: Text.AlignVCenter;
|
||||
|
@ -592,26 +686,58 @@ Rectangle {
|
|||
// "Rez" button
|
||||
HifiControlsUit.Button {
|
||||
id: rezNowButton;
|
||||
enabled: root.canRezCertifiedItems || root.isWearable;
|
||||
buttonGlyph: hifi.glyphs.lightning;
|
||||
enabled: (root.itemType === "entity" && root.canRezCertifiedItems) ||
|
||||
(root.itemType === "contentSet" && Entities.canReplaceContent()) ||
|
||||
root.itemType === "wearable" || root.itemType === "avatar" || root.itemType === "app";
|
||||
buttonGlyph: (root.buttonGlyph)[itemTypesArray.indexOf(root.itemType)];
|
||||
color: hifi.buttons.red;
|
||||
colorScheme: hifi.colorSchemes.light;
|
||||
anchors.top: completeText2.bottom;
|
||||
anchors.topMargin: 30;
|
||||
anchors.topMargin: 27;
|
||||
height: 50;
|
||||
anchors.left: parent.left;
|
||||
anchors.right: parent.right;
|
||||
text: root.isWearable ? "Wear It" : "Rez It"
|
||||
text: root.itemType === "app" && root.isInstalled ? "OPEN APP" : (root.buttonTextNormal)[itemTypesArray.indexOf(root.itemType)];
|
||||
onClicked: {
|
||||
sendToScript({method: 'checkout_rezClicked', itemHref: root.itemHref, isWearable: root.isWearable});
|
||||
rezzedNotifContainer.visible = true;
|
||||
rezzedNotifContainerTimer.start();
|
||||
UserActivityLogger.commerceEntityRezzed(root.itemId, "checkout", root.isWearable ? "rez" : "wear");
|
||||
if (root.itemType === "contentSet") {
|
||||
lightboxPopup.titleText = "Replace Content";
|
||||
lightboxPopup.bodyText = "Rezzing this content set will replace the existing environment and all of the items in this domain. " +
|
||||
"If you want to save the state of the content in this domain, create a backup before proceeding.<br><br>" +
|
||||
"For more information about backing up and restoring content, " +
|
||||
"<a href='https://docs.highfidelity.com/create-and-explore/start-working-in-your-sandbox/restoring-sandbox-content'>" +
|
||||
"click here to open info on your desktop browser.";
|
||||
lightboxPopup.button1text = "CANCEL";
|
||||
lightboxPopup.button1method = "root.visible = false;"
|
||||
lightboxPopup.button2text = "CONFIRM";
|
||||
lightboxPopup.button2method = "Commerce.replaceContentSet('" + root.itemHref + "');" +
|
||||
"root.visible = false;rezzedNotifContainer.visible = true; rezzedNotifContainerTimer.start();" +
|
||||
"UserActivityLogger.commerceEntityRezzed('" + root.itemId + "', 'checkout', '" + root.itemType + "');";
|
||||
lightboxPopup.visible = true;
|
||||
} else if (root.itemType === "avatar") {
|
||||
lightboxPopup.titleText = "Change Avatar";
|
||||
lightboxPopup.bodyText = "This will change your current avatar to " + root.itemName + " while retaining your wearables.";
|
||||
lightboxPopup.button1text = "CANCEL";
|
||||
lightboxPopup.button1method = "root.visible = false;"
|
||||
lightboxPopup.button2text = "CONFIRM";
|
||||
lightboxPopup.button2method = "MyAvatar.useFullAvatarURL('" + root.itemHref + "'); root.visible = false;";
|
||||
lightboxPopup.visible = true;
|
||||
} else if (root.itemType === "app") {
|
||||
if (root.isInstalled) {
|
||||
Commerce.openApp(root.itemHref);
|
||||
} else {
|
||||
Commerce.installApp(root.itemHref);
|
||||
}
|
||||
} else {
|
||||
sendToScript({method: 'checkout_rezClicked', itemHref: root.itemHref, itemType: root.itemType});
|
||||
rezzedNotifContainer.visible = true;
|
||||
rezzedNotifContainerTimer.start();
|
||||
UserActivityLogger.commerceEntityRezzed(root.itemId, "checkout", root.itemType);
|
||||
}
|
||||
}
|
||||
}
|
||||
RalewaySemiBold {
|
||||
id: noPermissionText;
|
||||
visible: !root.canRezCertifiedItems && !root.isWearable;
|
||||
visible: !root.canRezCertifiedItems && root.itemType === "entity";
|
||||
text: '<font color="' + hifi.colors.redAccent + '"><a href="#">You do not have Certified Rez permissions in this domain.</a></font>'
|
||||
// Text size
|
||||
size: 16;
|
||||
|
@ -640,7 +766,7 @@ Rectangle {
|
|||
}
|
||||
RalewaySemiBold {
|
||||
id: explainRezText;
|
||||
visible: !root.isWearable;
|
||||
visible: root.itemType === "entity";
|
||||
text: '<font color="' + hifi.colors.redAccent + '"><a href="#">What does "Rez" mean?</a></font>'
|
||||
// Text size
|
||||
size: 16;
|
||||
|
@ -663,9 +789,9 @@ Rectangle {
|
|||
|
||||
RalewaySemiBold {
|
||||
id: myPurchasesLink;
|
||||
text: '<font color="' + hifi.colors.blueAccent + '"><a href="#">View this item in My Purchases</a></font>';
|
||||
text: '<font color="' + hifi.colors.primaryHighlight + '"><a href="#">View this item in My Purchases</a></font>';
|
||||
// Text size
|
||||
size: 20;
|
||||
size: 18;
|
||||
// Anchors
|
||||
anchors.top: explainRezText.visible ? explainRezText.bottom : (noPermissionText.visible ? noPermissionText.bottom : rezNowButton.bottom);
|
||||
anchors.topMargin: 40;
|
||||
|
@ -685,12 +811,12 @@ Rectangle {
|
|||
|
||||
RalewaySemiBold {
|
||||
id: walletLink;
|
||||
text: '<font color="' + hifi.colors.blueAccent + '"><a href="#">View receipt in Wallet</a></font>';
|
||||
text: '<font color="' + hifi.colors.primaryHighlight + '"><a href="#">View receipt in Wallet</a></font>';
|
||||
// Text size
|
||||
size: 20;
|
||||
size: 18;
|
||||
// Anchors
|
||||
anchors.top: myPurchasesLink.bottom;
|
||||
anchors.topMargin: 20;
|
||||
anchors.topMargin: 16;
|
||||
height: paintedHeight;
|
||||
anchors.left: parent.left;
|
||||
anchors.right: parent.right;
|
||||
|
@ -708,12 +834,12 @@ Rectangle {
|
|||
RalewayRegular {
|
||||
id: pendingText;
|
||||
text: 'Your item is marked "pending" while your purchase is being confirmed. ' +
|
||||
'<font color="' + hifi.colors.blueAccent + '"><a href="#">Learn More</a></font>';
|
||||
'<b><font color="' + hifi.colors.primaryHighlight + '"><a href="#">Learn More</a></font></b>';
|
||||
// Text size
|
||||
size: 20;
|
||||
size: 18;
|
||||
// Anchors
|
||||
anchors.top: walletLink.bottom;
|
||||
anchors.topMargin: 60;
|
||||
anchors.topMargin: 32;
|
||||
height: paintedHeight;
|
||||
anchors.left: parent.left;
|
||||
anchors.right: parent.right;
|
||||
|
@ -739,11 +865,10 @@ Rectangle {
|
|||
color: hifi.buttons.noneBorderlessGray;
|
||||
colorScheme: hifi.colorSchemes.light;
|
||||
anchors.bottom: parent.bottom;
|
||||
anchors.bottomMargin: 20;
|
||||
anchors.bottomMargin: 54;
|
||||
anchors.right: parent.right;
|
||||
anchors.rightMargin: 14;
|
||||
width: parent.width/2 - anchors.rightMargin;
|
||||
height: 60;
|
||||
width: 193;
|
||||
height: 44;
|
||||
text: "Continue Shopping";
|
||||
onClicked: {
|
||||
sendToScript({method: 'checkout_continueShopping', itemId: itemId});
|
||||
|
@ -851,7 +976,7 @@ Rectangle {
|
|||
buyButton.color = hifi.buttons.red;
|
||||
root.shouldBuyWithControlledFailure = true;
|
||||
} else {
|
||||
buyButton.text = (itemIsJson ? ((purchasesReceived && balanceReceived) ? (root.alreadyOwned ? "Buy Another" : "Buy"): "--") : "Get Item");
|
||||
buyButton.text = (root.isCertified ? ((ownershipStatusReceived && balanceReceived) ? (root.alreadyOwned ? "Buy Another" : "Buy"): "--") : "Get Item");
|
||||
buyButton.color = hifi.buttons.blue;
|
||||
root.shouldBuyWithControlledFailure = false;
|
||||
}
|
||||
|
@ -883,7 +1008,7 @@ Rectangle {
|
|||
itemHref = message.params.itemHref;
|
||||
referrer = message.params.referrer;
|
||||
itemAuthor = message.params.itemAuthor;
|
||||
setBuyText();
|
||||
refreshBuyUI();
|
||||
break;
|
||||
default:
|
||||
console.log('Unrecognized message from marketplaces.js:', JSON.stringify(message));
|
||||
|
@ -891,22 +1016,13 @@ Rectangle {
|
|||
}
|
||||
signal sendToScript(var message);
|
||||
|
||||
function purchasesContains(purchasesJson, id) {
|
||||
for (var idx = 0; idx < purchasesJson.length; idx++) {
|
||||
if(purchasesJson[idx].id === id) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function setBuyText() {
|
||||
if (root.itemIsJson) {
|
||||
if (root.purchasesReceived && root.balanceReceived) {
|
||||
function refreshBuyUI() {
|
||||
if (root.isCertified) {
|
||||
if (root.ownershipStatusReceived && root.balanceReceived) {
|
||||
if (root.balanceAfterPurchase < 0) {
|
||||
if (root.alreadyOwned) {
|
||||
buyText.text = "<b>Your Wallet does not have sufficient funds to purchase this item again.<br>" +
|
||||
'<font color="' + hifi.colors.blueAccent + '"><a href="#">View the copy you own in My Purchases</a></font></b>';
|
||||
buyText.text = "<b>Your Wallet does not have sufficient funds to purchase this item again.</b>";
|
||||
viewInMyPurchasesButton.visible = true;
|
||||
} else {
|
||||
buyText.text = "<b>Your Wallet does not have sufficient funds to purchase this item.</b>";
|
||||
}
|
||||
|
@ -916,15 +1032,19 @@ Rectangle {
|
|||
buyGlyph.size = 54;
|
||||
} else {
|
||||
if (root.alreadyOwned) {
|
||||
buyText.text = '<b>You already own this item.<br>Purchasing it will buy another copy.<br><font color="'
|
||||
+ hifi.colors.blueAccent + '"><a href="#">View this item in My Purchases</a></font></b>';
|
||||
buyTextContainer.color = "#FFD6AD";
|
||||
buyTextContainer.border.color = "#FAC07D";
|
||||
buyGlyph.text = hifi.glyphs.alert;
|
||||
buyGlyph.size = 46;
|
||||
viewInMyPurchasesButton.visible = true;
|
||||
} else {
|
||||
buyText.text = "";
|
||||
}
|
||||
|
||||
if (root.itemType === "contentSet" && !Entities.canReplaceContent()) {
|
||||
buyText.text = "The domain owner must enable 'Replace Content' permissions for you in this " +
|
||||
"<b>domain's server settings</b> before you can replace this domain's content with <b>" + root.itemName + "</b>";
|
||||
buyTextContainer.color = "#FFC3CD";
|
||||
buyTextContainer.border.color = "#F3808F";
|
||||
buyGlyph.text = hifi.glyphs.alert;
|
||||
buyGlyph.size = 54;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
buyText.text = "";
|
||||
|
@ -945,8 +1065,8 @@ Rectangle {
|
|||
root.activeView = "checkoutSuccess";
|
||||
}
|
||||
root.balanceReceived = false;
|
||||
root.purchasesReceived = false;
|
||||
Commerce.inventory();
|
||||
root.ownershipStatusReceived = false;
|
||||
Commerce.alreadyOwned(root.itemId);
|
||||
Commerce.balance();
|
||||
}
|
||||
|
||||
|
|
|
@ -25,10 +25,13 @@ Rectangle {
|
|||
property string titleText;
|
||||
property string bodyImageSource;
|
||||
property string bodyText;
|
||||
property string button1color: hifi.buttons.noneBorderlessGray;
|
||||
property string button1text;
|
||||
property string button1method;
|
||||
property string button2color: hifi.buttons.noneBorderless;
|
||||
property string button2text;
|
||||
property string button2method;
|
||||
property string buttonLayout: "leftright";
|
||||
|
||||
readonly property string securityPicBodyText: "When you see your Security Pic, your actions and data are securely making use of your " +
|
||||
"Wallet's private keys.<br><br>You can change your Security Pic in your Wallet.";
|
||||
|
@ -39,6 +42,12 @@ Rectangle {
|
|||
color: Qt.rgba(0, 0, 0, 0.5);
|
||||
z: 999;
|
||||
|
||||
onVisibleChanged: {
|
||||
if (!visible) {
|
||||
resetLightbox();
|
||||
}
|
||||
}
|
||||
|
||||
// This object is always used in a popup.
|
||||
// This MouseArea is used to prevent a user from being
|
||||
// able to click on a button/mouseArea underneath the popup.
|
||||
|
@ -100,6 +109,10 @@ Rectangle {
|
|||
size: 20;
|
||||
verticalAlignment: Text.AlignTop;
|
||||
wrapMode: Text.WordWrap;
|
||||
|
||||
onLinkActivated: {
|
||||
sendToParent({ method: 'commerceLightboxLinkClicked', linkUrl: link });
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
|
@ -108,18 +121,21 @@ Rectangle {
|
|||
anchors.topMargin: 30;
|
||||
anchors.left: parent.left;
|
||||
anchors.right: parent.right;
|
||||
height: 70;
|
||||
height: root.buttonLayout === "leftright" ? 70 : 150;
|
||||
|
||||
// Button 1
|
||||
HifiControlsUit.Button {
|
||||
color: hifi.buttons.noneBorderlessGray;
|
||||
id: button1;
|
||||
color: root.button1color;
|
||||
colorScheme: hifi.colorSchemes.light;
|
||||
anchors.top: parent.top;
|
||||
anchors.bottom: parent.bottom;
|
||||
anchors.bottomMargin: 20;
|
||||
anchors.top: root.buttonLayout === "leftright" ? parent.top : parent.top;
|
||||
anchors.left: parent.left;
|
||||
anchors.leftMargin: 10;
|
||||
width: root.button2text ? parent.width/2 - anchors.leftMargin*2 : parent.width - anchors.leftMargin * 2;
|
||||
anchors.right: root.buttonLayout === "leftright" ? undefined : parent.right;
|
||||
anchors.rightMargin: root.buttonLayout === "leftright" ? undefined : 10;
|
||||
width: root.buttonLayout === "leftright" ? (root.button2text ? parent.width/2 - anchors.leftMargin*2 : parent.width - anchors.leftMargin * 2) :
|
||||
(undefined);
|
||||
height: 50;
|
||||
text: root.button1text;
|
||||
onClicked: {
|
||||
eval(button1method);
|
||||
|
@ -128,15 +144,18 @@ Rectangle {
|
|||
|
||||
// Button 2
|
||||
HifiControlsUit.Button {
|
||||
id: button2;
|
||||
visible: root.button2text;
|
||||
color: hifi.buttons.noneBorderless;
|
||||
color: root.button2color;
|
||||
colorScheme: hifi.colorSchemes.light;
|
||||
anchors.top: parent.top;
|
||||
anchors.bottom: parent.bottom;
|
||||
anchors.bottomMargin: 20;
|
||||
anchors.top: root.buttonLayout === "leftright" ? parent.top : button1.bottom;
|
||||
anchors.topMargin: root.buttonLayout === "leftright" ? undefined : 20;
|
||||
anchors.left: root.buttonLayout === "leftright" ? undefined : parent.left;
|
||||
anchors.leftMargin: root.buttonLayout === "leftright" ? undefined : 10;
|
||||
anchors.right: parent.right;
|
||||
anchors.rightMargin: 10;
|
||||
width: parent.width/2 - anchors.rightMargin*2;
|
||||
width: root.buttonLayout === "leftright" ? parent.width/2 - anchors.rightMargin*2 : undefined;
|
||||
height: 50;
|
||||
text: root.button2text;
|
||||
onClicked: {
|
||||
eval(button2method);
|
||||
|
@ -149,6 +168,19 @@ Rectangle {
|
|||
// FUNCTION DEFINITIONS START
|
||||
//
|
||||
signal sendToParent(var msg);
|
||||
|
||||
function resetLightbox() {
|
||||
root.titleText = "";
|
||||
root.bodyImageSource = "";
|
||||
root.bodyText = "";
|
||||
root.button1color = hifi.buttons.noneBorderlessGray;
|
||||
root.button1text = "";
|
||||
root.button1method = "";
|
||||
root.button2color = hifi.buttons.noneBorderless;
|
||||
root.button2text = "";
|
||||
root.button2method = "";
|
||||
root.buttonLayout = "leftright";
|
||||
}
|
||||
//
|
||||
// FUNCTION DEFINITIONS END
|
||||
//
|
||||
|
|
|
@ -37,9 +37,9 @@ Item {
|
|||
onWalletStatusResult: {
|
||||
if (walletStatus === 0) {
|
||||
sendToParent({method: "needsLogIn"});
|
||||
} else if (walletStatus === 3) {
|
||||
} else if (walletStatus === 5) {
|
||||
Commerce.getSecurityImage();
|
||||
} else if (walletStatus > 3) {
|
||||
} else if (walletStatus > 5) {
|
||||
console.log("ERROR in EmulatedMarketplaceHeader.qml: Unknown wallet status: " + walletStatus);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -90,11 +90,11 @@ Rectangle {
|
|||
id: introText2;
|
||||
text: "My Purchases";
|
||||
// Text size
|
||||
size: 28;
|
||||
size: 22;
|
||||
// Anchors
|
||||
anchors.top: introText1.bottom;
|
||||
anchors.left: parent.left;
|
||||
anchors.leftMargin: 12;
|
||||
anchors.leftMargin: 24;
|
||||
anchors.right: parent.right;
|
||||
height: paintedHeight;
|
||||
// Style
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 58 KiB |
|
@ -208,6 +208,7 @@ Rectangle {
|
|||
// able to click on a button/mouseArea underneath the popup/section.
|
||||
MouseArea {
|
||||
anchors.fill: parent;
|
||||
hoverEnabled: true;
|
||||
propagateComposedEvents: false;
|
||||
}
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@ import "../../../styles-uit"
|
|||
import "../../../controls-uit" as HifiControlsUit
|
||||
import "../../../controls" as HifiControls
|
||||
import "../wallet" as HifiWallet
|
||||
import TabletScriptingInterface 1.0
|
||||
|
||||
// references XXX from root context
|
||||
|
||||
|
@ -29,7 +30,6 @@ Item {
|
|||
id: root;
|
||||
property string purchaseStatus;
|
||||
property bool purchaseStatusChanged;
|
||||
property bool canRezCertifiedItems: false;
|
||||
property string itemName;
|
||||
property string itemId;
|
||||
property string itemPreviewImageUrl;
|
||||
|
@ -39,7 +39,15 @@ Item {
|
|||
property int itemEdition;
|
||||
property int numberSold;
|
||||
property int limitedRun;
|
||||
property bool isWearable;
|
||||
property string itemType;
|
||||
property var itemTypesArray: ["entity", "wearable", "contentSet", "app", "avatar"];
|
||||
property var buttonTextNormal: ["REZ", "WEAR", "REPLACE", "INSTALL", "WEAR"];
|
||||
property var buttonTextClicked: ["REZZED", "WORN", "REPLACED", "INSTALLED", "WORN"]
|
||||
property var buttonGlyph: [hifi.glyphs.wand, hifi.glyphs.hat, hifi.glyphs.globe, hifi.glyphs.install, hifi.glyphs.avatar];
|
||||
property bool showConfirmation: false;
|
||||
property bool hasPermissionToRezThis;
|
||||
property bool permissionExplanationCardVisible;
|
||||
property bool isInstalled;
|
||||
|
||||
property string originalStatusText;
|
||||
property string originalStatusColor;
|
||||
|
@ -47,6 +55,47 @@ Item {
|
|||
height: 110;
|
||||
width: parent.width;
|
||||
|
||||
Connections {
|
||||
target: Commerce;
|
||||
|
||||
onContentSetChanged: {
|
||||
if (contentSetHref === root.itemHref) {
|
||||
showConfirmation = true;
|
||||
}
|
||||
}
|
||||
|
||||
onAppInstalled: {
|
||||
if (appHref === root.itemHref) {
|
||||
root.isInstalled = true;
|
||||
}
|
||||
}
|
||||
|
||||
onAppUninstalled: {
|
||||
if (appHref === root.itemHref) {
|
||||
root.isInstalled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: MyAvatar;
|
||||
|
||||
onSkeletonModelURLChanged: {
|
||||
if (skeletonModelURL === root.itemHref) {
|
||||
showConfirmation = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onItemTypeChanged: {
|
||||
if ((itemType === "entity" && (!Entities.canRezCertified() && !Entities.canRezTmpCertified())) ||
|
||||
(itemType === "contentSet" && !Entities.canReplaceContent())) {
|
||||
root.hasPermissionToRezThis = false;
|
||||
} else {
|
||||
root.hasPermissionToRezThis = true;
|
||||
}
|
||||
}
|
||||
|
||||
onPurchaseStatusChangedChanged: {
|
||||
if (root.purchaseStatusChanged === true && root.purchaseStatus === "confirmed") {
|
||||
root.originalStatusText = statusText.text;
|
||||
|
@ -57,6 +106,15 @@ Item {
|
|||
}
|
||||
}
|
||||
|
||||
onShowConfirmationChanged: {
|
||||
if (root.showConfirmation) {
|
||||
rezzedNotifContainer.visible = true;
|
||||
rezzedNotifContainerTimer.start();
|
||||
UserActivityLogger.commerceEntityRezzed(root.itemId, "purchases", root.itemType);
|
||||
root.showConfirmation = false;
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: confirmedTimer;
|
||||
interval: 3000;
|
||||
|
@ -73,10 +131,10 @@ Item {
|
|||
color: hifi.colors.white;
|
||||
// Size
|
||||
anchors.left: parent.left;
|
||||
anchors.leftMargin: 8;
|
||||
anchors.leftMargin: 16;
|
||||
anchors.right: parent.right;
|
||||
anchors.rightMargin: 8;
|
||||
anchors.top: parent.top;
|
||||
anchors.rightMargin: 16;
|
||||
anchors.verticalCenter: parent.verticalCenter;
|
||||
height: root.height - 10;
|
||||
|
||||
Image {
|
||||
|
@ -96,15 +154,20 @@ Item {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
TextMetrics {
|
||||
id: itemNameTextMetrics;
|
||||
font: itemName.font;
|
||||
text: itemName.text;
|
||||
}
|
||||
RalewaySemiBold {
|
||||
id: itemName;
|
||||
anchors.top: itemPreviewImage.top;
|
||||
anchors.topMargin: 4;
|
||||
anchors.left: itemPreviewImage.right;
|
||||
anchors.leftMargin: 8;
|
||||
anchors.right: buttonContainer.left;
|
||||
anchors.rightMargin: 8;
|
||||
width: !noPermissionGlyph.visible ? (buttonContainer.x - itemPreviewImage.x - itemPreviewImage.width - anchors.leftMargin) :
|
||||
Math.min(itemNameTextMetrics.tightBoundingRect.width + 2,
|
||||
buttonContainer.x - itemPreviewImage.x - itemPreviewImage.width - anchors.leftMargin - noPermissionGlyph.width + 2);
|
||||
height: paintedHeight;
|
||||
// Text size
|
||||
size: 24;
|
||||
|
@ -130,6 +193,93 @@ Item {
|
|||
}
|
||||
}
|
||||
}
|
||||
HiFiGlyphs {
|
||||
id: noPermissionGlyph;
|
||||
visible: !root.hasPermissionToRezThis;
|
||||
anchors.verticalCenter: itemName.verticalCenter;
|
||||
anchors.left: itemName.right;
|
||||
anchors.leftMargin: itemName.truncated ? -10 : -2;
|
||||
text: hifi.glyphs.info;
|
||||
// Size
|
||||
size: 40;
|
||||
width: 32;
|
||||
// Style
|
||||
color: hifi.colors.redAccent;
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent;
|
||||
hoverEnabled: true;
|
||||
|
||||
onEntered: {
|
||||
noPermissionGlyph.color = hifi.colors.redHighlight;
|
||||
}
|
||||
onExited: {
|
||||
noPermissionGlyph.color = hifi.colors.redAccent;
|
||||
}
|
||||
onClicked: {
|
||||
root.sendToPurchases({ method: 'openPermissionExplanationCard' });
|
||||
}
|
||||
}
|
||||
}
|
||||
Rectangle {
|
||||
id: permissionExplanationCard;
|
||||
z: 995;
|
||||
visible: root.permissionExplanationCardVisible;
|
||||
anchors.fill: parent;
|
||||
color: hifi.colors.white;
|
||||
|
||||
RalewayRegular {
|
||||
id: permissionExplanationText;
|
||||
text: {
|
||||
if (root.itemType === "contentSet") {
|
||||
"You do not have 'Replace Content' permissions in this domain. <a href='#replaceContentPermission'>Learn more</a>";
|
||||
} else if (root.itemType === "entity") {
|
||||
"You do not have 'Rez Certified' permissions in this domain. <a href='#rezCertifiedPermission'>Learn more</a>";
|
||||
} else {
|
||||
""
|
||||
}
|
||||
}
|
||||
size: 16;
|
||||
anchors.left: parent.left;
|
||||
anchors.leftMargin: 30;
|
||||
anchors.top: parent.top;
|
||||
anchors.bottom: parent.bottom;
|
||||
anchors.right: permissionExplanationGlyph.left;
|
||||
color: hifi.colors.baseGray;
|
||||
wrapMode: Text.WordWrap;
|
||||
verticalAlignment: Text.AlignVCenter;
|
||||
|
||||
onLinkActivated: {
|
||||
sendToPurchases({method: 'showPermissionsExplanation', itemType: root.itemType});
|
||||
}
|
||||
}
|
||||
// "Close" button
|
||||
HiFiGlyphs {
|
||||
id: permissionExplanationGlyph;
|
||||
text: hifi.glyphs.close;
|
||||
color: hifi.colors.baseGray;
|
||||
size: 26;
|
||||
anchors.top: parent.top;
|
||||
anchors.bottom: parent.bottom;
|
||||
anchors.right: parent.right;
|
||||
width: 77;
|
||||
horizontalAlignment: Text.AlignHCenter;
|
||||
verticalAlignment: Text.AlignVCenter;
|
||||
MouseArea {
|
||||
anchors.fill: parent;
|
||||
hoverEnabled: true;
|
||||
onEntered: {
|
||||
parent.text = hifi.glyphs.closeInverted;
|
||||
}
|
||||
onExited: {
|
||||
parent.text = hifi.glyphs.close;
|
||||
}
|
||||
onClicked: {
|
||||
root.sendToPurchases({ method: 'openPermissionExplanationCard', closeAll: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: certificateContainer;
|
||||
|
@ -151,19 +301,19 @@ Item {
|
|||
anchors.bottom: parent.bottom;
|
||||
width: 32;
|
||||
// Style
|
||||
color: hifi.colors.lightGray;
|
||||
color: hifi.colors.black;
|
||||
}
|
||||
|
||||
RalewayRegular {
|
||||
id: viewCertificateText;
|
||||
text: "VIEW CERTIFICATE";
|
||||
size: 14;
|
||||
size: 13;
|
||||
anchors.left: certificateIcon.right;
|
||||
anchors.leftMargin: 4;
|
||||
anchors.top: parent.top;
|
||||
anchors.bottom: parent.bottom;
|
||||
anchors.right: parent.right;
|
||||
color: hifi.colors.lightGray;
|
||||
color: hifi.colors.black;
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
|
@ -173,13 +323,13 @@ Item {
|
|||
sendToPurchases({method: 'purchases_itemCertificateClicked', itemCertificateId: root.certificateId});
|
||||
}
|
||||
onEntered: {
|
||||
certificateIcon.color = hifi.colors.black;
|
||||
viewCertificateText.color = hifi.colors.black;
|
||||
}
|
||||
onExited: {
|
||||
certificateIcon.color = hifi.colors.lightGray;
|
||||
viewCertificateText.color = hifi.colors.lightGray;
|
||||
}
|
||||
onExited: {
|
||||
certificateIcon.color = hifi.colors.black;
|
||||
viewCertificateText.color = hifi.colors.black;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -193,14 +343,14 @@ Item {
|
|||
anchors.right: buttonContainer.left;
|
||||
anchors.rightMargin: 2;
|
||||
|
||||
FiraSansRegular {
|
||||
RalewayRegular {
|
||||
anchors.left: parent.left;
|
||||
anchors.top: parent.top;
|
||||
anchors.bottom: parent.bottom;
|
||||
width: paintedWidth;
|
||||
text: "#" + root.itemEdition;
|
||||
size: 15;
|
||||
color: "#cc6a6a6a";
|
||||
size: 13;
|
||||
color: hifi.colors.black;
|
||||
verticalAlignment: Text.AlignTop;
|
||||
}
|
||||
}
|
||||
|
@ -311,7 +461,7 @@ Item {
|
|||
id: rezzedNotifContainer;
|
||||
z: 998;
|
||||
visible: false;
|
||||
color: hifi.colors.blueHighlight;
|
||||
color: "#1FC6A6";
|
||||
anchors.fill: buttonContainer;
|
||||
MouseArea {
|
||||
anchors.fill: parent;
|
||||
|
@ -321,8 +471,8 @@ Item {
|
|||
|
||||
RalewayBold {
|
||||
anchors.fill: parent;
|
||||
text: "REZZED";
|
||||
size: 18;
|
||||
text: (root.buttonTextClicked)[itemTypesArray.indexOf(root.itemType)];
|
||||
size: 15;
|
||||
color: hifi.colors.white;
|
||||
verticalAlignment: Text.AlignVCenter;
|
||||
horizontalAlignment: Text.AlignHCenter;
|
||||
|
@ -335,27 +485,89 @@ Item {
|
|||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: appButtonContainer;
|
||||
color: hifi.colors.white;
|
||||
z: 994;
|
||||
visible: root.isInstalled;
|
||||
anchors.fill: buttonContainer;
|
||||
|
||||
HifiControlsUit.Button {
|
||||
id: openAppButton;
|
||||
color: hifi.buttons.blue;
|
||||
colorScheme: hifi.colorSchemes.light;
|
||||
anchors.top: parent.top;
|
||||
anchors.right: parent.right;
|
||||
anchors.left: parent.left;
|
||||
width: 92;
|
||||
height: 44;
|
||||
text: "OPEN"
|
||||
onClicked: {
|
||||
Commerce.openApp(root.itemHref);
|
||||
}
|
||||
}
|
||||
|
||||
HifiControlsUit.Button {
|
||||
id: uninstallAppButton;
|
||||
color: hifi.buttons.noneBorderless;
|
||||
colorScheme: hifi.colorSchemes.light;
|
||||
anchors.bottom: parent.bottom;
|
||||
anchors.right: parent.right;
|
||||
anchors.left: parent.left;
|
||||
height: 44;
|
||||
text: "UNINSTALL"
|
||||
onClicked: {
|
||||
Commerce.uninstallApp(root.itemHref);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
id: buttonContainer;
|
||||
property int color: hifi.buttons.red;
|
||||
property int color: hifi.buttons.blue;
|
||||
property int colorScheme: hifi.colorSchemes.light;
|
||||
|
||||
anchors.top: parent.top;
|
||||
anchors.topMargin: 4;
|
||||
anchors.bottom: parent.bottom;
|
||||
anchors.bottomMargin: 4;
|
||||
anchors.right: parent.right;
|
||||
anchors.rightMargin: 4;
|
||||
width: height;
|
||||
enabled: (root.canRezCertifiedItems || root.isWearable) && root.purchaseStatus !== "invalidated";
|
||||
enabled: root.hasPermissionToRezThis &&
|
||||
root.purchaseStatus !== "invalidated" &&
|
||||
MyAvatar.skeletonModelURL !== root.itemHref;
|
||||
|
||||
onHoveredChanged: {
|
||||
if (hovered) {
|
||||
Tablet.playSound(TabletEnums.ButtonHover);
|
||||
}
|
||||
}
|
||||
|
||||
onFocusChanged: {
|
||||
if (focus) {
|
||||
Tablet.playSound(TabletEnums.ButtonHover);
|
||||
}
|
||||
}
|
||||
|
||||
onClicked: {
|
||||
sendToPurchases({method: 'purchases_rezClicked', itemHref: root.itemHref, isWearable: root.isWearable});
|
||||
rezzedNotifContainer.visible = true;
|
||||
rezzedNotifContainerTimer.start();
|
||||
UserActivityLogger.commerceEntityRezzed(root.itemId, "purchases", root.isWearable ? "rez" : "wear");
|
||||
Tablet.playSound(TabletEnums.ButtonClick);
|
||||
if (root.itemType === "contentSet") {
|
||||
sendToPurchases({method: 'showReplaceContentLightbox', itemHref: root.itemHref});
|
||||
} else if (root.itemType === "avatar") {
|
||||
sendToPurchases({method: 'showChangeAvatarLightbox', itemName: root.itemName, itemHref: root.itemHref});
|
||||
} else if (root.itemType === "app") {
|
||||
// "Run" and "Uninstall" buttons are separate.
|
||||
Commerce.installApp(root.itemHref);
|
||||
} else {
|
||||
sendToPurchases({method: 'purchases_rezClicked', itemHref: root.itemHref, itemType: root.itemType});
|
||||
root.showConfirmation = true;
|
||||
}
|
||||
}
|
||||
|
||||
style: ButtonStyle {
|
||||
|
||||
background: Rectangle {
|
||||
radius: 4;
|
||||
gradient: Gradient {
|
||||
GradientStop {
|
||||
position: 0.2
|
||||
|
@ -390,13 +602,13 @@ Item {
|
|||
|
||||
label: Item {
|
||||
HiFiGlyphs {
|
||||
id: lightningIcon;
|
||||
text: hifi.glyphs.lightning;
|
||||
id: rezIcon;
|
||||
text: (root.buttonGlyph)[itemTypesArray.indexOf(root.itemType)];
|
||||
// Size
|
||||
size: 32;
|
||||
size: 60;
|
||||
// Anchors
|
||||
anchors.top: parent.top;
|
||||
anchors.topMargin: 12;
|
||||
anchors.topMargin: 0;
|
||||
anchors.left: parent.left;
|
||||
anchors.right: parent.right;
|
||||
horizontalAlignment: Text.AlignHCenter;
|
||||
|
@ -405,18 +617,19 @@ Item {
|
|||
: hifi.buttons.disabledTextColor[control.colorScheme]
|
||||
}
|
||||
RalewayBold {
|
||||
anchors.top: lightningIcon.bottom;
|
||||
anchors.topMargin: -20;
|
||||
id: rezIconLabel;
|
||||
anchors.top: rezIcon.bottom;
|
||||
anchors.topMargin: -4;
|
||||
anchors.right: parent.right;
|
||||
anchors.left: parent.left;
|
||||
anchors.bottom: parent.bottom;
|
||||
font.capitalization: Font.AllUppercase
|
||||
color: enabled ? hifi.buttons.textColor[control.color]
|
||||
: hifi.buttons.disabledTextColor[control.colorScheme]
|
||||
size: 16;
|
||||
size: 15;
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: root.isWearable ? "Wear It" : "Rez It"
|
||||
text: MyAvatar.skeletonModelURL === root.itemHref ? "CURRENT" : (root.buttonTextNormal)[itemTypesArray.indexOf(root.itemType)];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -425,11 +638,11 @@ Item {
|
|||
|
||||
DropShadow {
|
||||
anchors.fill: mainContainer;
|
||||
horizontalOffset: 3;
|
||||
verticalOffset: 3;
|
||||
radius: 8.0;
|
||||
samples: 17;
|
||||
color: "#80000000";
|
||||
horizontalOffset: 0;
|
||||
verticalOffset: 4;
|
||||
radius: 4.0;
|
||||
samples: 9
|
||||
color: Qt.rgba(0, 0, 0, 0.25);
|
||||
source: mainContainer;
|
||||
}
|
||||
|
||||
|
|
|
@ -32,11 +32,11 @@ Rectangle {
|
|||
property bool securityImageResultReceived: false;
|
||||
property bool purchasesReceived: false;
|
||||
property bool punctuationMode: false;
|
||||
property bool canRezCertifiedItems: Entities.canRezCertified() || Entities.canRezTmpCertified();
|
||||
property bool pendingInventoryReply: true;
|
||||
property bool isShowingMyItems: false;
|
||||
property bool isDebuggingFirstUseTutorial: false;
|
||||
property int pendingItemCount: 0;
|
||||
property string installedApps;
|
||||
// Style
|
||||
color: hifi.colors.white;
|
||||
Connections {
|
||||
|
@ -47,21 +47,22 @@ Rectangle {
|
|||
if (root.activeView !== "needsLogIn") {
|
||||
root.activeView = "needsLogIn";
|
||||
}
|
||||
} else if (walletStatus === 1) {
|
||||
} else if ((walletStatus === 1) || (walletStatus === 2) || (walletStatus === 3)) {
|
||||
if (root.activeView !== "notSetUp") {
|
||||
root.activeView = "notSetUp";
|
||||
notSetUpTimer.start();
|
||||
}
|
||||
} else if (walletStatus === 2) {
|
||||
} else if (walletStatus === 4) {
|
||||
if (root.activeView !== "passphraseModal") {
|
||||
root.activeView = "passphraseModal";
|
||||
UserActivityLogger.commercePassphraseEntry("marketplace purchases");
|
||||
}
|
||||
} else if (walletStatus === 3) {
|
||||
} else if (walletStatus === 5) {
|
||||
if ((Settings.getValue("isFirstUseOfPurchases", true) || root.isDebuggingFirstUseTutorial) && root.activeView !== "firstUseTutorial") {
|
||||
root.activeView = "firstUseTutorial";
|
||||
} else if (!Settings.getValue("isFirstUseOfPurchases", true) && root.activeView === "initialize") {
|
||||
root.activeView = "purchasesMain";
|
||||
root.installedApps = Commerce.getInstalledApps();
|
||||
Commerce.inventory();
|
||||
}
|
||||
} else {
|
||||
|
@ -148,7 +149,11 @@ Rectangle {
|
|||
|
||||
Connections {
|
||||
onSendToParent: {
|
||||
sendToScript(msg);
|
||||
if (msg.method === 'commerceLightboxLinkClicked') {
|
||||
Qt.openUrlExternally(msg.linkUrl);
|
||||
} else {
|
||||
sendToScript(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -266,6 +271,7 @@ Rectangle {
|
|||
case 'tutorial_finished':
|
||||
Settings.setValue("isFirstUseOfPurchases", false);
|
||||
root.activeView = "purchasesMain";
|
||||
root.installedApps = Commerce.getInstalledApps();
|
||||
Commerce.inventory();
|
||||
break;
|
||||
}
|
||||
|
@ -297,7 +303,7 @@ Rectangle {
|
|||
anchors.left: parent.left;
|
||||
anchors.leftMargin: 8;
|
||||
anchors.right: parent.right;
|
||||
anchors.rightMargin: 12;
|
||||
anchors.rightMargin: 16;
|
||||
anchors.top: parent.top;
|
||||
anchors.topMargin: 4;
|
||||
|
||||
|
@ -308,11 +314,11 @@ Rectangle {
|
|||
anchors.bottom: parent.bottom;
|
||||
anchors.bottomMargin: 10;
|
||||
anchors.left: parent.left;
|
||||
anchors.leftMargin: 4;
|
||||
anchors.leftMargin: 16;
|
||||
width: paintedWidth;
|
||||
text: isShowingMyItems ? "My Items" : "My Purchases";
|
||||
color: hifi.colors.baseGray;
|
||||
size: 28;
|
||||
color: hifi.colors.black;
|
||||
size: 22;
|
||||
}
|
||||
|
||||
HifiControlsUit.TextField {
|
||||
|
@ -323,8 +329,8 @@ Rectangle {
|
|||
hasRoundedBorder: true;
|
||||
anchors.left: myText.right;
|
||||
anchors.leftMargin: 16;
|
||||
anchors.top: parent.top;
|
||||
anchors.bottom: parent.bottom;
|
||||
height: 39;
|
||||
anchors.verticalCenter: parent.verticalCenter;
|
||||
anchors.right: parent.right;
|
||||
placeholderText: "filter items";
|
||||
|
||||
|
@ -345,7 +351,7 @@ Rectangle {
|
|||
|
||||
HifiControlsUit.Separator {
|
||||
id: separator;
|
||||
colorScheme: 1;
|
||||
colorScheme: 2;
|
||||
anchors.left: parent.left;
|
||||
anchors.right: parent.right;
|
||||
anchors.top: filterBarContainer.bottom;
|
||||
|
@ -365,69 +371,6 @@ Rectangle {
|
|||
id: filteredPurchasesModel;
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: cantRezCertified;
|
||||
visible: !root.canRezCertifiedItems;
|
||||
color: "#FFC3CD";
|
||||
radius: 4;
|
||||
border.color: hifi.colors.redAccent;
|
||||
border.width: 1;
|
||||
anchors.top: separator.bottom;
|
||||
anchors.topMargin: 12;
|
||||
anchors.left: parent.left;
|
||||
anchors.leftMargin: 16;
|
||||
anchors.right: parent.right;
|
||||
anchors.rightMargin: 16;
|
||||
height: 80;
|
||||
|
||||
HiFiGlyphs {
|
||||
id: lightningIcon;
|
||||
text: hifi.glyphs.lightning;
|
||||
// Size
|
||||
size: 36;
|
||||
// Anchors
|
||||
anchors.top: parent.top;
|
||||
anchors.topMargin: 18;
|
||||
anchors.left: parent.left;
|
||||
anchors.leftMargin: 12;
|
||||
horizontalAlignment: Text.AlignHCenter;
|
||||
// Style
|
||||
color: hifi.colors.lightGray;
|
||||
}
|
||||
|
||||
RalewayRegular {
|
||||
text: "You don't have permission to rez certified items in this domain. " +
|
||||
'<b><font color="' + hifi.colors.blueAccent + '"><a href="#">Learn More</a></font></b>';
|
||||
// Text size
|
||||
size: 18;
|
||||
// Anchors
|
||||
anchors.top: parent.top;
|
||||
anchors.topMargin: 4;
|
||||
anchors.left: lightningIcon.right;
|
||||
anchors.leftMargin: 8;
|
||||
anchors.right: parent.right;
|
||||
anchors.rightMargin: 8;
|
||||
anchors.bottom: parent.bottom;
|
||||
anchors.bottomMargin: 4;
|
||||
// Style
|
||||
color: hifi.colors.baseGray;
|
||||
wrapMode: Text.WordWrap;
|
||||
// Alignment
|
||||
verticalAlignment: Text.AlignVCenter;
|
||||
|
||||
onLinkActivated: {
|
||||
lightboxPopup.titleText = "Rez Permission Required";
|
||||
lightboxPopup.bodyText = "You don't have permission to rez certified items in this domain.<br><br>" +
|
||||
"Use the <b>GOTO app</b> to visit another domain or <b>go to your own sandbox.</b>";
|
||||
lightboxPopup.button1text = "CLOSE";
|
||||
lightboxPopup.button1method = "root.visible = false;"
|
||||
lightboxPopup.button2text = "OPEN GOTO";
|
||||
lightboxPopup.button2method = "sendToParent({method: 'purchases_openGoTo'});";
|
||||
lightboxPopup.visible = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: purchasesContentsList;
|
||||
visible: (root.isShowingMyItems && filteredPurchasesModel.count !== 0) || (!root.isShowingMyItems && filteredPurchasesModel.count !== 0);
|
||||
|
@ -436,13 +379,12 @@ Rectangle {
|
|||
snapMode: ListView.SnapToItem;
|
||||
highlightRangeMode: ListView.StrictlyEnforceRange;
|
||||
// Anchors
|
||||
anchors.top: root.canRezCertifiedItems ? separator.bottom : cantRezCertified.bottom;
|
||||
anchors.top: separator.bottom;
|
||||
anchors.topMargin: 12;
|
||||
anchors.left: parent.left;
|
||||
anchors.bottom: parent.bottom;
|
||||
width: parent.width;
|
||||
delegate: PurchasedItem {
|
||||
canRezCertifiedItems: root.canRezCertifiedItems;
|
||||
itemName: title;
|
||||
itemId: id;
|
||||
itemPreviewImageUrl: preview;
|
||||
|
@ -454,16 +396,32 @@ Rectangle {
|
|||
numberSold: model.number_sold;
|
||||
limitedRun: model.limited_run;
|
||||
displayedItemCount: model.displayedItemCount;
|
||||
isWearable: model.categories.indexOf("Wearables") > -1;
|
||||
anchors.topMargin: 12;
|
||||
anchors.bottomMargin: 12;
|
||||
permissionExplanationCardVisible: model.permissionExplanationCardVisible;
|
||||
isInstalled: model.isInstalled;
|
||||
itemType: {
|
||||
if (model.root_file_url.indexOf(".fst") > -1) {
|
||||
"avatar";
|
||||
} else if (model.categories.indexOf("Wearables") > -1) {
|
||||
"wearable";
|
||||
} else if (model.root_file_url.endsWith('.json.gz')) {
|
||||
"contentSet";
|
||||
} else if (model.root_file_url.endsWith('.app.json')) {
|
||||
"app";
|
||||
} else if (model.root_file_url.endsWith('.json')) {
|
||||
"entity";
|
||||
} else {
|
||||
"unknown";
|
||||
}
|
||||
}
|
||||
anchors.topMargin: 10;
|
||||
anchors.bottomMargin: 10;
|
||||
|
||||
Connections {
|
||||
onSendToPurchases: {
|
||||
if (msg.method === 'purchases_itemInfoClicked') {
|
||||
sendToScript({method: 'purchases_itemInfoClicked', itemId: itemId});
|
||||
} else if (msg.method === "purchases_rezClicked") {
|
||||
sendToScript({method: 'purchases_rezClicked', itemHref: itemHref, isWearable: isWearable});
|
||||
sendToScript({method: 'purchases_rezClicked', itemHref: itemHref, itemType: itemType});
|
||||
} else if (msg.method === 'purchases_itemCertificateClicked') {
|
||||
inspectionCertificate.visible = true;
|
||||
inspectionCertificate.isLightbox = true;
|
||||
|
@ -482,8 +440,51 @@ Rectangle {
|
|||
lightboxPopup.button1text = "CLOSE";
|
||||
lightboxPopup.button1method = "root.visible = false;"
|
||||
lightboxPopup.visible = true;
|
||||
} else if (msg.method === "showReplaceContentLightbox") {
|
||||
lightboxPopup.titleText = "Replace Content";
|
||||
lightboxPopup.bodyText = "Rezzing this content set will replace the existing environment and all of the items in this domain. " +
|
||||
"If you want to save the state of the content in this domain, create a backup before proceeding.<br><br>" +
|
||||
"For more information about backing up and restoring content, " +
|
||||
"<a href='https://docs.highfidelity.com/create-and-explore/start-working-in-your-sandbox/restoring-sandbox-content'>" +
|
||||
"click here to open info on your desktop browser.";
|
||||
lightboxPopup.button1text = "CANCEL";
|
||||
lightboxPopup.button1method = "root.visible = false;"
|
||||
lightboxPopup.button2text = "CONFIRM";
|
||||
lightboxPopup.button2method = "Commerce.replaceContentSet('" + msg.itemHref + "'); root.visible = false;";
|
||||
lightboxPopup.visible = true;
|
||||
} else if (msg.method === "showChangeAvatarLightbox") {
|
||||
lightboxPopup.titleText = "Change Avatar";
|
||||
lightboxPopup.bodyText = "This will change your current avatar to " + msg.itemName + " while retaining your wearables.";
|
||||
lightboxPopup.button1text = "CANCEL";
|
||||
lightboxPopup.button1method = "root.visible = false;"
|
||||
lightboxPopup.button2text = "CONFIRM";
|
||||
lightboxPopup.button2method = "MyAvatar.useFullAvatarURL('" + msg.itemHref + "'); root.visible = false;";
|
||||
lightboxPopup.visible = true;
|
||||
} else if (msg.method === "showPermissionsExplanation") {
|
||||
if (msg.itemType === "entity") {
|
||||
lightboxPopup.titleText = "Rez Certified Permission";
|
||||
lightboxPopup.bodyText = "You don't have permission to rez certified items in this domain.<br><br>" +
|
||||
"Use the <b>GOTO app</b> to visit another domain or <b>go to your own sandbox.</b>";
|
||||
lightboxPopup.button2text = "OPEN GOTO";
|
||||
lightboxPopup.button2method = "sendToParent({method: 'purchases_openGoTo'});";
|
||||
} else if (msg.itemType === "contentSet") {
|
||||
lightboxPopup.titleText = "Replace Content Permission";
|
||||
lightboxPopup.bodyText = "You do not have the permission 'Replace Content' in this <b>domain's server settings</b>. The domain owner " +
|
||||
"must enable it for you before you can replace content sets in this domain.";
|
||||
}
|
||||
lightboxPopup.button1text = "CLOSE";
|
||||
lightboxPopup.button1method = "root.visible = false;"
|
||||
lightboxPopup.visible = true;
|
||||
} else if (msg.method === "setFilterText") {
|
||||
filterBar.text = msg.filterText;
|
||||
} else if (msg.method === "openPermissionExplanationCard") {
|
||||
for (var i = 0; i < filteredPurchasesModel.count; i++) {
|
||||
if (i !== index || msg.closeAll) {
|
||||
filteredPurchasesModel.setProperty(i, "permissionExplanationCardVisible", false);
|
||||
} else {
|
||||
filteredPurchasesModel.setProperty(i, "permissionExplanationCardVisible", true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -683,8 +684,13 @@ Rectangle {
|
|||
|
||||
if (sameItemCount !== tempPurchasesModel.count || filterBar.text !== filterBar.previousText) {
|
||||
filteredPurchasesModel.clear();
|
||||
var currentId;
|
||||
for (var i = 0; i < tempPurchasesModel.count; i++) {
|
||||
currentId = tempPurchasesModel.get(i).id;
|
||||
|
||||
filteredPurchasesModel.append(tempPurchasesModel.get(i));
|
||||
filteredPurchasesModel.setProperty(i, 'permissionExplanationCardVisible', false);
|
||||
filteredPurchasesModel.setProperty(i, 'isInstalled', ((root.installedApps).indexOf(currentId) > -1));
|
||||
}
|
||||
|
||||
populateDisplayedItemCounts();
|
||||
|
|
|
@ -60,48 +60,88 @@ Item {
|
|||
|
||||
ListElement {
|
||||
isExpanded: false;
|
||||
question: "How can I get HFC?"
|
||||
answer: qsTr("High Fidelity commerce is in closed beta right now.<br><br>To request entry and get free HFC, <b>please contact info@highfidelity.com with your High Fidelity account username and the email address registered to that account.</b>");
|
||||
question: "How can I get HFC?";
|
||||
answer: "High Fidelity commerce is in open beta right now. Want more HFC? \
|
||||
Get it by going to <br><br><b><font color='#0093C5'><a href='#bank'>BankOfHighFidelity.</a></font></b> and meeting with the banker!";
|
||||
}
|
||||
ListElement {
|
||||
isExpanded: false;
|
||||
question: "What are private keys?"
|
||||
answer: qsTr("A private key is a secret piece of text that is used to prove ownership, unlock confidential information, and sign transactions.<br><br>In High Fidelity, <b>your private keys are used to securely access the contents of your Wallet and Purchases.</b>");
|
||||
question: "What are private keys and where are they stored?";
|
||||
answer:
|
||||
"A private key is a secret piece of text that is used to prove ownership, unlock confidential information, and sign transactions. \
|
||||
In High Fidelity, your private key is used to securely access the contents of your Wallet and Purchases. \
|
||||
After wallet setup, a hifikey file is stored on your computer in High Fidelity Interface's AppData directory. \
|
||||
Your hifikey file contains your private key and is protected by your wallet passphrase. \
|
||||
<br><br>It is very important to back up your hifikey file! \
|
||||
<b><font color='#0093C5'><a href='#privateKeyPath'>Tap here to open the folder where your HifiKeys are stored on your main display.</a></font></b>"
|
||||
}
|
||||
ListElement {
|
||||
isExpanded: false;
|
||||
question: "Where are my private keys stored?"
|
||||
answer: qsTr('By default, your private keys are <b>only stored on your hard drive</b> in High Fidelity Interface\'s AppData directory.<br><br><b><font color="#0093C5"><a href="#privateKeyPath">Tap here to open the folder where your HifiKeys are stored on your main display.</a></font></b>');
|
||||
question: "How do I back up my private keys?";
|
||||
answer: "You can back up your hifikey file (which contains your private key and is encrypted using your wallet passphrase) by copying it to a USB flash drive, or to a service like Dropbox or Google Drive. \
|
||||
Restore your hifikey file by replacing the file in Interface's AppData directory with your backup copy. \
|
||||
Others with access to your back up should not be able to spend your HFC without your passphrase. \
|
||||
<b><font color='#0093C5'><a href='#privateKeyPath'>Tap here to open the folder where your HifiKeys are stored on your main display.</a></font></b>";
|
||||
}
|
||||
ListElement {
|
||||
isExpanded: false;
|
||||
question: "How can I backup my private keys?"
|
||||
answer: qsTr('You may backup the file containing your private keys by copying it to a USB flash drive, or to a service like Dropbox or Google Drive.<br><br>Restore your backup by replacing the file in Interface\'s AppData directory with your backed-up copy.<br><br><b><font color="#0093C5"><a href="#privateKeyPath">Tap here to open the folder where your HifiKeys are stored on your main display.</a></font></b>');
|
||||
question: "What happens if I lose my private keys?";
|
||||
answer: "We cannot stress enough that you should keep a backup! For security reasons, High Fidelity does not keep a copy, and cannot restore it for you. \
|
||||
If you lose your private key, you will no longer have access to the contents of your Wallet or My Purchases. \
|
||||
Here are some things to try:<ul>\
|
||||
<li>If you have backed up your hifikey file before, search your backup location</li>\
|
||||
<li>Search your AppData directory in the last machine you used to set up the Wallet</li>\
|
||||
<li>If you are a developer and have installed multiple builds of High Fidelity, your hifikey file might be in another folder</li>\
|
||||
</ul><br><br>As a last resort, you can set up your Wallet again and generate a new hifikey file. \
|
||||
Unfortunately, this means you will start with 0 HFC and your purchased items will not be transferred over.";
|
||||
}
|
||||
ListElement {
|
||||
isExpanded: false;
|
||||
question: "What happens if I lose my passphrase?"
|
||||
answer: qsTr("Your passphrase is used to encrypt your private keys. If you lose your passphrase, you will no longer be able to decrypt your private key file. You will also no longer have access to the contents of your Wallet or My Purchases.<br><br><b>Nobody can help you recover your passphrase, including High Fidelity.</b> Please write it down and store it securely.");
|
||||
question: "What if I forget my wallet passphrase?";
|
||||
answer: "Your wallet passphrase is used to encrypt your private keys. Please write it down and store it securely! \
|
||||
<br><br>If you forget your passphrase, you will no longer be able to decrypt the hifikey file that the passphrase protects. \
|
||||
You will also no longer have access to the contents of your Wallet or My Purchases. \
|
||||
For security reasons, High Fidelity does not keep a copy of your passphrase, and can't restore it for you. \
|
||||
<br><br>If you still cannot remember your wallet passphrase, you can set up your Wallet again and generate a new hifikey file. \
|
||||
Unfortunately, this means you will start with 0 HFC and your purchased items will not be transferred over.";
|
||||
}
|
||||
ListElement {
|
||||
isExpanded: false;
|
||||
question: "What is a 'Security Pic'?"
|
||||
answer: qsTr("Your Security Pic is an encrypted image that you selected during Wallet Setup. <b>It acts as an extra layer of Wallet security.</b><br><br>When you see your Security Pic, you know that your actions and data are securely making use of your private keys.<br><br><b>If you don't see your Security Pic on a page that is asking you for your Wallet passphrase, someone untrustworthy may be trying to gain access to your Wallet.</b><br><br>The encrypted Pic is stored on your hard drive inside the same file as your private keys.");
|
||||
question: "How do I send HFC to other people?";
|
||||
answer: "You can send HFC to a High Fidelity connection (someone you've shaken hands with in-world) or somebody Nearby (currently in the same domain as you). \
|
||||
In your Wallet's Send Money tab, choose from your list of connections, or choose Nearby and select the glowing sphere of the person's avatar.";
|
||||
}
|
||||
ListElement {
|
||||
isExpanded: false;
|
||||
question: "My HFC balance isn't what I expect it to be. Why?"
|
||||
answer: qsTr('High Fidelity Coin (HFC) transactions are backed by a <b>blockchain</b>, which takes time to update. The status of a transaction usually updates within a few seconds.<br><br><b><font color="#0093C5"><a href="#blockchain">Tap here to learn more about the blockchain.</a></font></b>');
|
||||
question: "What is a Security Pic?"
|
||||
answer: "Your Security Pic is an encrypted image that you select during Wallet Setup. \
|
||||
It acts as an extra layer of Wallet security. \
|
||||
When you see your Security Pic, you know that your actions and data are securely making use of your private keys.\
|
||||
<br><br>Don't enter your passphrase anywhere that doesn't display your Security Pic! \
|
||||
If you don't see your Security Pic on a page that requests your Wallet passphrase, someone untrustworthy may be trying to access your Wallet.";
|
||||
}
|
||||
ListElement {
|
||||
isExpanded: false;
|
||||
question: "Do I get charged money if a transaction fails?"
|
||||
answer: qsTr("<b>No.</b> Your HFC balance only changes after a transaction is confirmed.");
|
||||
question: "Why does my HFC balance not update instantly?";
|
||||
answer: "HFC transations sometimes takes a few seconds to update as they are backed by a blockchain. \
|
||||
<br><br><b><font color='#0093C5'><a href='#blockchain'>Tap here to learn more about the blockchain.</a></font></b>";
|
||||
}
|
||||
ListElement {
|
||||
isExpanded: false;
|
||||
question: "Do I get charged money if a transaction fails?";
|
||||
answer: "<b>No.</b> Your HFC balance only changes after a transaction is confirmed.";
|
||||
}
|
||||
ListElement {
|
||||
isExpanded: false;
|
||||
question: "How do I convert HFC to other currencies?"
|
||||
answer: qsTr("We are still building the tools needed to support a vibrant economy in High Fidelity. <b>There is currently no way to convert HFC to other currencies.</b>");
|
||||
answer: "We are hard at work building the tools needed to support a vibrant economy in High Fidelity. \
|
||||
At the moment, there is currently no way to convert HFC to other currencies. Stay tuned...";
|
||||
}
|
||||
ListElement {
|
||||
isExpanded: false;
|
||||
question: "Who can I reach out to with questions?";
|
||||
answer: "Please email us if you have any issues or questions: \
|
||||
<b><font color='#0093C5'><a href='#support'>support@highfidelity.com</a></font></b>";
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -212,6 +252,10 @@ Item {
|
|||
Qt.openUrlExternally("file:///" + root.keyFilePath.substring(0, root.keyFilePath.lastIndexOf('/')));
|
||||
} else if (link === "#blockchain") {
|
||||
Qt.openUrlExternally("https://docs.highfidelity.com/high-fidelity-commerce");
|
||||
} else if (link === "#bank") {
|
||||
Qt.openUrlExternally("hifi://BankOfHighFidelity");
|
||||
} else if (link === "#support") {
|
||||
Qt.openUrlExternally("mailto:support@highfidelity.com");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -75,8 +75,6 @@ Item {
|
|||
// TODO: Fix this unlikely bug
|
||||
onVisibleChanged: {
|
||||
if (visible) {
|
||||
passphraseField.error = false;
|
||||
passphraseField.focus = true;
|
||||
sendSignalToParent({method: 'disableHmdPreview'});
|
||||
} else {
|
||||
sendSignalToParent({method: 'maybeEnableHmdPreview'});
|
||||
|
@ -206,6 +204,14 @@ Item {
|
|||
placeholderText: "passphrase";
|
||||
activeFocusOnPress: true;
|
||||
activeFocusOnTab: true;
|
||||
|
||||
onVisibleChanged: {
|
||||
if (visible) {
|
||||
error = false;
|
||||
focus = true;
|
||||
forceActiveFocus();
|
||||
}
|
||||
}
|
||||
|
||||
onFocusChanged: {
|
||||
root.keyboardRaised = focus;
|
||||
|
|
|
@ -47,20 +47,22 @@ Rectangle {
|
|||
}
|
||||
} else if (walletStatus === 1) {
|
||||
if (root.activeView !== "walletSetup") {
|
||||
root.activeView = "walletSetup";
|
||||
Commerce.resetLocalWalletOnly();
|
||||
var timestamp = new Date();
|
||||
walletSetup.startingTimestamp = timestamp;
|
||||
walletSetup.setupAttemptID = generateUUID();
|
||||
UserActivityLogger.commerceWalletSetupStarted(timestamp, setupAttemptID, walletSetup.setupFlowVersion, walletSetup.referrer ? walletSetup.referrer : "wallet app",
|
||||
(AddressManager.placename || AddressManager.hostname || '') + (AddressManager.pathname ? AddressManager.pathname.match(/\/[^\/]+/)[0] : ''));
|
||||
walletResetSetup();
|
||||
}
|
||||
} else if (walletStatus === 2) {
|
||||
if (root.activeView != "preexisting") {
|
||||
root.activeView = "preexisting";
|
||||
}
|
||||
} else if (walletStatus === 3) {
|
||||
if (root.activeView != "conflicting") {
|
||||
root.activeView = "conflicting";
|
||||
}
|
||||
} else if (walletStatus === 4) {
|
||||
if (root.activeView !== "passphraseModal") {
|
||||
root.activeView = "passphraseModal";
|
||||
UserActivityLogger.commercePassphraseEntry("wallet app");
|
||||
}
|
||||
} else if (walletStatus === 3) {
|
||||
} else if (walletStatus === 5) {
|
||||
if (root.activeView !== "walletSetup") {
|
||||
root.activeView = "walletHome";
|
||||
Commerce.getSecurityImage();
|
||||
|
@ -169,6 +171,25 @@ Rectangle {
|
|||
// TITLE BAR END
|
||||
//
|
||||
|
||||
WalletChoice {
|
||||
id: walletChoice;
|
||||
proceedFunction: function (isReset) {
|
||||
console.log(isReset ? "Reset wallet." : "Trying again with new wallet.");
|
||||
Commerce.setSoftReset();
|
||||
if (isReset) {
|
||||
walletResetSetup();
|
||||
} else {
|
||||
var msg = { referrer: walletChoice.referrer }
|
||||
followReferrer(msg);
|
||||
}
|
||||
}
|
||||
copyFunction: Commerce.copyKeyFileFrom;
|
||||
z: 997;
|
||||
visible: (root.activeView === "preexisting") || (root.activeView === "conflicting");
|
||||
activeView: root.activeView;
|
||||
anchors.fill: parent;
|
||||
}
|
||||
|
||||
WalletSetup {
|
||||
id: walletSetup;
|
||||
visible: root.activeView === "walletSetup";
|
||||
|
@ -178,14 +199,7 @@ Rectangle {
|
|||
Connections {
|
||||
onSendSignalToWallet: {
|
||||
if (msg.method === 'walletSetup_finished') {
|
||||
if (msg.referrer === '' || msg.referrer === 'marketplace cta') {
|
||||
root.activeView = "initialize";
|
||||
Commerce.getWalletStatus();
|
||||
} else if (msg.referrer === 'purchases') {
|
||||
sendToScript({method: 'goToPurchases'});
|
||||
} else {
|
||||
sendToScript({method: 'goToMarketplaceItemPage', itemId: msg.referrer});
|
||||
}
|
||||
followReferrer(msg);
|
||||
} else if (msg.method === 'walletSetup_raiseKeyboard') {
|
||||
root.keyboardRaised = true;
|
||||
root.isPassword = msg.isPasswordField;
|
||||
|
@ -738,6 +752,7 @@ Rectangle {
|
|||
switch (message.method) {
|
||||
case 'updateWalletReferrer':
|
||||
walletSetup.referrer = message.referrer;
|
||||
walletChoice.referrer = message.referrer;
|
||||
break;
|
||||
case 'inspectionCertificate_resetCert':
|
||||
// NOP
|
||||
|
@ -768,6 +783,28 @@ Rectangle {
|
|||
return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16);
|
||||
});
|
||||
}
|
||||
|
||||
function walletResetSetup() {
|
||||
root.activeView = "walletSetup";
|
||||
var timestamp = new Date();
|
||||
walletSetup.startingTimestamp = timestamp;
|
||||
walletSetup.setupAttemptID = generateUUID();
|
||||
UserActivityLogger.commerceWalletSetupStarted(timestamp, walletSetup.setupAttemptID, walletSetup.setupFlowVersion, walletSetup.referrer ? walletSetup.referrer : "wallet app",
|
||||
(AddressManager.placename || AddressManager.hostname || '') + (AddressManager.pathname ? AddressManager.pathname.match(/\/[^\/]+/)[0] : ''));
|
||||
}
|
||||
|
||||
function followReferrer(msg) {
|
||||
if (msg.referrer === '' || msg.referrer === 'marketplace cta') {
|
||||
root.activeView = "initialize";
|
||||
Commerce.getWalletStatus();
|
||||
} else if (msg.referrer === 'purchases') {
|
||||
sendToScript({method: 'goToPurchases'});
|
||||
} else if (msg.referrer === 'marketplace cta' || msg.referrer === 'mainPage') {
|
||||
sendToScript({method: 'goToMarketplaceMainPage', itemId: msg.referrer});
|
||||
} else {
|
||||
sendToScript({method: 'goToMarketplaceItemPage', itemId: msg.referrer});
|
||||
}
|
||||
}
|
||||
//
|
||||
// FUNCTION DEFINITIONS END
|
||||
//
|
||||
|
|
297
interface/resources/qml/hifi/commerce/wallet/WalletChoice.qml
Normal file
297
interface/resources/qml/hifi/commerce/wallet/WalletChoice.qml
Normal file
|
@ -0,0 +1,297 @@
|
|||
//
|
||||
// WalletChoice.qml
|
||||
// qml/hifi/commerce/wallet
|
||||
//
|
||||
// WalletChoice
|
||||
//
|
||||
// Created by Howard Stearns
|
||||
// Copyright 2018 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
import Hifi 1.0 as Hifi
|
||||
import QtQuick 2.5
|
||||
import "../common" as HifiCommerceCommon
|
||||
import "../../../styles-uit"
|
||||
import "../../../controls-uit" as HifiControlsUit
|
||||
|
||||
|
||||
Item {
|
||||
HifiConstants { id: hifi; }
|
||||
|
||||
id: root;
|
||||
property string activeView: "conflict";
|
||||
property var proceedFunction: nil;
|
||||
property var copyFunction: nil;
|
||||
property string referrer: "";
|
||||
|
||||
Image {
|
||||
anchors.fill: parent;
|
||||
source: "images/wallet-bg.jpg";
|
||||
}
|
||||
|
||||
HifiCommerceCommon.CommerceLightbox {
|
||||
id: lightboxPopup;
|
||||
visible: false;
|
||||
anchors.fill: parent;
|
||||
}
|
||||
|
||||
// This object is always used in a popup.
|
||||
// This MouseArea is used to prevent a user from being
|
||||
// able to click on a button/mouseArea underneath the popup.
|
||||
MouseArea {
|
||||
anchors.fill: parent;
|
||||
propagateComposedEvents: false;
|
||||
hoverEnabled: true;
|
||||
}
|
||||
|
||||
//
|
||||
// TITLE BAR START
|
||||
//
|
||||
Item {
|
||||
id: titleBarContainer;
|
||||
// Size
|
||||
height: 50;
|
||||
// Anchors
|
||||
anchors.left: parent.left;
|
||||
anchors.top: parent.top;
|
||||
anchors.right: parent.right;
|
||||
|
||||
// Wallet icon
|
||||
HiFiGlyphs {
|
||||
id: walletIcon;
|
||||
text: hifi.glyphs.wallet;
|
||||
// Size
|
||||
size: parent.height * 0.8;
|
||||
// Anchors
|
||||
anchors.left: parent.left;
|
||||
anchors.leftMargin: 8;
|
||||
anchors.verticalCenter: parent.verticalCenter;
|
||||
// Style
|
||||
color: hifi.colors.blueHighlight;
|
||||
}
|
||||
|
||||
// Title Bar text
|
||||
RalewayRegular {
|
||||
id: titleBarText;
|
||||
text: "Wallet Setup";
|
||||
// Text size
|
||||
size: hifi.fontSizes.overlayTitle;
|
||||
// Anchors
|
||||
anchors.top: parent.top;
|
||||
anchors.left: walletIcon.right;
|
||||
anchors.leftMargin: 8;
|
||||
anchors.bottom: parent.bottom;
|
||||
width: paintedWidth;
|
||||
// Style
|
||||
color: hifi.colors.white;
|
||||
// Alignment
|
||||
horizontalAlignment: Text.AlignHLeft;
|
||||
verticalAlignment: Text.AlignVCenter;
|
||||
}
|
||||
}
|
||||
//
|
||||
// TITLE BAR END
|
||||
//
|
||||
|
||||
//
|
||||
// MAIN PAGE START
|
||||
//
|
||||
Item {
|
||||
id: preexistingContainer;
|
||||
// Anchors
|
||||
anchors.top: titleBarContainer.bottom;
|
||||
anchors.bottom: parent.bottom;
|
||||
anchors.left: parent.left;
|
||||
anchors.right: parent.right;
|
||||
|
||||
HiFiGlyphs {
|
||||
id: bigKeyIcon;
|
||||
text: hifi.glyphs.walletKey;
|
||||
// Size
|
||||
size: 180;
|
||||
// Anchors
|
||||
anchors.top: parent.top;
|
||||
anchors.topMargin: 40;
|
||||
anchors.horizontalCenter: parent.horizontalCenter;
|
||||
// Style
|
||||
color: hifi.colors.white;
|
||||
}
|
||||
|
||||
RalewayRegular {
|
||||
id: text01;
|
||||
text: root.activeView === "preexisting" ?
|
||||
"Where are your private keys?" :
|
||||
"Hmm, your keys are different"
|
||||
// Text size
|
||||
size: 26;
|
||||
// Anchors
|
||||
anchors.top: bigKeyIcon.bottom;
|
||||
anchors.left: parent.left;
|
||||
anchors.leftMargin: 16;
|
||||
anchors.right: parent.right;
|
||||
anchors.rightMargin: 16;
|
||||
height: paintedHeight;
|
||||
width: paintedWidth;
|
||||
// Style
|
||||
color: hifi.colors.white;
|
||||
wrapMode: Text.WordWrap;
|
||||
// Alignment
|
||||
horizontalAlignment: Text.AlignHCenter;
|
||||
verticalAlignment: Text.AlignVCenter;
|
||||
}
|
||||
|
||||
RalewayRegular {
|
||||
id: text02;
|
||||
text: root.activeView === "preexisting" ?
|
||||
"Our records indicate that you created a wallet, but the private keys are not in the folder where we checked." :
|
||||
"Our records indicate that you created a wallet with different keys than the keys you're providing."
|
||||
// Text size
|
||||
size: 18;
|
||||
// Anchors
|
||||
anchors.top: text01.bottom;
|
||||
anchors.topMargin: 40;
|
||||
anchors.left: parent.left;
|
||||
anchors.leftMargin: 65;
|
||||
anchors.right: parent.right;
|
||||
anchors.rightMargin: 65;
|
||||
height: paintedHeight;
|
||||
width: paintedWidth;
|
||||
// Style
|
||||
color: hifi.colors.white;
|
||||
wrapMode: Text.WordWrap;
|
||||
// Alignment
|
||||
horizontalAlignment: Text.AlignHCenter;
|
||||
verticalAlignment: Text.AlignVCenter;
|
||||
}
|
||||
|
||||
// "Locate" button
|
||||
HifiControlsUit.Button {
|
||||
id: locateButton;
|
||||
color: hifi.buttons.blue;
|
||||
colorScheme: hifi.colorSchemes.dark;
|
||||
anchors.top: text02.bottom;
|
||||
anchors.topMargin: 40;
|
||||
anchors.horizontalCenter: parent.horizontalCenter;
|
||||
width: parent.width/2;
|
||||
height: 50;
|
||||
text: root.activeView === "preexisting" ?
|
||||
"LOCATE MY KEYS" :
|
||||
"LOCATE OTHER KEYS"
|
||||
onClicked: {
|
||||
walletChooser();
|
||||
}
|
||||
}
|
||||
|
||||
// "Create New" OR "Continue" button
|
||||
HifiControlsUit.Button {
|
||||
id: button02;
|
||||
color: hifi.buttons.none;
|
||||
colorScheme: hifi.colorSchemes.dark;
|
||||
anchors.top: locateButton.bottom;
|
||||
anchors.topMargin: 20;
|
||||
anchors.horizontalCenter: parent.horizontalCenter;
|
||||
width: parent.width/2;
|
||||
height: 50;
|
||||
text: root.activeView === "preexisting" ?
|
||||
"CREATE NEW WALLET" :
|
||||
"CONTINUE WITH THESE KEYS"
|
||||
onClicked: {
|
||||
lightboxPopup.titleText = "Are you sure?";
|
||||
lightboxPopup.bodyText = "Taking this step will abandon your old wallet and you will no " +
|
||||
"longer be able to access your money and your past purchases.<br><br>" +
|
||||
"This step should only be used if you cannot find your keys.<br><br>" +
|
||||
"This step cannot be undone.";
|
||||
lightboxPopup.button1color = hifi.buttons.red;
|
||||
lightboxPopup.button1text = "YES, CREATE NEW WALLET";
|
||||
lightboxPopup.button1method = "root.visible = false;proceed(true);";
|
||||
lightboxPopup.button2text = "CANCEL";
|
||||
lightboxPopup.button2method = "root.visible = false;"
|
||||
lightboxPopup.buttonLayout = "topbottom";
|
||||
lightboxPopup.visible = true;
|
||||
}
|
||||
}
|
||||
|
||||
// "What's This?" link
|
||||
RalewayRegular {
|
||||
id: whatsThisLink;
|
||||
text: '<font color="#FFFFFF"><a href="#whatsthis">What\'s this?</a></font>';
|
||||
// Anchors
|
||||
anchors.bottom: parent.bottom;
|
||||
anchors.bottomMargin: 48;
|
||||
anchors.horizontalCenter: parent.horizontalCenter;
|
||||
width: paintedWidth;
|
||||
height: paintedHeight;
|
||||
// Text size
|
||||
size: 18;
|
||||
// Style
|
||||
color: hifi.colors.white;
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent;
|
||||
|
||||
onClicked: {
|
||||
if (root.activeView === "preexisting") {
|
||||
lightboxPopup.titleText = "Your wallet's private keys are not in the folder we expected";
|
||||
lightboxPopup.bodyText = "We see that you have created a wallet but the private keys " +
|
||||
"for it seem to have been moved to a different folder.<br><br>" +
|
||||
"To tell us where the keys are, click 'Locate My Keys'. <br><br>" +
|
||||
"If you'd prefer to create a new wallet (not recommended - you will lose your money and past " +
|
||||
"purchases), click 'Create New Wallet'.";
|
||||
lightboxPopup.button1text = "CLOSE";
|
||||
lightboxPopup.button1method = "root.visible = false;"
|
||||
lightboxPopup.visible = true;
|
||||
} else {
|
||||
lightboxPopup.titleText = "You may have set up more than one wallet";
|
||||
lightboxPopup.bodyText = "We see that the private keys stored on your computer are different " +
|
||||
"from the ones you used last time. This may mean that you set up more than one wallet. " +
|
||||
"If you would like to use these keys, click 'Continue With These Keys'.<br><br>" +
|
||||
"If you would prefer to use another wallet, click 'Locate Other Keys' to show us where " +
|
||||
"you've stored the private keys for that wallet.";
|
||||
lightboxPopup.button1text = "CLOSE";
|
||||
lightboxPopup.button1method = "root.visible = false;"
|
||||
lightboxPopup.visible = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//
|
||||
// MAIN PAGE END
|
||||
//
|
||||
|
||||
//
|
||||
// FUNCTION DEFINITIONS START
|
||||
//
|
||||
function onFileOpenChanged(filename) {
|
||||
// disconnect the event, otherwise the requests will stack up
|
||||
try { // Not all calls to onFileOpenChanged() connect an event.
|
||||
Window.browseChanged.disconnect(onFileOpenChanged);
|
||||
} catch (e) {
|
||||
console.log('WalletChoice.qml ignoring', e);
|
||||
}
|
||||
if (filename) {
|
||||
if (copyFunction && copyFunction(filename)) {
|
||||
proceed(false);
|
||||
} else {
|
||||
console.log("WalletChoice.qml copyFunction", copyFunction, "failed.");
|
||||
}
|
||||
} // Else we're still at WalletChoice
|
||||
}
|
||||
function walletChooser() {
|
||||
Window.browseChanged.connect(onFileOpenChanged);
|
||||
Window.browseAsync("Locate your .hifikey file", "", "*.hifikey");
|
||||
}
|
||||
function proceed(isReset) {
|
||||
if (!proceedFunction) {
|
||||
console.log("Provide a function of no arguments to WalletChoice.qml.");
|
||||
} else {
|
||||
proceedFunction(isReset);
|
||||
}
|
||||
}
|
||||
//
|
||||
// FUNCTION DEFINITIONS END
|
||||
//
|
||||
}
|
|
@ -310,7 +310,7 @@ Item {
|
|||
height: parent.height;
|
||||
|
||||
HifiControlsUit.Separator {
|
||||
colorScheme: 1;
|
||||
colorScheme: 1;
|
||||
anchors.left: parent.left;
|
||||
anchors.right: parent.right;
|
||||
anchors.top: parent.top;
|
||||
|
@ -318,20 +318,42 @@ Item {
|
|||
|
||||
RalewayRegular {
|
||||
id: noActivityText;
|
||||
text: "<b>The Wallet app is in closed Beta.</b><br><br>To request entry and <b>receive free HFC</b>, please contact " +
|
||||
"<b>info@highfidelity.com</b> with your High Fidelity account username and the email address registered to that account.";
|
||||
// Text size
|
||||
size: 24;
|
||||
// Style
|
||||
color: hifi.colors.blueAccent;
|
||||
anchors.left: parent.left;
|
||||
anchors.leftMargin: 12;
|
||||
anchors.right: parent.right;
|
||||
anchors.rightMargin: 12;
|
||||
anchors.verticalCenter: parent.verticalCenter;
|
||||
height: paintedHeight;
|
||||
wrapMode: Text.WordWrap;
|
||||
horizontalAlignment: Text.AlignHCenter;
|
||||
text: "Congrats! Your wallet is all set!<br><br>" +
|
||||
"<b>Where's my HFC?</b><br>" +
|
||||
"High Fidelity commerce is in open beta right now. Want more HFC? Get it by meeting with a banker at " +
|
||||
"<a href='#goToBank'>BankOfHighFidelity</a>!"
|
||||
// Text size
|
||||
size: 22;
|
||||
// Style
|
||||
color: hifi.colors.blueAccent;
|
||||
anchors.top: parent.top;
|
||||
anchors.topMargin: 36;
|
||||
anchors.left: parent.left;
|
||||
anchors.leftMargin: 12;
|
||||
anchors.right: parent.right;
|
||||
anchors.rightMargin: 12;
|
||||
height: paintedHeight;
|
||||
wrapMode: Text.WordWrap;
|
||||
horizontalAlignment: Text.AlignHCenter;
|
||||
|
||||
onLinkActivated: {
|
||||
sendSignalToWallet({ method: "transactionHistory_goToBank" });
|
||||
}
|
||||
}
|
||||
|
||||
HifiControlsUit.Button {
|
||||
id: bankButton;
|
||||
color: hifi.buttons.blue;
|
||||
colorScheme: hifi.colorSchemes.dark;
|
||||
anchors.top: noActivityText.bottom;
|
||||
anchors.topMargin: 30;
|
||||
anchors.horizontalCenter: parent.horizontalCenter;
|
||||
width: parent.width/2;
|
||||
height: 50;
|
||||
text: "VISIT BANK OF HIGH FIDELITY";
|
||||
onClicked: {
|
||||
sendSignalToWallet({ method: "transactionHistory_goToBank" });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -441,7 +441,7 @@ Item {
|
|||
}
|
||||
Item {
|
||||
id: choosePassphraseContainer;
|
||||
visible: root.activeView === "step_3";
|
||||
visible: root.hasShownSecurityImageTip && root.activeView === "step_3";
|
||||
// Anchors
|
||||
anchors.top: titleBarContainer.bottom;
|
||||
anchors.topMargin: 30;
|
||||
|
@ -451,7 +451,10 @@ Item {
|
|||
|
||||
onVisibleChanged: {
|
||||
if (visible) {
|
||||
sendSignalToWallet({method: 'disableHmdPreview'});
|
||||
Commerce.getWalletAuthenticatedStatus();
|
||||
} else {
|
||||
sendSignalToWallet({method: 'maybeEnableHmdPreview'});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -923,7 +923,7 @@ Item {
|
|||
anchors.leftMargin: 20;
|
||||
anchors.right: parent.right;
|
||||
anchors.rightMargin: 20;
|
||||
height: 140;
|
||||
height: 95;
|
||||
|
||||
FontLoader { id: firaSansSemiBold; source: "../../../../../fonts/FiraSans-SemiBold.ttf"; }
|
||||
TextArea {
|
||||
|
@ -947,8 +947,14 @@ Item {
|
|||
wrapMode: TextEdit.Wrap;
|
||||
activeFocusOnPress: true;
|
||||
activeFocusOnTab: true;
|
||||
// Workaround for no max length on TextAreas
|
||||
onTextChanged: {
|
||||
// Don't allow tabs or newlines
|
||||
if ((/[\n\r\t]/g).test(text)) {
|
||||
var cursor = cursorPosition;
|
||||
text = text.replace(/[\n\r\t]/g, '');
|
||||
cursorPosition = cursor-1;
|
||||
}
|
||||
// Workaround for no max length on TextAreas
|
||||
if (text.length > maximumLength) {
|
||||
var cursor = cursorPosition;
|
||||
text = previousText;
|
||||
|
|
|
@ -199,7 +199,7 @@ Rectangle {
|
|||
var SHAPE_TYPE_BOX = 4;
|
||||
var SHAPE_TYPE_SPHERE = 5;
|
||||
|
||||
var SHAPE_TYPES = [];ww
|
||||
var SHAPE_TYPES = [];
|
||||
SHAPE_TYPES[SHAPE_TYPE_NONE] = "No Collision";
|
||||
SHAPE_TYPES[SHAPE_TYPE_SIMPLE_HULL] = "Basic - Whole model";
|
||||
SHAPE_TYPES[SHAPE_TYPE_SIMPLE_COMPOUND] = "Good - Sub-meshes";
|
||||
|
@ -207,7 +207,7 @@ Rectangle {
|
|||
SHAPE_TYPES[SHAPE_TYPE_BOX] = "Box";
|
||||
SHAPE_TYPES[SHAPE_TYPE_SPHERE] = "Sphere";
|
||||
|
||||
var SHAPE_TYPE_DEFAULT = SHAPE_TYPE_STATIC_MESH;
|
||||
var SHAPE_TYPE_DEFAULT = SHAPE_TYPE_SIMPLE_COMPOUND;
|
||||
var DYNAMIC_DEFAULT = false;
|
||||
var prompt = tabletRoot.customInputDialog({
|
||||
textInput: {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Controls 2.2 // Need both for short-term fix
|
||||
import QtWebEngine 1.1
|
||||
import QtWebChannel 1.0
|
||||
import QtQuick.Controls.Styles 1.4
|
||||
|
@ -10,6 +11,7 @@ import "../../controls-uit" as HifiControls
|
|||
import "../../styles-uit"
|
||||
|
||||
|
||||
|
||||
TabView {
|
||||
id: editTabView
|
||||
// anchors.fill: parent
|
||||
|
@ -23,8 +25,27 @@ TabView {
|
|||
|
||||
Rectangle {
|
||||
color: "#404040"
|
||||
id: container
|
||||
|
||||
Flickable {
|
||||
height: parent.height
|
||||
width: parent.width
|
||||
|
||||
contentHeight: createEntitiesFlow.height + importButton.height + assetServerButton.height +
|
||||
header.anchors.topMargin + createEntitiesFlow.anchors.topMargin +
|
||||
assetServerButton.anchors.topMargin + importButton.anchors.topMargin
|
||||
contentWidth: width
|
||||
|
||||
ScrollBar.vertical : ScrollBar {
|
||||
visible: parent.contentHeight > parent.height
|
||||
width: 20
|
||||
background: Rectangle {
|
||||
color: hifi.colors.tableScrollBackgroundDark
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
id: header
|
||||
color: "#ffffff"
|
||||
text: "Choose an Entity Type to Create:"
|
||||
font.pixelSize: 14
|
||||
|
@ -176,6 +197,7 @@ TabView {
|
|||
}
|
||||
|
||||
HifiControls.Button {
|
||||
id: importButton
|
||||
text: "Import Entities (.json)"
|
||||
color: hifi.buttons.black
|
||||
colorScheme: hifi.colorSchemes.dark
|
||||
|
@ -192,6 +214,7 @@ TabView {
|
|||
}
|
||||
}
|
||||
}
|
||||
} // Flickable
|
||||
}
|
||||
|
||||
Tab {
|
||||
|
|
|
@ -131,7 +131,7 @@ Rectangle {
|
|||
spacing: 5
|
||||
|
||||
anchors.horizontalCenter: column3.horizontalCenter
|
||||
anchors.horizontalCenterOffset: -20
|
||||
anchors.horizontalCenterOffset: 0
|
||||
|
||||
Button {
|
||||
id: button1
|
||||
|
|
|
@ -130,6 +130,7 @@ Item {
|
|||
flickableDirection: Flickable.AutoFlickIfNeeded
|
||||
keyNavigationEnabled: false
|
||||
highlightFollowsCurrentItem: false
|
||||
interactive: false
|
||||
|
||||
property int previousGridIndex: -1
|
||||
|
||||
|
|
|
@ -218,7 +218,7 @@ Item {
|
|||
readonly property var colorStart: [ colors.white, colors.primaryHighlight, "#d42043", "#343434", Qt.rgba(0, 0, 0, 0), Qt.rgba(0, 0, 0, 0), Qt.rgba(0, 0, 0, 0), Qt.rgba(0, 0, 0, 0) ]
|
||||
readonly property var colorFinish: [ colors.lightGrayText, colors.blueAccent, "#94132e", colors.black, Qt.rgba(0, 0, 0, 0), Qt.rgba(0, 0, 0, 0), Qt.rgba(0, 0, 0, 0), Qt.rgba(0, 0, 0, 0) ]
|
||||
readonly property var hoveredColor: [ colorStart[white], colorStart[blue], colorStart[red], colorFinish[black], colorStart[none], colorStart[noneBorderless], colorStart[noneBorderlessWhite], colorStart[noneBorderlessGray] ]
|
||||
readonly property var pressedColor: [ colorFinish[white], colorFinish[blue], colorFinish[red], colorStart[black], colorStart[none], colorStart[noneBorderless], colorStart[noneBorderlessWhite], colorStart[noneBorderlessGray] ]
|
||||
readonly property var pressedColor: [ colorFinish[white], colorFinish[blue], colorFinish[red], colorStart[black], colorStart[none], colorStart[noneBorderless], colorStart[noneBorderlessWhite], colors.lightGrayText ]
|
||||
readonly property var focusedColor: [ colors.lightGray50, colors.blueAccent, colors.redAccent, colors.darkGray, colorStart[none], colorStart[noneBorderless], colorStart[noneBorderlessWhite], colorStart[noneBorderlessGray] ]
|
||||
readonly property var disabledColorStart: [ colorStart[white], colors.baseGrayHighlight]
|
||||
readonly property var disabledColorFinish: [ colorFinish[white], colors.baseGrayShadow]
|
||||
|
@ -354,5 +354,9 @@ Item {
|
|||
readonly property string wallet: "\ue027"
|
||||
readonly property string paperPlane: "\ue028"
|
||||
readonly property string passphrase: "\ue029"
|
||||
readonly property string globe: "\ue02c"
|
||||
readonly property string wand: "\ue02d"
|
||||
readonly property string hat: "\ue02e"
|
||||
readonly property string install: "\ue02f"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -68,7 +68,6 @@
|
|||
#include <Midi.h>
|
||||
#include <AudioInjectorManager.h>
|
||||
#include <AvatarBookmarks.h>
|
||||
#include <AvatarEntitiesBookmarks.h>
|
||||
#include <CursorManager.h>
|
||||
#include <DebugDraw.h>
|
||||
#include <DeferredLightingEffect.h>
|
||||
|
@ -393,7 +392,7 @@ const QHash<QString, Application::AcceptURLMethod> Application::_acceptedExtensi
|
|||
class DeadlockWatchdogThread : public QThread {
|
||||
public:
|
||||
static const unsigned long HEARTBEAT_UPDATE_INTERVAL_SECS = 1;
|
||||
static const unsigned long MAX_HEARTBEAT_AGE_USECS = 30 * USECS_PER_SECOND;
|
||||
static const unsigned long MAX_HEARTBEAT_AGE_USECS = 120 * USECS_PER_SECOND; // 2 mins with no checkin probably a deadlock
|
||||
static const int WARNING_ELAPSED_HEARTBEAT = 500 * USECS_PER_MSEC; // warn if elapsed heartbeat average is large
|
||||
static const int HEARTBEAT_SAMPLES = 100000; // ~5 seconds worth of samples
|
||||
|
||||
|
@ -576,10 +575,7 @@ void messageHandler(QtMsgType type, const QMessageLogContext& context, const QSt
|
|||
QString logMessage = LogHandler::getInstance().printMessage((LogMsgType) type, context, message);
|
||||
|
||||
if (!logMessage.isEmpty()) {
|
||||
#ifdef Q_OS_WIN
|
||||
OutputDebugStringA(logMessage.toLocal8Bit().constData());
|
||||
OutputDebugStringA("\n");
|
||||
#elif defined Q_OS_ANDROID
|
||||
#ifdef Q_OS_ANDROID
|
||||
const char * local=logMessage.toStdString().c_str();
|
||||
switch (type) {
|
||||
case QtDebugMsg:
|
||||
|
@ -600,7 +596,7 @@ void messageHandler(QtMsgType type, const QMessageLogContext& context, const QSt
|
|||
abort();
|
||||
}
|
||||
#endif
|
||||
qApp->getLogger()->addMessage(qPrintable(logMessage + "\n"));
|
||||
qApp->getLogger()->addMessage(qPrintable(logMessage));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -855,7 +851,6 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) {
|
|||
DependencyManager::set<GooglePolyScriptingInterface>();
|
||||
DependencyManager::set<OctreeStatsProvider>(nullptr, qApp->getOcteeSceneStats());
|
||||
DependencyManager::set<AvatarBookmarks>();
|
||||
DependencyManager::set<AvatarEntitiesBookmarks>();
|
||||
DependencyManager::set<LocationBookmarks>();
|
||||
DependencyManager::set<Snapshot>();
|
||||
DependencyManager::set<CloseEventSender>();
|
||||
|
@ -1585,6 +1580,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
settingsTimer->setSingleShot(false);
|
||||
settingsTimer->setInterval(SAVE_SETTINGS_INTERVAL); // 10s, Qt::CoarseTimer acceptable
|
||||
QObject::connect(settingsTimer, &QTimer::timeout, this, &Application::saveSettings);
|
||||
settingsTimer->start();
|
||||
}, QThread::LowestPriority);
|
||||
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::FirstPerson)) {
|
||||
|
@ -1789,6 +1785,10 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
properties["has_async_reprojection"] = displayPlugin->hasAsyncReprojection();
|
||||
properties["hardware_stats"] = displayPlugin->getHardwareStats();
|
||||
|
||||
// deadlock watchdog related stats
|
||||
properties["deadlock_watchdog_maxElapsed"] = (int)DeadlockWatchdogThread::_maxElapsed;
|
||||
properties["deadlock_watchdog_maxElapsedAverage"] = (int)DeadlockWatchdogThread::_maxElapsedAverage;
|
||||
|
||||
auto bandwidthRecorder = DependencyManager::get<BandwidthRecorder>();
|
||||
properties["packet_rate_in"] = bandwidthRecorder->getCachedTotalAverageInputPacketsPerSecond();
|
||||
properties["packet_rate_out"] = bandwidthRecorder->getCachedTotalAverageOutputPacketsPerSecond();
|
||||
|
@ -2676,7 +2676,6 @@ void Application::onDesktopRootContextCreated(QQmlContext* surfaceContext) {
|
|||
surfaceContext->setContextProperty("Settings", SettingsScriptingInterface::getInstance());
|
||||
surfaceContext->setContextProperty("ScriptDiscoveryService", DependencyManager::get<ScriptEngines>().data());
|
||||
surfaceContext->setContextProperty("AvatarBookmarks", DependencyManager::get<AvatarBookmarks>().data());
|
||||
surfaceContext->setContextProperty("AvatarEntitiesBookmarks", DependencyManager::get<AvatarEntitiesBookmarks>().data());
|
||||
surfaceContext->setContextProperty("LocationBookmarks", DependencyManager::get<LocationBookmarks>().data());
|
||||
|
||||
// Caches
|
||||
|
@ -4899,7 +4898,6 @@ void Application::setKeyboardFocusHighlight(const glm::vec3& position, const glm
|
|||
if (_keyboardFocusHighlightID == UNKNOWN_OVERLAY_ID || !getOverlays().isAddedOverlay(_keyboardFocusHighlightID)) {
|
||||
_keyboardFocusHighlight = std::make_shared<Cube3DOverlay>();
|
||||
_keyboardFocusHighlight->setAlpha(1.0f);
|
||||
_keyboardFocusHighlight->setBorderSize(1.0f);
|
||||
_keyboardFocusHighlight->setColor({ 0xFF, 0xEF, 0x00 });
|
||||
_keyboardFocusHighlight->setIsSolid(false);
|
||||
_keyboardFocusHighlight->setPulseMin(0.5);
|
||||
|
@ -6039,7 +6037,6 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointe
|
|||
DependencyManager::get<TabletScriptingInterface>().data()->setToolbarScriptingInterface(toolbarScriptingInterface);
|
||||
|
||||
scriptEngine->registerGlobalObject("Window", DependencyManager::get<WindowScriptingInterface>().data());
|
||||
qScriptRegisterMetaType(scriptEngine.data(), CustomPromptResultToScriptValue, CustomPromptResultFromScriptValue);
|
||||
scriptEngine->registerGetterSetter("location", LocationScriptingInterface::locationGetter,
|
||||
LocationScriptingInterface::locationSetter, "Window");
|
||||
// register `location` on the global object.
|
||||
|
@ -6059,7 +6056,6 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointe
|
|||
scriptEngine->registerGlobalObject("AudioStats", DependencyManager::get<AudioClient>()->getStats().data());
|
||||
scriptEngine->registerGlobalObject("AudioScope", DependencyManager::get<AudioScope>().data());
|
||||
scriptEngine->registerGlobalObject("AvatarBookmarks", DependencyManager::get<AvatarBookmarks>().data());
|
||||
scriptEngine->registerGlobalObject("AvatarEntitiesBookmarks", DependencyManager::get<AvatarEntitiesBookmarks>().data());
|
||||
scriptEngine->registerGlobalObject("LocationBookmarks", DependencyManager::get<LocationBookmarks>().data());
|
||||
|
||||
scriptEngine->registerGlobalObject("RayPick", DependencyManager::get<RayPickScriptingInterface>().data());
|
||||
|
@ -6359,6 +6355,24 @@ bool Application::askToWearAvatarAttachmentUrl(const QString& url) {
|
|||
return true;
|
||||
}
|
||||
|
||||
void Application::replaceDomainContent(const QString& url) {
|
||||
qCDebug(interfaceapp) << "Attempting to replace domain content: " << url;
|
||||
QByteArray urlData(url.toUtf8());
|
||||
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
|
||||
limitedNodeList->eachMatchingNode([](const SharedNodePointer& node) {
|
||||
return node->getType() == NodeType::EntityServer && node->getActiveSocket();
|
||||
}, [&urlData, limitedNodeList](const SharedNodePointer& octreeNode) {
|
||||
auto octreeFilePacket = NLPacket::create(PacketType::OctreeFileReplacementFromUrl, urlData.size(), true);
|
||||
octreeFilePacket->write(urlData);
|
||||
limitedNodeList->sendPacket(std::move(octreeFilePacket), *octreeNode);
|
||||
});
|
||||
auto addressManager = DependencyManager::get<AddressManager>();
|
||||
addressManager->handleLookupString(DOMAIN_SPAWNING_POINT);
|
||||
QString newHomeAddress = addressManager->getHost() + DOMAIN_SPAWNING_POINT;
|
||||
qCDebug(interfaceapp) << "Setting new home bookmark to: " << newHomeAddress;
|
||||
DependencyManager::get<LocationBookmarks>()->setHomeLocationToAddress(newHomeAddress);
|
||||
}
|
||||
|
||||
bool Application::askToReplaceDomainContent(const QString& url) {
|
||||
QString methodDetails;
|
||||
const int MAX_CHARACTERS_PER_LINE = 90;
|
||||
|
@ -6378,21 +6392,7 @@ bool Application::askToReplaceDomainContent(const QString& url) {
|
|||
QString details;
|
||||
if (static_cast<QMessageBox::StandardButton>(answer.toInt()) == QMessageBox::Yes) {
|
||||
// Given confirmation, send request to domain server to replace content
|
||||
qCDebug(interfaceapp) << "Attempting to replace domain content: " << url;
|
||||
QByteArray urlData(url.toUtf8());
|
||||
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
|
||||
limitedNodeList->eachMatchingNode([](const SharedNodePointer& node) {
|
||||
return node->getType() == NodeType::EntityServer && node->getActiveSocket();
|
||||
}, [&urlData, limitedNodeList](const SharedNodePointer& octreeNode) {
|
||||
auto octreeFilePacket = NLPacket::create(PacketType::OctreeFileReplacementFromUrl, urlData.size(), true);
|
||||
octreeFilePacket->write(urlData);
|
||||
limitedNodeList->sendPacket(std::move(octreeFilePacket), *octreeNode);
|
||||
});
|
||||
auto addressManager = DependencyManager::get<AddressManager>();
|
||||
addressManager->handleLookupString(DOMAIN_SPAWNING_POINT);
|
||||
QString newHomeAddress = addressManager->getHost() + DOMAIN_SPAWNING_POINT;
|
||||
qCDebug(interfaceapp) << "Setting new home bookmark to: " << newHomeAddress;
|
||||
DependencyManager::get<LocationBookmarks>()->setHomeLocationToAddress(newHomeAddress);
|
||||
replaceDomainContent(url);
|
||||
details = "SuccessfulRequestToReplaceContent";
|
||||
} else {
|
||||
details = "UserDeclinedToReplaceContent";
|
||||
|
@ -7613,6 +7613,18 @@ void Application::deadlockApplication() {
|
|||
}
|
||||
}
|
||||
|
||||
// cause main thread to be unresponsive for 35 seconds
|
||||
void Application::unresponsiveApplication() {
|
||||
// to avoid compiler warnings about a loop that will never exit
|
||||
uint64_t start = usecTimestampNow();
|
||||
uint64_t UNRESPONSIVE_FOR_SECONDS = 35;
|
||||
uint64_t UNRESPONSIVE_FOR_USECS = UNRESPONSIVE_FOR_SECONDS * USECS_PER_SECOND;
|
||||
qCDebug(interfaceapp) << "Intentionally cause Interface to be unresponsive for " << UNRESPONSIVE_FOR_SECONDS << " seconds";
|
||||
while (usecTimestampNow() - start < UNRESPONSIVE_FOR_USECS) {
|
||||
QThread::sleep(1);
|
||||
}
|
||||
}
|
||||
|
||||
void Application::setActiveDisplayPlugin(const QString& pluginName) {
|
||||
auto menu = Menu::getInstance();
|
||||
foreach(DisplayPluginPointer displayPlugin, PluginManager::getInstance()->getDisplayPlugins()) {
|
||||
|
|
|
@ -266,9 +266,8 @@ public:
|
|||
|
||||
float getGameLoopRate() const { return _gameLoopCounter.rate(); }
|
||||
|
||||
// Note that takeSnapshot has a default value, as this method is used internally.
|
||||
void takeSnapshot(bool notify, bool includeAnimated = false, float aspectRatio = 0.0f, const QString& filename = QString());
|
||||
void takeSecondaryCameraSnapshot(const QString& filename);
|
||||
void takeSecondaryCameraSnapshot(const QString& filename = QString());
|
||||
|
||||
void shareSnapshot(const QString& filename, const QUrl& href = QUrl(""));
|
||||
|
||||
|
@ -286,6 +285,8 @@ public:
|
|||
bool getSaveAvatarOverrideUrl() { return _saveAvatarOverrideUrl; }
|
||||
void saveNextPhysicsStats(QString filename);
|
||||
|
||||
void replaceDomainContent(const QString& url);
|
||||
|
||||
signals:
|
||||
void svoImportRequested(const QString& url);
|
||||
|
||||
|
@ -367,6 +368,7 @@ public slots:
|
|||
void updateHeartbeat() const;
|
||||
|
||||
static void deadlockApplication();
|
||||
static void unresponsiveApplication(); // cause main thread to be unresponsive for 35 seconds
|
||||
|
||||
void rotationModeChanged() const;
|
||||
|
||||
|
|
|
@ -19,6 +19,11 @@
|
|||
#include <Application.h>
|
||||
#include <OffscreenUi.h>
|
||||
#include <avatar/AvatarManager.h>
|
||||
#include <EntityItemID.h>
|
||||
#include <EntityTree.h>
|
||||
#include <PhysicalEntitySimulation.h>
|
||||
#include <EntityEditPacketSender.h>
|
||||
#include <VariantMapToScriptValue.h>
|
||||
|
||||
#include "MainWindow.h"
|
||||
#include "Menu.h"
|
||||
|
@ -29,6 +34,62 @@
|
|||
|
||||
#include <QtQuick/QQuickWindow>
|
||||
|
||||
|
||||
void addAvatarEntities(const QVariantList& avatarEntities) {
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
const QUuid myNodeID = nodeList->getSessionUUID();
|
||||
EntityTreePointer entityTree = DependencyManager::get<EntityTreeRenderer>()->getTree();
|
||||
if (!entityTree) {
|
||||
return;
|
||||
}
|
||||
EntitySimulationPointer entitySimulation = entityTree->getSimulation();
|
||||
PhysicalEntitySimulationPointer physicalEntitySimulation = std::static_pointer_cast<PhysicalEntitySimulation>(entitySimulation);
|
||||
EntityEditPacketSender* entityPacketSender = physicalEntitySimulation->getPacketSender();
|
||||
QScriptEngine scriptEngine;
|
||||
for (int index = 0; index < avatarEntities.count(); index++) {
|
||||
const QVariantMap& avatarEntityProperties = avatarEntities.at(index).toMap();
|
||||
QVariant variantProperties = avatarEntityProperties["properties"];
|
||||
QVariantMap asMap = variantProperties.toMap();
|
||||
QScriptValue scriptProperties = variantMapToScriptValue(asMap, scriptEngine);
|
||||
EntityItemProperties entityProperties;
|
||||
EntityItemPropertiesFromScriptValueHonorReadOnly(scriptProperties, entityProperties);
|
||||
|
||||
entityProperties.setParentID(myNodeID);
|
||||
entityProperties.setClientOnly(true);
|
||||
entityProperties.setOwningAvatarID(myNodeID);
|
||||
entityProperties.setSimulationOwner(myNodeID, AVATAR_ENTITY_SIMULATION_PRIORITY);
|
||||
entityProperties.markAllChanged();
|
||||
|
||||
EntityItemID id = EntityItemID(QUuid::createUuid());
|
||||
bool success = true;
|
||||
entityTree->withWriteLock([&] {
|
||||
EntityItemPointer entity = entityTree->addEntity(id, entityProperties);
|
||||
if (entity) {
|
||||
if (entityProperties.queryAACubeRelatedPropertyChanged()) {
|
||||
// due to parenting, the server may not know where something is in world-space, so include the bounding cube.
|
||||
bool success;
|
||||
AACube queryAACube = entity->getQueryAACube(success);
|
||||
if (success) {
|
||||
entityProperties.setQueryAACube(queryAACube);
|
||||
}
|
||||
}
|
||||
|
||||
entity->setLastBroadcast(usecTimestampNow());
|
||||
// since we're creating this object we will immediately volunteer to own its simulation
|
||||
entity->flagForOwnershipBid(VOLUNTEER_SIMULATION_PRIORITY);
|
||||
entityProperties.setLastEdited(entity->getLastEdited());
|
||||
} else {
|
||||
qCDebug(entities) << "AvatarEntitiesBookmark failed to add new Entity to local Octree";
|
||||
success = false;
|
||||
}
|
||||
});
|
||||
|
||||
if (success) {
|
||||
entityPacketSender->queueEditEntityMessage(PacketType::EntityAdd, entityTree, id, entityProperties);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AvatarBookmarks::AvatarBookmarks() {
|
||||
_bookmarksFilename = PathUtils::getAppDataPath() + "/" + AVATARBOOKMARKS_FILENAME;
|
||||
readFromFile();
|
||||
|
@ -38,7 +99,7 @@ void AvatarBookmarks::readFromFile() {
|
|||
// migrate old avatarbookmarks.json, used to be in 'local' folder on windows
|
||||
QString oldConfigPath = QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/" + AVATARBOOKMARKS_FILENAME;
|
||||
QFile oldConfig(oldConfigPath);
|
||||
|
||||
|
||||
// I imagine that in a year from now, this code for migrating (as well as the two lines above)
|
||||
// may be removed since all bookmarks should have been migrated by then
|
||||
// - Robbie Uvanni (6.8.2017)
|
||||
|
@ -48,9 +109,9 @@ void AvatarBookmarks::readFromFile() {
|
|||
} else {
|
||||
qCDebug(interfaceapp) << "Failed to migrate" << AVATARBOOKMARKS_FILENAME;
|
||||
}
|
||||
}
|
||||
|
||||
Bookmarks::readFromFile();
|
||||
}
|
||||
|
||||
Bookmarks::readFromFile();
|
||||
}
|
||||
|
||||
void AvatarBookmarks::setupMenus(Menu* menubar, MenuWrapper* menu) {
|
||||
|
@ -81,23 +142,27 @@ void AvatarBookmarks::changeToBookmarkedAvatar() {
|
|||
myAvatar->useFullAvatarURL(action->data().toString());
|
||||
qCDebug(interfaceapp) << " Using Legacy V1 Avatar Bookmark ";
|
||||
} else {
|
||||
|
||||
|
||||
const QMap<QString, QVariant> bookmark = action->data().toMap();
|
||||
// Not magic value. This is the current made version, and if it changes this interpreter should be updated to
|
||||
// Not magic value. This is the current made version, and if it changes this interpreter should be updated to
|
||||
// handle the new one separately.
|
||||
// This is where the avatar bookmark entry is parsed. If adding new Value, make sure to have backward compatability with previous
|
||||
if (bookmark.value(ENTRY_VERSION) == 3) {
|
||||
const QString& avatarUrl = bookmark.value(ENTRY_AVATAR_URL, "").toString();
|
||||
myAvatar->useFullAvatarURL(avatarUrl);
|
||||
qCDebug(interfaceapp) << "Avatar On " << avatarUrl;
|
||||
const QList<QVariant>& attachments = bookmark.value(ENTRY_AVATAR_ATTACHMENTS, QList<QVariant>()).toList();
|
||||
myAvatar->removeAvatarEntities();
|
||||
const QString& avatarUrl = bookmark.value(ENTRY_AVATAR_URL, "").toString();
|
||||
myAvatar->useFullAvatarURL(avatarUrl);
|
||||
qCDebug(interfaceapp) << "Avatar On " << avatarUrl;
|
||||
const QList<QVariant>& attachments = bookmark.value(ENTRY_AVATAR_ATTACHMENTS, QList<QVariant>()).toList();
|
||||
|
||||
qCDebug(interfaceapp) << "Attach " << attachments;
|
||||
myAvatar->setAttachmentsVariant(attachments);
|
||||
qCDebug(interfaceapp) << "Attach " << attachments;
|
||||
myAvatar->setAttachmentsVariant(attachments);
|
||||
|
||||
const float& qScale = bookmark.value(ENTRY_AVATAR_SCALE, 1.0f).toFloat();
|
||||
myAvatar->setAvatarScale(qScale);
|
||||
|
||||
const QVariantList& avatarEntities = bookmark.value(ENTRY_AVATAR_ENTITIES, QVariantList()).toList();
|
||||
addAvatarEntities(avatarEntities);
|
||||
|
||||
const float& qScale = bookmark.value(ENTRY_AVATAR_SCALE, 1.0f).toFloat();
|
||||
myAvatar->setAvatarScale(qScale);
|
||||
|
||||
} else {
|
||||
qCDebug(interfaceapp) << " Bookmark entry does not match client version, make sure client has a handler for the new AvatarBookmark";
|
||||
}
|
||||
|
@ -126,6 +191,7 @@ void AvatarBookmarks::addBookmark() {
|
|||
bookmark.insert(ENTRY_AVATAR_URL, avatarUrl);
|
||||
bookmark.insert(ENTRY_AVATAR_SCALE, avatarScale);
|
||||
bookmark.insert(ENTRY_AVATAR_ATTACHMENTS, myAvatar->getAttachmentsVariant());
|
||||
bookmark.insert(ENTRY_AVATAR_ENTITIES, myAvatar->getAvatarEntitiesVariant());
|
||||
|
||||
Bookmarks::addBookmarkToFile(bookmarkName, bookmark);
|
||||
});
|
||||
|
|
|
@ -34,6 +34,7 @@ private:
|
|||
const QString AVATARBOOKMARKS_FILENAME = "avatarbookmarks.json";
|
||||
const QString ENTRY_AVATAR_URL = "avatarUrl";
|
||||
const QString ENTRY_AVATAR_ATTACHMENTS = "attachments";
|
||||
const QString ENTRY_AVATAR_ENTITIES = "avatarEntites";
|
||||
const QString ENTRY_AVATAR_SCALE = "avatarScale";
|
||||
const QString ENTRY_VERSION = "version";
|
||||
|
||||
|
|
|
@ -1,168 +0,0 @@
|
|||
//
|
||||
// AvatarEntitiesBookmarks.cpp
|
||||
// interface/src
|
||||
//
|
||||
// Created by Dante Ruiz on 15/01/18.
|
||||
// Copyright 2018 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <QAction>
|
||||
#include <QInputDialog>
|
||||
#include <QMessageBox>
|
||||
#include <QStandardPaths>
|
||||
#include <QQmlContext>
|
||||
#include <QList>
|
||||
|
||||
#include <Application.h>
|
||||
#include <OffscreenUi.h>
|
||||
#include <EntityItemProperties.h>
|
||||
#include <GLMHelpers.h>
|
||||
#include <avatar/AvatarManager.h>
|
||||
#include <EntityItemID.h>
|
||||
#include <EntityTree.h>
|
||||
#include <PhysicalEntitySimulation.h>
|
||||
#include <EntityEditPacketSender.h>
|
||||
#include <VariantMapToScriptValue.h>
|
||||
|
||||
#include "MainWindow.h"
|
||||
#include "Menu.h"
|
||||
#include "AvatarEntitiesBookmarks.h"
|
||||
#include "InterfaceLogging.h"
|
||||
|
||||
#include "QVariantGLM.h"
|
||||
|
||||
#include <QtQuick/QQuickWindow>
|
||||
|
||||
void addAvatarEntities(const QVariantList& avatarEntities) {
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
const QUuid myNodeID = nodeList->getSessionUUID();
|
||||
EntityTreePointer entityTree = DependencyManager::get<EntityTreeRenderer>()->getTree();
|
||||
if (!entityTree) {
|
||||
return;
|
||||
}
|
||||
EntitySimulationPointer entitySimulation = entityTree->getSimulation();
|
||||
PhysicalEntitySimulationPointer physicalEntitySimulation = std::static_pointer_cast<PhysicalEntitySimulation>(entitySimulation);
|
||||
EntityEditPacketSender* entityPacketSender = physicalEntitySimulation->getPacketSender();
|
||||
QScriptEngine scriptEngine;
|
||||
for (int index = 0; index < avatarEntities.count(); index++) {
|
||||
const QVariantMap& avatarEntityProperties = avatarEntities.at(index).toMap();
|
||||
QVariant variantProperties = avatarEntityProperties["properties"];
|
||||
QVariantMap asMap = variantProperties.toMap();
|
||||
QScriptValue scriptProperties = variantMapToScriptValue(asMap, scriptEngine);
|
||||
EntityItemProperties entityProperties;
|
||||
EntityItemPropertiesFromScriptValueHonorReadOnly(scriptProperties, entityProperties);
|
||||
|
||||
entityProperties.setParentID(myNodeID);
|
||||
entityProperties.setClientOnly(true);
|
||||
entityProperties.setOwningAvatarID(myNodeID);
|
||||
entityProperties.setSimulationOwner(myNodeID, AVATAR_ENTITY_SIMULATION_PRIORITY);
|
||||
entityProperties.markAllChanged();
|
||||
|
||||
EntityItemID id = EntityItemID(QUuid::createUuid());
|
||||
bool success = true;
|
||||
entityTree->withWriteLock([&] {
|
||||
EntityItemPointer entity = entityTree->addEntity(id, entityProperties);
|
||||
if (entity) {
|
||||
if (entityProperties.queryAACubeRelatedPropertyChanged()) {
|
||||
// due to parenting, the server may not know where something is in world-space, so include the bounding cube.
|
||||
bool success;
|
||||
AACube queryAACube = entity->getQueryAACube(success);
|
||||
if (success) {
|
||||
entityProperties.setQueryAACube(queryAACube);
|
||||
}
|
||||
}
|
||||
|
||||
entity->setLastBroadcast(usecTimestampNow());
|
||||
// since we're creating this object we will immediately volunteer to own its simulation
|
||||
entity->flagForOwnershipBid(VOLUNTEER_SIMULATION_PRIORITY);
|
||||
entityProperties.setLastEdited(entity->getLastEdited());
|
||||
} else {
|
||||
qCDebug(entities) << "AvatarEntitiesBookmark failed to add new Entity to local Octree";
|
||||
success = false;
|
||||
}
|
||||
});
|
||||
|
||||
if (success) {
|
||||
entityPacketSender->queueEditEntityMessage(PacketType::EntityAdd, entityTree, id, entityProperties);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AvatarEntitiesBookmarks::AvatarEntitiesBookmarks() {
|
||||
_bookmarksFilename = PathUtils::getAppDataPath() + "/" + AVATAR_ENTITIES_BOOKMARKS_FILENAME;
|
||||
Bookmarks::readFromFile();
|
||||
}
|
||||
|
||||
void AvatarEntitiesBookmarks::setupMenus(Menu* menubar, MenuWrapper* menu) {
|
||||
auto bookmarkAction = menubar->addActionToQMenuAndActionHash(menu, MenuOption::BookmarkAvatarEntities);
|
||||
QObject::connect(bookmarkAction, SIGNAL(triggered()), this, SLOT(addBookmark()), Qt::QueuedConnection);
|
||||
_bookmarksMenu = menu->addMenu(MenuOption::AvatarEntitiesBookmarks);
|
||||
_deleteBookmarksAction = menubar->addActionToQMenuAndActionHash(menu, MenuOption::DeleteAvatarEntitiesBookmark);
|
||||
QObject::connect(_deleteBookmarksAction, SIGNAL(triggered()), this, SLOT(deleteBookmark()), Qt::QueuedConnection);
|
||||
|
||||
for (auto it = _bookmarks.begin(); it != _bookmarks.end(); ++it) {
|
||||
addBookmarkToMenu(menubar, it.key(), it.value());
|
||||
}
|
||||
|
||||
Bookmarks::sortActions(menubar, _bookmarksMenu);
|
||||
}
|
||||
|
||||
void AvatarEntitiesBookmarks::applyBookmarkedAvatarEntities() {
|
||||
QAction* action = qobject_cast<QAction*>(sender());
|
||||
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
|
||||
|
||||
const QMap<QString, QVariant> bookmark = action->data().toMap();
|
||||
|
||||
if (bookmark.value(ENTRY_VERSION) == AVATAR_ENTITIES_BOOKMARK_VERSION) {
|
||||
myAvatar->removeAvatarEntities();
|
||||
const QString& avatarUrl = bookmark.value(ENTRY_AVATAR_URL, "").toString();
|
||||
myAvatar->useFullAvatarURL(avatarUrl);
|
||||
const QVariantList& avatarEntities = bookmark.value(ENTRY_AVATAR_ENTITIES, QVariantList()).toList();
|
||||
addAvatarEntities(avatarEntities);
|
||||
const float& avatarScale = bookmark.value(ENTRY_AVATAR_SCALE, 1.0f).toFloat();
|
||||
myAvatar->setAvatarScale(avatarScale);
|
||||
} else {
|
||||
qCDebug(interfaceapp) << " Bookmark entry does not match client version, make sure client has a handler for the new AvatarEntitiesBookmark";
|
||||
}
|
||||
}
|
||||
|
||||
void AvatarEntitiesBookmarks::addBookmark() {
|
||||
ModalDialogListener* dlg = OffscreenUi::getTextAsync(OffscreenUi::ICON_PLACEMARK, "Bookmark Avatar Entities", "Name", QString());
|
||||
connect(dlg, &ModalDialogListener::response, this, [=] (QVariant response) {
|
||||
disconnect(dlg, &ModalDialogListener::response, this, nullptr);
|
||||
auto bookmarkName = response.toString();
|
||||
bookmarkName = bookmarkName.trimmed().replace(QRegExp("(\r\n|[\r\n\t\v ])+"), " ");
|
||||
if (bookmarkName.length() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
|
||||
|
||||
const QString& avatarUrl = myAvatar->getSkeletonModelURL().toString();
|
||||
const QVariant& avatarScale = myAvatar->getAvatarScale();
|
||||
|
||||
QVariantMap bookmark;
|
||||
bookmark.insert(ENTRY_VERSION, AVATAR_ENTITIES_BOOKMARK_VERSION);
|
||||
bookmark.insert(ENTRY_AVATAR_URL, avatarUrl);
|
||||
bookmark.insert(ENTRY_AVATAR_SCALE, avatarScale);
|
||||
bookmark.insert(ENTRY_AVATAR_ENTITIES, myAvatar->getAvatarEntitiesVariant());
|
||||
|
||||
Bookmarks::addBookmarkToFile(bookmarkName, bookmark);
|
||||
});
|
||||
}
|
||||
|
||||
void AvatarEntitiesBookmarks::addBookmarkToMenu(Menu* menubar, const QString& name, const QVariant& bookmark) {
|
||||
QAction* changeAction = _bookmarksMenu->newAction();
|
||||
changeAction->setData(bookmark);
|
||||
connect(changeAction, SIGNAL(triggered()), this, SLOT(applyBookmarkedAvatarEntities()));
|
||||
if (!_isMenuSorted) {
|
||||
menubar->addActionToQMenuAndActionHash(_bookmarksMenu, changeAction, name, 0, QAction::NoRole);
|
||||
} else {
|
||||
// TODO: this is aggressive but other alternatives have proved less fruitful so far.
|
||||
menubar->addActionToQMenuAndActionHash(_bookmarksMenu, changeAction, name, 0, QAction::NoRole);
|
||||
Bookmarks::sortActions(menubar, _bookmarksMenu);
|
||||
}
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
//
|
||||
// AvatarEntitiesBookmarks.h
|
||||
// interface/src
|
||||
//
|
||||
// Created by Dante Ruiz on 15/01/18.
|
||||
// Copyright 2018 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_AvatarEntitiesBookmarks_h
|
||||
#define hifi_AvatarEntitiesBookmarks_h
|
||||
|
||||
#include <DependencyManager.h>
|
||||
#include "Bookmarks.h"
|
||||
|
||||
class AvatarEntitiesBookmarks: public Bookmarks, public Dependency {
|
||||
Q_OBJECT
|
||||
SINGLETON_DEPENDENCY
|
||||
|
||||
public:
|
||||
AvatarEntitiesBookmarks();
|
||||
void setupMenus(Menu* menubar, MenuWrapper* menu) override;
|
||||
|
||||
public slots:
|
||||
void addBookmark();
|
||||
|
||||
protected:
|
||||
void addBookmarkToMenu(Menu* menubar, const QString& name, const QVariant& bookmark) override;
|
||||
|
||||
private:
|
||||
const QString AVATAR_ENTITIES_BOOKMARKS_FILENAME = "AvatarEntitiesBookmarks.json";
|
||||
const QString ENTRY_AVATAR_URL = "AvatarUrl";
|
||||
const QString ENTRY_AVATAR_SCALE = "AvatarScale";
|
||||
const QString ENTRY_AVATAR_ENTITIES = "AvatarEntities";
|
||||
const QString ENTRY_VERSION = "version";
|
||||
|
||||
const int AVATAR_ENTITIES_BOOKMARK_VERSION = 1;
|
||||
|
||||
private slots:
|
||||
void applyBookmarkedAvatarEntities();
|
||||
};
|
||||
|
||||
#endif
|
|
@ -34,7 +34,6 @@
|
|||
#include "audio/AudioScope.h"
|
||||
#include "avatar/AvatarManager.h"
|
||||
#include "AvatarBookmarks.h"
|
||||
#include "AvatarEntitiesBookmarks.h"
|
||||
#include "devices/DdeFaceTracker.h"
|
||||
#include "MainWindow.h"
|
||||
#include "render/DrawStatus.h"
|
||||
|
@ -207,9 +206,6 @@ Menu::Menu() {
|
|||
auto avatarBookmarks = DependencyManager::get<AvatarBookmarks>();
|
||||
avatarBookmarks->setupMenus(this, avatarMenu);
|
||||
|
||||
auto avatarEntitiesBookmarks = DependencyManager::get<AvatarEntitiesBookmarks>();
|
||||
avatarEntitiesBookmarks->setupMenus(this, avatarMenu);
|
||||
|
||||
// Display menu ----------------------------------
|
||||
// FIXME - this is not yet matching Alan's spec because it doesn't have
|
||||
// menus for "2D"/"3D" - we need to add support for detecting the appropriate
|
||||
|
@ -712,6 +708,7 @@ Menu::Menu() {
|
|||
MenuWrapper* crashMenu = developerMenu->addMenu("Crash");
|
||||
|
||||
addActionToQMenuAndActionHash(crashMenu, MenuOption::DeadlockInterface, 0, qApp, SLOT(deadlockApplication()));
|
||||
addActionToQMenuAndActionHash(crashMenu, MenuOption::UnresponsiveInterface, 0, qApp, SLOT(unresponsiveApplication()));
|
||||
|
||||
action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashPureVirtualFunction);
|
||||
connect(action, &QAction::triggered, qApp, []() { crash::pureVirtualCall(); });
|
||||
|
|
|
@ -46,7 +46,6 @@ namespace MenuOption {
|
|||
const QString AutoMuteAudio = "Auto Mute Microphone";
|
||||
const QString AvatarReceiveStats = "Show Receive Stats";
|
||||
const QString AvatarBookmarks = "Avatar Bookmarks";
|
||||
const QString AvatarEntitiesBookmarks = "Avatar Entities Bookmarks";
|
||||
const QString Back = "Back";
|
||||
const QString BinaryEyelidControl = "Binary Eyelid Control";
|
||||
const QString BookmarkAvatar = "Bookmark Avatar";
|
||||
|
@ -77,6 +76,7 @@ namespace MenuOption {
|
|||
const QString CrashNewFault = "New Fault";
|
||||
const QString CrashNewFaultThreaded = "New Fault (threaded)";
|
||||
const QString DeadlockInterface = "Deadlock Interface";
|
||||
const QString UnresponsiveInterface = "Unresponsive Interface";
|
||||
const QString DecreaseAvatarSize = "Decrease Avatar Size";
|
||||
const QString DefaultSkybox = "Default Skybox";
|
||||
const QString DeleteAvatarBookmark = "Delete Avatar Bookmark...";
|
||||
|
|
|
@ -79,6 +79,8 @@ float DEFAULT_SCRIPTED_MOTOR_TIMESCALE = 1.0e6f;
|
|||
const int SCRIPTED_MOTOR_CAMERA_FRAME = 0;
|
||||
const int SCRIPTED_MOTOR_AVATAR_FRAME = 1;
|
||||
const int SCRIPTED_MOTOR_WORLD_FRAME = 2;
|
||||
const int SCRIPTED_MOTOR_SIMPLE_MODE = 0;
|
||||
const int SCRIPTED_MOTOR_DYNAMIC_MODE = 1;
|
||||
const QString& DEFAULT_AVATAR_COLLISION_SOUND_URL = "https://hifi-public.s3.amazonaws.com/sounds/Collisions-otherorganic/Body_Hits_Impact.wav";
|
||||
|
||||
const float MyAvatar::ZOOM_MIN = 0.5f;
|
||||
|
@ -92,6 +94,7 @@ MyAvatar::MyAvatar(QThread* thread) :
|
|||
_pitchSpeed(PITCH_SPEED_DEFAULT),
|
||||
_scriptedMotorTimescale(DEFAULT_SCRIPTED_MOTOR_TIMESCALE),
|
||||
_scriptedMotorFrame(SCRIPTED_MOTOR_CAMERA_FRAME),
|
||||
_scriptedMotorMode(SCRIPTED_MOTOR_SIMPLE_MODE),
|
||||
_motionBehaviors(AVATAR_MOTION_DEFAULTS),
|
||||
_characterController(this),
|
||||
_eyeContactTarget(LEFT_EYE),
|
||||
|
@ -1479,7 +1482,7 @@ void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) {
|
|||
});
|
||||
saveAvatarUrl();
|
||||
emit skeletonChanged();
|
||||
|
||||
emit skeletonModelURLChanged();
|
||||
}
|
||||
|
||||
void MyAvatar::removeAvatarEntities() {
|
||||
|
@ -1623,20 +1626,27 @@ controller::Pose MyAvatar::getControllerPoseInAvatarFrame(controller::Action act
|
|||
void MyAvatar::updateMotors() {
|
||||
_characterController.clearMotors();
|
||||
glm::quat motorRotation;
|
||||
|
||||
const float FLYING_MOTOR_TIMESCALE = 0.05f;
|
||||
const float WALKING_MOTOR_TIMESCALE = 0.2f;
|
||||
const float INVALID_MOTOR_TIMESCALE = 1.0e6f;
|
||||
|
||||
float horizontalMotorTimescale;
|
||||
float verticalMotorTimescale;
|
||||
|
||||
if (_characterController.getState() == CharacterController::State::Hover ||
|
||||
_characterController.computeCollisionGroup() == BULLET_COLLISION_GROUP_COLLISIONLESS) {
|
||||
horizontalMotorTimescale = FLYING_MOTOR_TIMESCALE;
|
||||
verticalMotorTimescale = FLYING_MOTOR_TIMESCALE;
|
||||
} else {
|
||||
horizontalMotorTimescale = WALKING_MOTOR_TIMESCALE;
|
||||
verticalMotorTimescale = INVALID_MOTOR_TIMESCALE;
|
||||
}
|
||||
|
||||
if (_motionBehaviors & AVATAR_MOTION_ACTION_MOTOR_ENABLED) {
|
||||
|
||||
const float FLYING_MOTOR_TIMESCALE = 0.05f;
|
||||
const float WALKING_MOTOR_TIMESCALE = 0.2f;
|
||||
const float INVALID_MOTOR_TIMESCALE = 1.0e6f;
|
||||
|
||||
float horizontalMotorTimescale;
|
||||
float verticalMotorTimescale;
|
||||
|
||||
if (_characterController.getState() == CharacterController::State::Hover ||
|
||||
_characterController.computeCollisionGroup() == BULLET_COLLISION_GROUP_COLLISIONLESS) {
|
||||
motorRotation = getMyHead()->getHeadOrientation();
|
||||
horizontalMotorTimescale = FLYING_MOTOR_TIMESCALE;
|
||||
verticalMotorTimescale = FLYING_MOTOR_TIMESCALE;
|
||||
} else {
|
||||
// non-hovering = walking: follow camera twist about vertical but not lift
|
||||
// we decompose camera's rotation and store the twist part in motorRotation
|
||||
|
@ -1647,8 +1657,6 @@ void MyAvatar::updateMotors() {
|
|||
glm::quat liftRotation;
|
||||
swingTwistDecomposition(headOrientation, Vectors::UNIT_Y, liftRotation, motorRotation);
|
||||
motorRotation = orientation * motorRotation;
|
||||
horizontalMotorTimescale = WALKING_MOTOR_TIMESCALE;
|
||||
verticalMotorTimescale = INVALID_MOTOR_TIMESCALE;
|
||||
}
|
||||
|
||||
if (_isPushing || _isBraking || !_isBeingPushed) {
|
||||
|
@ -1668,7 +1676,12 @@ void MyAvatar::updateMotors() {
|
|||
// world-frame
|
||||
motorRotation = glm::quat();
|
||||
}
|
||||
_characterController.addMotor(_scriptedMotorVelocity, motorRotation, _scriptedMotorTimescale);
|
||||
if (_scriptedMotorMode == SCRIPTED_MOTOR_SIMPLE_MODE) {
|
||||
_characterController.addMotor(_scriptedMotorVelocity, motorRotation, _scriptedMotorTimescale);
|
||||
} else {
|
||||
// dynamic mode
|
||||
_characterController.addMotor(_scriptedMotorVelocity, motorRotation, horizontalMotorTimescale, verticalMotorTimescale);
|
||||
}
|
||||
}
|
||||
|
||||
// legacy support for 'MyAvatar::applyThrust()', which has always been implemented as a
|
||||
|
@ -1752,6 +1765,14 @@ QString MyAvatar::getScriptedMotorFrame() const {
|
|||
return frame;
|
||||
}
|
||||
|
||||
QString MyAvatar::getScriptedMotorMode() const {
|
||||
QString mode = "simple";
|
||||
if (_scriptedMotorMode == SCRIPTED_MOTOR_DYNAMIC_MODE) {
|
||||
mode = "dynamic";
|
||||
}
|
||||
return mode;
|
||||
}
|
||||
|
||||
void MyAvatar::setScriptedMotorVelocity(const glm::vec3& velocity) {
|
||||
float MAX_SCRIPTED_MOTOR_SPEED = 500.0f;
|
||||
_scriptedMotorVelocity = velocity;
|
||||
|
@ -1778,6 +1799,14 @@ void MyAvatar::setScriptedMotorFrame(QString frame) {
|
|||
}
|
||||
}
|
||||
|
||||
void MyAvatar::setScriptedMotorMode(QString mode) {
|
||||
if (mode.toLower() == "simple") {
|
||||
_scriptedMotorMode = SCRIPTED_MOTOR_SIMPLE_MODE;
|
||||
} else if (mode.toLower() == "dynamic") {
|
||||
_scriptedMotorMode = SCRIPTED_MOTOR_DYNAMIC_MODE;
|
||||
}
|
||||
}
|
||||
|
||||
void MyAvatar::clearScriptableSettings() {
|
||||
_scriptedMotorVelocity = Vectors::ZERO;
|
||||
_scriptedMotorTimescale = DEFAULT_SCRIPTED_MOTOR_TIMESCALE;
|
||||
|
|
|
@ -69,6 +69,7 @@ class MyAvatar : public Avatar {
|
|||
* @property motorTimescale {float} Specifies how quickly the avatar should accelerate to meet the motorVelocity,
|
||||
* smaller values will result in higher acceleration.
|
||||
* @property motorReferenceFrame {string} Reference frame of the motorVelocity, must be one of the following: "avatar", "camera", "world"
|
||||
* @property motorMode {string} Type of scripted motor behavior, "simple" = use motorTimescale property (default mode) and "dynamic" = use action motor's timescales
|
||||
* @property collisionSoundURL {string} Specifies the sound to play when the avatar experiences a collision.
|
||||
* You can provide a mono or stereo 16-bit WAV file running at either 24 Khz or 48 Khz.
|
||||
* The latter is downsampled by the audio mixer, so all audio effectively plays back at a 24 Khz sample rate.
|
||||
|
@ -124,6 +125,7 @@ class MyAvatar : public Avatar {
|
|||
Q_PROPERTY(glm::vec3 motorVelocity READ getScriptedMotorVelocity WRITE setScriptedMotorVelocity)
|
||||
Q_PROPERTY(float motorTimescale READ getScriptedMotorTimescale WRITE setScriptedMotorTimescale)
|
||||
Q_PROPERTY(QString motorReferenceFrame READ getScriptedMotorFrame WRITE setScriptedMotorFrame)
|
||||
Q_PROPERTY(QString motorMode READ getScriptedMotorMode WRITE setScriptedMotorMode)
|
||||
Q_PROPERTY(QString collisionSoundURL READ getCollisionSoundURL WRITE setCollisionSoundURL)
|
||||
Q_PROPERTY(AudioListenerMode audioListenerMode READ getAudioListenerMode WRITE setAudioListenerMode)
|
||||
Q_PROPERTY(glm::vec3 customListenPosition READ getCustomListenPosition WRITE setCustomListenPosition)
|
||||
|
@ -662,9 +664,11 @@ private:
|
|||
glm::vec3 getScriptedMotorVelocity() const { return _scriptedMotorVelocity; }
|
||||
float getScriptedMotorTimescale() const { return _scriptedMotorTimescale; }
|
||||
QString getScriptedMotorFrame() const;
|
||||
QString getScriptedMotorMode() const;
|
||||
void setScriptedMotorVelocity(const glm::vec3& velocity);
|
||||
void setScriptedMotorTimescale(float timescale);
|
||||
void setScriptedMotorFrame(QString frame);
|
||||
void setScriptedMotorMode(QString mode);
|
||||
virtual void attach(const QString& modelURL, const QString& jointName = QString(),
|
||||
const glm::vec3& translation = glm::vec3(), const glm::quat& rotation = glm::quat(),
|
||||
float scale = 1.0f, bool isSoft = false,
|
||||
|
@ -706,6 +710,7 @@ private:
|
|||
glm::vec3 _scriptedMotorVelocity; // target local-frame velocity of avatar (analog script)
|
||||
float _scriptedMotorTimescale; // timescale for avatar to achieve its target velocity
|
||||
int _scriptedMotorFrame;
|
||||
int _scriptedMotorMode;
|
||||
quint32 _motionBehaviors;
|
||||
QString _collisionSoundURL;
|
||||
|
||||
|
|
|
@ -22,6 +22,8 @@
|
|||
// inventory answers {status: 'success', data: {assets: [{id: "guid", title: "name", preview: "url"}....]}}
|
||||
// balance answers {status: 'success', data: {balance: integer}}
|
||||
// buy and receive_at answer {status: 'success'}
|
||||
// account synthesizes a result {status: 'success', data: {keyStatus: "preexisting"|"conflicting"|"ok"}}
|
||||
|
||||
|
||||
QJsonObject Ledger::apiResponse(const QString& label, QNetworkReply& reply) {
|
||||
QByteArray response = reply.readAll();
|
||||
|
@ -49,6 +51,7 @@ Handler(balance)
|
|||
Handler(inventory)
|
||||
Handler(transferHfcToNode)
|
||||
Handler(transferHfcToUsername)
|
||||
Handler(alreadyOwned)
|
||||
|
||||
void Ledger::send(const QString& endpoint, const QString& success, const QString& fail, QNetworkAccessManager::Operation method, AccountManagerAuth::Type authType, QJsonObject request) {
|
||||
auto accountManager = DependencyManager::get<AccountManager>();
|
||||
|
@ -98,7 +101,7 @@ void Ledger::buy(const QString& hfc_key, int cost, const QString& asset_id, cons
|
|||
signedSend("transaction", transactionString, hfc_key, "buy", "buySuccess", "buyFailure", controlled_failure);
|
||||
}
|
||||
|
||||
bool Ledger::receiveAt(const QString& hfc_key, const QString& old_key) {
|
||||
bool Ledger::receiveAt(const QString& hfc_key, const QString& signing_key) {
|
||||
auto accountManager = DependencyManager::get<AccountManager>();
|
||||
if (!accountManager->isLoggedIn()) {
|
||||
qCWarning(commerce) << "Cannot set receiveAt when not logged in.";
|
||||
|
@ -107,7 +110,7 @@ bool Ledger::receiveAt(const QString& hfc_key, const QString& old_key) {
|
|||
return false; // We know right away that we will fail, so tell the caller.
|
||||
}
|
||||
|
||||
signedSend("public_key", hfc_key.toUtf8(), old_key, "receive_at", "receiveAtSuccess", "receiveAtFailure");
|
||||
signedSend("public_key", hfc_key.toUtf8(), signing_key, "receive_at", "receiveAtSuccess", "receiveAtFailure");
|
||||
return true; // Note that there may still be an asynchronous signal of failure that callers might be interested in.
|
||||
}
|
||||
|
||||
|
@ -178,7 +181,7 @@ QString transactionString(const QJsonObject& valueObject) {
|
|||
} else {
|
||||
result += valueObject["message"].toString();
|
||||
}
|
||||
|
||||
|
||||
// no matter what we append a smaller date to the bottom of this...
|
||||
result += QString("<br><font size='-2' color='#1080B8'>%1").arg(createdAt.toLocalTime().toString(Qt::DefaultLocaleShortDate));
|
||||
return result;
|
||||
|
@ -245,18 +248,33 @@ void Ledger::accountSuccess(QNetworkReply& reply) {
|
|||
auto iv = QByteArray::fromBase64(data["iv"].toString().toUtf8());
|
||||
auto ckey = QByteArray::fromBase64(data["ckey"].toString().toUtf8());
|
||||
QString remotePublicKey = data["public_key"].toString();
|
||||
bool isOverride = wallet->wasSoftReset();
|
||||
|
||||
wallet->setSalt(salt);
|
||||
wallet->setIv(iv);
|
||||
wallet->setCKey(ckey);
|
||||
|
||||
QString keyStatus = "ok";
|
||||
QStringList localPublicKeys = wallet->listPublicKeys();
|
||||
if (remotePublicKey.isEmpty() && !localPublicKeys.isEmpty()) {
|
||||
receiveAt(localPublicKeys.first(), "");
|
||||
if (remotePublicKey.isEmpty() || isOverride) {
|
||||
if (!localPublicKeys.isEmpty()) {
|
||||
QString key = localPublicKeys.first();
|
||||
receiveAt(key, key);
|
||||
}
|
||||
} else {
|
||||
if (localPublicKeys.isEmpty()) {
|
||||
keyStatus = "preexisting";
|
||||
} else if (localPublicKeys.first() != remotePublicKey) {
|
||||
keyStatus = "conflicting";
|
||||
}
|
||||
}
|
||||
|
||||
// none of the hfc account info should be emitted
|
||||
emit accountResult(QJsonObject{ {"status", "success"} });
|
||||
QJsonObject json;
|
||||
QJsonObject responseData{ { "status", "success"} };
|
||||
json["keyStatus"] = keyStatus;
|
||||
responseData["data"] = json;
|
||||
emit accountResult(responseData);
|
||||
}
|
||||
|
||||
void Ledger::accountFailure(QNetworkReply& reply) {
|
||||
|
@ -336,3 +354,12 @@ void Ledger::transferHfcToUsername(const QString& hfc_key, const QString& userna
|
|||
auto transactionString = transactionDoc.toJson(QJsonDocument::Compact);
|
||||
signedSend("transaction", transactionString, hfc_key, "transfer_hfc_to_user", "transferHfcToUsernameSuccess", "transferHfcToUsernameFailure");
|
||||
}
|
||||
|
||||
void Ledger::alreadyOwned(const QString& marketplaceId) {
|
||||
auto wallet = DependencyManager::get<Wallet>();
|
||||
QString endpoint = "already_owned";
|
||||
QJsonObject request;
|
||||
request["public_keys"] = QJsonArray::fromStringList(wallet->listPublicKeys());
|
||||
request["marketplace_item_id"] = marketplaceId;
|
||||
send(endpoint, "alreadyOwnedSuccess", "alreadyOwnedFailure", QNetworkAccessManager::PutOperation, AccountManagerAuth::Required, request);
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ class Ledger : public QObject, public Dependency {
|
|||
|
||||
public:
|
||||
void buy(const QString& hfc_key, int cost, const QString& asset_id, const QString& inventory_key, const bool controlled_failure = false);
|
||||
bool receiveAt(const QString& hfc_key, const QString& old_key);
|
||||
bool receiveAt(const QString& hfc_key, const QString& signing_key);
|
||||
void balance(const QStringList& keys);
|
||||
void inventory(const QStringList& keys);
|
||||
void history(const QStringList& keys, const int& pageNumber);
|
||||
|
@ -35,6 +35,7 @@ public:
|
|||
void certificateInfo(const QString& certificateId);
|
||||
void transferHfcToNode(const QString& hfc_key, const QString& nodeID, const int& amount, const QString& optionalMessage);
|
||||
void transferHfcToUsername(const QString& hfc_key, const QString& username, const int& amount, const QString& optionalMessage);
|
||||
void alreadyOwned(const QString& marketplaceId);
|
||||
|
||||
enum CertificateStatus {
|
||||
CERTIFICATE_STATUS_UNKNOWN = 0,
|
||||
|
@ -55,6 +56,7 @@ signals:
|
|||
void certificateInfoResult(QJsonObject result);
|
||||
void transferHfcToNodeResult(QJsonObject result);
|
||||
void transferHfcToUsernameResult(QJsonObject result);
|
||||
void alreadyOwnedResult(QJsonObject result);
|
||||
|
||||
void updateCertificateStatus(const QString& certID, uint certStatus);
|
||||
|
||||
|
@ -79,6 +81,8 @@ public slots:
|
|||
void transferHfcToNodeFailure(QNetworkReply& reply);
|
||||
void transferHfcToUsernameSuccess(QNetworkReply& reply);
|
||||
void transferHfcToUsernameFailure(QNetworkReply& reply);
|
||||
void alreadyOwnedSuccess(QNetworkReply& reply);
|
||||
void alreadyOwnedFailure(QNetworkReply& reply);
|
||||
|
||||
private:
|
||||
QJsonObject apiResponse(const QString& label, QNetworkReply& reply);
|
||||
|
|
|
@ -10,11 +10,17 @@
|
|||
//
|
||||
|
||||
#include "QmlCommerce.h"
|
||||
#include "CommerceLogging.h"
|
||||
#include "Application.h"
|
||||
#include "DependencyManager.h"
|
||||
#include "Ledger.h"
|
||||
#include "Wallet.h"
|
||||
#include <AccountManager.h>
|
||||
#include <Application.h>
|
||||
#include <UserActivityLogger.h>
|
||||
#include <ScriptEngines.h>
|
||||
#include <ui/TabletScriptingInterface.h>
|
||||
#include "scripting/HMDScriptingInterface.h"
|
||||
|
||||
QmlCommerce::QmlCommerce() {
|
||||
auto ledger = DependencyManager::get<Ledger>();
|
||||
|
@ -28,14 +34,18 @@ QmlCommerce::QmlCommerce() {
|
|||
connect(ledger.data(), &Ledger::accountResult, this, &QmlCommerce::accountResult);
|
||||
connect(wallet.data(), &Wallet::walletStatusResult, this, &QmlCommerce::walletStatusResult);
|
||||
connect(ledger.data(), &Ledger::certificateInfoResult, this, &QmlCommerce::certificateInfoResult);
|
||||
connect(ledger.data(), &Ledger::alreadyOwnedResult, this, &QmlCommerce::alreadyOwnedResult);
|
||||
connect(ledger.data(), &Ledger::updateCertificateStatus, this, &QmlCommerce::updateCertificateStatus);
|
||||
connect(ledger.data(), &Ledger::transferHfcToNodeResult, this, &QmlCommerce::transferHfcToNodeResult);
|
||||
connect(ledger.data(), &Ledger::transferHfcToUsernameResult, this, &QmlCommerce::transferHfcToUsernameResult);
|
||||
connect(ledger.data(), &Ledger::transferHfcToUsernameResult, this, &QmlCommerce::transferHfcToUsernameResult);
|
||||
|
||||
auto accountManager = DependencyManager::get<AccountManager>();
|
||||
connect(accountManager.data(), &AccountManager::usernameChanged, this, [&]() {
|
||||
setPassphrase("");
|
||||
});
|
||||
|
||||
_appsPath = PathUtils::getAppDataPath() + "Apps/";
|
||||
}
|
||||
|
||||
void QmlCommerce::getWalletStatus() {
|
||||
|
@ -52,6 +62,11 @@ void QmlCommerce::getKeyFilePathIfExists() {
|
|||
emit keyFilePathIfExistsResult(wallet->getKeyFilePath());
|
||||
}
|
||||
|
||||
bool QmlCommerce::copyKeyFileFrom(const QString& pathname) {
|
||||
auto wallet = DependencyManager::get<Wallet>();
|
||||
return wallet->copyKeyFileFrom(pathname);
|
||||
}
|
||||
|
||||
void QmlCommerce::getWalletAuthenticatedStatus() {
|
||||
auto wallet = DependencyManager::get<Wallet>();
|
||||
emit walletAuthenticatedStatusResult(wallet->walletIsAuthenticatedWithPassphrase());
|
||||
|
@ -118,6 +133,11 @@ void QmlCommerce::changePassphrase(const QString& oldPassphrase, const QString&
|
|||
}
|
||||
}
|
||||
|
||||
void QmlCommerce::setSoftReset() {
|
||||
auto wallet = DependencyManager::get<Wallet>();
|
||||
wallet->setSoftReset();
|
||||
}
|
||||
|
||||
void QmlCommerce::setPassphrase(const QString& passphrase) {
|
||||
auto wallet = DependencyManager::get<Wallet>();
|
||||
wallet->setPassphrase(passphrase);
|
||||
|
@ -163,3 +183,164 @@ void QmlCommerce::transferHfcToUsername(const QString& username, const int& amou
|
|||
QString key = keys[0];
|
||||
ledger->transferHfcToUsername(key, username, amount, optionalMessage);
|
||||
}
|
||||
|
||||
void QmlCommerce::replaceContentSet(const QString& itemHref) {
|
||||
qApp->replaceDomainContent(itemHref);
|
||||
QJsonObject messageProperties = {
|
||||
{ "status", "SuccessfulRequestToReplaceContent" },
|
||||
{ "content_set_url", itemHref }
|
||||
};
|
||||
UserActivityLogger::getInstance().logAction("replace_domain_content", messageProperties);
|
||||
|
||||
emit contentSetChanged(itemHref);
|
||||
}
|
||||
|
||||
void QmlCommerce::alreadyOwned(const QString& marketplaceId) {
|
||||
auto ledger = DependencyManager::get<Ledger>();
|
||||
ledger->alreadyOwned(marketplaceId);
|
||||
}
|
||||
|
||||
QString QmlCommerce::getInstalledApps() {
|
||||
QString installedAppsFromMarketplace;
|
||||
QStringList runningScripts = DependencyManager::get<ScriptEngines>()->getRunningScripts();
|
||||
|
||||
QDir directory(_appsPath);
|
||||
QStringList apps = directory.entryList(QStringList("*.app.json"));
|
||||
foreach(QString appFileName, apps) {
|
||||
installedAppsFromMarketplace += appFileName;
|
||||
installedAppsFromMarketplace += ",";
|
||||
QFile appFile(_appsPath + appFileName);
|
||||
if (appFile.open(QIODevice::ReadOnly)) {
|
||||
QJsonDocument appFileJsonDocument = QJsonDocument::fromJson(appFile.readAll());
|
||||
|
||||
appFile.close();
|
||||
|
||||
QJsonObject appFileJsonObject = appFileJsonDocument.object();
|
||||
QString scriptURL = appFileJsonObject["scriptURL"].toString();
|
||||
|
||||
// If the script .app.json is on the user's local disk but the associated script isn't running
|
||||
// for some reason, start that script again.
|
||||
if (!runningScripts.contains(scriptURL)) {
|
||||
if ((DependencyManager::get<ScriptEngines>()->loadScript(scriptURL.trimmed())).isNull()) {
|
||||
qCDebug(commerce) << "Couldn't start script while checking installed apps.";
|
||||
}
|
||||
}
|
||||
} else {
|
||||
qCDebug(commerce) << "Couldn't open local .app.json file for reading.";
|
||||
}
|
||||
}
|
||||
|
||||
return installedAppsFromMarketplace;
|
||||
}
|
||||
|
||||
bool QmlCommerce::installApp(const QString& itemHref) {
|
||||
if (!QDir(_appsPath).exists()) {
|
||||
if (!QDir().mkdir(_appsPath)) {
|
||||
qCDebug(commerce) << "Couldn't make _appsPath directory.";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
QUrl appHref(itemHref);
|
||||
|
||||
auto request = DependencyManager::get<ResourceManager>()->createResourceRequest(this, appHref);
|
||||
|
||||
if (!request) {
|
||||
qCDebug(commerce) << "Couldn't create resource request for app.";
|
||||
return false;
|
||||
}
|
||||
|
||||
connect(request, &ResourceRequest::finished, this, [=]() {
|
||||
if (request->getResult() != ResourceRequest::Success) {
|
||||
qCDebug(commerce) << "Failed to get .app.json file from remote.";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Copy the .app.json to the apps directory inside %AppData%/High Fidelity/Interface
|
||||
auto requestData = request->getData();
|
||||
QFile appFile(_appsPath + "/" + appHref.fileName());
|
||||
if (!appFile.open(QIODevice::WriteOnly)) {
|
||||
qCDebug(commerce) << "Couldn't open local .app.json file for creation.";
|
||||
return false;
|
||||
}
|
||||
if (appFile.write(requestData) == -1) {
|
||||
qCDebug(commerce) << "Couldn't write to local .app.json file.";
|
||||
return false;
|
||||
}
|
||||
// Close the file
|
||||
appFile.close();
|
||||
|
||||
// Read from the returned datastream to know what .js to add to Running Scripts
|
||||
QJsonDocument appFileJsonDocument = QJsonDocument::fromJson(requestData);
|
||||
QJsonObject appFileJsonObject = appFileJsonDocument.object();
|
||||
QString scriptUrl = appFileJsonObject["scriptURL"].toString();
|
||||
|
||||
if ((DependencyManager::get<ScriptEngines>()->loadScript(scriptUrl.trimmed())).isNull()) {
|
||||
qCDebug(commerce) << "Couldn't load script.";
|
||||
return false;
|
||||
}
|
||||
|
||||
emit appInstalled(itemHref);
|
||||
return true;
|
||||
});
|
||||
request->send();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool QmlCommerce::uninstallApp(const QString& itemHref) {
|
||||
QUrl appHref(itemHref);
|
||||
|
||||
// Read from the file to know what .js script to stop
|
||||
QFile appFile(_appsPath + "/" + appHref.fileName());
|
||||
if (!appFile.open(QIODevice::ReadOnly)) {
|
||||
qCDebug(commerce) << "Couldn't open local .app.json file for deletion.";
|
||||
return false;
|
||||
}
|
||||
QJsonDocument appFileJsonDocument = QJsonDocument::fromJson(appFile.readAll());
|
||||
QJsonObject appFileJsonObject = appFileJsonDocument.object();
|
||||
QString scriptUrl = appFileJsonObject["scriptURL"].toString();
|
||||
|
||||
if (!DependencyManager::get<ScriptEngines>()->stopScript(scriptUrl.trimmed(), false)) {
|
||||
qCDebug(commerce) << "Couldn't stop script.";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Delete the .app.json from the filesystem
|
||||
// remove() closes the file first.
|
||||
if (!appFile.remove()) {
|
||||
qCDebug(commerce) << "Couldn't delete local .app.json file.";
|
||||
return false;
|
||||
}
|
||||
|
||||
emit appUninstalled(itemHref);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool QmlCommerce::openApp(const QString& itemHref) {
|
||||
QUrl appHref(itemHref);
|
||||
|
||||
// Read from the file to know what .html or .qml document to open
|
||||
QFile appFile(_appsPath + "/" + appHref.fileName());
|
||||
if (!appFile.open(QIODevice::ReadOnly)) {
|
||||
qCDebug(commerce) << "Couldn't open local .app.json file.";
|
||||
return false;
|
||||
}
|
||||
QJsonDocument appFileJsonDocument = QJsonDocument::fromJson(appFile.readAll());
|
||||
QJsonObject appFileJsonObject = appFileJsonDocument.object();
|
||||
QString homeUrl = appFileJsonObject["homeURL"].toString();
|
||||
|
||||
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
|
||||
auto tablet = dynamic_cast<TabletProxy*>(tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system"));
|
||||
if (homeUrl.contains(".qml", Qt::CaseInsensitive)) {
|
||||
tablet->loadQMLSource(homeUrl);
|
||||
} else if (homeUrl.contains(".html", Qt::CaseInsensitive)) {
|
||||
tablet->gotoWebScreen(homeUrl);
|
||||
} else {
|
||||
qCDebug(commerce) << "Attempted to open unknown type of homeURL!";
|
||||
return false;
|
||||
}
|
||||
|
||||
DependencyManager::get<HMDScriptingInterface>()->openTablet();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -42,12 +42,18 @@ signals:
|
|||
void historyResult(QJsonObject result);
|
||||
void accountResult(QJsonObject result);
|
||||
void certificateInfoResult(QJsonObject result);
|
||||
void alreadyOwnedResult(QJsonObject result);
|
||||
|
||||
void updateCertificateStatus(const QString& certID, uint certStatus);
|
||||
|
||||
void transferHfcToNodeResult(QJsonObject result);
|
||||
void transferHfcToUsernameResult(QJsonObject result);
|
||||
|
||||
void contentSetChanged(const QString& contentSetHref);
|
||||
|
||||
void appInstalled(const QString& appHref);
|
||||
void appUninstalled(const QString& appHref);
|
||||
|
||||
protected:
|
||||
Q_INVOKABLE void getWalletStatus();
|
||||
|
||||
|
@ -55,10 +61,12 @@ protected:
|
|||
Q_INVOKABLE void getKeyFilePathIfExists();
|
||||
Q_INVOKABLE void getSecurityImage();
|
||||
Q_INVOKABLE void getWalletAuthenticatedStatus();
|
||||
Q_INVOKABLE bool copyKeyFileFrom(const QString& pathname);
|
||||
|
||||
Q_INVOKABLE void chooseSecurityImage(const QString& imageFile);
|
||||
Q_INVOKABLE void setPassphrase(const QString& passphrase);
|
||||
Q_INVOKABLE void changePassphrase(const QString& oldPassphrase, const QString& newPassphrase);
|
||||
Q_INVOKABLE void setSoftReset();
|
||||
|
||||
Q_INVOKABLE void buy(const QString& assetId, int cost, const bool controlledFailure = false);
|
||||
Q_INVOKABLE void balance();
|
||||
|
@ -68,9 +76,20 @@ protected:
|
|||
Q_INVOKABLE void account();
|
||||
|
||||
Q_INVOKABLE void certificateInfo(const QString& certificateId);
|
||||
Q_INVOKABLE void alreadyOwned(const QString& marketplaceId);
|
||||
|
||||
Q_INVOKABLE void transferHfcToNode(const QString& nodeID, const int& amount, const QString& optionalMessage);
|
||||
Q_INVOKABLE void transferHfcToUsername(const QString& username, const int& amount, const QString& optionalMessage);
|
||||
|
||||
Q_INVOKABLE void replaceContentSet(const QString& itemHref);
|
||||
|
||||
Q_INVOKABLE QString getInstalledApps();
|
||||
Q_INVOKABLE bool installApp(const QString& appHref);
|
||||
Q_INVOKABLE bool uninstallApp(const QString& appHref);
|
||||
Q_INVOKABLE bool openApp(const QString& appHref);
|
||||
|
||||
private:
|
||||
QString _appsPath;
|
||||
};
|
||||
|
||||
#endif // hifi_QmlCommerce_h
|
||||
|
|
|
@ -59,6 +59,23 @@ QString keyFilePath() {
|
|||
auto accountManager = DependencyManager::get<AccountManager>();
|
||||
return PathUtils::getAppDataFilePath(QString("%1.%2").arg(accountManager->getAccountInfo().getUsername(), KEY_FILE));
|
||||
}
|
||||
bool Wallet::copyKeyFileFrom(const QString& pathname) {
|
||||
QString existing = getKeyFilePath();
|
||||
qCDebug(commerce) << "Old keyfile" << existing;
|
||||
if (!existing.isEmpty()) {
|
||||
QString backup = QString(existing).insert(existing.indexOf(KEY_FILE) - 1,
|
||||
QDateTime::currentDateTime().toString(Qt::ISODate).replace(":", ""));
|
||||
qCDebug(commerce) << "Renaming old keyfile to" << backup;
|
||||
if (!QFile::rename(existing, backup)) {
|
||||
qCCritical(commerce) << "Unable to backup" << existing << "to" << backup;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
QString destination = keyFilePath();
|
||||
bool result = QFile::copy(pathname, destination);
|
||||
qCDebug(commerce) << "copy" << pathname << "to" << destination << "=>" << result;
|
||||
return result;
|
||||
}
|
||||
|
||||
// use the cached _passphrase if it exists, otherwise we need to prompt
|
||||
int passwordCallback(char* password, int maxPasswordSize, int rwFlag, void* u) {
|
||||
|
@ -300,17 +317,24 @@ Wallet::Wallet() {
|
|||
packetReceiver.registerListener(PacketType::ChallengeOwnership, this, "handleChallengeOwnershipPacket");
|
||||
packetReceiver.registerListener(PacketType::ChallengeOwnershipRequest, this, "handleChallengeOwnershipPacket");
|
||||
|
||||
connect(ledger.data(), &Ledger::accountResult, this, [&]() {
|
||||
connect(ledger.data(), &Ledger::accountResult, this, [&](QJsonObject result) {
|
||||
auto wallet = DependencyManager::get<Wallet>();
|
||||
auto walletScriptingInterface = DependencyManager::get<WalletScriptingInterface>();
|
||||
uint status;
|
||||
QString keyStatus = result.contains("data") ? result["data"].toObject()["keyStatus"].toString() : "";
|
||||
|
||||
if (wallet->getKeyFilePath() == "" || !wallet->getSecurityImage()) {
|
||||
status = (uint)WalletStatus::WALLET_STATUS_NOT_SET_UP;
|
||||
if (keyStatus == "preexisting") {
|
||||
status = (uint) WalletStatus::WALLET_STATUS_PREEXISTING;
|
||||
} else{
|
||||
status = (uint) WalletStatus::WALLET_STATUS_NOT_SET_UP;
|
||||
}
|
||||
} else if (!wallet->walletIsAuthenticatedWithPassphrase()) {
|
||||
status = (uint)WalletStatus::WALLET_STATUS_NOT_AUTHENTICATED;
|
||||
status = (uint) WalletStatus::WALLET_STATUS_NOT_AUTHENTICATED;
|
||||
} else if (keyStatus == "conflicting") {
|
||||
status = (uint) WalletStatus::WALLET_STATUS_CONFLICTING;
|
||||
} else {
|
||||
status = (uint)WalletStatus::WALLET_STATUS_READY;
|
||||
status = (uint) WalletStatus::WALLET_STATUS_READY;
|
||||
}
|
||||
|
||||
walletScriptingInterface->setWalletStatus(status);
|
||||
|
@ -524,17 +548,17 @@ bool Wallet::generateKeyPair() {
|
|||
|
||||
// TODO: redo this soon -- need error checking and so on
|
||||
writeSecurityImage(_securityImage, keyFilePath());
|
||||
QString oldKey = _publicKeys.count() == 0 ? "" : _publicKeys.last();
|
||||
QString key = keyPair.first->toBase64();
|
||||
_publicKeys.push_back(key);
|
||||
qCDebug(commerce) << "public key:" << key;
|
||||
_isOverridingServer = false;
|
||||
|
||||
// It's arguable whether we want to change the receiveAt every time, but:
|
||||
// 1. It's certainly needed the first time, when createIfNeeded answers true.
|
||||
// 2. It is maximally private, and we can step back from that later if desired.
|
||||
// 3. It maximally exercises all the machinery, so we are most likely to surface issues now.
|
||||
auto ledger = DependencyManager::get<Ledger>();
|
||||
return ledger->receiveAt(key, oldKey);
|
||||
return ledger->receiveAt(key, key);
|
||||
}
|
||||
|
||||
QStringList Wallet::listPublicKeys() {
|
||||
|
|
|
@ -35,6 +35,7 @@ public:
|
|||
void chooseSecurityImage(const QString& imageFile);
|
||||
bool getSecurityImage();
|
||||
QString getKeyFilePath();
|
||||
bool copyKeyFileFrom(const QString& pathname);
|
||||
|
||||
void setSalt(const QByteArray& salt) { _salt = salt; }
|
||||
QByteArray getSalt() { return _salt; }
|
||||
|
@ -48,11 +49,15 @@ public:
|
|||
bool getPassphraseIsCached() { return !(_passphrase->isEmpty()); }
|
||||
bool walletIsAuthenticatedWithPassphrase();
|
||||
bool changePassphrase(const QString& newPassphrase);
|
||||
void setSoftReset() { _isOverridingServer = true; }
|
||||
bool wasSoftReset() { bool was = _isOverridingServer; _isOverridingServer = false; return was; }
|
||||
|
||||
void getWalletStatus();
|
||||
enum WalletStatus {
|
||||
WALLET_STATUS_NOT_LOGGED_IN = 0,
|
||||
WALLET_STATUS_NOT_SET_UP,
|
||||
WALLET_STATUS_PREEXISTING,
|
||||
WALLET_STATUS_CONFLICTING,
|
||||
WALLET_STATUS_NOT_AUTHENTICATED,
|
||||
WALLET_STATUS_READY
|
||||
};
|
||||
|
@ -73,6 +78,7 @@ private:
|
|||
QByteArray _iv;
|
||||
QByteArray _ckey;
|
||||
QString* _passphrase { new QString("") };
|
||||
bool _isOverridingServer { false };
|
||||
|
||||
bool writeWallet(const QString& newPassphrase = QString(""));
|
||||
void updateImageProvider();
|
||||
|
|
|
@ -38,6 +38,7 @@ extern "C" {
|
|||
#endif
|
||||
|
||||
int main(int argc, const char* argv[]) {
|
||||
setupHifiApplication(BuildInfo::INTERFACE_NAME);
|
||||
|
||||
#ifdef Q_OS_LINUX
|
||||
QApplication::setAttribute(Qt::AA_DontUseNativeMenuBar);
|
||||
|
@ -51,17 +52,9 @@ int main(int argc, const char* argv[]) {
|
|||
QCoreApplication::setAttribute(Qt::AA_UseOpenGLES);
|
||||
#endif
|
||||
|
||||
disableQtBearerPoll(); // Fixes wifi ping spikes
|
||||
|
||||
QElapsedTimer startupTime;
|
||||
startupTime.start();
|
||||
|
||||
// Set application infos
|
||||
QCoreApplication::setApplicationName(BuildInfo::INTERFACE_NAME);
|
||||
QCoreApplication::setOrganizationName(BuildInfo::MODIFIED_ORGANIZATION);
|
||||
QCoreApplication::setOrganizationDomain(BuildInfo::ORGANIZATION_DOMAIN);
|
||||
QCoreApplication::setApplicationVersion(BuildInfo::VERSION);
|
||||
|
||||
Setting::init();
|
||||
|
||||
// Instance UserActivityLogger now that the settings are loaded
|
||||
|
|
|
@ -17,6 +17,8 @@
|
|||
#include "SceneScriptingInterface.h"
|
||||
|
||||
OctreePacketProcessor::OctreePacketProcessor() {
|
||||
setObjectName("Octree Packet Processor");
|
||||
|
||||
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
|
||||
|
||||
packetReceiver.registerDirectListenerForTypes({ PacketType::OctreeStats, PacketType::EntityData, PacketType::EntityErase },
|
||||
|
|
|
@ -38,7 +38,7 @@ class AccountServicesScriptingInterface : public QObject {
|
|||
Q_PROPERTY(QString username READ getUsername NOTIFY myUsernameChanged)
|
||||
Q_PROPERTY(bool loggedIn READ loggedIn NOTIFY loggedInChanged)
|
||||
Q_PROPERTY(QString findableBy READ getFindableBy WRITE setFindableBy NOTIFY findableByChanged)
|
||||
Q_PROPERTY(QUrl metaverseServerURL READ getMetaverseServerURL)
|
||||
Q_PROPERTY(QUrl metaverseServerURL READ getMetaverseServerURL CONSTANT)
|
||||
|
||||
public:
|
||||
static AccountServicesScriptingInterface* getInstance();
|
||||
|
|
|
@ -94,22 +94,6 @@ bool MenuScriptingInterface::menuItemExists(const QString& menu, const QString&
|
|||
return result;
|
||||
}
|
||||
|
||||
void MenuScriptingInterface::addActionGroup(const QString& groupName, const QStringList& actionList,
|
||||
const QString& selected) {
|
||||
static const char* slot = SLOT(menuItemTriggered());
|
||||
QMetaObject::invokeMethod(Menu::getInstance(), "addActionGroup",
|
||||
Q_ARG(const QString&, groupName),
|
||||
Q_ARG(const QStringList&, actionList),
|
||||
Q_ARG(const QString&, selected),
|
||||
Q_ARG(QObject*, this),
|
||||
Q_ARG(const char*, slot));
|
||||
}
|
||||
|
||||
void MenuScriptingInterface::removeActionGroup(const QString& groupName) {
|
||||
QMetaObject::invokeMethod(Menu::getInstance(), "removeActionGroup",
|
||||
Q_ARG(const QString&, groupName));
|
||||
}
|
||||
|
||||
bool MenuScriptingInterface::isOptionChecked(const QString& menuOption) {
|
||||
if (QThread::currentThread() == qApp->thread()) {
|
||||
return Menu::getInstance()->isOptionChecked(menuOption);
|
||||
|
@ -147,19 +131,3 @@ void MenuScriptingInterface::setMenuEnabled(const QString& menuOption, bool isCh
|
|||
void MenuScriptingInterface::triggerOption(const QString& menuOption) {
|
||||
QMetaObject::invokeMethod(Menu::getInstance(), "triggerOption", Q_ARG(const QString&, menuOption));
|
||||
}
|
||||
|
||||
void MenuScriptingInterface::closeInfoView(const QString& path) {
|
||||
QMetaObject::invokeMethod(Menu::getInstance(), "closeInfoView", Q_ARG(const QString&, path));
|
||||
}
|
||||
|
||||
bool MenuScriptingInterface::isInfoViewVisible(const QString& path) {
|
||||
if (QThread::currentThread() == qApp->thread()) {
|
||||
return Menu::getInstance()->isInfoViewVisible(path);
|
||||
}
|
||||
|
||||
bool result;
|
||||
BLOCKING_INVOKE_METHOD(Menu::getInstance(), "isInfoViewVisible",
|
||||
Q_RETURN_ARG(bool, result), Q_ARG(const QString&, path));
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
|
@ -163,13 +163,6 @@ public slots:
|
|||
*/
|
||||
bool menuItemExists(const QString& menuName, const QString& menuitem);
|
||||
|
||||
/**
|
||||
* TODO: Not working; don't document until fixed.
|
||||
*/
|
||||
void addActionGroup(const QString& groupName, const QStringList& actionList,
|
||||
const QString& selected = QString());
|
||||
void removeActionGroup(const QString& groupName);
|
||||
|
||||
/**jsdoc
|
||||
* Check whether a checkable menu item is checked.
|
||||
* @function Menu.isOptionChecked
|
||||
|
@ -222,12 +215,6 @@ public slots:
|
|||
*/
|
||||
void setMenuEnabled(const QString& menuName, bool isEnabled);
|
||||
|
||||
/**
|
||||
* TODO: Not used or useful; will not document until used.
|
||||
*/
|
||||
void closeInfoView(const QString& path);
|
||||
bool isInfoViewVisible(const QString& path);
|
||||
|
||||
signals:
|
||||
/**jsdoc
|
||||
* Triggered when a menu item is clicked (or triggered by {@link Menu.triggerOption}).
|
||||
|
|
|
@ -32,20 +32,6 @@ static const QString LAST_BROWSE_LOCATION_SETTING = "LastBrowseLocation";
|
|||
static const QString LAST_BROWSE_ASSETS_LOCATION_SETTING = "LastBrowseAssetsLocation";
|
||||
|
||||
|
||||
QScriptValue CustomPromptResultToScriptValue(QScriptEngine* engine, const CustomPromptResult& result) {
|
||||
if (!result.value.isValid()) {
|
||||
return QScriptValue::UndefinedValue;
|
||||
}
|
||||
|
||||
Q_ASSERT(result.value.userType() == qMetaTypeId<QVariantMap>());
|
||||
return engine->toScriptValue(result.value.toMap());
|
||||
}
|
||||
|
||||
void CustomPromptResultFromScriptValue(const QScriptValue& object, CustomPromptResult& result) {
|
||||
result.value = object.toVariant();
|
||||
}
|
||||
|
||||
|
||||
WindowScriptingInterface::WindowScriptingInterface() {
|
||||
const DomainHandler& domainHandler = DependencyManager::get<NodeList>()->getDomainHandler();
|
||||
connect(&domainHandler, &DomainHandler::connectedToDomain, this, &WindowScriptingInterface::domainChanged);
|
||||
|
@ -143,14 +129,6 @@ void WindowScriptingInterface::disconnectedFromDomain() {
|
|||
emit domainChanged("");
|
||||
}
|
||||
|
||||
CustomPromptResult WindowScriptingInterface::customPrompt(const QVariant& config) {
|
||||
CustomPromptResult result;
|
||||
bool ok = false;
|
||||
auto configMap = config.toMap();
|
||||
result.value = OffscreenUi::getCustomInfo(OffscreenUi::ICON_NONE, "", configMap, &ok);
|
||||
return ok ? result : CustomPromptResult();
|
||||
}
|
||||
|
||||
QString fixupPathForMac(const QString& directory) {
|
||||
// On OS X `directory` does not work as expected unless a file is included in the path, so we append a bogus
|
||||
// filename if the directory is valid.
|
||||
|
|
|
@ -22,17 +22,6 @@
|
|||
|
||||
#include <DependencyManager.h>
|
||||
|
||||
class CustomPromptResult {
|
||||
public:
|
||||
QVariant value;
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(CustomPromptResult);
|
||||
|
||||
QScriptValue CustomPromptResultToScriptValue(QScriptEngine* engine, const CustomPromptResult& result);
|
||||
void CustomPromptResultFromScriptValue(const QScriptValue& object, CustomPromptResult& result);
|
||||
|
||||
|
||||
/**jsdoc
|
||||
* The Window API provides various facilities not covered elsewhere: window dimensions, window focus, normal or entity camera
|
||||
* view, clipboard, announcements, user connections, common dialog boxes, snapshots, file import, domain changes, domain
|
||||
|
@ -142,15 +131,6 @@ public slots:
|
|||
*/
|
||||
void promptAsync(const QString& message = "", const QString& defaultText = "");
|
||||
|
||||
/**jsdoc
|
||||
* Prompt the user for input in a custom, modal dialog.
|
||||
* @deprecated This function is deprecated and will soon be removed.
|
||||
* @function Window.customPrompt
|
||||
* @param {object} config - Configures the modal dialog.
|
||||
* @returns {object} The user's response.
|
||||
*/
|
||||
CustomPromptResult customPrompt(const QVariant& config);
|
||||
|
||||
/**jsdoc
|
||||
* Prompt the user to choose a directory. Displays a modal dialog that navigates the directory tree.
|
||||
* @function Window.browseDir
|
||||
|
@ -325,6 +305,7 @@ public slots:
|
|||
* {@link Window.stillSnapshotTaken|stillSnapshotTaken} is emitted; when a still image plus moving images are captured,
|
||||
* {@link Window.processingGifStarted|processingGifStarted} and {@link Window.processingGifCompleted|processingGifCompleted}
|
||||
* are emitted. The path to store the snapshots and the length of the animated GIF to capture are specified in Settings >
|
||||
* NOTE: to provide a non-default value - all previous parameters must be provided.
|
||||
* General > Snapshots.
|
||||
* @function Window.takeSnapshot
|
||||
* @param {boolean} notify=true - This value is passed on through the {@link Window.stillSnapshotTaken|stillSnapshotTaken}
|
||||
|
@ -334,8 +315,10 @@ public slots:
|
|||
* @param {number} aspectRatio=0 - The width/height ratio of the snapshot required. If the value is <code>0</code> the
|
||||
* full resolution is used (window dimensions in desktop mode; HMD display dimensions in HMD mode), otherwise one of the
|
||||
* dimensions is adjusted in order to match the aspect ratio.
|
||||
* @param {string} filename=QString() - If this value is not null then the image will be saved to this filename, with an appended ",jpg".
|
||||
* otherwise, the image will be saved as 'hifi-snap-by-<user name>-YYYY-MM-DD_HH-MM-SS'
|
||||
* @param {string} filename="" - If this parameter is not given, the image will be saved as 'hifi-snap-by-<user name>-YYYY-MM-DD_HH-MM-SS'.
|
||||
* If this parameter is <code>""</code> then the image will be saved as ".jpg".
|
||||
* Otherwise, the image will be saved to this filename, with an appended ".jpg".
|
||||
*
|
||||
* @example <caption>Using the snapshot function and signals.</caption>
|
||||
* function onStillSnapshotTaken(path, notify) {
|
||||
* print("Still snapshot taken: " + path);
|
||||
|
@ -357,15 +340,18 @@ public slots:
|
|||
* var notify = true;
|
||||
* var animated = true;
|
||||
* var aspect = 1920 / 1080;
|
||||
* var filename = QString();
|
||||
* var filename = "";
|
||||
* Window.takeSnapshot(notify, animated, aspect, filename);
|
||||
*/
|
||||
void takeSnapshot(bool notify = true, bool includeAnimated = false, float aspectRatio = 0.0f, const QString& filename = QString());
|
||||
|
||||
/**jsdoc
|
||||
* Takes a still snapshot of the current view from the secondary camera that can be set up through the {@link Render} API.
|
||||
* NOTE: to provide a non-default value - all previous parameters must be provided.
|
||||
* @function Window.takeSecondaryCameraSnapshot
|
||||
* @param {string} filename=QString() - If this value is not null then the image will be saved to this filename, with an appended ".jpg"
|
||||
* @param {string} filename="" - If this parameter is not given, the image will be saved as 'hifi-snap-by-<user name>-YYYY-MM-DD_HH-MM-SS'.
|
||||
* If this parameter is <code>""</code> then the image will be saved as ".jpg".
|
||||
* Otherwise, the image will be saved to this filename, with an appended ".jpg".
|
||||
*
|
||||
* var filename = QString();
|
||||
*/
|
||||
|
|
|
@ -42,8 +42,6 @@ private:
|
|||
int _domainStatusBorder;
|
||||
int _magnifierBorder;
|
||||
|
||||
ivec2 _previousBorderSize{ -1 };
|
||||
|
||||
gpu::TexturePointer _uiTexture;
|
||||
gpu::TexturePointer _overlayDepthTexture;
|
||||
gpu::TexturePointer _overlayColorTexture;
|
||||
|
|
|
@ -125,13 +125,6 @@ Cube3DOverlay* Cube3DOverlay::createClone() const {
|
|||
|
||||
void Cube3DOverlay::setProperties(const QVariantMap& properties) {
|
||||
Volume3DOverlay::setProperties(properties);
|
||||
|
||||
auto borderSize = properties["borderSize"];
|
||||
|
||||
if (borderSize.isValid()) {
|
||||
float value = borderSize.toFloat();
|
||||
setBorderSize(value);
|
||||
}
|
||||
}
|
||||
|
||||
/**jsdoc
|
||||
|
@ -177,14 +170,8 @@ void Cube3DOverlay::setProperties(const QVariantMap& properties) {
|
|||
* <code>parentID</code> is an avatar skeleton. A value of <code>65535</code> means "no joint".
|
||||
*
|
||||
* @property {Vec3} dimensions - The dimensions of the overlay. Synonyms: <code>scale</code>, <code>size</code>.
|
||||
*
|
||||
* @property {number} borderSize - Not used.
|
||||
*/
|
||||
QVariant Cube3DOverlay::getProperty(const QString& property) {
|
||||
if (property == "borderSize") {
|
||||
return _borderSize;
|
||||
}
|
||||
|
||||
return Volume3DOverlay::getProperty(property);
|
||||
}
|
||||
|
||||
|
|
|
@ -29,10 +29,6 @@ public:
|
|||
|
||||
virtual Cube3DOverlay* createClone() const override;
|
||||
|
||||
float getBorderSize() const { return _borderSize; }
|
||||
|
||||
void setBorderSize(float value) { _borderSize = value; }
|
||||
|
||||
void setProperties(const QVariantMap& properties) override;
|
||||
QVariant getProperty(const QString& property) override;
|
||||
|
||||
|
@ -41,7 +37,6 @@ protected:
|
|||
Transform evalRenderTransform() override;
|
||||
|
||||
private:
|
||||
float _borderSize;
|
||||
// edges on a cube
|
||||
std::array<int, 12> _geometryIds;
|
||||
};
|
||||
|
|
|
@ -388,21 +388,6 @@ QString Overlays::getOverlayType(OverlayID overlayId) {
|
|||
return "";
|
||||
}
|
||||
|
||||
QObject* Overlays::getOverlayObject(OverlayID id) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QObject* result;
|
||||
PROFILE_RANGE(script, __FUNCTION__);
|
||||
BLOCKING_INVOKE_METHOD(this, "getOverlayObject", Q_RETURN_ARG(QObject*, result), Q_ARG(OverlayID, id));
|
||||
return result;
|
||||
}
|
||||
|
||||
Overlay::Pointer thisOverlay = getOverlay(id);
|
||||
if (thisOverlay) {
|
||||
return qobject_cast<QObject*>(&(*thisOverlay));
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
OverlayID Overlays::getOverlayAtPoint(const glm::vec2& point) {
|
||||
if (!_enabled) {
|
||||
return UNKNOWN_OVERLAY_ID;
|
||||
|
|
|
@ -98,6 +98,11 @@ public:
|
|||
OverlayID addOverlay(Overlay* overlay) { return addOverlay(Overlay::Pointer(overlay)); }
|
||||
OverlayID addOverlay(const Overlay::Pointer& overlay);
|
||||
|
||||
RayToOverlayIntersectionResult findRayIntersectionVector(const PickRay& ray, bool precisionPicking,
|
||||
const QVector<OverlayID>& overlaysToInclude,
|
||||
const QVector<OverlayID>& overlaysToDiscard,
|
||||
bool visibleOnly = false, bool collidableOnly = false);
|
||||
|
||||
bool mousePressEvent(QMouseEvent* event);
|
||||
bool mouseDoublePressEvent(QMouseEvent* event);
|
||||
bool mouseReleaseEvent(QMouseEvent* event);
|
||||
|
@ -227,15 +232,6 @@ public slots:
|
|||
*/
|
||||
QString getOverlayType(OverlayID overlayId);
|
||||
|
||||
/**jsdoc
|
||||
* Get the overlay script object.
|
||||
* @function Overlays.getOverlayObject
|
||||
* @deprecated This function is deprecated and will soon be removed.
|
||||
* @param {Uuid} overlayID - The ID of the overlay to get the script object of.
|
||||
* @returns {object} The script object for the overlay if found.
|
||||
*/
|
||||
QObject* getOverlayObject(OverlayID id);
|
||||
|
||||
/**jsdoc
|
||||
* Get the ID of the 2D overlay at a particular point on the screen or HUD.
|
||||
* @function Overlays.getOverlayAtPoint
|
||||
|
@ -356,28 +352,6 @@ public slots:
|
|||
bool visibleOnly = false,
|
||||
bool collidableOnly = false);
|
||||
|
||||
// TODO: Apart from the name, this function signature on JavaScript is identical to that of findRayIntersection() and should
|
||||
// probably be removed from the JavaScript API so as not to cause confusion.
|
||||
/**jsdoc
|
||||
* Find the closest 3D overlay intersected by a {@link PickRay}.
|
||||
* @function Overlays.findRayIntersectionVector
|
||||
* @deprecated Use {@link Overlays.findRayIntersection} instead; it has identical parameters and results.
|
||||
* @param {PickRay} pickRay - The PickRay to use for finding overlays.
|
||||
* @param {boolean} [precisionPicking=false] - <em>Unused</em>; exists to match Entity API.
|
||||
* @param {Array.<Uuid>} [overlayIDsToInclude=[]] - Whitelist for intersection test. If empty then the result isn't limited
|
||||
* to overlays in the list.
|
||||
* @param {Array.<Uuid>} [overlayIDsToExclude=[]] - Blacklist for intersection test. If empty then the result doesn't
|
||||
* exclude overlays in the list.
|
||||
* @param {boolean} [visibleOnly=false] - <em>Unused</em>; exists to match Entity API.
|
||||
* @param {boolean} [collidableOnly=false] - <em>Unused</em>; exists to match Entity API.
|
||||
* @returns {Overlays.RayToOverlayIntersectionResult} The closest 3D overlay intersected by <code>pickRay</code>, taking
|
||||
* into account <code>overlayIDsToInclude</code> and <code>overlayIDsToExclude</code> if they're not empty.
|
||||
*/
|
||||
RayToOverlayIntersectionResult findRayIntersectionVector(const PickRay& ray, bool precisionPicking,
|
||||
const QVector<OverlayID>& overlaysToInclude,
|
||||
const QVector<OverlayID>& overlaysToDiscard,
|
||||
bool visibleOnly = false, bool collidableOnly = false);
|
||||
|
||||
/**jsdoc
|
||||
* Return a list of 3D overlays with bounding boxes that touch a search sphere.
|
||||
* @function Overlays.findOverlays
|
||||
|
|
|
@ -99,13 +99,6 @@ void Shape3DOverlay::setProperties(const QVariantMap& properties) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto borderSize = properties["borderSize"];
|
||||
|
||||
if (borderSize.isValid()) {
|
||||
float value = borderSize.toFloat();
|
||||
setBorderSize(value);
|
||||
}
|
||||
}
|
||||
|
||||
/**jsdoc
|
||||
|
@ -153,13 +146,8 @@ void Shape3DOverlay::setProperties(const QVariantMap& properties) {
|
|||
* @property {Vec3} dimensions - The dimensions of the overlay. Synonyms: <code>scale</code>, <code>size</code>.
|
||||
*
|
||||
* @property {Shape} shape=Hexagon - The geometrical shape of the overlay.
|
||||
* @property {number} borderSize - Not used.
|
||||
*/
|
||||
QVariant Shape3DOverlay::getProperty(const QString& property) {
|
||||
if (property == "borderSize") {
|
||||
return _borderSize;
|
||||
}
|
||||
|
||||
if (property == "shape") {
|
||||
return shapeStrings[_shape];
|
||||
}
|
||||
|
|
|
@ -30,10 +30,6 @@ public:
|
|||
|
||||
virtual Shape3DOverlay* createClone() const override;
|
||||
|
||||
float getBorderSize() const { return _borderSize; }
|
||||
|
||||
void setBorderSize(float value) { _borderSize = value; }
|
||||
|
||||
void setProperties(const QVariantMap& properties) override;
|
||||
QVariant getProperty(const QString& property) override;
|
||||
|
||||
|
@ -42,7 +38,6 @@ protected:
|
|||
Transform evalRenderTransform() override;
|
||||
|
||||
private:
|
||||
float _borderSize;
|
||||
GeometryCache::Shape _shape { GeometryCache::Hexagon };
|
||||
};
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
#include <GeometryUtil.h>
|
||||
#include <gl/GLHelpers.h>
|
||||
#include <scripting/HMDScriptingInterface.h>
|
||||
#include <scripting/WindowScriptingInterface.h>
|
||||
#include <ui/OffscreenQmlSurface.h>
|
||||
#include <ui/OffscreenQmlSurfaceCache.h>
|
||||
#include <ui/TabletScriptingInterface.h>
|
||||
|
@ -233,6 +234,7 @@ void Web3DOverlay::setupQmlSurface() {
|
|||
_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("pathToFonts", "../../");
|
||||
|
||||
|
|
|
@ -434,9 +434,13 @@ void Rig::setJointRotation(int index, bool valid, const glm::quat& rotation, flo
|
|||
|
||||
bool Rig::getJointPositionInWorldFrame(int jointIndex, glm::vec3& position, glm::vec3 translation, glm::quat rotation) const {
|
||||
bool success { false };
|
||||
if (QThread::currentThread() == thread()) {
|
||||
glm::vec3 originalPosition = position;
|
||||
bool onOwnerThread = (QThread::currentThread() == thread());
|
||||
glm::vec3 poseSetTrans;
|
||||
if (onOwnerThread) {
|
||||
if (isIndexValid(jointIndex)) {
|
||||
position = (rotation * _internalPoseSet._absolutePoses[jointIndex].trans()) + translation;
|
||||
poseSetTrans = _internalPoseSet._absolutePoses[jointIndex].trans();
|
||||
position = (rotation * poseSetTrans) + translation;
|
||||
success = true;
|
||||
} else {
|
||||
success = false;
|
||||
|
@ -444,7 +448,8 @@ bool Rig::getJointPositionInWorldFrame(int jointIndex, glm::vec3& position, glm:
|
|||
} else {
|
||||
QReadLocker readLock(&_externalPoseSetLock);
|
||||
if (jointIndex >= 0 && jointIndex < (int)_externalPoseSet._absolutePoses.size()) {
|
||||
position = (rotation * _externalPoseSet._absolutePoses[jointIndex].trans()) + translation;
|
||||
poseSetTrans = _externalPoseSet._absolutePoses[jointIndex].trans();
|
||||
position = (rotation * poseSetTrans) + translation;
|
||||
success = true;
|
||||
} else {
|
||||
success = false;
|
||||
|
@ -452,7 +457,14 @@ bool Rig::getJointPositionInWorldFrame(int jointIndex, glm::vec3& position, glm:
|
|||
}
|
||||
|
||||
if (isNaN(position)) {
|
||||
qCWarning(animation) << "Rig::getJointPositionInWorldFrame produces NaN";
|
||||
qCWarning(animation) << "Rig::getJointPositionInWorldFrame produced NaN."
|
||||
<< " is owner thread = " << onOwnerThread
|
||||
<< " position = " << originalPosition
|
||||
<< " translation = " << translation
|
||||
<< " rotation = " << rotation
|
||||
<< " poseSetTrans = " << poseSetTrans
|
||||
<< " success = " << success
|
||||
<< " jointIndex = " << jointIndex;
|
||||
success = false;
|
||||
position = glm::vec3(0.0f);
|
||||
}
|
||||
|
|
|
@ -31,6 +31,8 @@ SkeletonModel::SkeletonModel(Avatar* owningAvatar, QObject* parent) :
|
|||
_defaultEyeModelPosition(glm::vec3(0.0f, 0.0f, 0.0f)),
|
||||
_headClipDistance(DEFAULT_NEAR_CLIP)
|
||||
{
|
||||
// SkeletonModels, and by extention Avatars, use Dual Quaternion skinning.
|
||||
_useDualQuaternionSkinning = true;
|
||||
assert(_owningAvatar);
|
||||
}
|
||||
|
||||
|
|
|
@ -373,7 +373,7 @@ class AvatarData : public QObject, public SpatiallyNestable {
|
|||
// The result is unique among all avatars present at the time.
|
||||
Q_PROPERTY(QString sessionDisplayName READ getSessionDisplayName WRITE setSessionDisplayName NOTIFY sessionDisplayNameChanged)
|
||||
Q_PROPERTY(bool lookAtSnappingEnabled MEMBER _lookAtSnappingEnabled NOTIFY lookAtSnappingChanged)
|
||||
Q_PROPERTY(QString skeletonModelURL READ getSkeletonModelURLFromScript WRITE setSkeletonModelURLFromScript)
|
||||
Q_PROPERTY(QString skeletonModelURL READ getSkeletonModelURLFromScript WRITE setSkeletonModelURLFromScript NOTIFY skeletonModelURLChanged)
|
||||
Q_PROPERTY(QVector<AttachmentData> attachmentData READ getAttachmentData WRITE setAttachmentData)
|
||||
|
||||
Q_PROPERTY(QStringList jointNames READ getJointNames)
|
||||
|
@ -702,6 +702,7 @@ public:
|
|||
signals:
|
||||
void displayNameChanged();
|
||||
void sessionDisplayNameChanged();
|
||||
void skeletonModelURLChanged();
|
||||
void lookAtSnappingChanged(bool enabled);
|
||||
void sessionUUIDChanged();
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ ScriptAvatarData::ScriptAvatarData(AvatarSharedPointer avatarData) :
|
|||
{
|
||||
QObject::connect(avatarData.get(), &AvatarData::displayNameChanged, this, &ScriptAvatarData::displayNameChanged);
|
||||
QObject::connect(avatarData.get(), &AvatarData::sessionDisplayNameChanged, this, &ScriptAvatarData::sessionDisplayNameChanged);
|
||||
QObject::connect(avatarData.get(), &AvatarData::skeletonModelURLChanged, this, &ScriptAvatarData::skeletonModelURLChanged);
|
||||
QObject::connect(avatarData.get(), &AvatarData::lookAtSnappingChanged, this, &ScriptAvatarData::lookAtSnappingChanged);
|
||||
}
|
||||
|
||||
|
|
|
@ -51,7 +51,7 @@ class ScriptAvatarData : public QObject {
|
|||
//
|
||||
// ATTACHMENT AND JOINT PROPERTIES
|
||||
//
|
||||
Q_PROPERTY(QString skeletonModelURL READ getSkeletonModelURLFromScript)
|
||||
Q_PROPERTY(QString skeletonModelURL READ getSkeletonModelURLFromScript NOTIFY skeletonModelURLChanged)
|
||||
Q_PROPERTY(QVector<AttachmentData> attachmentData READ getAttachmentData)
|
||||
Q_PROPERTY(QStringList jointNames READ getJointNames)
|
||||
|
||||
|
@ -132,6 +132,7 @@ public:
|
|||
signals:
|
||||
void displayNameChanged();
|
||||
void sessionDisplayNameChanged();
|
||||
void skeletonModelURLChanged();
|
||||
void lookAtSnappingChanged(bool enabled);
|
||||
|
||||
public slots:
|
||||
|
|
|
@ -133,6 +133,9 @@ void RenderableModelEntityItem::doInitialModelSimulation() {
|
|||
model->setRotation(getWorldOrientation());
|
||||
model->setTranslation(getWorldPosition());
|
||||
|
||||
glm::vec3 scale = model->getScale();
|
||||
model->setUseDualQuaternionSkinning(!isNonUniformScale(scale));
|
||||
|
||||
if (_needsInitialSimulation) {
|
||||
model->simulate(0.0f);
|
||||
_needsInitialSimulation = false;
|
||||
|
@ -243,6 +246,8 @@ void RenderableModelEntityItem::updateModelBounds() {
|
|||
}
|
||||
|
||||
if (updateRenderItems) {
|
||||
glm::vec3 scale = model->getScale();
|
||||
model->setUseDualQuaternionSkinning(!isNonUniformScale(scale));
|
||||
model->updateRenderItems();
|
||||
}
|
||||
}
|
||||
|
@ -1538,4 +1543,4 @@ void ModelEntityRenderer::processMaterials() {
|
|||
material.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -105,6 +105,7 @@ inline QScriptValue convertScriptValue(QScriptEngine* e, const glm::vec2& v) { r
|
|||
inline QScriptValue convertScriptValue(QScriptEngine* e, const glm::vec3& v) { return vec3toScriptValue(e, v); }
|
||||
inline QScriptValue convertScriptValue(QScriptEngine* e, float v) { return QScriptValue(v); }
|
||||
inline QScriptValue convertScriptValue(QScriptEngine* e, int v) { return QScriptValue(v); }
|
||||
inline QScriptValue convertScriptValue(QScriptEngine* e, bool v) { return QScriptValue(v); }
|
||||
inline QScriptValue convertScriptValue(QScriptEngine* e, quint16 v) { return QScriptValue(v); }
|
||||
inline QScriptValue convertScriptValue(QScriptEngine* e, quint32 v) { return QScriptValue(v); }
|
||||
inline QScriptValue convertScriptValue(QScriptEngine* e, quint64 v) { return QScriptValue((qsreal)v); }
|
||||
|
|
|
@ -101,6 +101,11 @@ bool EntityScriptingInterface::canWriteAssets() {
|
|||
return nodeList->getThisNodeCanWriteAssets();
|
||||
}
|
||||
|
||||
bool EntityScriptingInterface::canReplaceContent() {
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
return nodeList->getThisNodeCanReplaceContent();
|
||||
}
|
||||
|
||||
void EntityScriptingInterface::setEntityTree(EntityTreePointer elementTree) {
|
||||
if (_entityTree) {
|
||||
disconnect(_entityTree.get(), &EntityTree::addingEntity, this, &EntityScriptingInterface::addingEntity);
|
||||
|
|
|
@ -144,6 +144,12 @@ public slots:
|
|||
*/
|
||||
Q_INVOKABLE bool canWriteAssets();
|
||||
|
||||
/**jsdoc
|
||||
* @function Entities.canReplaceContent
|
||||
* @return {bool} `true` if the DomainServer will allow this Node/Avatar to replace the domain's content set
|
||||
*/
|
||||
Q_INVOKABLE bool canReplaceContent();
|
||||
|
||||
/**jsdoc
|
||||
* Add a new entity with the specified properties. If `clientOnly` is true, the entity will
|
||||
* not be sent to the server and will only be visible/accessible on the local client.
|
||||
|
|
|
@ -97,6 +97,7 @@ EntityItemPointer EntityTypes::constructEntityItem(EntityType entityType, const
|
|||
auto mutableProperties = properties;
|
||||
mutableProperties.markAllChanged();
|
||||
newEntityItem = factory(entityID, mutableProperties);
|
||||
newEntityItem->moveToThread(qApp->thread());
|
||||
}
|
||||
return newEntityItem;
|
||||
}
|
||||
|
|
|
@ -328,14 +328,14 @@ void Socket::checkForReadyReadBackup() {
|
|||
void Socket::readPendingDatagrams() {
|
||||
int packetSizeWithHeader = -1;
|
||||
|
||||
while ((packetSizeWithHeader = _udpSocket.pendingDatagramSize()) != -1) {
|
||||
while (_udpSocket.hasPendingDatagrams() && (packetSizeWithHeader = _udpSocket.pendingDatagramSize()) != -1) {
|
||||
|
||||
// we're reading a packet so re-start the readyRead backup timer
|
||||
_readyReadBackupTimer->start();
|
||||
|
||||
// grab a time point we can mark as the receive time of this packet
|
||||
auto receiveTime = p_high_resolution_clock::now();
|
||||
|
||||
|
||||
// setup a HifiSockAddr to read into
|
||||
HifiSockAddr senderSockAddr;
|
||||
|
||||
|
|
|
@ -331,9 +331,9 @@ void OffscreenSurface::finishQmlLoad(QQmlComponent* qmlComponent,
|
|||
qmlComponent->deleteLater();
|
||||
|
||||
onItemCreated(qmlContext, newItem);
|
||||
connect(newItem, SIGNAL(sendToScript(QVariant)), this, SIGNAL(fromQml(QVariant)));
|
||||
|
||||
if (!rootCreated) {
|
||||
connect(newItem, SIGNAL(sendToScript(QVariant)), this, SIGNAL(fromQml(QVariant)));
|
||||
onRootCreated();
|
||||
emit rootItemCreated(newItem);
|
||||
// Call this callback after rootitem is set, otherwise VrMenu wont work
|
||||
|
|
|
@ -20,16 +20,32 @@ using namespace render;
|
|||
CauterizedMeshPartPayload::CauterizedMeshPartPayload(ModelPointer model, int meshIndex, int partIndex, int shapeIndex, const Transform& transform, const Transform& offsetTransform)
|
||||
: ModelMeshPartPayload(model, meshIndex, partIndex, shapeIndex, transform, offsetTransform) {}
|
||||
|
||||
void CauterizedMeshPartPayload::updateClusterBuffer(const std::vector<TransformType>& clusterTransforms, const std::vector<TransformType>& cauterizedClusterTransforms) {
|
||||
ModelMeshPartPayload::updateClusterBuffer(clusterTransforms);
|
||||
void CauterizedMeshPartPayload::updateClusterBuffer(const std::vector<glm::mat4>& clusterMatrices,
|
||||
const std::vector<glm::mat4>& cauterizedClusterMatrices) {
|
||||
ModelMeshPartPayload::updateClusterBuffer(clusterMatrices);
|
||||
|
||||
if (cauterizedClusterTransforms.size() > 1) {
|
||||
if (cauterizedClusterMatrices.size() > 1) {
|
||||
if (!_cauterizedClusterBuffer) {
|
||||
_cauterizedClusterBuffer = std::make_shared<gpu::Buffer>(cauterizedClusterTransforms.size() * sizeof(TransformType),
|
||||
(const gpu::Byte*) cauterizedClusterTransforms.data());
|
||||
_cauterizedClusterBuffer = std::make_shared<gpu::Buffer>(cauterizedClusterMatrices.size() * sizeof(glm::mat4),
|
||||
(const gpu::Byte*) cauterizedClusterMatrices.data());
|
||||
} else {
|
||||
_cauterizedClusterBuffer->setSubData(0, cauterizedClusterTransforms.size() * sizeof(TransformType),
|
||||
(const gpu::Byte*) cauterizedClusterTransforms.data());
|
||||
_cauterizedClusterBuffer->setSubData(0, cauterizedClusterMatrices.size() * sizeof(glm::mat4),
|
||||
(const gpu::Byte*) cauterizedClusterMatrices.data());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CauterizedMeshPartPayload::updateClusterBuffer(const std::vector<Model::TransformDualQuaternion>& clusterDualQuaternions,
|
||||
const std::vector<Model::TransformDualQuaternion>& cauterizedClusterDualQuaternions) {
|
||||
ModelMeshPartPayload::updateClusterBuffer(clusterDualQuaternions);
|
||||
|
||||
if (cauterizedClusterDualQuaternions.size() > 1) {
|
||||
if (!_cauterizedClusterBuffer) {
|
||||
_cauterizedClusterBuffer = std::make_shared<gpu::Buffer>(cauterizedClusterDualQuaternions.size() * sizeof(Model::TransformDualQuaternion),
|
||||
(const gpu::Byte*) cauterizedClusterDualQuaternions.data());
|
||||
} else {
|
||||
_cauterizedClusterBuffer->setSubData(0, cauterizedClusterDualQuaternions.size() * sizeof(Model::TransformDualQuaternion),
|
||||
(const gpu::Byte*) cauterizedClusterDualQuaternions.data());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,13 +15,13 @@ class CauterizedMeshPartPayload : public ModelMeshPartPayload {
|
|||
public:
|
||||
CauterizedMeshPartPayload(ModelPointer model, int meshIndex, int partIndex, int shapeIndex, const Transform& transform, const Transform& offsetTransform);
|
||||
|
||||
#if defined(SKIN_DQ)
|
||||
using TransformType = Model::TransformDualQuaternion;
|
||||
#else
|
||||
using TransformType = glm::mat4;
|
||||
#endif
|
||||
// matrix palette skinning
|
||||
void updateClusterBuffer(const std::vector<glm::mat4>& clusterMatrices,
|
||||
const std::vector<glm::mat4>& cauterizedClusterMatrices);
|
||||
|
||||
void updateClusterBuffer(const std::vector<TransformType>& clusterTransforms, const std::vector<TransformType>& cauterizedClusterTransforms);
|
||||
// dual quaternion skinning
|
||||
void updateClusterBuffer(const std::vector<Model::TransformDualQuaternion>& clusterDualQuaternions,
|
||||
const std::vector<Model::TransformDualQuaternion>& cauterizedClusterQuaternions);
|
||||
|
||||
void updateTransformForCauterizedMesh(const Transform& renderTransform);
|
||||
|
||||
|
|
|
@ -35,8 +35,13 @@ bool CauterizedModel::updateGeometry() {
|
|||
const FBXGeometry& fbxGeometry = getFBXGeometry();
|
||||
foreach (const FBXMesh& mesh, fbxGeometry.meshes) {
|
||||
Model::MeshState state;
|
||||
state.clusterTransforms.resize(mesh.clusters.size());
|
||||
_cauterizeMeshStates.append(state);
|
||||
if (_useDualQuaternionSkinning) {
|
||||
state.clusterDualQuaternions.resize(mesh.clusters.size());
|
||||
_cauterizeMeshStates.append(state);
|
||||
} else {
|
||||
state.clusterMatrices.resize(mesh.clusters.size());
|
||||
_cauterizeMeshStates.append(state);
|
||||
}
|
||||
}
|
||||
}
|
||||
return needsFullUpdate;
|
||||
|
@ -111,33 +116,33 @@ void CauterizedModel::updateClusterMatrices() {
|
|||
const FBXMesh& mesh = geometry.meshes.at(i);
|
||||
for (int j = 0; j < mesh.clusters.size(); j++) {
|
||||
const FBXCluster& cluster = mesh.clusters.at(j);
|
||||
#if defined(SKIN_DQ)
|
||||
auto jointPose = _rig.getJointPose(cluster.jointIndex);
|
||||
Transform jointTransform(jointPose.rot(), jointPose.scale(), jointPose.trans());
|
||||
Transform clusterTransform;
|
||||
Transform::mult(clusterTransform, jointTransform, cluster.inverseBindTransform);
|
||||
state.clusterTransforms[j] = Model::TransformDualQuaternion(clusterTransform);
|
||||
state.clusterTransforms[j].setCauterizationParameters(0.0f, jointPose.trans());
|
||||
#else
|
||||
auto jointMatrix = _rig.getJointTransform(cluster.jointIndex);
|
||||
glm_mat4u_mul(jointMatrix, cluster.inverseBindMatrix, state.clusterTransforms[j]);
|
||||
#endif
|
||||
if (_useDualQuaternionSkinning) {
|
||||
auto jointPose = _rig.getJointPose(cluster.jointIndex);
|
||||
Transform jointTransform(jointPose.rot(), jointPose.scale(), jointPose.trans());
|
||||
Transform clusterTransform;
|
||||
Transform::mult(clusterTransform, jointTransform, cluster.inverseBindTransform);
|
||||
state.clusterDualQuaternions[j] = Model::TransformDualQuaternion(clusterTransform);
|
||||
state.clusterDualQuaternions[j].setCauterizationParameters(0.0f, jointPose.trans());
|
||||
} else {
|
||||
auto jointMatrix = _rig.getJointTransform(cluster.jointIndex);
|
||||
glm_mat4u_mul(jointMatrix, cluster.inverseBindMatrix, state.clusterMatrices[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// as an optimization, don't build cautrizedClusterMatrices if the boneSet is empty.
|
||||
if (!_cauterizeBoneSet.empty()) {
|
||||
#if defined(SKIN_DQ)
|
||||
|
||||
AnimPose cauterizePose = _rig.getJointPose(geometry.neckJointIndex);
|
||||
cauterizePose.scale() = glm::vec3(0.0001f, 0.0001f, 0.0001f);
|
||||
#else
|
||||
|
||||
static const glm::mat4 zeroScale(
|
||||
glm::vec4(0.0001f, 0.0f, 0.0f, 0.0f),
|
||||
glm::vec4(0.0f, 0.0001f, 0.0f, 0.0f),
|
||||
glm::vec4(0.0f, 0.0f, 0.0001f, 0.0f),
|
||||
glm::vec4(0.0f, 0.0f, 0.0f, 1.0f));
|
||||
auto cauterizeMatrix = _rig.getJointTransform(geometry.neckJointIndex) * zeroScale;
|
||||
#endif
|
||||
|
||||
for (int i = 0; i < _cauterizeMeshStates.size(); i++) {
|
||||
Model::MeshState& state = _cauterizeMeshStates[i];
|
||||
const FBXMesh& mesh = geometry.meshes.at(i);
|
||||
|
@ -145,19 +150,24 @@ void CauterizedModel::updateClusterMatrices() {
|
|||
for (int j = 0; j < mesh.clusters.size(); j++) {
|
||||
const FBXCluster& cluster = mesh.clusters.at(j);
|
||||
|
||||
if (_cauterizeBoneSet.find(cluster.jointIndex) == _cauterizeBoneSet.end()) {
|
||||
// not cauterized so just copy the value from the non-cauterized version.
|
||||
state.clusterTransforms[j] = _meshStates[i].clusterTransforms[j];
|
||||
if (_useDualQuaternionSkinning) {
|
||||
if (_cauterizeBoneSet.find(cluster.jointIndex) == _cauterizeBoneSet.end()) {
|
||||
// not cauterized so just copy the value from the non-cauterized version.
|
||||
state.clusterDualQuaternions[j] = _meshStates[i].clusterDualQuaternions[j];
|
||||
} else {
|
||||
Transform jointTransform(cauterizePose.rot(), cauterizePose.scale(), cauterizePose.trans());
|
||||
Transform clusterTransform;
|
||||
Transform::mult(clusterTransform, jointTransform, cluster.inverseBindTransform);
|
||||
state.clusterDualQuaternions[j] = Model::TransformDualQuaternion(clusterTransform);
|
||||
state.clusterDualQuaternions[j].setCauterizationParameters(1.0f, cauterizePose.trans());
|
||||
}
|
||||
} else {
|
||||
#if defined(SKIN_DQ)
|
||||
Transform jointTransform(cauterizePose.rot(), cauterizePose.scale(), cauterizePose.trans());
|
||||
Transform clusterTransform;
|
||||
Transform::mult(clusterTransform, jointTransform, cluster.inverseBindTransform);
|
||||
state.clusterTransforms[j] = Model::TransformDualQuaternion(clusterTransform);
|
||||
state.clusterTransforms[j].setCauterizationParameters(1.0f, cauterizePose.trans());
|
||||
#else
|
||||
glm_mat4u_mul(cauterizeMatrix, cluster.inverseBindMatrix, state.clusterTransforms[j]);
|
||||
#endif
|
||||
if (_cauterizeBoneSet.find(cluster.jointIndex) == _cauterizeBoneSet.end()) {
|
||||
// not cauterized so just copy the value from the non-cauterized version.
|
||||
state.clusterMatrices[j] = _meshStates[i].clusterMatrices[j];
|
||||
} else {
|
||||
glm_mat4u_mul(cauterizeMatrix, cluster.inverseBindMatrix, state.clusterMatrices[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -215,45 +225,59 @@ void CauterizedModel::updateRenderItems() {
|
|||
|
||||
auto itemID = self->_modelMeshRenderItemIDs[i];
|
||||
auto meshIndex = self->_modelMeshRenderItemShapes[i].meshIndex;
|
||||
auto clusterTransforms(self->getMeshState(meshIndex).clusterTransforms);
|
||||
auto clusterTransformsCauterized(self->getCauterizeMeshState(meshIndex).clusterTransforms);
|
||||
|
||||
const auto& meshState = self->getMeshState(meshIndex);
|
||||
const auto& cauterizedMeshState = self->getCauterizeMeshState(meshIndex);
|
||||
|
||||
bool invalidatePayloadShapeKey = self->shouldInvalidatePayloadShapeKey(meshIndex);
|
||||
bool useDualQuaternionSkinning = self->getUseDualQuaternionSkinning();
|
||||
|
||||
transaction.updateItem<CauterizedMeshPartPayload>(itemID, [modelTransform, clusterTransforms, clusterTransformsCauterized, invalidatePayloadShapeKey,
|
||||
transaction.updateItem<CauterizedMeshPartPayload>(itemID, [modelTransform, meshState, useDualQuaternionSkinning, cauterizedMeshState, invalidatePayloadShapeKey,
|
||||
isWireframe, isVisible, isLayeredInFront, isLayeredInHUD, enableCauterization](CauterizedMeshPartPayload& data) {
|
||||
data.updateClusterBuffer(clusterTransforms, clusterTransformsCauterized);
|
||||
if (useDualQuaternionSkinning) {
|
||||
data.updateClusterBuffer(meshState.clusterDualQuaternions,
|
||||
cauterizedMeshState.clusterDualQuaternions);
|
||||
} else {
|
||||
data.updateClusterBuffer(meshState.clusterMatrices,
|
||||
cauterizedMeshState.clusterMatrices);
|
||||
}
|
||||
|
||||
Transform renderTransform = modelTransform;
|
||||
if (clusterTransforms.size() == 1) {
|
||||
#if defined(SKIN_DQ)
|
||||
Transform transform(clusterTransforms[0].getRotation(),
|
||||
clusterTransforms[0].getScale(),
|
||||
clusterTransforms[0].getTranslation());
|
||||
renderTransform = modelTransform.worldTransform(transform);
|
||||
#else
|
||||
renderTransform = modelTransform.worldTransform(Transform(clusterTransforms[0]));
|
||||
#endif
|
||||
if (useDualQuaternionSkinning) {
|
||||
if (meshState.clusterDualQuaternions.size() == 1) {
|
||||
const auto& dq = meshState.clusterDualQuaternions[0];
|
||||
Transform transform(dq.getRotation(),
|
||||
dq.getScale(),
|
||||
dq.getTranslation());
|
||||
renderTransform = modelTransform.worldTransform(transform);
|
||||
}
|
||||
} else {
|
||||
if (meshState.clusterMatrices.size() == 1) {
|
||||
renderTransform = modelTransform.worldTransform(Transform(meshState.clusterMatrices[0]));
|
||||
}
|
||||
}
|
||||
data.updateTransformForSkinnedMesh(renderTransform, modelTransform);
|
||||
|
||||
renderTransform = modelTransform;
|
||||
if (clusterTransformsCauterized.size() == 1) {
|
||||
#if defined(SKIN_DQ)
|
||||
Transform transform(clusterTransformsCauterized[0].getRotation(),
|
||||
clusterTransformsCauterized[0].getScale(),
|
||||
clusterTransformsCauterized[0].getTranslation());
|
||||
renderTransform = modelTransform.worldTransform(Transform(transform));
|
||||
#else
|
||||
renderTransform = modelTransform.worldTransform(Transform(clusterTransformsCauterized[0]));
|
||||
#endif
|
||||
if (useDualQuaternionSkinning) {
|
||||
if (cauterizedMeshState.clusterDualQuaternions.size() == 1) {
|
||||
const auto& dq = cauterizedMeshState.clusterDualQuaternions[0];
|
||||
Transform transform(dq.getRotation(),
|
||||
dq.getScale(),
|
||||
dq.getTranslation());
|
||||
renderTransform = modelTransform.worldTransform(Transform(transform));
|
||||
}
|
||||
} else {
|
||||
if (cauterizedMeshState.clusterMatrices.size() == 1) {
|
||||
renderTransform = modelTransform.worldTransform(Transform(cauterizedMeshState.clusterMatrices[0]));
|
||||
}
|
||||
}
|
||||
data.updateTransformForCauterizedMesh(renderTransform);
|
||||
|
||||
data.setEnableCauterization(enableCauterization);
|
||||
data.updateKey(isVisible, isLayeredInFront || isLayeredInHUD, render::ItemKey::TAG_BITS_ALL);
|
||||
data.setLayer(isLayeredInFront, isLayeredInHUD);
|
||||
data.setShapeKey(invalidatePayloadShapeKey, isWireframe);
|
||||
data.setShapeKey(invalidatePayloadShapeKey, isWireframe, useDualQuaternionSkinning);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -222,25 +222,35 @@ ModelMeshPartPayload::ModelMeshPartPayload(ModelPointer model, int meshIndex, in
|
|||
_shapeID(shapeIndex) {
|
||||
|
||||
assert(model && model->isLoaded());
|
||||
|
||||
bool useDualQuaternionSkinning = model->getUseDualQuaternionSkinning();
|
||||
|
||||
_blendedVertexBuffer = model->_blendedVertexBuffers[_meshIndex];
|
||||
auto& modelMesh = model->getGeometry()->getMeshes().at(_meshIndex);
|
||||
const Model::MeshState& state = model->getMeshState(_meshIndex);
|
||||
|
||||
updateMeshPart(modelMesh, partIndex);
|
||||
computeAdjustedLocalBound(state.clusterTransforms);
|
||||
|
||||
if (useDualQuaternionSkinning) {
|
||||
computeAdjustedLocalBound(state.clusterDualQuaternions);
|
||||
} else {
|
||||
computeAdjustedLocalBound(state.clusterMatrices);
|
||||
}
|
||||
|
||||
updateTransform(transform, offsetTransform);
|
||||
Transform renderTransform = transform;
|
||||
if (state.clusterTransforms.size() == 1) {
|
||||
#if defined(SKIN_DQ)
|
||||
Transform transform(state.clusterTransforms[0].getRotation(),
|
||||
state.clusterTransforms[0].getScale(),
|
||||
state.clusterTransforms[0].getTranslation());
|
||||
renderTransform = transform.worldTransform(Transform(transform));
|
||||
#else
|
||||
renderTransform = transform.worldTransform(Transform(state.clusterTransforms[0]));
|
||||
#endif
|
||||
|
||||
if (useDualQuaternionSkinning) {
|
||||
if (state.clusterDualQuaternions.size() == 1) {
|
||||
const auto& dq = state.clusterDualQuaternions[0];
|
||||
Transform transform(dq.getRotation(),
|
||||
dq.getScale(),
|
||||
dq.getTranslation());
|
||||
renderTransform = transform.worldTransform(Transform(transform));
|
||||
}
|
||||
} else {
|
||||
if (state.clusterMatrices.size() == 1) {
|
||||
renderTransform = transform.worldTransform(Transform(state.clusterMatrices[0]));
|
||||
}
|
||||
}
|
||||
updateTransformForSkinnedMesh(renderTransform, transform);
|
||||
|
||||
|
@ -270,16 +280,44 @@ void ModelMeshPartPayload::notifyLocationChanged() {
|
|||
|
||||
}
|
||||
|
||||
void ModelMeshPartPayload::updateClusterBuffer(const std::vector<TransformType>& clusterTransforms) {
|
||||
void ModelMeshPartPayload::updateClusterBuffer(const std::vector<glm::mat4>& clusterMatrices) {
|
||||
|
||||
// reset cluster buffer if we change the cluster buffer type
|
||||
if (_clusterBufferType != ClusterBufferType::Matrices) {
|
||||
_clusterBuffer.reset();
|
||||
}
|
||||
_clusterBufferType = ClusterBufferType::Matrices;
|
||||
|
||||
// Once computed the cluster matrices, update the buffer(s)
|
||||
if (clusterTransforms.size() > 1) {
|
||||
if (clusterMatrices.size() > 1) {
|
||||
if (!_clusterBuffer) {
|
||||
_clusterBuffer = std::make_shared<gpu::Buffer>(clusterTransforms.size() * sizeof(TransformType),
|
||||
(const gpu::Byte*) clusterTransforms.data());
|
||||
_clusterBuffer = std::make_shared<gpu::Buffer>(clusterMatrices.size() * sizeof(glm::mat4),
|
||||
(const gpu::Byte*) clusterMatrices.data());
|
||||
}
|
||||
else {
|
||||
_clusterBuffer->setSubData(0, clusterTransforms.size() * sizeof(TransformType),
|
||||
(const gpu::Byte*) clusterTransforms.data());
|
||||
_clusterBuffer->setSubData(0, clusterMatrices.size() * sizeof(glm::mat4),
|
||||
(const gpu::Byte*) clusterMatrices.data());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ModelMeshPartPayload::updateClusterBuffer(const std::vector<Model::TransformDualQuaternion>& clusterDualQuaternions) {
|
||||
|
||||
// reset cluster buffer if we change the cluster buffer type
|
||||
if (_clusterBufferType != ClusterBufferType::DualQuaternions) {
|
||||
_clusterBuffer.reset();
|
||||
}
|
||||
_clusterBufferType = ClusterBufferType::DualQuaternions;
|
||||
|
||||
// Once computed the cluster matrices, update the buffer(s)
|
||||
if (clusterDualQuaternions.size() > 1) {
|
||||
if (!_clusterBuffer) {
|
||||
_clusterBuffer = std::make_shared<gpu::Buffer>(clusterDualQuaternions.size() * sizeof(Model::TransformDualQuaternion),
|
||||
(const gpu::Byte*) clusterDualQuaternions.data());
|
||||
}
|
||||
else {
|
||||
_clusterBuffer->setSubData(0, clusterDualQuaternions.size() * sizeof(Model::TransformDualQuaternion),
|
||||
(const gpu::Byte*) clusterDualQuaternions.data());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -336,7 +374,7 @@ int ModelMeshPartPayload::getLayer() const {
|
|||
return _layer;
|
||||
}
|
||||
|
||||
void ModelMeshPartPayload::setShapeKey(bool invalidateShapeKey, bool isWireframe) {
|
||||
void ModelMeshPartPayload::setShapeKey(bool invalidateShapeKey, bool isWireframe, bool useDualQuaternionSkinning) {
|
||||
if (invalidateShapeKey) {
|
||||
_shapeKey = ShapeKey::Builder::invalid();
|
||||
return;
|
||||
|
@ -383,6 +421,10 @@ void ModelMeshPartPayload::setShapeKey(bool invalidateShapeKey, bool isWireframe
|
|||
if (isWireframe) {
|
||||
builder.withWireframe();
|
||||
}
|
||||
if (isSkinned && useDualQuaternionSkinning) {
|
||||
builder.withDualQuatSkinned();
|
||||
}
|
||||
|
||||
_shapeKey = builder.build();
|
||||
}
|
||||
|
||||
|
@ -438,29 +480,33 @@ void ModelMeshPartPayload::render(RenderArgs* args) {
|
|||
args->_details._trianglesRendered += _drawPart._numIndices / INDICES_PER_TRIANGLE;
|
||||
}
|
||||
|
||||
|
||||
void ModelMeshPartPayload::computeAdjustedLocalBound(const std::vector<TransformType>& clusterTransforms) {
|
||||
void ModelMeshPartPayload::computeAdjustedLocalBound(const std::vector<glm::mat4>& clusterMatrices) {
|
||||
_adjustedLocalBound = _localBound;
|
||||
if (clusterTransforms.size() > 0) {
|
||||
#if defined(SKIN_DQ)
|
||||
Transform rootTransform(clusterTransforms[0].getRotation(),
|
||||
clusterTransforms[0].getScale(),
|
||||
clusterTransforms[0].getTranslation());
|
||||
_adjustedLocalBound.transform(rootTransform);
|
||||
#else
|
||||
_adjustedLocalBound.transform(clusterTransforms[0]);
|
||||
#endif
|
||||
if (clusterMatrices.size() > 0) {
|
||||
_adjustedLocalBound.transform(clusterMatrices[0]);
|
||||
|
||||
for (int i = 1; i < (int)clusterTransforms.size(); ++i) {
|
||||
for (int i = 1; i < (int)clusterMatrices.size(); ++i) {
|
||||
AABox clusterBound = _localBound;
|
||||
#if defined(SKIN_DQ)
|
||||
Transform transform(clusterTransforms[i].getRotation(),
|
||||
clusterTransforms[i].getScale(),
|
||||
clusterTransforms[i].getTranslation());
|
||||
clusterBound.transform(transform);
|
||||
#else
|
||||
clusterBound.transform(clusterTransforms[i]);
|
||||
#endif
|
||||
clusterBound.transform(clusterMatrices[i]);
|
||||
_adjustedLocalBound += clusterBound;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ModelMeshPartPayload::computeAdjustedLocalBound(const std::vector<Model::TransformDualQuaternion>& clusterDualQuaternions) {
|
||||
_adjustedLocalBound = _localBound;
|
||||
if (clusterDualQuaternions.size() > 0) {
|
||||
Transform rootTransform(clusterDualQuaternions[0].getRotation(),
|
||||
clusterDualQuaternions[0].getScale(),
|
||||
clusterDualQuaternions[0].getTranslation());
|
||||
_adjustedLocalBound.transform(rootTransform);
|
||||
|
||||
for (int i = 1; i < (int)clusterDualQuaternions.size(); ++i) {
|
||||
AABox clusterBound = _localBound;
|
||||
Transform transform(clusterDualQuaternions[i].getRotation(),
|
||||
clusterDualQuaternions[i].getScale(),
|
||||
clusterDualQuaternions[i].getTranslation());
|
||||
clusterBound.transform(transform);
|
||||
_adjustedLocalBound += clusterBound;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -92,14 +92,13 @@ public:
|
|||
|
||||
void notifyLocationChanged() override;
|
||||
|
||||
#if defined(SKIN_DQ)
|
||||
using TransformType = Model::TransformDualQuaternion;
|
||||
#else
|
||||
using TransformType = glm::mat4;
|
||||
#endif
|
||||
|
||||
void updateKey(bool isVisible, bool isLayered, uint8_t tagBits, bool isGroupCulled = false) override;
|
||||
void updateClusterBuffer(const std::vector<TransformType>& clusterTransforms);
|
||||
|
||||
// matrix palette skinning
|
||||
void updateClusterBuffer(const std::vector<glm::mat4>& clusterMatrices);
|
||||
|
||||
// dual quaternion skinning
|
||||
void updateClusterBuffer(const std::vector<Model::TransformDualQuaternion>& clusterDualQuaternions);
|
||||
void updateTransformForSkinnedMesh(const Transform& renderTransform, const Transform& boundTransform);
|
||||
|
||||
// Render Item interface
|
||||
|
@ -108,16 +107,23 @@ public:
|
|||
void render(RenderArgs* args) override;
|
||||
|
||||
void setLayer(bool isLayeredInFront, bool isLayeredInHUD);
|
||||
void setShapeKey(bool invalidateShapeKey, bool isWireframe);
|
||||
void setShapeKey(bool invalidateShapeKey, bool isWireframe, bool useDualQuaternionSkinning);
|
||||
|
||||
// ModelMeshPartPayload functions to perform render
|
||||
void bindMesh(gpu::Batch& batch) override;
|
||||
void bindTransform(gpu::Batch& batch, RenderArgs::RenderMode renderMode) const override;
|
||||
|
||||
void computeAdjustedLocalBound(const std::vector<TransformType>& clusterTransforms);
|
||||
// matrix palette skinning
|
||||
void computeAdjustedLocalBound(const std::vector<glm::mat4>& clusterMatrices);
|
||||
|
||||
// dual quaternion skinning
|
||||
void computeAdjustedLocalBound(const std::vector<Model::TransformDualQuaternion>& clusterDualQuaternions);
|
||||
|
||||
gpu::BufferPointer _clusterBuffer;
|
||||
|
||||
enum class ClusterBufferType { Matrices, DualQuaternions };
|
||||
ClusterBufferType _clusterBufferType { ClusterBufferType::Matrices };
|
||||
|
||||
int _meshIndex;
|
||||
int _shapeID;
|
||||
|
||||
|
|
|
@ -280,32 +280,42 @@ void Model::updateRenderItems() {
|
|||
|
||||
auto itemID = self->_modelMeshRenderItemIDs[i];
|
||||
auto meshIndex = self->_modelMeshRenderItemShapes[i].meshIndex;
|
||||
auto clusterTransforms(self->getMeshState(meshIndex).clusterTransforms);
|
||||
|
||||
const auto& meshState = self->getMeshState(meshIndex);
|
||||
|
||||
bool invalidatePayloadShapeKey = self->shouldInvalidatePayloadShapeKey(meshIndex);
|
||||
bool useDualQuaternionSkinning = self->getUseDualQuaternionSkinning();
|
||||
|
||||
transaction.updateItem<ModelMeshPartPayload>(itemID, [modelTransform, clusterTransforms,
|
||||
transaction.updateItem<ModelMeshPartPayload>(itemID, [modelTransform, meshState, useDualQuaternionSkinning,
|
||||
invalidatePayloadShapeKey, isWireframe, isVisible,
|
||||
viewTagBits, isLayeredInFront,
|
||||
isLayeredInHUD, isGroupCulled](ModelMeshPartPayload& data) {
|
||||
data.updateClusterBuffer(clusterTransforms);
|
||||
if (useDualQuaternionSkinning) {
|
||||
data.updateClusterBuffer(meshState.clusterDualQuaternions);
|
||||
} else {
|
||||
data.updateClusterBuffer(meshState.clusterMatrices);
|
||||
}
|
||||
|
||||
Transform renderTransform = modelTransform;
|
||||
if (clusterTransforms.size() == 1) {
|
||||
#if defined(SKIN_DQ)
|
||||
Transform transform(clusterTransforms[0].getRotation(),
|
||||
clusterTransforms[0].getScale(),
|
||||
clusterTransforms[0].getTranslation());
|
||||
renderTransform = modelTransform.worldTransform(Transform(transform));
|
||||
#else
|
||||
renderTransform = modelTransform.worldTransform(Transform(clusterTransforms[0]));
|
||||
#endif
|
||||
|
||||
if (useDualQuaternionSkinning) {
|
||||
if (meshState.clusterDualQuaternions.size() == 1) {
|
||||
const auto& dq = meshState.clusterDualQuaternions[0];
|
||||
Transform transform(dq.getRotation(),
|
||||
dq.getScale(),
|
||||
dq.getTranslation());
|
||||
renderTransform = modelTransform.worldTransform(Transform(transform));
|
||||
}
|
||||
} else {
|
||||
if (meshState.clusterMatrices.size() == 1) {
|
||||
renderTransform = modelTransform.worldTransform(Transform(meshState.clusterMatrices[0]));
|
||||
}
|
||||
}
|
||||
data.updateTransformForSkinnedMesh(renderTransform, modelTransform);
|
||||
|
||||
data.updateKey(isVisible, isLayeredInFront || isLayeredInHUD, viewTagBits, isGroupCulled);
|
||||
data.setLayer(isLayeredInFront, isLayeredInHUD);
|
||||
data.setShapeKey(invalidatePayloadShapeKey, isWireframe);
|
||||
data.setShapeKey(invalidatePayloadShapeKey, isWireframe, useDualQuaternionSkinning);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -352,7 +362,8 @@ bool Model::updateGeometry() {
|
|||
const FBXGeometry& fbxGeometry = getFBXGeometry();
|
||||
foreach (const FBXMesh& mesh, fbxGeometry.meshes) {
|
||||
MeshState state;
|
||||
state.clusterTransforms.resize(mesh.clusters.size());
|
||||
state.clusterDualQuaternions.resize(mesh.clusters.size());
|
||||
state.clusterMatrices.resize(mesh.clusters.size());
|
||||
_meshStates.push_back(state);
|
||||
|
||||
// Note: we add empty buffers for meshes that lack blendshapes so we can access the buffers by index
|
||||
|
@ -1311,6 +1322,10 @@ void Model::snapToRegistrationPoint() {
|
|||
_snappedToRegistrationPoint = true;
|
||||
}
|
||||
|
||||
void Model::setUseDualQuaternionSkinning(bool value) {
|
||||
_useDualQuaternionSkinning = value;
|
||||
}
|
||||
|
||||
void Model::simulate(float deltaTime, bool fullUpdate) {
|
||||
DETAILED_PROFILE_RANGE(simulation_detail, __FUNCTION__);
|
||||
fullUpdate = updateGeometry() || fullUpdate || (_scaleToFit && !_scaledToFit)
|
||||
|
@ -1344,7 +1359,11 @@ void Model::updateRig(float deltaTime, glm::mat4 parentTransform) {
|
|||
void Model::computeMeshPartLocalBounds() {
|
||||
for (auto& part : _modelMeshRenderItems) {
|
||||
const Model::MeshState& state = _meshStates.at(part->_meshIndex);
|
||||
part->computeAdjustedLocalBound(state.clusterTransforms);
|
||||
if (_useDualQuaternionSkinning) {
|
||||
part->computeAdjustedLocalBound(state.clusterDualQuaternions);
|
||||
} else {
|
||||
part->computeAdjustedLocalBound(state.clusterMatrices);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1363,16 +1382,16 @@ void Model::updateClusterMatrices() {
|
|||
const FBXMesh& mesh = geometry.meshes.at(i);
|
||||
for (int j = 0; j < mesh.clusters.size(); j++) {
|
||||
const FBXCluster& cluster = mesh.clusters.at(j);
|
||||
#if defined(SKIN_DQ)
|
||||
auto jointPose = _rig.getJointPose(cluster.jointIndex);
|
||||
Transform jointTransform(jointPose.rot(), jointPose.scale(), jointPose.trans());
|
||||
Transform clusterTransform;
|
||||
Transform::mult(clusterTransform, jointTransform, cluster.inverseBindTransform);
|
||||
state.clusterTransforms[j] = Model::TransformDualQuaternion(clusterTransform);
|
||||
#else
|
||||
auto jointMatrix = _rig.getJointTransform(cluster.jointIndex);
|
||||
glm_mat4u_mul(jointMatrix, cluster.inverseBindMatrix, state.clusterTransforms[j]);
|
||||
#endif
|
||||
if (_useDualQuaternionSkinning) {
|
||||
auto jointPose = _rig.getJointPose(cluster.jointIndex);
|
||||
Transform jointTransform(jointPose.rot(), jointPose.scale(), jointPose.trans());
|
||||
Transform clusterTransform;
|
||||
Transform::mult(clusterTransform, jointTransform, cluster.inverseBindTransform);
|
||||
state.clusterDualQuaternions[j] = Model::TransformDualQuaternion(clusterTransform);
|
||||
} else {
|
||||
auto jointMatrix = _rig.getJointTransform(cluster.jointIndex);
|
||||
glm_mat4u_mul(jointMatrix, cluster.inverseBindMatrix, state.clusterMatrices[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1642,12 +1661,13 @@ void Model::addMaterial(graphics::MaterialLayer material, const std::string& par
|
|||
bool wireframe = isWireframe();
|
||||
auto meshIndex = _modelMeshRenderItemShapes[shapeID].meshIndex;
|
||||
bool invalidatePayloadShapeKey = shouldInvalidatePayloadShapeKey(meshIndex);
|
||||
bool useDualQuaternionSkinning = _useDualQuaternionSkinning;
|
||||
transaction.updateItem<ModelMeshPartPayload>(itemID, [material, visible, layeredInFront, layeredInHUD, viewTagBits,
|
||||
invalidatePayloadShapeKey, wireframe](ModelMeshPartPayload& data) {
|
||||
invalidatePayloadShapeKey, wireframe, useDualQuaternionSkinning](ModelMeshPartPayload& data) {
|
||||
data.addMaterial(material);
|
||||
// if the material changed, we might need to update our item key or shape key
|
||||
data.updateKey(visible, layeredInFront || layeredInHUD, viewTagBits);
|
||||
data.setShapeKey(invalidatePayloadShapeKey, wireframe);
|
||||
data.setShapeKey(invalidatePayloadShapeKey, wireframe, useDualQuaternionSkinning);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1667,12 +1687,13 @@ void Model::removeMaterial(graphics::MaterialPointer material, const std::string
|
|||
bool wireframe = isWireframe();
|
||||
auto meshIndex = _modelMeshRenderItemShapes[shapeID].meshIndex;
|
||||
bool invalidatePayloadShapeKey = shouldInvalidatePayloadShapeKey(meshIndex);
|
||||
bool useDualQuaternionSkinning = _useDualQuaternionSkinning;
|
||||
transaction.updateItem<ModelMeshPartPayload>(itemID, [material, visible, layeredInFront, layeredInHUD, viewTagBits,
|
||||
invalidatePayloadShapeKey, wireframe](ModelMeshPartPayload& data) {
|
||||
invalidatePayloadShapeKey, wireframe, useDualQuaternionSkinning](ModelMeshPartPayload& data) {
|
||||
data.removeMaterial(material);
|
||||
// if the material changed, we might need to update our item key or shape key
|
||||
data.updateKey(visible, layeredInFront || layeredInHUD, viewTagBits);
|
||||
data.setShapeKey(invalidatePayloadShapeKey, wireframe);
|
||||
data.setShapeKey(invalidatePayloadShapeKey, wireframe, useDualQuaternionSkinning);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -257,8 +257,6 @@ public:
|
|||
int getRenderInfoDrawCalls() const { return _renderInfoDrawCalls; }
|
||||
bool getRenderInfoHasTransparent() const { return _renderInfoHasTransparent; }
|
||||
|
||||
|
||||
#if defined(SKIN_DQ)
|
||||
class TransformDualQuaternion {
|
||||
public:
|
||||
TransformDualQuaternion() {}
|
||||
|
@ -296,15 +294,11 @@ public:
|
|||
DualQuaternion _dq;
|
||||
glm::vec4 _cauterizedPosition { 0.0f, 0.0f, 0.0f, 1.0f };
|
||||
};
|
||||
#endif
|
||||
|
||||
class MeshState {
|
||||
public:
|
||||
#if defined(SKIN_DQ)
|
||||
std::vector<TransformDualQuaternion> clusterTransforms;
|
||||
#else
|
||||
std::vector<glm::mat4> clusterTransforms;
|
||||
#endif
|
||||
std::vector<TransformDualQuaternion> clusterDualQuaternions;
|
||||
std::vector<glm::mat4> clusterMatrices;
|
||||
};
|
||||
|
||||
const MeshState& getMeshState(int index) { return _meshStates.at(index); }
|
||||
|
@ -322,6 +316,8 @@ public:
|
|||
virtual bool replaceScriptableModelMeshPart(scriptable::ScriptableModelBasePointer model, int meshIndex, int partIndex) override;
|
||||
|
||||
void scaleToFit();
|
||||
bool getUseDualQuaternionSkinning() const { return _useDualQuaternionSkinning; }
|
||||
void setUseDualQuaternionSkinning(bool value);
|
||||
|
||||
void addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName);
|
||||
void removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName);
|
||||
|
@ -428,6 +424,7 @@ protected:
|
|||
virtual void createCollisionRenderItemSet();
|
||||
|
||||
bool _isWireframe;
|
||||
bool _useDualQuaternionSkinning { false };
|
||||
|
||||
// debug rendering support
|
||||
int _debugMeshBoxesID = GeometryCache::UNKNOWN_ID;
|
||||
|
|
|
@ -26,6 +26,8 @@
|
|||
#include "model_lightmap_normal_map_vert.h"
|
||||
#include "skin_model_vert.h"
|
||||
#include "skin_model_normal_map_vert.h"
|
||||
#include "skin_model_dq_vert.h"
|
||||
#include "skin_model_normal_map_dq_vert.h"
|
||||
|
||||
#include "model_lightmap_fade_vert.h"
|
||||
#include "model_lightmap_normal_map_fade_vert.h"
|
||||
|
@ -33,6 +35,8 @@
|
|||
#include "model_translucent_normal_map_vert.h"
|
||||
#include "skin_model_fade_vert.h"
|
||||
#include "skin_model_normal_map_fade_vert.h"
|
||||
#include "skin_model_fade_dq_vert.h"
|
||||
#include "skin_model_normal_map_fade_dq_vert.h"
|
||||
|
||||
#include "simple_vert.h"
|
||||
#include "simple_textured_frag.h"
|
||||
|
@ -95,6 +99,7 @@
|
|||
|
||||
#include "model_shadow_vert.h"
|
||||
#include "skin_model_shadow_vert.h"
|
||||
#include "skin_model_shadow_dq_vert.h"
|
||||
|
||||
#include "model_shadow_frag.h"
|
||||
#include "skin_model_shadow_frag.h"
|
||||
|
@ -195,16 +200,28 @@ void initDeferredPipelines(render::ShapePlumber& plumber, const render::ShapePip
|
|||
auto modelTranslucentVertex = model_translucent_vert::getShader();
|
||||
auto modelTranslucentNormalMapVertex = model_translucent_normal_map_vert::getShader();
|
||||
auto modelShadowVertex = model_shadow_vert::getShader();
|
||||
|
||||
auto modelLightmapFadeVertex = model_lightmap_fade_vert::getShader();
|
||||
auto modelLightmapNormalMapFadeVertex = model_lightmap_normal_map_fade_vert::getShader();
|
||||
|
||||
// matrix palette skinned
|
||||
auto skinModelVertex = skin_model_vert::getShader();
|
||||
auto skinModelNormalMapVertex = skin_model_normal_map_vert::getShader();
|
||||
auto skinModelShadowVertex = skin_model_shadow_vert::getShader();
|
||||
auto modelLightmapFadeVertex = model_lightmap_fade_vert::getShader();
|
||||
auto modelLightmapNormalMapFadeVertex = model_lightmap_normal_map_fade_vert::getShader();
|
||||
auto skinModelFadeVertex = skin_model_fade_vert::getShader();
|
||||
auto skinModelNormalMapFadeVertex = skin_model_normal_map_fade_vert::getShader();
|
||||
auto skinModelTranslucentVertex = skinModelFadeVertex; // We use the same because it ouputs world position per vertex
|
||||
auto skinModelNormalMapTranslucentVertex = skinModelNormalMapFadeVertex; // We use the same because it ouputs world position per vertex
|
||||
|
||||
// dual quaternion skinned
|
||||
auto skinModelDualQuatVertex = skin_model_dq_vert::getShader();
|
||||
auto skinModelNormalMapDualQuatVertex = skin_model_normal_map_dq_vert::getShader();
|
||||
auto skinModelShadowDualQuatVertex = skin_model_shadow_dq_vert::getShader();
|
||||
auto skinModelFadeDualQuatVertex = skin_model_fade_dq_vert::getShader();
|
||||
auto skinModelNormalMapFadeDualQuatVertex = skin_model_normal_map_fade_dq_vert::getShader();
|
||||
auto skinModelTranslucentDualQuatVertex = skinModelFadeDualQuatVertex; // We use the same because it ouputs world position per vertex
|
||||
auto skinModelNormalMapTranslucentDualQuatVertex = skinModelNormalMapFadeDualQuatVertex; // We use the same because it ouputs world position per vertex
|
||||
|
||||
auto modelFadeVertex = model_fade_vert::getShader();
|
||||
auto modelNormalMapFadeVertex = model_normal_map_fade_vert::getShader();
|
||||
auto simpleFadeVertex = simple_fade_vert::getShader();
|
||||
|
@ -376,7 +393,7 @@ void initDeferredPipelines(render::ShapePlumber& plumber, const render::ShapePip
|
|||
Key::Builder().withMaterial().withLightmap().withTangents().withSpecular().withFade(),
|
||||
modelLightmapNormalMapFadeVertex, modelLightmapNormalSpecularMapFadePixel, batchSetter, itemSetter);
|
||||
|
||||
// Skinned
|
||||
// matrix palette skinned
|
||||
addPipeline(
|
||||
Key::Builder().withMaterial().withSkinned(),
|
||||
skinModelVertex, modelPixel, nullptr, nullptr);
|
||||
|
@ -403,7 +420,7 @@ void initDeferredPipelines(render::ShapePlumber& plumber, const render::ShapePip
|
|||
Key::Builder().withMaterial().withSkinned().withTangents().withSpecular().withFade(),
|
||||
skinModelNormalMapFadeVertex, modelNormalSpecularMapFadePixel, batchSetter, itemSetter);
|
||||
|
||||
// Skinned and Translucent
|
||||
// matrix palette skinned and translucent
|
||||
addPipeline(
|
||||
Key::Builder().withMaterial().withSkinned().withTranslucent(),
|
||||
skinModelTranslucentVertex, modelTranslucentPixel, nullptr, nullptr);
|
||||
|
@ -430,6 +447,60 @@ void initDeferredPipelines(render::ShapePlumber& plumber, const render::ShapePip
|
|||
Key::Builder().withMaterial().withSkinned().withTranslucent().withTangents().withSpecular().withFade(),
|
||||
skinModelNormalMapFadeVertex, modelTranslucentNormalMapFadePixel, batchSetter, itemSetter);
|
||||
|
||||
// dual quaternion skinned
|
||||
addPipeline(
|
||||
Key::Builder().withMaterial().withSkinned().withDualQuatSkinned(),
|
||||
skinModelDualQuatVertex, modelPixel, nullptr, nullptr);
|
||||
addPipeline(
|
||||
Key::Builder().withMaterial().withSkinned().withDualQuatSkinned().withTangents(),
|
||||
skinModelNormalMapDualQuatVertex, modelNormalMapPixel, nullptr, nullptr);
|
||||
addPipeline(
|
||||
Key::Builder().withMaterial().withSkinned().withDualQuatSkinned().withSpecular(),
|
||||
skinModelDualQuatVertex, modelSpecularMapPixel, nullptr, nullptr);
|
||||
addPipeline(
|
||||
Key::Builder().withMaterial().withSkinned().withDualQuatSkinned().withTangents().withSpecular(),
|
||||
skinModelNormalMapDualQuatVertex, modelNormalSpecularMapPixel, nullptr, nullptr);
|
||||
// Same thing but with Fade on
|
||||
addPipeline(
|
||||
Key::Builder().withMaterial().withSkinned().withDualQuatSkinned().withFade(),
|
||||
skinModelFadeDualQuatVertex, modelFadePixel, batchSetter, itemSetter);
|
||||
addPipeline(
|
||||
Key::Builder().withMaterial().withSkinned().withDualQuatSkinned().withTangents().withFade(),
|
||||
skinModelNormalMapFadeDualQuatVertex, modelNormalMapFadePixel, batchSetter, itemSetter);
|
||||
addPipeline(
|
||||
Key::Builder().withMaterial().withSkinned().withDualQuatSkinned().withSpecular().withFade(),
|
||||
skinModelFadeDualQuatVertex, modelSpecularMapFadePixel, batchSetter, itemSetter);
|
||||
addPipeline(
|
||||
Key::Builder().withMaterial().withSkinned().withDualQuatSkinned().withTangents().withSpecular().withFade(),
|
||||
skinModelNormalMapFadeDualQuatVertex, modelNormalSpecularMapFadePixel, batchSetter, itemSetter);
|
||||
|
||||
// dual quaternion skinned and translucent
|
||||
addPipeline(
|
||||
Key::Builder().withMaterial().withSkinned().withDualQuatSkinned().withTranslucent(),
|
||||
skinModelTranslucentDualQuatVertex, modelTranslucentPixel, nullptr, nullptr);
|
||||
addPipeline(
|
||||
Key::Builder().withMaterial().withSkinned().withDualQuatSkinned().withTranslucent().withTangents(),
|
||||
skinModelNormalMapTranslucentDualQuatVertex, modelTranslucentNormalMapPixel, nullptr, nullptr);
|
||||
addPipeline(
|
||||
Key::Builder().withMaterial().withSkinned().withDualQuatSkinned().withTranslucent().withSpecular(),
|
||||
skinModelTranslucentDualQuatVertex, modelTranslucentPixel, nullptr, nullptr);
|
||||
addPipeline(
|
||||
Key::Builder().withMaterial().withSkinned().withDualQuatSkinned().withTranslucent().withTangents().withSpecular(),
|
||||
skinModelNormalMapTranslucentDualQuatVertex, modelTranslucentNormalMapPixel, nullptr, nullptr);
|
||||
// Same thing but with Fade on
|
||||
addPipeline(
|
||||
Key::Builder().withMaterial().withSkinned().withDualQuatSkinned().withTranslucent().withFade(),
|
||||
skinModelFadeVertex, modelTranslucentFadePixel, batchSetter, itemSetter);
|
||||
addPipeline(
|
||||
Key::Builder().withMaterial().withSkinned().withDualQuatSkinned().withTranslucent().withTangents().withFade(),
|
||||
skinModelNormalMapFadeDualQuatVertex, modelTranslucentNormalMapFadePixel, batchSetter, itemSetter);
|
||||
addPipeline(
|
||||
Key::Builder().withMaterial().withSkinned().withDualQuatSkinned().withTranslucent().withSpecular().withFade(),
|
||||
skinModelFadeDualQuatVertex, modelTranslucentFadePixel, batchSetter, itemSetter);
|
||||
addPipeline(
|
||||
Key::Builder().withMaterial().withSkinned().withDualQuatSkinned().withTranslucent().withTangents().withSpecular().withFade(),
|
||||
skinModelNormalMapFadeDualQuatVertex, modelTranslucentNormalMapFadePixel, batchSetter, itemSetter);
|
||||
|
||||
// Depth-only
|
||||
addPipeline(
|
||||
Key::Builder().withDepthOnly(),
|
||||
|
|
|
@ -89,7 +89,9 @@ float evalShadowCascadeAttenuation(int cascadeIndex, ShadowSampleOffsets offsets
|
|||
|
||||
float evalShadowAttenuation(vec3 worldLightDir, vec4 worldPosition, float viewDepth, vec3 worldNormal) {
|
||||
ShadowSampleOffsets offsets = evalShadowFilterOffsets(worldPosition);
|
||||
vec4 cascadeShadowCoords[2] = { vec4(0), vec4(0) };
|
||||
vec4 cascadeShadowCoords[2];
|
||||
cascadeShadowCoords[0] = vec4(0);
|
||||
cascadeShadowCoords[1] = vec4(0);
|
||||
ivec2 cascadeIndices;
|
||||
float cascadeMix = determineShadowCascadesOnPixel(worldPosition, viewDepth, cascadeShadowCoords, cascadeIndices);
|
||||
|
||||
|
|
|
@ -11,18 +11,16 @@
|
|||
<@if not SKINNING_SLH@>
|
||||
<@def SKINNING_SLH@>
|
||||
|
||||
// Use dual quaternion skinning
|
||||
// Must match #define SKIN_DQ in Model.h
|
||||
<@def SKIN_DQ@>
|
||||
|
||||
const int MAX_CLUSTERS = 128;
|
||||
const int INDICES_PER_VERTEX = 4;
|
||||
|
||||
<@func declareUseDualQuaternionSkinning(USE_DUAL_QUATERNION_SKINNING)@>
|
||||
|
||||
layout(std140) uniform skinClusterBuffer {
|
||||
mat4 clusterMatrices[MAX_CLUSTERS];
|
||||
};
|
||||
|
||||
<@if SKIN_DQ@>
|
||||
<@if USE_DUAL_QUATERNION_SKINNING@>
|
||||
|
||||
mat4 dualQuatToMat4(vec4 real, vec4 dual) {
|
||||
float twoRealXSq = 2.0 * real.x * real.x;
|
||||
|
@ -211,7 +209,7 @@ void skinPositionNormalTangent(ivec4 skinClusterIndex, vec4 skinClusterWeight, v
|
|||
skinnedTangent = vec3(m * vec4(inTangent, 0));
|
||||
}
|
||||
|
||||
<@else@> // SKIN_DQ
|
||||
<@else@> // USE_DUAL_QUATERNION_SKINNING
|
||||
|
||||
void skinPosition(ivec4 skinClusterIndex, vec4 skinClusterWeight, vec4 inPosition, out vec4 skinnedPosition) {
|
||||
vec4 newPosition = vec4(0.0, 0.0, 0.0, 0.0);
|
||||
|
@ -260,6 +258,8 @@ void skinPositionNormalTangent(ivec4 skinClusterIndex, vec4 skinClusterWeight, v
|
|||
skinnedTangent = newTangent.xyz;
|
||||
}
|
||||
|
||||
<@endif@> // if SKIN_DQ
|
||||
<@endif@> // if USE_DUAL_QUATERNION_SKINNING
|
||||
|
||||
<@endfunc@> // func declareUseDualQuaternionSkinning(USE_DUAL_QUATERNION_SKINNING)
|
||||
|
||||
<@endif@> // if not SKINNING_SLH
|
||||
|
|
|
@ -52,27 +52,27 @@ void SoftAttachmentModel::updateClusterMatrices() {
|
|||
|
||||
// TODO: cache these look-ups as an optimization
|
||||
int jointIndexOverride = getJointIndexOverride(cluster.jointIndex);
|
||||
#if defined(SKIN_DQ)
|
||||
glm::mat4 jointMatrix;
|
||||
if (jointIndexOverride >= 0 && jointIndexOverride < _rigOverride.getJointStateCount()) {
|
||||
jointMatrix = _rigOverride.getJointTransform(jointIndexOverride);
|
||||
} else {
|
||||
jointMatrix = _rig.getJointTransform(cluster.jointIndex);
|
||||
}
|
||||
if (_useDualQuaternionSkinning) {
|
||||
glm::mat4 jointMatrix;
|
||||
if (jointIndexOverride >= 0 && jointIndexOverride < _rigOverride.getJointStateCount()) {
|
||||
jointMatrix = _rigOverride.getJointTransform(jointIndexOverride);
|
||||
} else {
|
||||
jointMatrix = _rig.getJointTransform(cluster.jointIndex);
|
||||
}
|
||||
|
||||
glm::mat4 m;
|
||||
glm_mat4u_mul(jointMatrix, cluster.inverseBindMatrix, m);
|
||||
state.clusterTransforms[j] = Model::TransformDualQuaternion(m);
|
||||
#else
|
||||
glm::mat4 jointMatrix;
|
||||
if (jointIndexOverride >= 0 && jointIndexOverride < _rigOverride.getJointStateCount()) {
|
||||
jointMatrix = _rigOverride.getJointTransform(jointIndexOverride);
|
||||
glm::mat4 m;
|
||||
glm_mat4u_mul(jointMatrix, cluster.inverseBindMatrix, m);
|
||||
state.clusterDualQuaternions[j] = Model::TransformDualQuaternion(m);
|
||||
} else {
|
||||
jointMatrix = _rig.getJointTransform(cluster.jointIndex);
|
||||
}
|
||||
glm::mat4 jointMatrix;
|
||||
if (jointIndexOverride >= 0 && jointIndexOverride < _rigOverride.getJointStateCount()) {
|
||||
jointMatrix = _rigOverride.getJointTransform(jointIndexOverride);
|
||||
} else {
|
||||
jointMatrix = _rig.getJointTransform(cluster.jointIndex);
|
||||
}
|
||||
|
||||
glm_mat4u_mul(jointMatrix, cluster.inverseBindMatrix, state.clusterTransforms[j]);
|
||||
#endif
|
||||
glm_mat4u_mul(jointMatrix, cluster.inverseBindMatrix, state.clusterMatrices[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
<$declareStandardTransform()$>
|
||||
|
||||
<@include Skinning.slh@>
|
||||
<$declareUseDualQuaternionSkinning()$>
|
||||
|
||||
<@include MaterialTextures.slh@>
|
||||
<$declareMaterialTexMapArrayBuffer()$>
|
||||
|
|
52
libraries/render-utils/src/skin_model_dq.slv
Normal file
52
libraries/render-utils/src/skin_model_dq.slv
Normal file
|
@ -0,0 +1,52 @@
|
|||
<@include gpu/Config.slh@>
|
||||
<$VERSION_HEADER$>
|
||||
// Generated on <$_SCRIBE_DATE$>
|
||||
//
|
||||
// skin_model.vert
|
||||
// vertex shader
|
||||
//
|
||||
// Created by Andrzej Kapolka on 10/14/13.
|
||||
// Copyright 2013 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
<@include gpu/Inputs.slh@>
|
||||
<@include gpu/Color.slh@>
|
||||
<@include gpu/Transform.slh@>
|
||||
<$declareStandardTransform()$>
|
||||
|
||||
<@include Skinning.slh@>
|
||||
<$declareUseDualQuaternionSkinning(1)$>
|
||||
|
||||
<@include MaterialTextures.slh@>
|
||||
<$declareMaterialTexMapArrayBuffer()$>
|
||||
|
||||
out vec4 _position;
|
||||
out vec2 _texCoord0;
|
||||
out vec2 _texCoord1;
|
||||
out vec3 _normal;
|
||||
out vec3 _color;
|
||||
out float _alpha;
|
||||
|
||||
void main(void) {
|
||||
vec4 position = vec4(0.0, 0.0, 0.0, 0.0);
|
||||
vec3 interpolatedNormal = vec3(0.0, 0.0, 0.0);
|
||||
|
||||
skinPositionNormal(inSkinClusterIndex, inSkinClusterWeight, inPosition, inNormal.xyz, position, interpolatedNormal);
|
||||
|
||||
// pass along the color
|
||||
_color = colorToLinearRGB(inColor.rgb);
|
||||
_alpha = inColor.a;
|
||||
|
||||
TexMapArray texMapArray = getTexMapArray();
|
||||
<$evalTexMapArrayTexcoord0(texMapArray, inTexCoord0, _texCoord0)$>
|
||||
<$evalTexMapArrayTexcoord1(texMapArray, inTexCoord0, _texCoord1)$>
|
||||
|
||||
// standard transform
|
||||
TransformCamera cam = getTransformCamera();
|
||||
TransformObject obj = getTransformObject();
|
||||
<$transformModelToEyeAndClipPos(cam, obj, position, _position, gl_Position)$>
|
||||
<$transformModelToWorldDir(cam, obj, interpolatedNormal.xyz, _normal.xyz)$>
|
||||
}
|
|
@ -18,6 +18,7 @@
|
|||
<$declareStandardTransform()$>
|
||||
|
||||
<@include Skinning.slh@>
|
||||
<$declareUseDualQuaternionSkinning()$>
|
||||
|
||||
<@include MaterialTextures.slh@>
|
||||
<$declareMaterialTexMapArrayBuffer()$>
|
||||
|
|
54
libraries/render-utils/src/skin_model_fade_dq.slv
Normal file
54
libraries/render-utils/src/skin_model_fade_dq.slv
Normal file
|
@ -0,0 +1,54 @@
|
|||
<@include gpu/Config.slh@>
|
||||
<$VERSION_HEADER$>
|
||||
// Generated on <$_SCRIBE_DATE$>
|
||||
//
|
||||
// skin_model_fade.vert
|
||||
// vertex shader
|
||||
//
|
||||
// Created by Olivier Prat on 06/045/17.
|
||||
// Copyright 2017 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
<@include gpu/Inputs.slh@>
|
||||
<@include gpu/Color.slh@>
|
||||
<@include gpu/Transform.slh@>
|
||||
<$declareStandardTransform()$>
|
||||
|
||||
<@include Skinning.slh@>
|
||||
<$declareUseDualQuaternionSkinning(1)$>
|
||||
|
||||
<@include MaterialTextures.slh@>
|
||||
<$declareMaterialTexMapArrayBuffer()$>
|
||||
|
||||
out vec4 _position;
|
||||
out vec2 _texCoord0;
|
||||
out vec2 _texCoord1;
|
||||
out vec3 _normal;
|
||||
out vec3 _color;
|
||||
out float _alpha;
|
||||
out vec4 _worldPosition;
|
||||
|
||||
void main(void) {
|
||||
vec4 position = vec4(0.0, 0.0, 0.0, 0.0);
|
||||
vec3 interpolatedNormal = vec3(0.0, 0.0, 0.0);
|
||||
|
||||
skinPositionNormal(inSkinClusterIndex, inSkinClusterWeight, inPosition, inNormal.xyz, position, interpolatedNormal);
|
||||
|
||||
// pass along the color
|
||||
_color = colorToLinearRGB(inColor.rgb);
|
||||
_alpha = inColor.a;
|
||||
|
||||
TexMapArray texMapArray = getTexMapArray();
|
||||
<$evalTexMapArrayTexcoord0(texMapArray, inTexCoord0, _texCoord0)$>
|
||||
<$evalTexMapArrayTexcoord1(texMapArray, inTexCoord0, _texCoord1)$>
|
||||
|
||||
// standard transform
|
||||
TransformCamera cam = getTransformCamera();
|
||||
TransformObject obj = getTransformObject();
|
||||
<$transformModelToEyeAndClipPos(cam, obj, position, _position, gl_Position)$>
|
||||
<$transformModelToWorldPos(obj, position, _worldPosition)$>
|
||||
<$transformModelToWorldDir(cam, obj, interpolatedNormal.xyz, _normal.xyz)$>
|
||||
}
|
|
@ -18,6 +18,7 @@
|
|||
<$declareStandardTransform()$>
|
||||
|
||||
<@include Skinning.slh@>
|
||||
<$declareUseDualQuaternionSkinning()$>
|
||||
|
||||
<@include MaterialTextures.slh@>
|
||||
<$declareMaterialTexMapArrayBuffer()$>
|
||||
|
|
61
libraries/render-utils/src/skin_model_normal_map_dq.slv
Normal file
61
libraries/render-utils/src/skin_model_normal_map_dq.slv
Normal file
|
@ -0,0 +1,61 @@
|
|||
<@include gpu/Config.slh@>
|
||||
<$VERSION_HEADER$>
|
||||
// Generated on <$_SCRIBE_DATE$>
|
||||
//
|
||||
// skin_model_normal_map.vert
|
||||
// vertex shader
|
||||
//
|
||||
// Created by Andrzej Kapolka on 10/29/13.
|
||||
// Copyright 2013 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
<@include gpu/Inputs.slh@>
|
||||
<@include gpu/Color.slh@>
|
||||
<@include gpu/Transform.slh@>
|
||||
<$declareStandardTransform()$>
|
||||
|
||||
<@include Skinning.slh@>
|
||||
<$declareUseDualQuaternionSkinning(1)$>
|
||||
|
||||
<@include MaterialTextures.slh@>
|
||||
<$declareMaterialTexMapArrayBuffer()$>
|
||||
|
||||
out vec4 _position;
|
||||
out vec2 _texCoord0;
|
||||
out vec2 _texCoord1;
|
||||
out vec3 _normal;
|
||||
out vec3 _tangent;
|
||||
out vec3 _color;
|
||||
out float _alpha;
|
||||
|
||||
void main(void) {
|
||||
vec4 position = vec4(0.0, 0.0, 0.0, 0.0);
|
||||
vec4 interpolatedNormal = vec4(0.0, 0.0, 0.0, 0.0);
|
||||
vec4 interpolatedTangent = vec4(0.0, 0.0, 0.0, 0.0);
|
||||
|
||||
skinPositionNormalTangent(inSkinClusterIndex, inSkinClusterWeight, inPosition, inNormal.xyz, inTangent.xyz, position, interpolatedNormal.xyz, interpolatedTangent.xyz);
|
||||
|
||||
// pass along the color
|
||||
_color = colorToLinearRGB(inColor.rgb);
|
||||
_alpha = inColor.a;
|
||||
|
||||
TexMapArray texMapArray = getTexMapArray();
|
||||
<$evalTexMapArrayTexcoord0(texMapArray, inTexCoord0, _texCoord0)$>
|
||||
<$evalTexMapArrayTexcoord1(texMapArray, inTexCoord0, _texCoord1)$>
|
||||
|
||||
interpolatedNormal = vec4(normalize(interpolatedNormal.xyz), 0.0);
|
||||
interpolatedTangent = vec4(normalize(interpolatedTangent.xyz), 0.0);
|
||||
|
||||
// standard transform
|
||||
TransformCamera cam = getTransformCamera();
|
||||
TransformObject obj = getTransformObject();
|
||||
<$transformModelToEyeAndClipPos(cam, obj, position, _position, gl_Position)$>
|
||||
<$transformModelToWorldDir(cam, obj, interpolatedNormal.xyz, interpolatedNormal.xyz)$>
|
||||
<$transformModelToWorldDir(cam, obj, interpolatedTangent.xyz, interpolatedTangent.xyz)$>
|
||||
|
||||
_normal = interpolatedNormal.xyz;
|
||||
_tangent = interpolatedTangent.xyz;
|
||||
}
|
|
@ -18,6 +18,7 @@
|
|||
<$declareStandardTransform()$>
|
||||
|
||||
<@include Skinning.slh@>
|
||||
<$declareUseDualQuaternionSkinning()$>
|
||||
|
||||
<@include MaterialTextures.slh@>
|
||||
<$declareMaterialTexMapArrayBuffer()$>
|
||||
|
|
61
libraries/render-utils/src/skin_model_normal_map_fade_dq.slv
Normal file
61
libraries/render-utils/src/skin_model_normal_map_fade_dq.slv
Normal file
|
@ -0,0 +1,61 @@
|
|||
<@include gpu/Config.slh@>
|
||||
<$VERSION_HEADER$>
|
||||
// Generated on <$_SCRIBE_DATE$>
|
||||
//
|
||||
// skin_model_normal_map.vert
|
||||
// vertex shader
|
||||
//
|
||||
// Created by Andrzej Kapolka on 10/29/13.
|
||||
// Copyright 2013 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
<@include gpu/Inputs.slh@>
|
||||
<@include gpu/Color.slh@>
|
||||
<@include gpu/Transform.slh@>
|
||||
<$declareStandardTransform()$>
|
||||
|
||||
<@include Skinning.slh@>
|
||||
<$declareUseDualQuaternionSkinning(1)$>
|
||||
|
||||
<@include MaterialTextures.slh@>
|
||||
<$declareMaterialTexMapArrayBuffer()$>
|
||||
|
||||
out vec4 _position;
|
||||
out vec2 _texCoord0;
|
||||
out vec2 _texCoord1;
|
||||
out vec3 _normal;
|
||||
out vec3 _tangent;
|
||||
out vec3 _color;
|
||||
out float _alpha;
|
||||
|
||||
void main(void) {
|
||||
vec4 position = vec4(0.0, 0.0, 0.0, 0.0);
|
||||
vec4 interpolatedNormal = vec4(0.0, 0.0, 0.0, 0.0);
|
||||
vec4 interpolatedTangent = vec4(0.0, 0.0, 0.0, 0.0);
|
||||
|
||||
skinPositionNormalTangent(inSkinClusterIndex, inSkinClusterWeight, inPosition, inNormal.xyz, inTangent.xyz, position, interpolatedNormal.xyz, interpolatedTangent.xyz);
|
||||
|
||||
// pass along the color
|
||||
_color = colorToLinearRGB(inColor.rgb);
|
||||
_alpha = inColor.a;
|
||||
|
||||
TexMapArray texMapArray = getTexMapArray();
|
||||
<$evalTexMapArrayTexcoord0(texMapArray, inTexCoord0, _texCoord0)$>
|
||||
<$evalTexMapArrayTexcoord1(texMapArray, inTexCoord0, _texCoord1)$>
|
||||
|
||||
interpolatedNormal = vec4(normalize(interpolatedNormal.xyz), 0.0);
|
||||
interpolatedTangent = vec4(normalize(interpolatedTangent.xyz), 0.0);
|
||||
|
||||
// standard transform
|
||||
TransformCamera cam = getTransformCamera();
|
||||
TransformObject obj = getTransformObject();
|
||||
<$transformModelToEyeAndClipPos(cam, obj, position, _position, gl_Position)$>
|
||||
<$transformModelToWorldDir(cam, obj, interpolatedNormal.xyz, interpolatedNormal.xyz)$>
|
||||
<$transformModelToWorldDir(cam, obj, interpolatedTangent.xyz, interpolatedTangent.xyz)$>
|
||||
|
||||
_normal = interpolatedNormal.xyz;
|
||||
_tangent = interpolatedTangent.xyz;
|
||||
}
|
|
@ -17,6 +17,7 @@
|
|||
<$declareStandardTransform()$>
|
||||
|
||||
<@include Skinning.slh@>
|
||||
<$declareUseDualQuaternionSkinning()$>
|
||||
|
||||
void main(void) {
|
||||
vec4 position = vec4(0.0, 0.0, 0.0, 0.0);
|
||||
|
|
30
libraries/render-utils/src/skin_model_shadow_dq.slv
Normal file
30
libraries/render-utils/src/skin_model_shadow_dq.slv
Normal file
|
@ -0,0 +1,30 @@
|
|||
<@include gpu/Config.slh@>
|
||||
<$VERSION_HEADER$>
|
||||
// Generated on <$_SCRIBE_DATE$>
|
||||
//
|
||||
// skin_model_shadow.vert
|
||||
// vertex shader
|
||||
//
|
||||
// Created by Andrzej Kapolka on 3/24/14.
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
<@include gpu/Inputs.slh@>
|
||||
<@include gpu/Transform.slh@>
|
||||
<$declareStandardTransform()$>
|
||||
|
||||
<@include Skinning.slh@>
|
||||
<$declareUseDualQuaternionSkinning(1)$>
|
||||
|
||||
void main(void) {
|
||||
vec4 position = vec4(0.0, 0.0, 0.0, 0.0);
|
||||
skinPosition(inSkinClusterIndex, inSkinClusterWeight, inPosition, position);
|
||||
|
||||
// standard transform
|
||||
TransformCamera cam = getTransformCamera();
|
||||
TransformObject obj = getTransformObject();
|
||||
<$transformModelToClipPos(cam, obj, position, gl_Position)$>
|
||||
}
|
|
@ -17,6 +17,7 @@
|
|||
<$declareStandardTransform()$>
|
||||
|
||||
<@include Skinning.slh@>
|
||||
<$declareUseDualQuaternionSkinning()$>
|
||||
|
||||
out vec4 _worldPosition;
|
||||
|
||||
|
|
33
libraries/render-utils/src/skin_model_shadow_fade_dq.slv
Normal file
33
libraries/render-utils/src/skin_model_shadow_fade_dq.slv
Normal file
|
@ -0,0 +1,33 @@
|
|||
<@include gpu/Config.slh@>
|
||||
<$VERSION_HEADER$>
|
||||
// Generated on <$_SCRIBE_DATE$>
|
||||
//
|
||||
// skin_model_shadow_fade.vert
|
||||
// vertex shader
|
||||
//
|
||||
// Created by Olivier Prat on 06/045/17.
|
||||
// Copyright 2017 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
<@include gpu/Inputs.slh@>
|
||||
<@include gpu/Transform.slh@>
|
||||
<$declareStandardTransform()$>
|
||||
|
||||
<@include Skinning.slh@>
|
||||
<$declareUseDualQuaternionSkinning(1)$>
|
||||
|
||||
out vec4 _worldPosition;
|
||||
|
||||
void main(void) {
|
||||
vec4 position = vec4(0.0, 0.0, 0.0, 0.0);
|
||||
skinPosition(inSkinClusterIndex, inSkinClusterWeight, inPosition, position);
|
||||
|
||||
// standard transform
|
||||
TransformCamera cam = getTransformCamera();
|
||||
TransformObject obj = getTransformObject();
|
||||
<$transformModelToClipPos(cam, obj, position, gl_Position)$>
|
||||
<$transformModelToWorldPos(obj, position, _worldPosition)$>
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue