Merge remote-tracking branch 'upstream/master' into android

This commit is contained in:
Brad Davis 2018-01-17 17:51:39 -08:00
commit cbb2d9d854
48 changed files with 2593 additions and 726 deletions

View file

@ -1,256 +0,0 @@
//
// ToolWindow.qml
//
// Created by Bradley Austin Davis on 12 Jan 2016
// Copyright 2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
import QtQuick 2.5
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import QtWebEngine 1.1
import QtWebChannel 1.0
import Qt.labs.settings 1.0
import "windows"
import "controls-uit"
import "styles-uit"
ScrollingWindow {
id: toolWindow
resizable: true
objectName: "ToolWindow"
destroyOnCloseButton: false
destroyOnHidden: false
closable: true
shown: false
title: "Edit"
property alias tabView: tabView
implicitWidth: 520; implicitHeight: 695
minSize: Qt.vector2d(456, 500)
HifiConstants { id: hifi }
onParentChanged: {
if (parent) {
x = 120;
y = 120;
}
}
onShownChanged: {
keyboardEnabled = HMD.active;
}
Settings {
category: "ToolWindow.Position"
property alias x: toolWindow.x
property alias y: toolWindow.y
}
Item {
id: toolWindowTabViewItem
height: pane.scrollHeight
width: pane.contentWidth
anchors.left: parent.left
anchors.top: parent.top
TabView {
id: tabView
width: pane.contentWidth
// Pane height so that don't use Window's scrollbars otherwise tabs may be scrolled out of view.
height: pane.scrollHeight
property int tabCount: 0
Repeater {
model: 4
Tab {
// Force loading of the content even if the tab is not visible
// (required for letting the C++ code access the webview)
active: true
enabled: false
property string originalUrl: ""
WebView {
id: webView
anchors.fill: parent
enabled: false
Component.onCompleted: {
webChannel.registerObject("eventBridge", eventBridge);
webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper);
}
onEnabledChanged: toolWindow.updateVisiblity()
}
}
}
style: TabViewStyle {
frame: Rectangle { // Background shown before content loads.
anchors.fill: parent
color: hifi.colors.baseGray
}
frameOverlap: 0
tab: Rectangle {
implicitWidth: text.width
implicitHeight: 3 * text.height
color: styleData.selected ? hifi.colors.black : hifi.colors.tabBackgroundDark
RalewayRegular {
id: text
text: styleData.title
font.capitalization: Font.AllUppercase
size: hifi.fontSizes.tabName
width: tabView.tabCount > 1 ? styleData.availableWidth / tabView.tabCount : implicitWidth + 2 * hifi.dimensions.contentSpacing.x
elide: Text.ElideRight
color: styleData.selected ? hifi.colors.primaryHighlight : hifi.colors.lightGrayText
horizontalAlignment: Text.AlignHCenter
anchors.centerIn: parent
}
Rectangle { // Separator.
width: 1
height: parent.height
color: hifi.colors.black
anchors.left: parent.left
anchors.top: parent.top
visible: styleData.index > 0
Rectangle {
width: 1
height: 1
color: hifi.colors.baseGray
anchors.left: parent.left
anchors.bottom: parent.bottom
}
}
Rectangle { // Active underline.
width: parent.width - (styleData.index > 0 ? 1 : 0)
height: 1
anchors.right: parent.right
anchors.bottom: parent.bottom
color: styleData.selected ? hifi.colors.primaryHighlight : hifi.colors.baseGray
}
}
tabOverlap: 0
}
}
}
function updateVisiblity() {
if (visible) {
for (var i = 0; i < tabView.count; ++i) {
if (tabView.getTab(i).enabled) {
return;
}
}
shown = false;
}
}
function findIndexForUrl(source) {
for (var i = 0; i < tabView.count; ++i) {
var tab = tabView.getTab(i);
if (tab.originalUrl === source) {
return i;
}
}
return -1;
}
function findTabForUrl(source) {
var index = findIndexForUrl(source);
if (index < 0) {
return;
}
return tabView.getTab(index);
}
function showTabForUrl(source, newVisible) {
var index = findIndexForUrl(source);
if (index < 0) {
return;
}
var tab = tabView.getTab(index);
if (newVisible) {
toolWindow.shown = true
tab.enabled = true
} else {
tab.enabled = false;
updateVisiblity();
}
}
function findFreeTab() {
for (var i = 0; i < tabView.count; ++i) {
var tab = tabView.getTab(i);
if (tab && (!tab.originalUrl || tab.originalUrl === "")) {
return i;
}
}
return -1;
}
function removeTabForUrl(source) {
var index = findIndexForUrl(source);
if (index < 0) {
return;
}
var tab = tabView.getTab(index);
tab.title = "";
tab.enabled = false;
tab.originalUrl = "";
tab.item.url = "about:blank";
tab.item.enabled = false;
tabView.tabCount--;
}
function addWebTab(properties) {
if (!properties.source) {
console.warn("Attempted to open Web Tool Pane without URL");
return;
}
var existingTabIndex = findIndexForUrl(properties.source);
if (existingTabIndex >= 0) {
var tab = tabView.getTab(existingTabIndex);
return tab.item;
}
var freeTabIndex = findFreeTab();
if (freeTabIndex === -1) {
console.warn("Unable to add new tab");
return;
}
if (properties.width) {
tabView.width = Math.min(Math.max(tabView.width, properties.width), toolWindow.maxSize.x);
}
if (properties.height) {
tabView.height = Math.min(Math.max(tabView.height, properties.height), toolWindow.maxSize.y);
}
var tab = tabView.getTab(freeTabIndex);
tab.title = properties.title || "Unknown";
tab.enabled = true;
tab.originalUrl = properties.source;
var result = tab.item;
result.enabled = true;
tabView.tabCount++;
result.url = properties.source;
return result;
}
}

View file

@ -27,10 +27,12 @@ TextField {
property bool hasRoundedBorder: false
property bool error: false;
property bool hasClearButton: false;
property string leftPlaceholderGlyph: "";
placeholderText: textField.placeholderText
FontLoader { id: firaSansSemiBold; source: "../../fonts/FiraSans-SemiBold.ttf"; }
FontLoader { id: hifiGlyphs; source: "../../fonts/hifi-glyphs.ttf"; }
font.family: firaSansSemiBold.name
font.pixelSize: hifi.fontSizes.textFieldInput
font.italic: textField.text == ""
@ -54,6 +56,7 @@ TextField {
}
style: TextFieldStyle {
id: style;
textColor: {
if (isLightColorScheme) {
if (textField.activeFocus) {
@ -102,6 +105,16 @@ TextField {
border.width: textField.activeFocus || hasRoundedBorder || textField.error ? 1 : 0
radius: isSearchField ? textField.height / 2 : (hasRoundedBorder ? 4 : 0)
HiFiGlyphs {
text: textField.leftPlaceholderGlyph;
color: textColor;
size: hifi.fontSizes.textFieldSearchIcon;
anchors.left: parent.left;
anchors.verticalCenter: parent.verticalCenter;
anchors.leftMargin: hifi.dimensions.textPadding - 2;
visible: text;
}
HiFiGlyphs {
text: hifi.glyphs.search
color: textColor
@ -132,7 +145,7 @@ TextField {
placeholderTextColor: isFaintGrayColorScheme ? hifi.colors.lightGrayText : hifi.colors.lightGray
selectedTextColor: hifi.colors.black
selectionColor: hifi.colors.primaryHighlight
padding.left: (isSearchField ? textField.height - 2 : 0) + hifi.dimensions.textPadding
padding.left: ((isSearchField || textField.leftPlaceholderGlyph !== "") ? textField.height - 2 : 0) + hifi.dimensions.textPadding
padding.right: (hasClearButton ? textField.height - 2 : 0) + hifi.dimensions.textPadding
}

View file

@ -1,73 +0,0 @@
//
// SendMoney.qml
// qml/hifi/commerce/wallet
//
// SendMoney
//
// Created by Zach Fox on 2017-08-18
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
import Hifi 1.0 as Hifi
import QtQuick 2.5
import QtQuick.Controls 1.4
import "../../../styles-uit"
import "../../../controls-uit" as HifiControlsUit
import "../../../controls" as HifiControls
// references XXX from root context
Item {
HifiConstants { id: hifi; }
id: root;
Connections {
target: Commerce;
}
// "Unavailable"
RalewayRegular {
text: "You currently cannot send money to other High Fidelity users.";
// Anchors
anchors.fill: parent;
// Text size
size: 24;
// Style
color: hifi.colors.faintGray;
wrapMode: Text.WordWrap;
// Alignment
horizontalAlignment: Text.AlignHCenter;
verticalAlignment: Text.AlignVCenter;
}
//
// FUNCTION DEFINITIONS START
//
//
// Function Name: fromScript()
//
// Relevant Variables:
// None
//
// Arguments:
// message: The message sent from the JavaScript.
// Messages are in format "{method, params}", like json-rpc.
//
// Description:
// Called when a message is received from a script.
//
function fromScript(message) {
switch (message.method) {
default:
console.log('Unrecognized message from wallet.js:', JSON.stringify(message));
}
}
signal sendSignalToWallet(var msg);
//
// FUNCTION DEFINITIONS END
//
}

View file

@ -19,8 +19,7 @@ import "../../../styles-uit"
import "../../../controls-uit" as HifiControlsUit
import "../../../controls" as HifiControls
import "../common" as HifiCommerceCommon
// references XXX from root context
import "./sendMoney"
Rectangle {
HifiConstants { id: hifi; }
@ -316,18 +315,29 @@ Rectangle {
Connections {
onSendSignalToWallet: {
sendToScript(msg);
if (msg.method === 'transactionHistory_usernameLinkClicked') {
userInfoViewer.url = msg.usernameLink;
userInfoViewer.visible = true;
} else {
sendToScript(msg);
}
}
}
}
SendMoney {
id: sendMoney;
z: 997;
visible: root.activeView === "sendMoney";
anchors.top: titleBarContainer.bottom;
anchors.bottom: tabButtonsContainer.top;
anchors.left: parent.left;
anchors.right: parent.right;
anchors.fill: parent;
parentAppTitleBarHeight: titleBarContainer.height;
parentAppNavBarHeight: tabButtonsContainer.height;
Connections {
onSendSignalToWallet: {
sendToScript(msg);
}
}
}
Security {
@ -497,7 +507,7 @@ Rectangle {
Rectangle {
id: sendMoneyButtonContainer;
visible: !walletSetup.visible;
color: hifi.colors.black;
color: root.activeView === "sendMoney" ? hifi.colors.blueAccent : hifi.colors.black;
anchors.top: parent.top;
anchors.left: exchangeMoneyButtonContainer.right;
anchors.bottom: parent.bottom;
@ -513,7 +523,7 @@ Rectangle {
anchors.top: parent.top;
anchors.topMargin: -2;
// Style
color: hifi.colors.lightGray50;
color: root.activeView === "sendMoney" || sendMoneyTabMouseArea.containsMouse ? hifi.colors.white : hifi.colors.blueHighlight;
}
RalewaySemiBold {
@ -528,12 +538,24 @@ Rectangle {
anchors.right: parent.right;
anchors.rightMargin: 4;
// Style
color: hifi.colors.lightGray50;
color: root.activeView === "sendMoney" || sendMoneyTabMouseArea.containsMouse ? hifi.colors.white : hifi.colors.blueHighlight;
wrapMode: Text.WordWrap;
// Alignment
horizontalAlignment: Text.AlignHCenter;
verticalAlignment: Text.AlignTop;
}
MouseArea {
id: sendMoneyTabMouseArea;
anchors.fill: parent;
hoverEnabled: enabled;
onClicked: {
root.activeView = "sendMoney";
tabButtonsContainer.resetTabButtonColors();
}
onEntered: parent.color = hifi.colors.blueHighlight;
onExited: parent.color = root.activeView === "sendMoney" ? hifi.colors.blueAccent : hifi.colors.black;
}
}
// "SECURITY" tab button
@ -665,9 +687,16 @@ Rectangle {
// TAB BUTTONS END
//
HifiControls.TabletWebView {
id: userInfoViewer;
z: 998;
anchors.fill: parent;
visible: false;
}
Item {
id: keyboardContainer;
z: 998;
z: 999;
visible: keyboard.raised;
property bool punctuationMode: false;
anchors {
@ -713,6 +742,13 @@ Rectangle {
case 'inspectionCertificate_resetCert':
// NOP
break;
case 'updateConnections':
sendMoney.updateConnections(message.connections);
break;
case 'selectRecipient':
case 'updateSelectedRecipientUsername':
sendMoney.fromScript(message);
break;
default:
console.log('Unrecognized message from wallet.js:', JSON.stringify(message));
}

View file

@ -19,8 +19,6 @@ import "../../../styles-uit"
import "../../../controls-uit" as HifiControlsUit
import "../../../controls" as HifiControls
// references XXX from root context
Item {
HifiConstants { id: hifi; }
@ -32,6 +30,20 @@ Item {
property int currentHistoryPage: 1;
property var pagesAlreadyAdded: new Array();
onVisibleChanged: {
if (visible) {
transactionHistoryModel.clear();
Commerce.balance();
initialHistoryReceived = false;
root.currentHistoryPage = 1;
root.noMoreHistoryData = false;
root.historyRequestPending = true;
Commerce.history(root.currentHistoryPage);
} else {
refreshTimer.stop();
}
}
Connections {
target: Commerce;
@ -189,20 +201,6 @@ Item {
color: hifi.colors.white;
// Alignment
verticalAlignment: Text.AlignVCenter;
onVisibleChanged: {
if (visible) {
transactionHistoryModel.clear();
Commerce.balance();
initialHistoryReceived = false;
root.currentHistoryPage = 1;
root.noMoreHistoryData = false;
root.historyRequestPending = true;
Commerce.history(root.currentHistoryPage);
} else {
refreshTimer.stop();
}
}
}
// "balance" text below field
@ -384,8 +382,8 @@ Item {
height: visible ? parent.height : 0;
AnonymousProRegular {
id: dateText;
text: model.created_at ? getFormattedDate(model.created_at * 1000) : "";
id: hfcText;
text: model.hfc_text || '';
// Style
size: 18;
anchors.left: parent.left;
@ -393,28 +391,33 @@ Item {
anchors.topMargin: 15;
width: 118;
height: paintedHeight;
color: hifi.colors.blueAccent;
wrapMode: Text.WordWrap;
font.bold: true;
// Alignment
horizontalAlignment: Text.AlignRight;
}
AnonymousProRegular {
id: transactionText;
text: model.text ? (model.status === "invalidated" ? ("INVALIDATED: " + model.text) : model.text) : "";
text: model.transaction_text ? (model.status === "invalidated" ? ("INVALIDATED: " + model.transaction_text) : model.transaction_text) : "";
size: 18;
anchors.top: parent.top;
anchors.topMargin: 15;
anchors.left: dateText.right;
anchors.left: hfcText.right;
anchors.leftMargin: 20;
anchors.right: parent.right;
height: paintedHeight;
color: model.status === "invalidated" ? hifi.colors.redAccent : hifi.colors.baseGrayHighlight;
linkColor: hifi.colors.blueAccent;
wrapMode: Text.WordWrap;
font.strikeout: model.status === "invalidated";
onLinkActivated: {
sendSignalToWallet({method: 'transactionHistory_linkClicked', marketplaceLink: link});
if (link.indexOf("users/") !== -1) {
sendSignalToWallet({method: 'transactionHistory_usernameLinkClicked', usernameLink: link});
} else {
sendSignalToWallet({method: 'transactionHistory_linkClicked', marketplaceLink: link});
}
}
}

View file

@ -0,0 +1,128 @@
//
// ConnectionItem.qml
// qml/hifi/commerce/wallet/sendMoney
//
// ConnectionItem
//
// Created by Zach Fox on 2018-01-09
// 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 QtGraphicalEffects 1.0
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import "../../../../styles-uit"
import "../../../../controls-uit" as HifiControlsUit
import "../../../../controls" as HifiControls
import "../../wallet" as HifiWallet
Item {
HifiConstants { id: hifi; }
id: root;
property bool isSelected: false;
property string userName;
property string profilePicUrl;
height: 65;
width: parent.width;
Rectangle {
id: mainContainer;
// Style
color: root.isSelected ? hifi.colors.faintGray : hifi.colors.white;
// Size
anchors.left: parent.left;
anchors.right: parent.right;
anchors.top: parent.top;
height: root.height;
Item {
id: avatarImage;
visible: profileUrl !== "" && userName !== "";
// Size
anchors.verticalCenter: parent.verticalCenter;
anchors.left: parent.left;
anchors.leftMargin: 36;
height: root.height - 15;
width: visible ? height : 0;
clip: true;
Image {
id: userImage;
source: root.profilePicUrl !== "" ? ((0 === root.profilePicUrl.indexOf("http")) ?
root.profilePicUrl : (Account.metaverseServerURL + root.profilePicUrl)) : "";
mipmap: true;
// Anchors
anchors.fill: parent
layer.enabled: true
layer.effect: OpacityMask {
maskSource: Item {
width: userImage.width;
height: userImage.height;
Rectangle {
anchors.centerIn: parent;
width: userImage.width; // This works because userImage is square
height: width;
radius: width;
}
}
}
}
AnimatedImage {
source: "../../../../../icons/profilePicLoading.gif"
anchors.fill: parent;
visible: userImage.status != Image.Ready;
}
}
RalewaySemiBold {
id: userName;
anchors.left: avatarImage.right;
anchors.leftMargin: 16;
anchors.top: parent.top;
anchors.bottom: parent.bottom;
anchors.right: chooseButton.visible ? chooseButton.left : parent.right;
anchors.rightMargin: chooseButton.visible ? 10 : 0;
// Text size
size: 20;
// Style
color: hifi.colors.baseGray;
text: root.userName;
elide: Text.ElideRight;
// Alignment
horizontalAlignment: Text.AlignLeft;
verticalAlignment: Text.AlignVCenter;
}
// "Choose" button
HifiControlsUit.Button {
id: chooseButton;
visible: root.isSelected;
color: hifi.buttons.blue;
colorScheme: hifi.colorSchemes.dark;
anchors.verticalCenter: parent.verticalCenter;
anchors.right: parent.right;
anchors.rightMargin: 24;
height: root.height - 20;
width: 110;
text: "CHOOSE";
onClicked: {
var msg = { method: 'chooseConnection', userName: root.userName, profilePicUrl: root.profilePicUrl };
sendToSendMoney(msg);
}
}
}
//
// FUNCTION DEFINITIONS START
//
signal sendToSendMoney(var msg);
//
// FUNCTION DEFINITIONS END
//
}

View file

@ -0,0 +1,116 @@
//
// RecipientDisplay.qml
// qml/hifi/commerce/wallet/sendMoney
//
// RecipientDisplay
//
// Created by Zach Fox on 2018-01-11
// 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.6
import QtQuick.Controls 2.2
import QtGraphicalEffects 1.0
import "../../../../styles-uit"
import "../../../../controls-uit" as HifiControlsUit
import "../../../../controls" as HifiControls
import "../../common" as HifiCommerceCommon
Item {
HifiConstants { id: hifi; }
id: root;
property bool isDisplayingNearby; // as opposed to 'connections'
property string displayName;
property string userName;
property string profilePic;
Item {
visible: root.isDisplayingNearby;
anchors.fill: parent;
RalewaySemiBold {
id: recipientDisplayName;
text: root.displayName;
// Anchors
anchors.top: parent.top;
anchors.left: parent.left;
anchors.right: parent.right;
anchors.rightMargin: 12;
height: parent.height/2;
// Text size
size: 18;
// Style
color: hifi.colors.baseGray;
verticalAlignment: Text.AlignBottom;
elide: Text.ElideRight;
}
RalewaySemiBold {
text: root.userName;
// Anchors
anchors.bottom: parent.bottom;
anchors.left: recipientDisplayName.anchors.left;
anchors.leftMargin: recipientDisplayName.anchors.leftMargin;
anchors.right: recipientDisplayName.anchors.right;
anchors.rightMargin: recipientDisplayName.anchors.rightMargin;
height: parent.height/2;
// Text size
size: 16;
// Style
color: hifi.colors.baseGray;
verticalAlignment: Text.AlignTop;
elide: Text.ElideRight;
}
}
Item {
visible: !root.isDisplayingNearby;
anchors.fill: parent;
Image {
id: userImage;
source: root.profilePic;
mipmap: true;
// Anchors
anchors.left: parent.left;
anchors.verticalCenter: parent.verticalCenter;
height: parent.height - 36;
width: height;
layer.enabled: true;
layer.effect: OpacityMask {
maskSource: Item {
width: userImage.width;
height: userImage.height;
Rectangle {
anchors.centerIn: parent;
width: userImage.width; // This works because userImage is square
height: width;
radius: width;
}
}
}
}
RalewaySemiBold {
text: root.userName;
// Anchors
anchors.left: userImage.right;
anchors.leftMargin: 8;
anchors.right: parent.right;
anchors.verticalCenter: parent.verticalCenter;
height: parent.height - 4;
// Text size
size: 16;
// Style
color: hifi.colors.baseGray;
verticalAlignment: Text.AlignVCenter;
elide: Text.ElideRight;
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -2351,7 +2351,7 @@ void Application::initializeUi() {
QUrl{ "hifi/commerce/wallet/SecurityImageChange.qml" },
QUrl{ "hifi/commerce/wallet/SecurityImageModel.qml" },
QUrl{ "hifi/commerce/wallet/SecurityImageSelection.qml" },
QUrl{ "hifi/commerce/wallet/SendMoney.qml" },
QUrl{ "hifi/commerce/wallet/sendMoney/SendMoney.qml" },
QUrl{ "hifi/commerce/wallet/Wallet.qml" },
QUrl{ "hifi/commerce/wallet/WalletHome.qml" },
QUrl{ "hifi/commerce/wallet/WalletSetup.qml" },

View file

@ -574,8 +574,6 @@ Menu::Menu() {
avatar.get(), SLOT(setEnableMeshVisible(bool)));
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::DisableEyelidAdjustment, 0, false);
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::TurnWithHead, 0, false);
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::UseAnimPreAndPostRotations, 0, true,
avatar.get(), SLOT(setUseAnimPreAndPostRotations(bool)));
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::EnableInverseKinematics, 0, true,
avatar.get(), SLOT(setEnableInverseKinematics(bool)));
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderSensorToWorldMatrix, 0, false,

View file

@ -194,7 +194,6 @@ namespace MenuOption {
const QString TurnWithHead = "Turn using Head";
const QString UseAudioForMouth = "Use Audio for Mouth";
const QString UseCamera = "Use Camera";
const QString UseAnimPreAndPostRotations = "Use Anim Pre and Post Rotations";
const QString VelocityFilter = "Velocity Filter";
const QString VisibleToEveryone = "Everyone";
const QString VisibleToFriends = "Friends";

View file

@ -1062,11 +1062,6 @@ void MyAvatar::setEnableMeshVisible(bool isEnabled) {
_skeletonModel->setVisibleInScene(isEnabled, qApp->getMain3DScene());
}
void MyAvatar::setUseAnimPreAndPostRotations(bool isEnabled) {
AnimClip::usePreAndPostPoseFromAnim = isEnabled;
reset(true);
}
void MyAvatar::setEnableInverseKinematics(bool isEnabled) {
_skeletonModel->getRig().setEnableInverseKinematics(isEnabled);
}
@ -1930,7 +1925,7 @@ void MyAvatar::preDisplaySide(RenderArgs* renderArgs) {
_prevShouldDrawHead = shouldDrawHead;
}
const float RENDER_HEAD_CUTOFF_DISTANCE = 0.3f;
const float RENDER_HEAD_CUTOFF_DISTANCE = 0.47f;
bool MyAvatar::cameraInsideHead(const glm::vec3& cameraPosition) const {
return glm::length(cameraPosition - getHeadPosition()) < (RENDER_HEAD_CUTOFF_DISTANCE * getModelScale());
@ -2101,7 +2096,7 @@ void MyAvatar::updateActionMotor(float deltaTime) {
_actionMotorVelocity = motorSpeed * direction;
} else {
// we're interacting with a floor --> simple horizontal speed and exponential decay
_actionMotorVelocity = getSensorToWorldScale() * DEFAULT_AVATAR_MAX_WALKING_SPEED * direction;
_actionMotorVelocity = getSensorToWorldScale() * _walkSpeed.get() * direction;
}
float boomChange = getDriveKey(ZOOM);
@ -2693,6 +2688,14 @@ float MyAvatar::getUserEyeHeight() const {
return userHeight - userHeight * ratio;
}
float MyAvatar::getWalkSpeed() const {
return _walkSpeed.get();
}
void MyAvatar::setWalkSpeed(float value) {
_walkSpeed.set(value);
}
glm::vec3 MyAvatar::getPositionForAudio() {
switch (_audioListenerMode) {
case AudioListenerMode::FROM_HEAD:

View file

@ -163,6 +163,8 @@ class MyAvatar : public Avatar {
Q_PROPERTY(QUuid SELF_ID READ getSelfID CONSTANT)
Q_PROPERTY(float walkSpeed READ getWalkSpeed WRITE setWalkSpeed);
const QString DOMINANT_LEFT_HAND = "left";
const QString DOMINANT_RIGHT_HAND = "right";
@ -557,6 +559,9 @@ public:
const QUuid& getSelfID() const { return AVATAR_SELF_ID; }
void setWalkSpeed(float value);
float getWalkSpeed() const;
public slots:
void increaseSize();
void decreaseSize();
@ -594,7 +599,6 @@ public slots:
bool getEnableMeshVisible() const { return _skeletonModel->isVisible(); }
void setEnableMeshVisible(bool isEnabled);
void setUseAnimPreAndPostRotations(bool isEnabled);
void setEnableInverseKinematics(bool isEnabled);
QUrl getAnimGraphOverrideUrl() const; // thread-safe
@ -841,6 +845,9 @@ private:
// height of user in sensor space, when standing erect.
ThreadSafeValueCache<float> _userHeight { DEFAULT_AVATAR_HEIGHT };
// max unscaled forward movement speed
ThreadSafeValueCache<float> _walkSpeed { DEFAULT_AVATAR_MAX_WALKING_SPEED };
};
QScriptValue audioListenModeToScriptValue(QScriptEngine* engine, const AudioListenerMode& audioListenerMode);

View file

@ -46,6 +46,8 @@ Handler(buy)
Handler(receiveAt)
Handler(balance)
Handler(inventory)
Handler(transferHfcToNode)
Handler(transferHfcToUsername)
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>();
@ -116,23 +118,60 @@ void Ledger::inventory(const QStringList& keys) {
keysQuery("inventory", "inventorySuccess", "inventoryFailure");
}
QString amountString(const QString& label, const QString&color, const QJsonValue& moneyValue, const QJsonValue& certsValue) {
int money = moneyValue.toInt();
int certs = certsValue.toInt();
if (money <= 0 && certs <= 0) {
return QString();
QString hfcString(const QJsonValue& sentValue, const QJsonValue& receivedValue) {
int sent = sentValue.toInt();
int received = receivedValue.toInt();
if (sent <= 0 && received <= 0) {
return QString("-");
}
QString result(QString("<font color='#%1'> %2").arg(color, label));
if (money > 0) {
result += QString(" %1 HFC").arg(money);
}
if (certs > 0) {
if (money > 0) {
result += QString(",");
QString result;
if (sent > 0) {
result += QString("<font color='#B70A37'>-%1 HFC</font>").arg(sent);
if (received > 0) {
result += QString("<br>");
}
result += QString((certs == 1) ? " %1 certificate" : " %1 certificates").arg(certs);
}
return result + QString("</font>");
if (received > 0) {
result += QString("<font color='#3AA38F'>%1 HFC</font>").arg(received);
}
return result;
}
static const QString USER_PAGE_BASE_URL = NetworkingConstants::METAVERSE_SERVER_URL().toString() + "/users/";
QString userLink(const QString& username) {
if (username.isEmpty()) {
return QString("someone");
}
return QString("<a href=\"%1%2\">%2</a>").arg(USER_PAGE_BASE_URL, username);
}
QString transactionString(const QJsonObject& valueObject) {
int sentCerts = valueObject["sent_certs"].toInt();
int receivedCerts = valueObject["received_certs"].toInt();
int sent = valueObject["sent_money"].toInt();
int dateInteger = valueObject["created_at"].toInt();
QString message = valueObject["message"].toString();
QDateTime createdAt(QDateTime::fromSecsSinceEpoch(dateInteger, Qt::UTC));
QString result;
if (sentCerts <= 0 && receivedCerts <= 0) {
// this is an hfc transfer.
if (sent > 0) {
QString recipient = userLink(valueObject["recipient_name"].toString());
result += QString("Money sent to %1").arg(recipient);
} else {
QString sender = userLink(valueObject["sender_name"].toString());
result += QString("Money from %1").arg(sender);
}
if (!message.isEmpty()) {
result += QString("<br>with memo: <i>\"%1\"</i>").arg(message);
}
} 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;
}
static const QString MARKETPLACE_ITEMS_BASE_URL = NetworkingConstants::METAVERSE_SERVER_URL().toString() + "/marketplace/items/";
@ -155,16 +194,13 @@ void Ledger::historySuccess(QNetworkReply& reply) {
// TODO: do this with 0 copies if possible
for (auto it = historyArray.begin(); it != historyArray.end(); it++) {
// We have 2 text fields to synthesize, the one on the left is a listing
// of the HFC in/out of your wallet. The one on the right contains an explaination
// of the transaction. That could be just the memo (if it is a regular purchase), or
// more text (plus the optional memo) if an hfc transfer
auto valueObject = (*it).toObject();
QString sent = amountString("sent", "EA4C5F", valueObject["sent_money"], valueObject["sent_certs"]);
QString received = amountString("received", "1FC6A6", valueObject["received_money"], valueObject["received_certs"]);
// turns out on my machine, toLocalTime convert to some weird timezone, yet the
// systemTimeZone is correct. To avoid a strange bug with other's systems too, lets
// be explicit
QDateTime createdAt = QDateTime::fromSecsSinceEpoch(valueObject["created_at"].toInt(), Qt::UTC);
QDateTime localCreatedAt = createdAt.toTimeZone(QTimeZone::systemTimeZone());
valueObject["text"] = QString("%1%2%3").arg(valueObject["message"].toString(), sent, received);
valueObject["hfc_text"] = hfcString(valueObject["sent_money"], valueObject["received_money"]);
valueObject["transaction_text"] = transactionString(valueObject);
newHistoryArray.push_back(valueObject);
}
// now copy the rest of the json -- this is inefficient
@ -198,11 +234,17 @@ void Ledger::accountSuccess(QNetworkReply& reply) {
auto salt = QByteArray::fromBase64(data["salt"].toString().toUtf8());
auto iv = QByteArray::fromBase64(data["iv"].toString().toUtf8());
auto ckey = QByteArray::fromBase64(data["ckey"].toString().toUtf8());
QString remotePublicKey = data["public_key"].toString();
wallet->setSalt(salt);
wallet->setIv(iv);
wallet->setCKey(ckey);
QStringList localPublicKeys = wallet->listPublicKeys();
if (remotePublicKey.isEmpty() && !localPublicKeys.isEmpty()) {
receiveAt(localPublicKeys.first(), "");
}
// none of the hfc account info should be emitted
emit accountResult(QJsonObject{ {"status", "success"} });
}
@ -261,3 +303,25 @@ void Ledger::certificateInfo(const QString& certificateId) {
request["certificate_id"] = certificateId;
send(endpoint, "certificateInfoSuccess", "certificateInfoFailure", QNetworkAccessManager::PutOperation, AccountManagerAuth::None, request);
}
void Ledger::transferHfcToNode(const QString& hfc_key, const QString& nodeID, const int& amount, const QString& optionalMessage) {
QJsonObject transaction;
transaction["public_key"] = hfc_key;
transaction["node_id"] = nodeID;
transaction["quantity"] = amount;
transaction["message"] = optionalMessage;
QJsonDocument transactionDoc{ transaction };
auto transactionString = transactionDoc.toJson(QJsonDocument::Compact);
signedSend("transaction", transactionString, hfc_key, "transfer_hfc_to_node", "transferHfcToNodeSuccess", "transferHfcToNodeFailure");
}
void Ledger::transferHfcToUsername(const QString& hfc_key, const QString& username, const int& amount, const QString& optionalMessage) {
QJsonObject transaction;
transaction["public_key"] = hfc_key;
transaction["username"] = username;
transaction["quantity"] = amount;
transaction["message"] = optionalMessage;
QJsonDocument transactionDoc{ transaction };
auto transactionString = transactionDoc.toJson(QJsonDocument::Compact);
signedSend("transaction", transactionString, hfc_key, "transfer_hfc_to_user", "transferHfcToUsernameSuccess", "transferHfcToUsernameFailure");
}

View file

@ -33,6 +33,8 @@ public:
void account();
void updateLocation(const QString& asset_id, const QString location, const bool controlledFailure = false);
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);
enum CertificateStatus {
CERTIFICATE_STATUS_UNKNOWN = 0,
@ -51,6 +53,8 @@ signals:
void accountResult(QJsonObject result);
void locationUpdateResult(QJsonObject result);
void certificateInfoResult(QJsonObject result);
void transferHfcToNodeResult(QJsonObject result);
void transferHfcToUsernameResult(QJsonObject result);
void updateCertificateStatus(const QString& certID, uint certStatus);
@ -71,6 +75,10 @@ public slots:
void updateLocationFailure(QNetworkReply& reply);
void certificateInfoSuccess(QNetworkReply& reply);
void certificateInfoFailure(QNetworkReply& reply);
void transferHfcToNodeSuccess(QNetworkReply& reply);
void transferHfcToNodeFailure(QNetworkReply& reply);
void transferHfcToUsernameSuccess(QNetworkReply& reply);
void transferHfcToUsernameFailure(QNetworkReply& reply);
private:
QJsonObject apiResponse(const QString& label, QNetworkReply& reply);

View file

@ -29,6 +29,8 @@ QmlCommerce::QmlCommerce() {
connect(wallet.data(), &Wallet::walletStatusResult, this, &QmlCommerce::walletStatusResult);
connect(ledger.data(), &Ledger::certificateInfoResult, this, &QmlCommerce::certificateInfoResult);
connect(ledger.data(), &Ledger::updateCertificateStatus, this, &QmlCommerce::updateCertificateStatus);
connect(ledger.data(), &Ledger::transferHfcToNodeResult, this, &QmlCommerce::transferHfcToNodeResult);
connect(ledger.data(), &Ledger::transferHfcToUsernameResult, this, &QmlCommerce::transferHfcToUsernameResult);
auto accountManager = DependencyManager::get<AccountManager>();
connect(accountManager.data(), &AccountManager::usernameChanged, this, [&]() {
@ -137,3 +139,27 @@ void QmlCommerce::certificateInfo(const QString& certificateId) {
auto ledger = DependencyManager::get<Ledger>();
ledger->certificateInfo(certificateId);
}
void QmlCommerce::transferHfcToNode(const QString& nodeID, const int& amount, const QString& optionalMessage) {
auto ledger = DependencyManager::get<Ledger>();
auto wallet = DependencyManager::get<Wallet>();
QStringList keys = wallet->listPublicKeys();
if (keys.count() == 0) {
QJsonObject result{ { "status", "fail" },{ "message", "Uninitialized Wallet." } };
return emit buyResult(result);
}
QString key = keys[0];
ledger->transferHfcToNode(key, nodeID, amount, optionalMessage);
}
void QmlCommerce::transferHfcToUsername(const QString& username, const int& amount, const QString& optionalMessage) {
auto ledger = DependencyManager::get<Ledger>();
auto wallet = DependencyManager::get<Wallet>();
QStringList keys = wallet->listPublicKeys();
if (keys.count() == 0) {
QJsonObject result{ { "status", "fail" },{ "message", "Uninitialized Wallet." } };
return emit buyResult(result);
}
QString key = keys[0];
ledger->transferHfcToUsername(key, username, amount, optionalMessage);
}

View file

@ -45,6 +45,9 @@ signals:
void updateCertificateStatus(const QString& certID, uint certStatus);
void transferHfcToNodeResult(QJsonObject result);
void transferHfcToUsernameResult(QJsonObject result);
protected:
Q_INVOKABLE void getWalletStatus();
@ -65,6 +68,9 @@ protected:
Q_INVOKABLE void account();
Q_INVOKABLE void certificateInfo(const QString& certificateId);
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);
};
#endif // hifi_QmlCommerce_h

View file

@ -411,6 +411,11 @@ int WindowScriptingInterface::getY() {
}
void WindowScriptingInterface::copyToClipboard(const QString& text) {
if (QThread::currentThread() != qApp->thread()) {
QMetaObject::invokeMethod(this, "copyToClipboard", Q_ARG(QString, text));
return;
}
qDebug() << "Copying";
QApplication::clipboard()->setText(text);
}

View file

@ -13,8 +13,6 @@
#include "AnimationLogging.h"
#include "AnimUtil.h"
bool AnimClip::usePreAndPostPoseFromAnim = true;
AnimClip::AnimClip(const QString& id, const QString& url, float startFrame, float endFrame, float timeScale, bool loopFlag, bool mirrorFlag) :
AnimNode(AnimNode::Type::Clip, id),
_startFrame(startFrame),
@ -138,14 +136,8 @@ void AnimClip::copyFromNetworkAnim() {
if (skeletonJoint >= 0 && skeletonJoint < skeletonJointCount) {
AnimPose preRot, postRot;
if (usePreAndPostPoseFromAnim) {
preRot = animSkeleton.getPreRotationPose(animJoint);
postRot = animSkeleton.getPostRotationPose(animJoint);
} else {
// In order to support Blender, which does not have preRotation FBX support, we use the models defaultPose as the reference frame for the animations.
preRot = AnimPose(glm::vec3(1.0f), _skeleton->getRelativeBindPose(skeletonJoint).rot(), glm::vec3());
postRot = AnimPose::identity;
}
preRot = animSkeleton.getPreRotationPose(animJoint);
postRot = animSkeleton.getPostRotationPose(animJoint);
// cancel out scale
preRot.scale() = glm::vec3(1.0f);

View file

@ -25,8 +25,6 @@ class AnimClip : public AnimNode {
public:
friend class AnimTests;
static bool usePreAndPostPoseFromAnim;
AnimClip(const QString& id, const QString& url, float startFrame, float endFrame, float timeScale, bool loopFlag, bool mirrorFlag);
virtual ~AnimClip() override;

View file

@ -1255,7 +1255,7 @@ void AnimInverseKinematics::initConstraints() {
// / /
// O--O O--O
loadDefaultPoses(_skeleton->getRelativeBindPoses());
loadDefaultPoses(_skeleton->getRelativeDefaultPoses());
int numJoints = (int)_defaultRelativePoses.size();

View file

@ -33,7 +33,7 @@ AnimManipulator::~AnimManipulator() {
}
const AnimPoseVec& AnimManipulator::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut) {
return overlay(animVars, context, dt, triggersOut, _skeleton->getRelativeBindPoses());
return overlay(animVars, context, dt, triggersOut, _skeleton->getRelativeDefaultPoses());
}
const AnimPoseVec& AnimManipulator::overlay(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut, const AnimPoseVec& underPoses) {

View file

@ -56,14 +56,6 @@ int AnimSkeleton::getChainDepth(int jointIndex) const {
}
}
const AnimPose& AnimSkeleton::getAbsoluteBindPose(int jointIndex) const {
return _absoluteBindPoses[jointIndex];
}
const AnimPose& AnimSkeleton::getRelativeBindPose(int jointIndex) const {
return _relativeBindPoses[jointIndex];
}
const AnimPose& AnimSkeleton::getRelativeDefaultPose(int jointIndex) const {
return _relativeDefaultPoses[jointIndex];
}
@ -164,8 +156,6 @@ void AnimSkeleton::buildSkeletonFromJoints(const std::vector<FBXJoint>& joints)
_joints = joints;
_jointsSize = (int)joints.size();
// build a cache of bind poses
_absoluteBindPoses.reserve(_jointsSize);
_relativeBindPoses.reserve(_jointsSize);
// build a chache of default poses
_absoluteDefaultPoses.reserve(_jointsSize);
@ -192,28 +182,6 @@ void AnimSkeleton::buildSkeletonFromJoints(const std::vector<FBXJoint>& joints)
} else {
_absoluteDefaultPoses.push_back(relDefaultPose);
}
// build relative and absolute bind poses
if (_joints[i].bindTransformFoundInCluster) {
// Use the FBXJoint::bindTransform, which is absolute model coordinates
// i.e. not relative to it's parent.
AnimPose absoluteBindPose(_joints[i].bindTransform);
_absoluteBindPoses.push_back(absoluteBindPose);
if (parentIndex >= 0) {
AnimPose inverseParentAbsoluteBindPose = _absoluteBindPoses[parentIndex].inverse();
_relativeBindPoses.push_back(inverseParentAbsoluteBindPose * absoluteBindPose);
} else {
_relativeBindPoses.push_back(absoluteBindPose);
}
} else {
// use default transform instead
_relativeBindPoses.push_back(relDefaultPose);
if (parentIndex >= 0) {
_absoluteBindPoses.push_back(_absoluteBindPoses[parentIndex] * relDefaultPose);
} else {
_absoluteBindPoses.push_back(relDefaultPose);
}
}
}
for (int i = 0; i < _jointsSize; i++) {
@ -251,8 +219,6 @@ void AnimSkeleton::dump(bool verbose) const {
qCDebug(animation) << " {";
qCDebug(animation) << " index =" << i;
qCDebug(animation) << " name =" << getJointName(i);
qCDebug(animation) << " absBindPose =" << getAbsoluteBindPose(i);
qCDebug(animation) << " relBindPose =" << getRelativeBindPose(i);
qCDebug(animation) << " absDefaultPose =" << getAbsoluteDefaultPose(i);
qCDebug(animation) << " relDefaultPose =" << getRelativeDefaultPose(i);
if (verbose) {
@ -287,8 +253,6 @@ void AnimSkeleton::dump(const AnimPoseVec& poses) const {
qCDebug(animation) << " {";
qCDebug(animation) << " index =" << i;
qCDebug(animation) << " name =" << getJointName(i);
qCDebug(animation) << " absBindPose =" << getAbsoluteBindPose(i);
qCDebug(animation) << " relBindPose =" << getRelativeBindPose(i);
qCDebug(animation) << " absDefaultPose =" << getAbsoluteDefaultPose(i);
qCDebug(animation) << " relDefaultPose =" << getRelativeDefaultPose(i);
qCDebug(animation) << " pose =" << poses[i];

View file

@ -30,13 +30,6 @@ public:
int getNumJoints() const;
int getChainDepth(int jointIndex) const;
// absolute pose, not relative to parent
const AnimPose& getAbsoluteBindPose(int jointIndex) const;
// relative to parent pose
const AnimPose& getRelativeBindPose(int jointIndex) const;
const AnimPoseVec& getRelativeBindPoses() const { return _relativeBindPoses; }
// the default poses are the orientations of the joints on frame 0.
const AnimPose& getRelativeDefaultPose(int jointIndex) const;
const AnimPoseVec& getRelativeDefaultPoses() const { return _relativeDefaultPoses; }
@ -72,8 +65,6 @@ protected:
std::vector<FBXJoint> _joints;
int _jointsSize { 0 };
AnimPoseVec _absoluteBindPoses;
AnimPoseVec _relativeBindPoses;
AnimPoseVec _relativeDefaultPoses;
AnimPoseVec _absoluteDefaultPoses;
AnimPoseVec _relativePreRotationPoses;

View file

@ -179,7 +179,7 @@ void Rig::restoreRoleAnimation(const QString& role) {
} else {
qCWarning(animation) << "Rig::restoreRoleAnimation could not find role " << role;
}
auto statesIter = _roleAnimStates.find(role);
if (statesIter != _roleAnimStates.end()) {
_roleAnimStates.erase(statesIter);
@ -1050,52 +1050,6 @@ void Rig::updateAnimations(float deltaTime, const glm::mat4& rootTransform, cons
}
}
void Rig::inverseKinematics(int endIndex, glm::vec3 targetPosition, const glm::quat& targetRotation, float priority,
const QVector<int>& freeLineage, glm::mat4 rootTransform) {
ASSERT(false);
}
bool Rig::restoreJointPosition(int jointIndex, float fraction, float priority, const QVector<int>& freeLineage) {
ASSERT(false);
return false;
}
float Rig::getLimbLength(int jointIndex, const QVector<int>& freeLineage,
const glm::vec3 scale, const QVector<FBXJoint>& fbxJoints) const {
ASSERT(false);
return 1.0f;
}
glm::quat Rig::setJointRotationInBindFrame(int jointIndex, const glm::quat& rotation, float priority) {
ASSERT(false);
return glm::quat();
}
glm::vec3 Rig::getJointDefaultTranslationInConstrainedFrame(int jointIndex) {
ASSERT(false);
return glm::vec3();
}
glm::quat Rig::setJointRotationInConstrainedFrame(int jointIndex, glm::quat targetRotation, float priority, float mix) {
ASSERT(false);
return glm::quat();
}
bool Rig::getJointRotationInConstrainedFrame(int jointIndex, glm::quat& quatOut) const {
ASSERT(false);
return false;
}
void Rig::clearJointStatePriorities() {
ASSERT(false);
}
glm::quat Rig::getJointDefaultRotationInParentFrame(int jointIndex) {
ASSERT(false);
return glm::quat();
}
void Rig::updateFromEyeParameters(const EyeParameters& params) {
updateEyeJoint(params.leftEyeJointIndex, params.modelTranslation, params.modelRotation, params.eyeLookAt, params.eyeSaccade);
updateEyeJoint(params.rightEyeJointIndex, params.modelTranslation, params.modelRotation, params.eyeLookAt, params.eyeSaccade);

View file

@ -172,36 +172,6 @@ public:
// Regardless of who started the animations or how many, update the joints.
void updateAnimations(float deltaTime, const glm::mat4& rootTransform, const glm::mat4& rigToWorldTransform);
// legacy
void inverseKinematics(int endIndex, glm::vec3 targetPosition, const glm::quat& targetRotation, float priority,
const QVector<int>& freeLineage, glm::mat4 rootTransform);
// legacy
bool restoreJointPosition(int jointIndex, float fraction, float priority, const QVector<int>& freeLineage);
// legacy
float getLimbLength(int jointIndex, const QVector<int>& freeLineage,
const glm::vec3 scale, const QVector<FBXJoint>& fbxJoints) const;
// legacy
glm::quat setJointRotationInBindFrame(int jointIndex, const glm::quat& rotation, float priority);
// legacy
glm::vec3 getJointDefaultTranslationInConstrainedFrame(int jointIndex);
// legacy
glm::quat setJointRotationInConstrainedFrame(int jointIndex, glm::quat targetRotation,
float priority, float mix = 1.0f);
// legacy
bool getJointRotationInConstrainedFrame(int jointIndex, glm::quat& rotOut) const;
// legacy
glm::quat getJointDefaultRotationInParentFrame(int jointIndex);
// legacy
void clearJointStatePriorities();
void updateFromControllerParameters(const ControllerParameters& params, float dt);
void updateFromEyeParameters(const EyeParameters& params);
@ -345,7 +315,7 @@ protected:
float firstFrame;
float lastFrame;
};
struct RoleAnimState {
RoleAnimState() {}
RoleAnimState(const QString& roleId, const QString& urlIn, float fpsIn, bool loopIn, float firstFrameIn, float lastFrameIn) :

View file

@ -237,30 +237,14 @@ bool SkeletonModel::getRightHandPosition(glm::vec3& position) const {
return getJointPositionInWorldFrame(getRightHandJointIndex(), position);
}
bool SkeletonModel::restoreLeftHandPosition(float fraction, float priority) {
return restoreJointPosition(getLeftHandJointIndex(), fraction, priority);
}
bool SkeletonModel::getLeftShoulderPosition(glm::vec3& position) const {
return getJointPositionInWorldFrame(getLastFreeJointIndex(getLeftHandJointIndex()), position);
}
float SkeletonModel::getLeftArmLength() const {
return getLimbLength(getLeftHandJointIndex());
}
bool SkeletonModel::restoreRightHandPosition(float fraction, float priority) {
return restoreJointPosition(getRightHandJointIndex(), fraction, priority);
}
bool SkeletonModel::getRightShoulderPosition(glm::vec3& position) const {
return getJointPositionInWorldFrame(getLastFreeJointIndex(getRightHandJointIndex()), position);
}
float SkeletonModel::getRightArmLength() const {
return getLimbLength(getRightHandJointIndex());
}
bool SkeletonModel::getHeadPosition(glm::vec3& headPosition) const {
return isActive() && getJointPositionInWorldFrame(getFBXGeometry().headJointIndex, headPosition);
}

View file

@ -57,11 +57,6 @@ public:
/// \return true whether or not the position was found
bool getRightHandPosition(glm::vec3& position) const;
/// Restores some fraction of the default position of the left hand.
/// \param fraction the fraction of the default position to restore
/// \return whether or not the left hand joint was found
bool restoreLeftHandPosition(float fraction = 1.0f, float priority = 1.0f);
/// Gets the position of the left shoulder.
/// \return whether or not the left shoulder joint was found
bool getLeftShoulderPosition(glm::vec3& position) const;
@ -69,18 +64,10 @@ public:
/// Returns the extended length from the left hand to its last free ancestor.
float getLeftArmLength() const;
/// Restores some fraction of the default position of the right hand.
/// \param fraction the fraction of the default position to restore
/// \return whether or not the right hand joint was found
bool restoreRightHandPosition(float fraction = 1.0f, float priority = 1.0f);
/// Gets the position of the right shoulder.
/// \return whether or not the right shoulder joint was found
bool getRightShoulderPosition(glm::vec3& position) const;
/// Returns the extended length from the right hand to its first free ancestor.
float getRightArmLength() const;
/// Returns the position of the head joint.
/// \return whether or not the head was found
bool getHeadPosition(glm::vec3& headPosition) const;

View file

@ -358,7 +358,7 @@ class AvatarData : public QObject, public SpatiallyNestable {
Q_PROPERTY(QString displayName READ getDisplayName WRITE setDisplayName NOTIFY displayNameChanged)
// sessionDisplayName is sanitized, defaulted version displayName that is defined by the AvatarMixer rather than by Interface clients.
// The result is unique among all avatars present at the time.
Q_PROPERTY(QString sessionDisplayName READ getSessionDisplayName WRITE setSessionDisplayName)
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(QVector<AttachmentData> attachmentData READ getAttachmentData WRITE setAttachmentData)
@ -685,6 +685,7 @@ public:
signals:
void displayNameChanged();
void sessionDisplayNameChanged();
void lookAtSnappingChanged(bool enabled);
void sessionUUIDChanged();

View file

@ -15,6 +15,7 @@ ScriptAvatarData::ScriptAvatarData(AvatarSharedPointer avatarData) :
_avatarData(avatarData)
{
QObject::connect(avatarData.get(), &AvatarData::displayNameChanged, this, &ScriptAvatarData::displayNameChanged);
QObject::connect(avatarData.get(), &AvatarData::sessionDisplayNameChanged, this, &ScriptAvatarData::sessionDisplayNameChanged);
QObject::connect(avatarData.get(), &AvatarData::lookAtSnappingChanged, this, &ScriptAvatarData::lookAtSnappingChanged);
}

View file

@ -44,7 +44,7 @@ class ScriptAvatarData : public QObject {
//
Q_PROPERTY(QUuid sessionUUID READ getSessionUUID)
Q_PROPERTY(QString displayName READ getDisplayName NOTIFY displayNameChanged)
Q_PROPERTY(QString sessionDisplayName READ getSessionDisplayName)
Q_PROPERTY(QString sessionDisplayName READ getSessionDisplayName NOTIFY sessionDisplayNameChanged)
Q_PROPERTY(bool isReplicated READ getIsReplicated)
Q_PROPERTY(bool lookAtSnappingEnabled READ getLookAtSnappingEnabled NOTIFY lookAtSnappingChanged)
@ -131,6 +131,7 @@ public:
signals:
void displayNameChanged();
void sessionDisplayNameChanged();
void lookAtSnappingChanged(bool enabled);
public slots:

View file

@ -603,7 +603,7 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) {
if (!blendShape.normals.empty() && blendShape.tangents.empty()) {
// Fill with a dummy value to force tangents to be present if there are normals
blendShape.tangents.reserve(blendShape.normals.size());
std::fill_n(std::back_inserter(fbxMesh.tangents), blendShape.normals.size(), Vectors::UNIT_X);
std::fill_n(std::back_inserter(blendShape.tangents), blendShape.normals.size(), Vectors::UNIT_X);
}
}

View file

@ -15,6 +15,7 @@
#include <QJsonDocument>
#include <QRegExp>
#include <QStringList>
#include <QThread>
#include <BuildInfo.h>
#include <GLMHelpers.h>
@ -757,11 +758,21 @@ void AddressManager::refreshPreviousLookup() {
}
void AddressManager::copyAddress() {
if (QThread::currentThread() != qApp->thread()) {
QMetaObject::invokeMethod(this, "copyAddress");
return;
}
// assume that the address is being copied because the user wants a shareable address
QApplication::clipboard()->setText(currentShareableAddress().toString());
}
void AddressManager::copyPath() {
if (QThread::currentThread() != qApp->thread()) {
QMetaObject::invokeMethod(this, "copyPath");
return;
}
QApplication::clipboard()->setText(currentPath());
}

View file

@ -1267,25 +1267,6 @@ void Model::updateClusterMatrices() {
}
}
void Model::inverseKinematics(int endIndex, glm::vec3 targetPosition, const glm::quat& targetRotation, float priority) {
const FBXGeometry& geometry = getFBXGeometry();
const QVector<int>& freeLineage = geometry.joints.at(endIndex).freeLineage;
glm::mat4 parentTransform = glm::scale(_scale) * glm::translate(_offset);
_rig.inverseKinematics(endIndex, targetPosition, targetRotation, priority, freeLineage, parentTransform);
}
bool Model::restoreJointPosition(int jointIndex, float fraction, float priority) {
const FBXGeometry& geometry = getFBXGeometry();
const QVector<int>& freeLineage = geometry.joints.at(jointIndex).freeLineage;
return _rig.restoreJointPosition(jointIndex, fraction, priority, freeLineage);
}
float Model::getLimbLength(int jointIndex) const {
const FBXGeometry& geometry = getFBXGeometry();
const QVector<int>& freeLineage = geometry.joints.at(jointIndex).freeLineage;
return _rig.getLimbLength(jointIndex, freeLineage, _scale, geometry.joints);
}
bool Model::maybeStartBlender() {
if (isLoaded()) {
const FBXGeometry& fbxGeometry = getFBXGeometry();

View file

@ -199,8 +199,6 @@ public:
/// Returns the index of the parent of the indexed joint, or -1 if not found.
int getParentJointIndex(int jointIndex) const;
void inverseKinematics(int jointIndex, glm::vec3 position, const glm::quat& rotation, float priority);
/// Returns the extents of the model in its bind pose.
Extents getBindExtents() const;
@ -376,16 +374,6 @@ protected:
void computeMeshPartLocalBounds();
virtual void updateRig(float deltaTime, glm::mat4 parentTransform);
/// Restores the indexed joint to its default position.
/// \param fraction the fraction of the default position to apply (i.e., 0.25f to slerp one fourth of the way to
/// the original position
/// \return true if the joint was found
bool restoreJointPosition(int jointIndex, float fraction = 1.0f, float priority = 0.0f);
/// Computes and returns the extended length of the limb terminating at the specified joint and starting at the joint's
/// first free ancestor.
float getLimbLength(int jointIndex) const;
/// Allow sub classes to force invalidating the bboxes
void invalidCalculatedMeshBoxes() {
_triangleSetsValid = false;

View file

@ -666,7 +666,6 @@ void OffscreenUi::createDesktop(const QUrl& url) {
Q_UNUSED(context)
_desktop = static_cast<QQuickItem*>(newObject);
getSurfaceContext()->setContextProperty("desktop", _desktop);
_toolWindow = _desktop->findChild<QQuickItem*>("ToolWindow");
_vrMenu = new VrMenu(this);
for (const auto& menuInitializer : _queuedMenuInitializers) {
@ -686,10 +685,6 @@ QObject* OffscreenUi::getRootMenu() {
return getRootItem()->findChild<QObject*>("rootMenu");
}
QQuickItem* OffscreenUi::getToolWindow() {
return _toolWindow;
}
void OffscreenUi::unfocusWindows() {
bool invokeResult = QMetaObject::invokeMethod(_desktop, "unfocusWindows");
Q_ASSERT(invokeResult);

View file

@ -80,7 +80,6 @@ public:
QObject* getFlags();
Q_INVOKABLE bool isPointOnDesktopWindow(QVariant point);
QQuickItem* getDesktop();
QQuickItem* getToolWindow();
QObject* getRootMenu();
enum Icon {
ICON_NONE = 0,
@ -261,7 +260,6 @@ private:
ModalDialogListener* assetDialogAsync(const QVariantMap& properties);
QQuickItem* _desktop { nullptr };
QQuickItem* _toolWindow { nullptr };
QList<QObject*> _modalDialogListeners;
std::unordered_map<int, bool> _pressedKeys;
VrMenu* _vrMenu { nullptr };

View file

@ -32,7 +32,6 @@ static const char* const EVENT_BRIDGE_PROPERTY = "eventBridge";
static const char* const WIDTH_PROPERTY = "width";
static const char* const HEIGHT_PROPERTY = "height";
static const char* const VISIBILE_PROPERTY = "visible";
static const char* const TOOLWINDOW_PROPERTY = "toolWindow";
static const uvec2 MAX_QML_WINDOW_SIZE { 1280, 720 };
static const uvec2 MIN_QML_WINDOW_SIZE { 120, 80 };
@ -53,9 +52,6 @@ QVariantMap QmlWindowClass::parseArguments(QScriptContext* context) {
if (context->argument(3).isNumber()) {
properties[HEIGHT_PROPERTY] = context->argument(3).toInt32();
}
if (context->argument(4).isBool()) {
properties[TOOLWINDOW_PROPERTY] = context->argument(4).toBool();
}
} else {
properties = context->argument(0).toVariant().toMap();
}
@ -96,52 +92,37 @@ void QmlWindowClass::initQml(QVariantMap properties) {
auto offscreenUi = DependencyManager::get<OffscreenUi>();
_source = properties[SOURCE_PROPERTY].toString();
#if QML_TOOL_WINDOW
_toolWindow = properties.contains(TOOLWINDOW_PROPERTY) && properties[TOOLWINDOW_PROPERTY].toBool();
if (_toolWindow) {
// Build the event bridge and wrapper on the main thread
_qmlWindow = offscreenUi->getToolWindow();
properties[EVENT_BRIDGE_PROPERTY] = QVariant::fromValue(this);
QVariant newTabVar;
bool invokeResult = QMetaObject::invokeMethod(_qmlWindow, "addWebTab", Qt::DirectConnection,
Q_RETURN_ARG(QVariant, newTabVar),
Q_ARG(QVariant, QVariant::fromValue(properties)));
Q_ASSERT(invokeResult);
} else {
#endif
// Build the event bridge and wrapper on the main thread
offscreenUi->loadInNewContext(qmlSource(), [&](QQmlContext* context, QObject* object) {
_qmlWindow = object;
context->setContextProperty(EVENT_BRIDGE_PROPERTY, this);
context->engine()->setObjectOwnership(this, QQmlEngine::CppOwnership);
context->engine()->setObjectOwnership(object, QQmlEngine::CppOwnership);
if (properties.contains(TITLE_PROPERTY)) {
object->setProperty(TITLE_PROPERTY, properties[TITLE_PROPERTY].toString());
}
if (properties.contains(HEIGHT_PROPERTY) && properties.contains(WIDTH_PROPERTY)) {
uvec2 requestedSize { properties[WIDTH_PROPERTY].toUInt(), properties[HEIGHT_PROPERTY].toUInt() };
requestedSize = glm::clamp(requestedSize, MIN_QML_WINDOW_SIZE, MAX_QML_WINDOW_SIZE);
asQuickItem()->setSize(QSize(requestedSize.x, requestedSize.y));
}
// Build the event bridge and wrapper on the main thread
offscreenUi->loadInNewContext(qmlSource(), [&](QQmlContext* context, QObject* object) {
_qmlWindow = object;
context->setContextProperty(EVENT_BRIDGE_PROPERTY, this);
context->engine()->setObjectOwnership(this, QQmlEngine::CppOwnership);
context->engine()->setObjectOwnership(object, QQmlEngine::CppOwnership);
if (properties.contains(TITLE_PROPERTY)) {
object->setProperty(TITLE_PROPERTY, properties[TITLE_PROPERTY].toString());
}
if (properties.contains(HEIGHT_PROPERTY) && properties.contains(WIDTH_PROPERTY)) {
uvec2 requestedSize { properties[WIDTH_PROPERTY].toUInt(), properties[HEIGHT_PROPERTY].toUInt() };
requestedSize = glm::clamp(requestedSize, MIN_QML_WINDOW_SIZE, MAX_QML_WINDOW_SIZE);
asQuickItem()->setSize(QSize(requestedSize.x, requestedSize.y));
}
bool visible = !properties.contains(VISIBILE_PROPERTY) || properties[VISIBILE_PROPERTY].toBool();
object->setProperty(OFFSCREEN_VISIBILITY_PROPERTY, visible);
object->setProperty(SOURCE_PROPERTY, _source);
bool visible = !properties.contains(VISIBILE_PROPERTY) || properties[VISIBILE_PROPERTY].toBool();
object->setProperty(OFFSCREEN_VISIBILITY_PROPERTY, visible);
object->setProperty(SOURCE_PROPERTY, _source);
const QMetaObject *metaObject = _qmlWindow->metaObject();
// Forward messages received from QML on to the script
connect(_qmlWindow, SIGNAL(sendToScript(QVariant)), this, SLOT(qmlToScript(const QVariant&)), Qt::QueuedConnection);
connect(_qmlWindow, SIGNAL(visibleChanged()), this, SIGNAL(visibleChanged()), Qt::QueuedConnection);
const QMetaObject *metaObject = _qmlWindow->metaObject();
// Forward messages received from QML on to the script
connect(_qmlWindow, SIGNAL(sendToScript(QVariant)), this, SLOT(qmlToScript(const QVariant&)), Qt::QueuedConnection);
connect(_qmlWindow, SIGNAL(visibleChanged()), this, SIGNAL(visibleChanged()), Qt::QueuedConnection);
if (metaObject->indexOfSignal("resized") >= 0)
connect(_qmlWindow, SIGNAL(resized(QSizeF)), this, SIGNAL(resized(QSizeF)), Qt::QueuedConnection);
if (metaObject->indexOfSignal("moved") >= 0)
connect(_qmlWindow, SIGNAL(moved(QVector2D)), this, SLOT(hasMoved(QVector2D)), Qt::QueuedConnection);
connect(_qmlWindow, SIGNAL(windowClosed()), this, SLOT(hasClosed()), Qt::QueuedConnection);
});
if (metaObject->indexOfSignal("resized") >= 0)
connect(_qmlWindow, SIGNAL(resized(QSizeF)), this, SIGNAL(resized(QSizeF)), Qt::QueuedConnection);
if (metaObject->indexOfSignal("moved") >= 0)
connect(_qmlWindow, SIGNAL(moved(QVector2D)), this, SLOT(hasMoved(QVector2D)), Qt::QueuedConnection);
connect(_qmlWindow, SIGNAL(windowClosed()), this, SLOT(hasClosed()), Qt::QueuedConnection);
});
#if QML_TOOL_WINDOW
}
#endif
Q_ASSERT(_qmlWindow);
Q_ASSERT(dynamic_cast<const QQuickItem*>(_qmlWindow.data()));
}
@ -215,11 +196,6 @@ QmlWindowClass::~QmlWindowClass() {
}
QQuickItem* QmlWindowClass::asQuickItem() const {
#if QML_TOOL_WINDOW
if (_toolWindow) {
return DependencyManager::get<OffscreenUi>()->getToolWindow();
}
#endif
return _qmlWindow.isNull() ? nullptr : dynamic_cast<QQuickItem*>(_qmlWindow.data());
}
@ -230,14 +206,6 @@ void QmlWindowClass::setVisible(bool visible) {
}
QQuickItem* targetWindow = asQuickItem();
#if QML_TOOL_WINDOW
if (_toolWindow) {
// For tool window tabs we special case visibility as a function call on the tab parent
// The tool window itself has special logic based on whether any tabs are visible
QMetaObject::invokeMethod(targetWindow, "showTabForUrl", Qt::QueuedConnection, Q_ARG(QVariant, _source), Q_ARG(QVariant, visible));
return;
}
#endif
targetWindow->setProperty(OFFSCREEN_VISIBILITY_PROPERTY, visible);
}
@ -253,12 +221,6 @@ bool QmlWindowClass::isVisible() {
return false;
}
#if QML_TOOL_WINDOW
if (_toolWindow) {
return dynamic_cast<QQuickItem*>(_qmlWindow.data())->isEnabled();
}
#endif
return asQuickItem()->isVisible();
}
@ -343,17 +305,6 @@ void QmlWindowClass::close() {
return;
}
#if QML_TOOL_WINDOW
if (_toolWindow) {
auto offscreenUi = DependencyManager::get<OffscreenUi>();
auto toolWindow = offscreenUi->getToolWindow();
auto invokeResult = QMetaObject::invokeMethod(toolWindow, "removeTabForUrl", Qt::DirectConnection,
Q_ARG(QVariant, _source));
Q_ASSERT(invokeResult);
return;
}
#endif
if (_qmlWindow) {
_qmlWindow->deleteLater();
}

View file

@ -19,8 +19,6 @@
class QScriptEngine;
class QScriptContext;
#define QML_TOOL_WINDOW 0
// FIXME refactor this class to be a QQuickItem derived type and eliminate the needless wrapping
class QmlWindowClass : public QObject {
Q_OBJECT
@ -87,12 +85,6 @@ protected:
virtual QString qmlSource() const { return "QmlWindow.qml"; }
#if QML_TOOL_WINDOW
// FIXME needs to be initialized in the ctor once we have support
// for tool window panes in QML
bool _toolWindow { false };
#endif
QPointer<QObject> _qmlWindow;
QString _source;

View file

@ -4,7 +4,6 @@ qmlWindow = new OverlayWindow({
source: qml,
height: 320,
width: 640,
toolWindow: false,
visible: true
});

View file

@ -4,7 +4,6 @@ qmlWindow = new OverlayWindow({
source: "qrc:///qml/OverlayWindowTest.qml",
height: 240,
width: 320,
toolWindow: false,
visible: true
});

View file

@ -15,7 +15,6 @@ qmlWindow = new OverlayWindow({
source: qml,
height: 240,
width: 320,
toolWindow: false,
visible: true
});

View file

@ -1,31 +0,0 @@
var TOGGLE_RATE = 1000;
var RUNTIME = 60 * 1000;
var webView;
var running = true;
function toggleWindow() {
if (!running) {
return;
}
if (webView) {
webView.close();
webView = null;
} else {
webView = new OverlayWebWindow({
title: 'Entity Properties',
source: "http://www.google.com",
toolWindow: true
});
webView.setVisible(true);
}
Script.setTimeout(toggleWindow, TOGGLE_RATE)
}
toggleWindow();
print("Creating window?")
Script.setTimeout(function(){
print("Shutting down")
}, RUNTIME)

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" x="0px" y="0px" viewBox="0 0 100 100" enable-background="new 0 0 100 100" xml:space="preserve"><path d="M56.3,18.2c-2.6,0-28.1,0-32.5,0s-5.3,7.1,0,7.1h23.7c0.9,0,1.2,0.6,0.8,1.3c0,0-6,10.8-8.3,14.3c-2.3,3.5-1.1,9,1.7,11 C49.1,57.4,60.5,66,60.5,66c0.7,0.5,1.2,1.6,1.2,2.4c0,0,0,15.2,0,21.7c0,6.5,9.6,6.2,9.6,0c0-4.9,0-27.1,0-27.1 c0-0.8-0.5-1.9-1.2-2.5L53.8,48.5C53.2,48,53,47,53.4,46.3l10.2-17.9C63.5,28.4,58.9,18.2,56.3,18.2z M38.1,53.8l-8.6,12.1 c0,0-4.4-3.1-9.5-7.2c-5-4.1-10.9,2.8-5.8,6.8c2.6,2,6.8,5.2,10.3,8c3.6,2.8,9,3.2,12.6-2.1c5.2-7.8,8.2-12,8.2-12L38.1,53.8z M61.2,39.8c0,0,4.8,7,6.8,10.1c2.1,3,6.7,2.3,9.8,0c3.1-2.3,6.1-4.3,8.3-6c3.6-2.7-0.1-8.6-3.9-6c-4.7,3.3-7.7,5.7-7.7,5.7 c-0.6,0.5-1.5,0.4-2-0.4L65.9,32L61.2,39.8z M61.8,7.5c-3.7,6.2,0.9,12.7,3.8,14.4c2.1,1.3,5.1,2.6,8.3-2.7s3.3-8.8-3-12.6 C68.8,5.4,64.3,3.3,61.8,7.5z"></path></svg>

After

Width:  |  Height:  |  Size: 955 B

View file

@ -15,6 +15,7 @@
(function () { // BEGIN LOCAL_SCOPE
Script.include("/~/system/libraries/accountUtils.js");
var request = Script.require('request').request;
var MARKETPLACE_URL = Account.metaverseServerURL + "/marketplace";
@ -51,6 +52,481 @@
tablet.sendToQml(message);
}
//***********************************************
//
// BEGIN Connection logic
//
//***********************************************
// Function Names:
// - requestJSON
// - getAvailableConnections
// - getInfoAboutUser
// - getConnectionData
//
// Description:
// - Update all the usernames that I am entitled to see, using my login but not dependent on canKick.
var METAVERSE_BASE = Account.metaverseServerURL;
function requestJSON(url, callback) { // callback(data) if successfull. Logs otherwise.
request({
uri: url
}, function (error, response) {
if (error || (response.status !== 'success')) {
print("Error: unable to get", url, error || response.status);
return;
}
callback(response.data);
});
}
function getAvailableConnections(domain, callback) { // callback([{usename, location}...]) if successful. (Logs otherwise)
url = METAVERSE_BASE + '/api/v1/users?'
if (domain) {
url += 'status=' + domain.slice(1, -1); // without curly braces
} else {
url += 'filter=connections'; // regardless of whether online
}
requestJSON(url, function (connectionsData) {
callback(connectionsData.users);
});
}
function getInfoAboutUser(specificUsername, callback) {
url = METAVERSE_BASE + '/api/v1/users?filter=connections'
requestJSON(url, function (connectionsData) {
for (user in connectionsData.users) {
if (connectionsData.users[user].username === specificUsername) {
callback(connectionsData.users[user]);
return;
}
}
callback(false);
});
}
function getConnectionData(specificUsername, domain) {
function frob(user) { // get into the right format
var formattedSessionId = user.location.node_id || '';
if (formattedSessionId !== '' && formattedSessionId.indexOf("{") != 0) {
formattedSessionId = "{" + formattedSessionId + "}";
}
return {
sessionId: formattedSessionId,
userName: user.username,
connection: user.connection,
profileUrl: user.images.thumbnail,
placeName: (user.location.root || user.location.domain || {}).name || ''
};
}
if (specificUsername) {
getInfoAboutUser(specificUsername, function (user) {
if (user) {
updateUser(frob(user));
} else {
print('Error: Unable to find information about ' + specificUsername + ' in connectionsData!');
}
});
} else {
getAvailableConnections(domain, function (users) {
if (domain) {
users.forEach(function (user) {
updateUser(frob(user));
});
} else {
sendToQml({ method: 'updateConnections', connections: users.map(frob) });
}
});
}
}
//***********************************************
//
// END Connection logic
//
//***********************************************
//***********************************************
//
// BEGIN Avatar Selector logic
//
//***********************************************
var UNSELECTED_TEXTURES = {
"idle-D": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-idle.png"),
"idle-E": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-idle.png")
};
var SELECTED_TEXTURES = {
"idle-D": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-selected.png"),
"idle-E": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-selected.png")
};
var HOVER_TEXTURES = {
"idle-D": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-hover.png"),
"idle-E": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-hover.png")
};
var UNSELECTED_COLOR = { red: 0x1F, green: 0xC6, blue: 0xA6 };
var SELECTED_COLOR = { red: 0xF3, green: 0x91, blue: 0x29 };
var HOVER_COLOR = { red: 0xD0, green: 0xD0, blue: 0xD0 };
var conserveResources = true;
var overlays = {}; // Keeps track of all our extended overlay data objects, keyed by target identifier.
function ExtendedOverlay(key, type, properties, selected, hasModel) { // A wrapper around overlays to store the key it is associated with.
overlays[key] = this;
if (hasModel) {
var modelKey = key + "-m";
this.model = new ExtendedOverlay(modelKey, "model", {
url: Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx"),
textures: textures(selected),
ignoreRayIntersection: true
}, false, false);
} else {
this.model = undefined;
}
this.key = key;
this.selected = selected || false; // not undefined
this.hovering = false;
this.activeOverlay = Overlays.addOverlay(type, properties); // We could use different overlays for (un)selected...
}
// Instance methods:
ExtendedOverlay.prototype.deleteOverlay = function () { // remove display and data of this overlay
Overlays.deleteOverlay(this.activeOverlay);
delete overlays[this.key];
};
ExtendedOverlay.prototype.editOverlay = function (properties) { // change display of this overlay
Overlays.editOverlay(this.activeOverlay, properties);
};
function color(selected, hovering) {
var base = hovering ? HOVER_COLOR : selected ? SELECTED_COLOR : UNSELECTED_COLOR;
function scale(component) {
var delta = 0xFF - component;
return component;
}
return { red: scale(base.red), green: scale(base.green), blue: scale(base.blue) };
}
function textures(selected, hovering) {
return hovering ? HOVER_TEXTURES : selected ? SELECTED_TEXTURES : UNSELECTED_TEXTURES;
}
// so we don't have to traverse the overlays to get the last one
var lastHoveringId = 0;
ExtendedOverlay.prototype.hover = function (hovering) {
this.hovering = hovering;
if (this.key === lastHoveringId) {
if (hovering) {
return;
}
lastHoveringId = 0;
}
this.editOverlay({ color: color(this.selected, hovering) });
if (this.model) {
this.model.editOverlay({ textures: textures(this.selected, hovering) });
}
if (hovering) {
// un-hover the last hovering overlay
if (lastHoveringId && lastHoveringId !== this.key) {
ExtendedOverlay.get(lastHoveringId).hover(false);
}
lastHoveringId = this.key;
}
};
ExtendedOverlay.prototype.select = function (selected) {
if (this.selected === selected) {
return;
}
this.editOverlay({ color: color(selected, this.hovering) });
if (this.model) {
this.model.editOverlay({ textures: textures(selected) });
}
this.selected = selected;
};
// Class methods:
var selectedIds = [];
ExtendedOverlay.isSelected = function (id) {
return -1 !== selectedIds.indexOf(id);
};
ExtendedOverlay.get = function (key) { // answer the extended overlay data object associated with the given avatar identifier
return overlays[key];
};
ExtendedOverlay.some = function (iterator) { // Bails early as soon as iterator returns truthy.
var key;
for (key in overlays) {
if (iterator(ExtendedOverlay.get(key))) {
return;
}
}
};
ExtendedOverlay.unHover = function () { // calls hover(false) on lastHoveringId (if any)
if (lastHoveringId) {
ExtendedOverlay.get(lastHoveringId).hover(false);
}
};
// hit(overlay) on the one overlay intersected by pickRay, if any.
// noHit() if no ExtendedOverlay was intersected (helps with hover)
ExtendedOverlay.applyPickRay = function (pickRay, hit, noHit) {
var pickedOverlay = Overlays.findRayIntersection(pickRay); // Depends on nearer coverOverlays to extend closer to us than farther ones.
if (!pickedOverlay.intersects) {
if (noHit) {
return noHit();
}
return;
}
ExtendedOverlay.some(function (overlay) { // See if pickedOverlay is one of ours.
if ((overlay.activeOverlay) === pickedOverlay.overlayID) {
hit(overlay);
return true;
}
});
};
function HighlightedEntity(id, entityProperties) {
this.id = id;
this.overlay = Overlays.addOverlay('cube', {
position: entityProperties.position,
rotation: entityProperties.rotation,
dimensions: entityProperties.dimensions,
solid: false,
color: {
red: 0xF3,
green: 0x91,
blue: 0x29
},
ignoreRayIntersection: true,
drawInFront: false // Arguable. For now, let's not distract with mysterious wires around the scene.
});
HighlightedEntity.overlays.push(this);
}
HighlightedEntity.overlays = [];
HighlightedEntity.clearOverlays = function clearHighlightedEntities() {
HighlightedEntity.overlays.forEach(function (highlighted) {
Overlays.deleteOverlay(highlighted.overlay);
});
HighlightedEntity.overlays = [];
};
HighlightedEntity.updateOverlays = function updateHighlightedEntities() {
HighlightedEntity.overlays.forEach(function (highlighted) {
var properties = Entities.getEntityProperties(highlighted.id, ['position', 'rotation', 'dimensions']);
Overlays.editOverlay(highlighted.overlay, {
position: properties.position,
rotation: properties.rotation,
dimensions: properties.dimensions
});
});
};
function addAvatarNode(id) {
var selected = ExtendedOverlay.isSelected(id);
return new ExtendedOverlay(id, "sphere", {
drawInFront: true,
solid: true,
alpha: 0.8,
color: color(selected, false),
ignoreRayIntersection: false
}, selected, !conserveResources);
}
var pingPong = true;
function updateOverlays() {
var eye = Camera.position;
AvatarList.getAvatarIdentifiers().forEach(function (id) {
if (!id) {
return; // don't update ourself, or avatars we're not interested in
}
var avatar = AvatarList.getAvatar(id);
if (!avatar) {
return; // will be deleted below if there had been an overlay.
}
var overlay = ExtendedOverlay.get(id);
if (!overlay) { // For now, we're treating this as a temporary loss, as from the personal space bubble. Add it back.
overlay = addAvatarNode(id);
}
var target = avatar.position;
var distance = Vec3.distance(target, eye);
var offset = 0.2;
var diff = Vec3.subtract(target, eye); // get diff between target and eye (a vector pointing to the eye from avatar position)
var headIndex = avatar.getJointIndex("Head"); // base offset on 1/2 distance from hips to head if we can
if (headIndex > 0) {
offset = avatar.getAbsoluteJointTranslationInObjectFrame(headIndex).y / 2;
}
// move a bit in front, towards the camera
target = Vec3.subtract(target, Vec3.multiply(Vec3.normalize(diff), offset));
// now bump it up a bit
target.y = target.y + offset;
overlay.ping = pingPong;
overlay.editOverlay({
color: color(ExtendedOverlay.isSelected(id), overlay.hovering),
position: target,
dimensions: 0.032 * distance
});
if (overlay.model) {
overlay.model.ping = pingPong;
overlay.model.editOverlay({
position: target,
scale: 0.2 * distance, // constant apparent size
rotation: Camera.orientation
});
}
});
pingPong = !pingPong;
ExtendedOverlay.some(function (overlay) { // Remove any that weren't updated. (User is gone.)
if (overlay.ping === pingPong) {
overlay.deleteOverlay();
}
});
// We could re-populateNearbyUserList if anything added or removed, but not for now.
HighlightedEntity.updateOverlays();
}
function removeOverlays() {
selectedIds = [];
lastHoveringId = 0;
HighlightedEntity.clearOverlays();
ExtendedOverlay.some(function (overlay) {
overlay.deleteOverlay();
});
}
//
// Clicks.
//
function usernameFromIDReply(id, username, machineFingerprint, isAdmin) {
if (selectedIds[0] === id) {
var message = {
method: 'updateSelectedRecipientUsername',
userName: username === "" ? "unknown username" : username
};
sendToQml(message);
}
}
function handleClick(pickRay) {
ExtendedOverlay.applyPickRay(pickRay, function (overlay) {
var nextSelectedStatus = !overlay.selected;
var avatarId = overlay.key;
selectedIds = nextSelectedStatus ? [avatarId] : [];
if (nextSelectedStatus) {
Users.requestUsernameFromID(avatarId);
}
var message = {
method: 'selectRecipient',
id: [avatarId],
isSelected: nextSelectedStatus,
displayName: '"' + AvatarList.getAvatar(avatarId).sessionDisplayName + '"',
userName: ''
};
sendToQml(message);
ExtendedOverlay.some(function (overlay) {
var id = overlay.key;
var selected = ExtendedOverlay.isSelected(id);
overlay.select(selected);
});
HighlightedEntity.clearOverlays();
if (selectedIds.length) {
Entities.findEntitiesInFrustum(Camera.frustum).forEach(function (id) {
// Because lastEditedBy is per session, the vast majority of entities won't match,
// so it would probably be worth reducing marshalling costs by asking for just we need.
// However, providing property name(s) is advisory and some additional properties are
// included anyway. As it turns out, asking for 'lastEditedBy' gives 'position', 'rotation',
// and 'dimensions', too, so we might as well make use of them instead of making a second
// getEntityProperties call.
// It would be nice if we could harden this against future changes by specifying all
// and only these four in an array, but see
// https://highfidelity.fogbugz.com/f/cases/2728/Entities-getEntityProperties-id-lastEditedBy-name-lastEditedBy-doesn-t-work
var properties = Entities.getEntityProperties(id, 'lastEditedBy');
if (ExtendedOverlay.isSelected(properties.lastEditedBy)) {
new HighlightedEntity(id, properties);
}
});
}
return true;
});
}
function handleMouseEvent(mousePressEvent) { // handleClick if we get one.
if (!mousePressEvent.isLeftButton) {
return;
}
handleClick(Camera.computePickRay(mousePressEvent.x, mousePressEvent.y));
}
function handleMouseMove(pickRay) { // given the pickRay, just do the hover logic
ExtendedOverlay.applyPickRay(pickRay, function (overlay) {
overlay.hover(true);
}, function () {
ExtendedOverlay.unHover();
});
}
// handy global to keep track of which hand is the mouse (if any)
var currentHandPressed = 0;
var TRIGGER_CLICK_THRESHOLD = 0.85;
var TRIGGER_PRESS_THRESHOLD = 0.05;
function handleMouseMoveEvent(event) { // find out which overlay (if any) is over the mouse position
var pickRay;
if (HMD.active) {
if (currentHandPressed !== 0) {
pickRay = controllerComputePickRay(currentHandPressed);
} else {
// nothing should hover, so
ExtendedOverlay.unHover();
return;
}
} else {
pickRay = Camera.computePickRay(event.x, event.y);
}
handleMouseMove(pickRay);
}
function handleTriggerPressed(hand, value) {
// The idea is if you press one trigger, it is the one
// we will consider the mouse. Even if the other is pressed,
// we ignore it until this one is no longer pressed.
var isPressed = value > TRIGGER_PRESS_THRESHOLD;
if (currentHandPressed === 0) {
currentHandPressed = isPressed ? hand : 0;
return;
}
if (currentHandPressed === hand) {
currentHandPressed = isPressed ? hand : 0;
return;
}
// otherwise, the other hand is still triggered
// so do nothing.
}
// We get mouseMoveEvents from the handControllers, via handControllerPointer.
// But we don't get mousePressEvents.
var triggerMapping = Controller.newMapping(Script.resolvePath('') + '-click');
var triggerPressMapping = Controller.newMapping(Script.resolvePath('') + '-press');
function controllerComputePickRay(hand) {
var controllerPose = getControllerWorldLocation(hand, true);
if (controllerPose.valid) {
return { origin: controllerPose.position, direction: Quat.getUp(controllerPose.orientation) };
}
}
function makeClickHandler(hand) {
return function (clicked) {
if (clicked > TRIGGER_CLICK_THRESHOLD) {
var pickRay = controllerComputePickRay(hand);
handleClick(pickRay);
}
};
}
function makePressHandler(hand) {
return function (value) {
handleTriggerPressed(hand, value);
};
}
triggerMapping.from(Controller.Standard.RTClick).peek().to(makeClickHandler(Controller.Standard.RightHand));
triggerMapping.from(Controller.Standard.LTClick).peek().to(makeClickHandler(Controller.Standard.LeftHand));
triggerPressMapping.from(Controller.Standard.RT).peek().to(makePressHandler(Controller.Standard.RightHand));
triggerPressMapping.from(Controller.Standard.LT).peek().to(makePressHandler(Controller.Standard.LeftHand));
//***********************************************
//
// END Avatar Selector logic
//
//***********************************************
// Function Name: fromQml()
//
// Description:
@ -110,6 +586,17 @@
case 'goToMarketplaceItemPage':
tablet.gotoWebScreen(MARKETPLACE_URL + '/items/' + message.itemId, MARKETPLACES_INJECT_SCRIPT_URL);
break;
case 'refreshConnections':
print('Refreshing Connections...');
getConnectionData(false);
break;
case 'enable_ChooseRecipientNearbyMode':
Script.update.connect(updateOverlays);
break;
case 'disable_ChooseRecipientNearbyMode':
Script.update.disconnect(updateOverlays);
removeOverlays();
break;
default:
print('Unrecognized message from QML:', JSON.stringify(message));
}
@ -154,6 +641,17 @@
if (button) {
button.editProperties({ isActive: onWalletScreen });
}
if (onWalletScreen) {
isWired = true;
Users.usernameFromIDReply.connect(usernameFromIDReply);
Controller.mousePressEvent.connect(handleMouseEvent);
Controller.mouseMoveEvent.connect(handleMouseMoveEvent);
triggerMapping.enable();
triggerPressMapping.enable();
} else {
off();
}
}
//
@ -176,6 +674,19 @@
tablet.screenChanged.connect(onTabletScreenChanged);
}
}
var isWired = false;
function off() {
if (isWired) { // It is not ok to disconnect these twice, hence guard.
Users.usernameFromIDReply.disconnect(usernameFromIDReply);
Script.update.disconnect(updateOverlays);
Controller.mousePressEvent.disconnect(handleMouseEvent);
Controller.mouseMoveEvent.disconnect(handleMouseMoveEvent);
isWired = false;
}
triggerMapping.disable(); // It's ok if we disable twice.
triggerPressMapping.disable(); // see above
removeOverlays();
}
function shutdown() {
button.clicked.disconnect(onButtonClicked);
tablet.removeButton(button);
@ -185,6 +696,7 @@
tablet.gotoHomeScreen();
}
}
off();
}
//

View file

@ -575,6 +575,11 @@ var selectionDisplay = null; // for gridTool.js to ignore
method: 'purchases_showMyItems'
});
break;
case 'refreshConnections':
case 'enable_ChooseRecipientNearbyMode':
case 'disable_ChooseRecipientNearbyMode':
// NOP
break;
default:
print('Unrecognized message from Checkout.qml or Purchases.qml: ' + JSON.stringify(message));
}

View file

@ -685,7 +685,6 @@ function startup() {
});
button.clicked.connect(onTabletButtonClicked);
tablet.screenChanged.connect(onTabletScreenChanged);
Users.usernameFromIDReply.connect(usernameFromIDReply);
Window.domainChanged.connect(clearLocalQMLDataAndClosePAL);
Window.domainConnectionRefused.connect(clearLocalQMLDataAndClosePAL);
Messages.subscribe(CHANNEL);
@ -708,6 +707,7 @@ function off() {
Controller.mousePressEvent.disconnect(handleMouseEvent);
Controller.mouseMoveEvent.disconnect(handleMouseMoveEvent);
tablet.tabletShownChanged.disconnect(tabletVisibilityChanged);
Users.usernameFromIDReply.disconnect(usernameFromIDReply);
isWired = false;
ContextOverlay.enabled = true
}
@ -744,6 +744,7 @@ function onTabletButtonClicked() {
Script.update.connect(updateOverlays);
Controller.mousePressEvent.connect(handleMouseEvent);
Controller.mouseMoveEvent.connect(handleMouseMoveEvent);
Users.usernameFromIDReply.connect(usernameFromIDReply);
triggerMapping.enable();
triggerPressMapping.enable();
audioTimer = createAudioInterval(conserveResources ? AUDIO_LEVEL_CONSERVED_UPDATE_INTERVAL_MS : AUDIO_LEVEL_UPDATE_INTERVAL_MS);
@ -890,7 +891,6 @@ function shutdown() {
button.clicked.disconnect(onTabletButtonClicked);
tablet.removeButton(button);
tablet.screenChanged.disconnect(onTabletScreenChanged);
Users.usernameFromIDReply.disconnect(usernameFromIDReply);
Window.domainChanged.disconnect(clearLocalQMLDataAndClosePAL);
Window.domainConnectionRefused.disconnect(clearLocalQMLDataAndClosePAL);
Messages.subscribe(CHANNEL);

39
scripts/system/run.js Normal file
View file

@ -0,0 +1,39 @@
"use strict";
/* global Script, Tablet, MyAvatar */
(function() { // BEGIN LOCAL_SCOPE
var WALK_SPEED = 2.6;
var RUN_SPEED = 4.5;
var MIDDLE_SPEED = (WALK_SPEED + RUN_SPEED) / 2.0;
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
var button = tablet.addButton({
icon: Script.resolvePath("assets/images/run.svg"),
text: "RUN",
sortOrder: 15
});
function onClicked() {
if (MyAvatar.walkSpeed < MIDDLE_SPEED) {
button.editProperties({isActive: true});
MyAvatar.walkSpeed = RUN_SPEED;
} else {
button.editProperties({isActive: false});
MyAvatar.walkSpeed = WALK_SPEED;
}
}
function cleanup() {
button.clicked.disconnect(onClicked);
tablet.removeButton(button);
}
button.clicked.connect(onClicked);
if (MyAvatar.walkSpeed < MIDDLE_SPEED) {
button.editProperties({isActive: false});
} else {
button.editProperties({isActive: true});
}
}());