This commit is contained in:
Nissim Hadar 2017-12-07 11:35:57 -08:00
commit bcf3afd758
52 changed files with 954 additions and 711 deletions

View file

@ -25,6 +25,23 @@ AvatarMixerClientData::AvatarMixerClientData(const QUuid& nodeID) :
_avatar->setID(nodeID);
}
uint64_t AvatarMixerClientData::getLastOtherAvatarEncodeTime(QUuid otherAvatar) const {
std::unordered_map<QUuid, uint64_t>::const_iterator itr = _lastOtherAvatarEncodeTime.find(otherAvatar);
if (itr != _lastOtherAvatarEncodeTime.end()) {
return itr->second;
}
return 0;
}
void AvatarMixerClientData::setLastOtherAvatarEncodeTime(const QUuid& otherAvatar, const uint64_t& time) {
std::unordered_map<QUuid, uint64_t>::iterator itr = _lastOtherAvatarEncodeTime.find(otherAvatar);
if (itr != _lastOtherAvatarEncodeTime.end()) {
itr->second = time;
} else {
_lastOtherAvatarEncodeTime.emplace(std::pair<QUuid, uint64_t>(otherAvatar, time));
}
}
void AvatarMixerClientData::queuePacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer node) {
if (!_packetQueue.node) {
_packetQueue.node = node;

View file

@ -110,16 +110,10 @@ public:
bool getRequestsDomainListData() { return _requestsDomainListData; }
void setRequestsDomainListData(bool requesting) { _requestsDomainListData = requesting; }
ViewFrustum getViewFrustom() const { return _currentViewFrustum; }
ViewFrustum getViewFrustum() const { return _currentViewFrustum; }
quint64 getLastOtherAvatarEncodeTime(QUuid otherAvatar) {
quint64 result = 0;
if (_lastOtherAvatarEncodeTime.find(otherAvatar) != _lastOtherAvatarEncodeTime.end()) {
result = _lastOtherAvatarEncodeTime[otherAvatar];
}
_lastOtherAvatarEncodeTime[otherAvatar] = usecTimestampNow();
return result;
}
uint64_t getLastOtherAvatarEncodeTime(QUuid otherAvatar) const;
void setLastOtherAvatarEncodeTime(const QUuid& otherAvatar, const uint64_t& time);
QVector<JointData>& getLastOtherAvatarSentJoints(QUuid otherAvatar) {
_lastOtherAvatarSentJoints[otherAvatar].resize(_avatar->getJointCount());
@ -143,7 +137,7 @@ private:
// this is a map of the last time we encoded an "other" avatar for
// sending to "this" node
std::unordered_map<QUuid, quint64> _lastOtherAvatarEncodeTime;
std::unordered_map<QUuid, uint64_t> _lastOtherAvatarEncodeTime;
std::unordered_map<QUuid, QVector<JointData>> _lastOtherAvatarSentJoints;
uint64_t _identityChangeTimestamp;

View file

@ -22,6 +22,7 @@
#include <NodeList.h>
#include <Node.h>
#include <OctreeConstants.h>
#include <PrioritySortUtil.h>
#include <udt/PacketHeaders.h>
#include <SharedUtil.h>
#include <StDev.h>
@ -32,6 +33,10 @@
#include "AvatarMixerClientData.h"
#include "AvatarMixerSlave.h"
namespace PrioritySortUtil {
// we declare this callback here but override it later
std::function<uint64_t(const AvatarSharedPointer&)> getAvatarAgeCallback = [] (const AvatarSharedPointer& avatar) { return 0; };
}
void AvatarMixerSlave::configure(ConstIter begin, ConstIter end) {
_begin = begin;
@ -184,10 +189,8 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
// setup list of AvatarData as well as maps to map betweeen the AvatarData and the original nodes
// for calling the AvatarData::sortAvatars() function and getting our sorted list of client nodes
QList<AvatarSharedPointer> avatarList;
std::vector<AvatarSharedPointer> avatarsToSort;
std::unordered_map<AvatarSharedPointer, SharedNodePointer> avatarDataToNodes;
std::for_each(_begin, _end, [&](const SharedNodePointer& otherNode) {
// make sure this is an agent that we have avatar data for before considering it for inclusion
if (otherNode->getType() == NodeType::Agent
@ -195,36 +198,61 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
const AvatarMixerClientData* otherNodeData = reinterpret_cast<const AvatarMixerClientData*>(otherNode->getLinkedData());
AvatarSharedPointer otherAvatar = otherNodeData->getAvatarSharedPointer();
avatarList << otherAvatar;
avatarsToSort.push_back(otherAvatar);
avatarDataToNodes[otherAvatar] = otherNode;
}
});
AvatarSharedPointer thisAvatar = nodeData->getAvatarSharedPointer();
ViewFrustum cameraView = nodeData->getViewFrustom();
std::priority_queue<AvatarPriority> sortedAvatars;
AvatarData::sortAvatars(avatarList, cameraView, sortedAvatars,
[&](AvatarSharedPointer avatar)->uint64_t {
auto avatarNode = avatarDataToNodes[avatar];
assert(avatarNode); // we can't have gotten here without the avatarData being a valid key in the map
return nodeData->getLastBroadcastTime(avatarNode->getUUID());
}, [&](AvatarSharedPointer avatar)->float{
glm::vec3 nodeBoxHalfScale = (avatar->getWorldPosition() - avatar->getGlobalBoundingBoxCorner() * avatar->getSensorToWorldScale());
return glm::max(nodeBoxHalfScale.x, glm::max(nodeBoxHalfScale.y, nodeBoxHalfScale.z));
}, [&](AvatarSharedPointer avatar)->bool {
// now that we've assembled the avatarDataToNodes map we can replace PrioritySortUtil::getAvatarAgeCallback
// with the true implementation
PrioritySortUtil::getAvatarAgeCallback = [&] (const AvatarSharedPointer& avatar) {
auto avatarNode = avatarDataToNodes[avatar];
assert(avatarNode); // we can't have gotten here without the avatarData being a valid key in the map
return nodeData->getLastOtherAvatarEncodeTime(avatarNode->getUUID());
};
class SortableAvatar: public PrioritySortUtil::Sortable {
public:
SortableAvatar() = delete;
SortableAvatar(const AvatarSharedPointer& avatar) : _avatar(avatar) {}
glm::vec3 getPosition() const override { return _avatar->getWorldPosition(); }
float getRadius() const override {
glm::vec3 nodeBoxHalfScale = (_avatar->getWorldPosition() - _avatar->getGlobalBoundingBoxCorner() * _avatar->getSensorToWorldScale());
return glm::max(nodeBoxHalfScale.x, glm::max(nodeBoxHalfScale.y, nodeBoxHalfScale.z));
}
uint64_t getTimestamp() const override {
// use the callback implemented above
return PrioritySortUtil::getAvatarAgeCallback(_avatar);
}
const AvatarSharedPointer& getAvatar() const { return _avatar; }
private:
AvatarSharedPointer _avatar;
};
// prepare to sort
ViewFrustum cameraView = nodeData->getViewFrustum();
PrioritySortUtil::PriorityQueue<SortableAvatar> sortedAvatars(cameraView,
AvatarData::_avatarSortCoefficientSize,
AvatarData::_avatarSortCoefficientCenter,
AvatarData::_avatarSortCoefficientAge);
// ignore or sort
const AvatarSharedPointer& thisAvatar = nodeData->getAvatarSharedPointer();
for (const auto& avatar : avatarsToSort) {
if (avatar == thisAvatar) {
return true; // ignore ourselves...
// don't echo updates to self
continue;
}
bool shouldIgnore = false;
// We will also ignore other nodes for a couple of different reasons:
// We ignore other nodes for a couple of reasons:
// 1) ignore bubbles and ignore specific node
// 2) the node hasn't really updated it's frame data recently, this can
// happen if for example the avatar is connected on a desktop and sending
// updates at ~30hz. So every 3 frames we skip a frame.
auto avatarNode = avatarDataToNodes[avatar];
auto avatarNode = avatarDataToNodes[avatar];
assert(avatarNode); // we can't have gotten here without the avatarData being a valid key in the map
const AvatarMixerClientData* avatarNodeData = reinterpret_cast<const AvatarMixerClientData*>(avatarNode->getLinkedData());
@ -240,7 +268,6 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
|| (avatarNode->isIgnoringNodeWithID(node->getUUID()) && !getsAnyIgnored)) {
shouldIgnore = true;
} else {
// Check to see if the space bubble is enabled
// Don't bother with these checks if the other avatar has their bubble enabled and we're gettingAnyIgnored
if (node->isIgnoreRadiusEnabled() || (avatarNode->isIgnoreRadiusEnabled() && !getsAnyIgnored)) {
@ -267,8 +294,6 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
nodeData->removeFromRadiusIgnoringSet(node, avatarNode->getUUID());
}
}
quint64 endIgnoreCalculation = usecTimestampNow();
_stats.ignoreCalculationElapsedTime += (endIgnoreCalculation - startIgnoreCalculation);
if (!shouldIgnore) {
AvatarDataSequenceNumber lastSeqToReceiver = nodeData->getLastBroadcastSequenceNumber(avatarNode->getUUID());
@ -292,20 +317,21 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
++numAvatarsWithSkippedFrames;
}
}
return shouldIgnore;
});
quint64 endIgnoreCalculation = usecTimestampNow();
_stats.ignoreCalculationElapsedTime += (endIgnoreCalculation - startIgnoreCalculation);
if (!shouldIgnore) {
// sort this one for later
sortedAvatars.push(SortableAvatar(avatar));
}
}
// loop through our sorted avatars and allocate our bandwidth to them accordingly
int avatarRank = 0;
// this is overly conservative, because it includes some avatars we might not consider
int remainingAvatars = (int)sortedAvatars.size();
while (!sortedAvatars.empty()) {
AvatarPriority sortData = sortedAvatars.top();
const auto& avatarData = sortedAvatars.top().getAvatar();
sortedAvatars.pop();
const auto& avatarData = sortData.avatar;
avatarRank++;
remainingAvatars--;
auto otherNode = avatarDataToNodes[avatarData];
@ -332,10 +358,8 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
nodeData->setLastBroadcastTime(otherNode->getUUID(), usecTimestampNow());
}
// determine if avatar is in view which determines how much data to send
glm::vec3 otherPosition = otherAvatar->getClientGlobalPosition();
// determine if avatar is in view, to determine how much data to include...
glm::vec3 otherNodeBoxScale = (otherPosition - otherNodeData->getGlobalBoundingBoxCorner()) * 2.0f * otherAvatar->getSensorToWorldScale();
AABox otherNodeBox(otherNodeData->getGlobalBoundingBoxCorner(), otherNodeBoxScale);
bool isInView = nodeData->otherAvatarInView(otherNodeBox);
@ -405,14 +429,18 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
// set the last sent sequence number for this sender on the receiver
nodeData->setLastBroadcastSequenceNumber(otherNode->getUUID(),
otherNodeData->getLastReceivedSequenceNumber());
nodeData->setLastOtherAvatarEncodeTime(otherNode->getUUID(), usecTimestampNow());
}
} else {
// TODO? this avatar is not included now, and will probably not be included next frame.
// It would be nice if we could tweak its future sort priority to put it at the back of the list.
}
avatarPacketList->endSegment();
quint64 endAvatarDataPacking = usecTimestampNow();
_stats.avatarDataPackingElapsedTime += (endAvatarDataPacking - startAvatarDataPacking);
};
}
quint64 startPacketSending = usecTimestampNow();

View file

@ -29,10 +29,6 @@ macro(GENERATE_INSTALLERS)
if (WIN32)
# Do not install the Visual Studio C runtime libraries. The installer will do this automatically
set(CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS_SKIP TRUE)
include(InstallRequiredSystemLibraries)
set(CPACK_NSIS_MUI_ICON "${HF_CMAKE_DIR}/installer/installer.ico")
# install and reference the Add/Remove icon
@ -49,6 +45,10 @@ macro(GENERATE_INSTALLERS)
set(_UNINSTALLER_HEADER_BAD_PATH "${HF_CMAKE_DIR}/installer/uninstaller-header.bmp")
set(UNINSTALLER_HEADER_IMAGE "")
fix_path_for_nsis(${_UNINSTALLER_HEADER_BAD_PATH} UNINSTALLER_HEADER_IMAGE)
# grab the latest VC redist (2017) and add it to the installer, our NSIS template
# will call it during the install
install(CODE "file(DOWNLOAD https://go.microsoft.com/fwlink/?LinkId=746572 \"\${CMAKE_INSTALL_PREFIX}/vcredist_x64.exe\")")
elseif (APPLE)
# produce a drag and drop DMG on OS X
set(CPACK_GENERATOR "DragNDrop")
@ -84,4 +84,3 @@ macro(GENERATE_INSTALLERS)
include(CPack)
endmacro()

View file

@ -45,5 +45,4 @@ else()
endif()
file(GLOB EXTRA_PLUGINS "${BUNDLE_PLUGIN_DIR}/*.${PLUGIN_EXTENSION}")
fixup_bundle("${BUNDLE_EXECUTABLE}" "${EXTRA_PLUGINS}" "@FIXUP_LIBS@")
fixup_bundle("${BUNDLE_EXECUTABLE}" "${EXTRA_PLUGINS}" "@FIXUP_LIBS@" IGNORE_ITEM "vcredist_x86.exe;vcredist_x64.exe")

View file

@ -65,21 +65,23 @@ var EventBridge;
// we need to listen to events that might precede the addition of this elements.
// A more robust hack will be to add a setInterval that look for DOM changes every 100-300 ms (low performance?)
window.onload = function(){
window.addEventListener("load",function(event) {
setTimeout(function() {
EventBridge.forceHtmlAudioOutputDeviceUpdate();
}, 1200);
};
document.onclick = function(){
}, false);
document.addEventListener("click",function(){
setTimeout(function() {
EventBridge.forceHtmlAudioOutputDeviceUpdate();
}, 1200);
};
document.onchange = function(){
}, false);
document.addEventListener("change",function(){
setTimeout(function() {
EventBridge.forceHtmlAudioOutputDeviceUpdate();
}, 1200);
};
}, false);
tempEventBridge._callbacks.forEach(function (callback) {
EventBridge.scriptEventReceived.connect(callback);

View file

@ -12,9 +12,9 @@
var MAX_WARNINGS = 3;
var numWarnings = 0;
var isWindowFocused = true;
var isKeyboardRaised = false;
var isNumericKeyboard = false;
var isPasswordField = false;
window.isKeyboardRaised = false;
window.isNumericKeyboard = false;
window.isPasswordField = false;
function shouldSetPasswordField() {
var nodeType = document.activeElement.type;
@ -62,7 +62,7 @@
var passwordField = shouldSetPasswordField();
if (isWindowFocused &&
(keyboardRaised !== isKeyboardRaised || numericKeyboard !== isNumericKeyboard || passwordField !== isPasswordField)) {
(keyboardRaised !== window.isKeyboardRaised || numericKeyboard !== window.isNumericKeyboard || passwordField !== window.isPasswordField)) {
if (typeof EventBridge !== "undefined" && EventBridge !== null) {
EventBridge.emitWebEvent(
@ -75,20 +75,20 @@
}
}
if (!isKeyboardRaised) {
if (!window.isKeyboardRaised) {
scheduleBringToView(250); // Allow time for keyboard to be raised in QML.
// 2DO: should it be rather done from 'client area height changed' event?
}
isKeyboardRaised = keyboardRaised;
isNumericKeyboard = numericKeyboard;
isPasswordField = passwordField;
window.isKeyboardRaised = keyboardRaised;
window.isNumericKeyboard = numericKeyboard;
window.isPasswordField = passwordField;
}
}, POLL_FREQUENCY);
window.addEventListener("click", function () {
var keyboardRaised = shouldRaiseKeyboard();
if(keyboardRaised && isKeyboardRaised) {
if (keyboardRaised && window.isKeyboardRaised) {
scheduleBringToView(150);
}
});
@ -99,7 +99,7 @@
window.addEventListener("blur", function () {
isWindowFocused = false;
isKeyboardRaised = false;
isNumericKeyboard = false;
window.isKeyboardRaised = false;
window.isNumericKeyboard = false;
});
})();

View file

@ -79,10 +79,12 @@ Rectangle {
if (result.status !== 'success') {
failureErrorText.text = result.message;
root.activeView = "checkoutFailure";
UserActivityLogger.commercePurchaseFailure(root.itemId, root.itemPrice, !root.alreadyOwned, result.message);
} else {
root.itemHref = result.data.download_url;
root.isWearable = result.data.categories.indexOf("Wearables") > -1;
root.activeView = "checkoutSuccess";
UserActivityLogger.commercePurchaseSuccess(root.itemId, root.itemPrice, !root.alreadyOwned);
}
}
@ -599,6 +601,7 @@ Rectangle {
sendToScript({method: 'checkout_rezClicked', itemHref: root.itemHref, isWearable: root.isWearable});
rezzedNotifContainer.visible = true;
rezzedNotifContainerTimer.start();
UserActivityLogger.commerceEntityRezzed(root.itemId, "checkout", root.isWearable ? "rez" : "wear");
}
}
RalewaySemiBold {

View file

@ -349,6 +349,7 @@ Item {
sendToPurchases({method: 'purchases_rezClicked', itemHref: root.itemHref, isWearable: root.isWearable});
rezzedNotifContainer.visible = true;
rezzedNotifContainerTimer.start();
UserActivityLogger.commerceEntityRezzed(root.itemId, "purchases", root.isWearable ? "rez" : "wear");
}
style: ButtonStyle {

View file

@ -206,16 +206,6 @@ Item {
root.isPasswordField = (focus && passphraseField.echoMode === TextInput.Password);
}
MouseArea {
anchors.fill: parent;
onClicked: {
root.keyboardRaised = true;
root.isPasswordField = (passphraseField.echoMode === TextInput.Password);
mouse.accepted = false;
}
}
onAccepted: {
submitPassphraseInputButton.enabled = false;
commerce.setPassphrase(passphraseField.text);
@ -362,25 +352,6 @@ Item {
right: parent.right;
}
Image {
id: lowerKeyboardButton;
z: 999;
source: "images/lowerKeyboard.png";
anchors.right: keyboard.right;
anchors.top: keyboard.showMirrorText ? keyboard.top : undefined;
anchors.bottom: keyboard.showMirrorText ? undefined : keyboard.bottom;
height: 50;
width: 60;
MouseArea {
anchors.fill: parent;
onClicked: {
root.keyboardRaised = false;
}
}
}
HifiControlsUit.Keyboard {
id: keyboard;
raised: HMD.mounted && root.keyboardRaised;

View file

@ -82,17 +82,6 @@ Item {
if (focus) {
var hidePassword = (currentPassphraseField.echoMode === TextInput.Password);
sendSignalToWallet({method: 'walletSetup_raiseKeyboard', isPasswordField: hidePassword});
} else if (!passphraseFieldAgain.focus) {
sendSignalToWallet({method: 'walletSetup_lowerKeyboard', isPasswordField: false});
}
}
MouseArea {
anchors.fill: parent;
onPressed: {
var hidePassword = (currentPassphraseField.echoMode === TextInput.Password);
sendSignalToWallet({method: 'walletSetup_raiseKeyboard', isPasswordField: hidePassword});
mouse.accepted = false;
}
}
@ -115,21 +104,10 @@ Item {
activeFocusOnPress: true;
activeFocusOnTab: true;
MouseArea {
anchors.fill: parent;
onPressed: {
var hidePassword = (passphraseField.echoMode === TextInput.Password);
sendSignalToWallet({method: 'walletSetup_raiseKeyboard', isPasswordField: hidePassword});
mouse.accepted = false;
}
}
onFocusChanged: {
if (focus) {
var hidePassword = (passphraseField.echoMode === TextInput.Password);
sendMessageToLightbox({method: 'walletSetup_raiseKeyboard', isPasswordField: hidePassword});
} else if (!passphraseFieldAgain.focus) {
sendMessageToLightbox({method: 'walletSetup_lowerKeyboard', isPasswordField: false});
}
}
@ -151,21 +129,10 @@ Item {
activeFocusOnPress: true;
activeFocusOnTab: true;
MouseArea {
anchors.fill: parent;
onPressed: {
var hidePassword = (passphraseFieldAgain.echoMode === TextInput.Password);
sendSignalToWallet({method: 'walletSetup_raiseKeyboard', isPasswordField: hidePassword});
mouse.accepted = false;
}
}
onFocusChanged: {
if (focus) {
var hidePassword = (passphraseFieldAgain.echoMode === TextInput.Password);
sendMessageToLightbox({method: 'walletSetup_raiseKeyboard', isPasswordField: hidePassword});
} else if (!passphraseField.focus) {
sendMessageToLightbox({method: 'walletSetup_lowerKeyboard', isPasswordField: false});
}
}

View file

@ -47,6 +47,12 @@ 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] : ''));
}
} else if (walletStatus === 2) {
if (root.activeView !== "passphraseModal") {
@ -172,7 +178,7 @@ Rectangle {
Connections {
onSendSignalToWallet: {
if (msg.method === 'walletSetup_finished') {
if (msg.referrer === '') {
if (msg.referrer === '' || msg.referrer === 'marketplace cta') {
root.activeView = "initialize";
commerce.getWalletStatus();
} else if (msg.referrer === 'purchases') {
@ -666,25 +672,6 @@ Rectangle {
right: parent.right;
}
Image {
id: lowerKeyboardButton;
z: 999;
source: "images/lowerKeyboard.png";
anchors.right: keyboard.right;
anchors.top: keyboard.showMirrorText ? keyboard.top : undefined;
anchors.bottom: keyboard.showMirrorText ? undefined : keyboard.bottom;
height: 50;
width: 60;
MouseArea {
anchors.fill: parent;
onClicked: {
root.keyboardRaised = false;
}
}
}
HifiControlsUit.Keyboard {
id: keyboard;
raised: HMD.mounted && root.keyboardRaised;
@ -719,12 +706,28 @@ Rectangle {
case 'updateWalletReferrer':
walletSetup.referrer = message.referrer;
break;
case 'inspectionCertificate_resetCert':
// NOP
break;
default:
console.log('Unrecognized message from wallet.js:', JSON.stringify(message));
}
}
signal sendToScript(var message);
// generateUUID() taken from:
// https://stackoverflow.com/a/8809472
function generateUUID() { // Public Domain/MIT
var d = new Date().getTime();
if (typeof performance !== 'undefined' && typeof performance.now === 'function'){
d += performance.now(); //use high-precision timer if available
}
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
var r = (d + Math.random() * 16) % 16 | 0;
d = Math.floor(d / 16);
return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16);
});
}
//
// FUNCTION DEFINITIONS END
//

View file

@ -68,6 +68,7 @@ Item {
Connections {
target: GlobalServices
onMyUsernameChanged: {
transactionHistoryModel.clear();
usernameText.text = Account.username;
}
}

View file

@ -31,6 +31,10 @@ Item {
property bool hasShownSecurityImageTip: false;
property string referrer;
property string keyFilePath;
property date startingTimestamp;
property string setupAttemptID;
readonly property int setupFlowVersion: 1;
readonly property var setupStepNames: [ "Setup Prompt", "Security Image Selection", "Passphrase Selection", "Private Keys Ready" ];
Image {
anchors.fill: parent;
@ -67,6 +71,13 @@ Item {
anchors.fill: parent;
}
onActiveViewChanged: {
var timestamp = new Date();
var currentStepNumber = root.activeView.substring(5);
UserActivityLogger.commerceWalletSetupProgress(timestamp, root.setupAttemptID,
Math.round((timestamp - root.startingTimestamp)/1000), currentStepNumber, root.setupStepNames[currentStepNumber - 1]);
}
//
// TITLE BAR START
//
@ -371,7 +382,7 @@ Item {
Item {
id: securityImageTip;
visible: false;
visible: !root.hasShownSecurityImageTip && root.activeView === "step_3";
z: 999;
anchors.fill: root;
@ -421,7 +432,6 @@ Item {
text: "Got It";
onClicked: {
root.hasShownSecurityImageTip = true;
securityImageTip.visible = false;
passphraseSelection.focusFirstTextField();
}
}
@ -439,9 +449,6 @@ Item {
onVisibleChanged: {
if (visible) {
commerce.getWalletAuthenticatedStatus();
if (!root.hasShownSecurityImageTip) {
securityImageTip.visible = true;
}
}
}
@ -732,7 +739,11 @@ Item {
text: "Finish";
onClicked: {
root.visible = false;
root.hasShownSecurityImageTip = false;
sendSignalToWallet({method: 'walletSetup_finished', referrer: root.referrer ? root.referrer : ""});
var timestamp = new Date();
UserActivityLogger.commerceWalletSetupFinished(timestamp, setupAttemptID, Math.round((timestamp - root.startingTimestamp)/1000));
}
}
}

View file

@ -9,33 +9,30 @@ Overlay {
Image {
id: image
property bool scaleFix: true;
property real xOffset: 0
property real yOffset: 0
property bool scaleFix: true
property real xStart: 0
property real yStart: 0
property real xSize: 0
property real ySize: 0
property real imageScale: 1.0
property var resizer: Timer {
interval: 50
repeat: false
running: false
onTriggered: {
var targetAspect = root.width / root.height;
var sourceAspect = image.sourceSize.width / image.sourceSize.height;
if (sourceAspect <= targetAspect) {
if (root.width === image.sourceSize.width) {
return;
}
image.imageScale = root.width / image.sourceSize.width;
} else if (sourceAspect > targetAspect){
if (root.height === image.sourceSize.height) {
return;
}
image.imageScale = root.height / image.sourceSize.height;
if (image.xSize === 0) {
image.xSize = image.sourceSize.width - image.xStart;
}
image.sourceSize = Qt.size(image.sourceSize.width * image.imageScale, image.sourceSize.height * image.imageScale);
if (image.ySize === 0) {
image.ySize = image.sourceSize.height - image.yStart;
}
image.anchors.leftMargin = -image.xStart * root.width / image.xSize;
image.anchors.topMargin = -image.yStart * root.height / image.ySize;
image.anchors.rightMargin = (image.xStart + image.xSize - image.sourceSize.width) * root.width / image.xSize;
image.anchors.bottomMargin = (image.yStart + image.ySize - image.sourceSize.height) * root.height / image.ySize;
}
}
x: -1 * xOffset * imageScale
y: -1 * yOffset * imageScale
onSourceSizeChanged: {
if (sourceSize.width !== 0 && sourceSize.height !== 0 && progress === 1.0 && scaleFix) {
@ -43,6 +40,8 @@ Overlay {
resizer.start();
}
}
anchors.fill: parent
}
ColorOverlay {
@ -57,8 +56,10 @@ Overlay {
var key = keys[i];
var value = subImage[key];
switch (key) {
case "x": image.xOffset = value; break;
case "y": image.yOffset = value; break;
case "x": image.xStart = value; break;
case "y": image.yStart = value; break;
case "width": image.xSize = value; break;
case "height": image.ySize = value; break;
}
}
}

View file

@ -7075,11 +7075,11 @@ QRect Application::getRecommendedHUDRect() const {
return result;
}
QSize Application::getDeviceSize() const {
glm::vec2 Application::getDeviceSize() const {
static const int MIN_SIZE = 1;
QSize result(MIN_SIZE, MIN_SIZE);
glm::vec2 result(MIN_SIZE);
if (_displayPlugin) {
result = fromGlm(getActiveDisplayPlugin()->getRecommendedRenderSize());
result = getActiveDisplayPlugin()->getRecommendedRenderSize();
}
return result;
}
@ -7098,10 +7098,6 @@ bool Application::hasFocus() const {
return (QApplication::activeWindow() != nullptr);
}
glm::vec2 Application::getViewportDimensions() const {
return toGlm(getDeviceSize());
}
void Application::setMaxOctreePacketsPerSecond(int maxOctreePPS) {
if (maxOctreePPS != _maxOctreePPS) {
_maxOctreePPS = maxOctreePPS;

View file

@ -158,7 +158,7 @@ public:
glm::uvec2 getUiSize() const;
QRect getRecommendedHUDRect() const;
QSize getDeviceSize() const;
glm::vec2 getDeviceSize() const;
bool hasFocus() const;
void showCursor(const Cursor::Icon& cursor);
@ -228,8 +228,6 @@ public:
FileLogger* getLogger() const { return _logger; }
glm::vec2 getViewportDimensions() const;
NodeToJurisdictionMap& getEntityServerJurisdictions() { return _entityServerJurisdictions; }
float getRenderResolutionScale() const;

View file

@ -104,8 +104,7 @@ void Application::paintGL() {
PerformanceTimer perfTimer("renderOverlay");
// NOTE: There is no batch associated with this renderArgs
// the ApplicationOverlay class assumes it's viewport is setup to be the device size
QSize size = getDeviceSize();
renderArgs._viewport = glm::ivec4(0, 0, size.width(), size.height());
renderArgs._viewport = glm::ivec4(0, 0, getDeviceSize());
_applicationOverlay.renderOverlay(&renderArgs);
}

View file

@ -28,6 +28,7 @@
#include <shared/QtHelpers.h>
#include <AvatarData.h>
#include <PerfStat.h>
#include <PrioritySortUtil.h>
#include <RegisteredMetaTypes.h>
#include <Rig.h>
#include <SettingHandle.h>
@ -142,32 +143,39 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
PerformanceTimer perfTimer("otherAvatars");
auto avatarMap = getHashCopy();
QList<AvatarSharedPointer> avatarList = avatarMap.values();
class SortableAvatar: public PrioritySortUtil::Sortable {
public:
SortableAvatar() = delete;
SortableAvatar(const AvatarSharedPointer& avatar) : _avatar(avatar) {}
glm::vec3 getPosition() const override { return _avatar->getWorldPosition(); }
float getRadius() const override { return std::static_pointer_cast<Avatar>(_avatar)->getBoundingRadius(); }
uint64_t getTimestamp() const override { return std::static_pointer_cast<Avatar>(_avatar)->getLastRenderUpdateTime(); }
const AvatarSharedPointer& getAvatar() const { return _avatar; }
private:
AvatarSharedPointer _avatar;
};
ViewFrustum cameraView;
qApp->copyDisplayViewFrustum(cameraView);
PrioritySortUtil::PriorityQueue<SortableAvatar> sortedAvatars(cameraView,
AvatarData::_avatarSortCoefficientSize,
AvatarData::_avatarSortCoefficientCenter,
AvatarData::_avatarSortCoefficientAge);
std::priority_queue<AvatarPriority> sortedAvatars;
AvatarData::sortAvatars(avatarList, cameraView, sortedAvatars,
[](AvatarSharedPointer avatar)->uint64_t{
return std::static_pointer_cast<Avatar>(avatar)->getLastRenderUpdateTime();
},
[](AvatarSharedPointer avatar)->float{
return std::static_pointer_cast<Avatar>(avatar)->getBoundingRadius();
},
[this](AvatarSharedPointer avatar)->bool{
const auto& castedAvatar = std::static_pointer_cast<Avatar>(avatar);
if (castedAvatar == _myAvatar || !castedAvatar->isInitialized()) {
// DO NOT update _myAvatar! Its update has already been done earlier in the main loop.
// DO NOT update or fade out uninitialized Avatars
return true; // ignore it
}
return false;
});
// sort
auto avatarMap = getHashCopy();
AvatarHash::iterator itr = avatarMap.begin();
while (itr != avatarMap.end()) {
const auto& avatar = std::static_pointer_cast<Avatar>(*itr);
// DO NOT update _myAvatar! Its update has already been done earlier in the main loop.
// DO NOT update or fade out uninitialized Avatars
if (avatar != _myAvatar && avatar->isInitialized()) {
sortedAvatars.push(SortableAvatar(avatar));
}
++itr;
}
// process in sorted order
uint64_t startTime = usecTimestampNow();
const uint64_t UPDATE_BUDGET = 2000; // usec
uint64_t updateExpiry = startTime + UPDATE_BUDGET;
@ -176,8 +184,8 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
render::Transaction transaction;
while (!sortedAvatars.empty()) {
const AvatarPriority& sortData = sortedAvatars.top();
const auto& avatar = std::static_pointer_cast<Avatar>(sortData.avatar);
const SortableAvatar& sortData = sortedAvatars.top();
const auto& avatar = std::static_pointer_cast<Avatar>(sortData.getAvatar());
bool ignoring = DependencyManager::get<NodeList>()->isPersonalMutingNode(avatar->getID());
if (ignoring) {
@ -207,7 +215,7 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
uint64_t now = usecTimestampNow();
if (now < updateExpiry) {
// we're within budget
bool inView = sortData.priority > OUT_OF_VIEW_THRESHOLD;
bool inView = sortData.getPriority() > OUT_OF_VIEW_THRESHOLD;
if (inView && avatar->hasNewJointData()) {
numAvatarsUpdated++;
}
@ -221,7 +229,7 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
// --> some avatar velocity measurements may be a little off
// no time simulate, but we take the time to count how many were tragically missed
bool inView = sortData.priority > OUT_OF_VIEW_THRESHOLD;
bool inView = sortData.getPriority() > OUT_OF_VIEW_THRESHOLD;
if (!inView) {
break;
}
@ -230,9 +238,9 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
}
sortedAvatars.pop();
while (inView && !sortedAvatars.empty()) {
const AvatarPriority& newSortData = sortedAvatars.top();
const auto& newAvatar = std::static_pointer_cast<Avatar>(newSortData.avatar);
inView = newSortData.priority > OUT_OF_VIEW_THRESHOLD;
const SortableAvatar& newSortData = sortedAvatars.top();
const auto& newAvatar = std::static_pointer_cast<Avatar>(newSortData.getAvatar());
inView = newSortData.getPriority() > OUT_OF_VIEW_THRESHOLD;
if (inView && newAvatar->hasNewJointData()) {
numAVatarsNotUpdated++;
}

View file

@ -128,6 +128,11 @@ void QmlCommerce::reset() {
wallet->reset();
}
void QmlCommerce::resetLocalWalletOnly() {
auto wallet = DependencyManager::get<Wallet>();
wallet->reset();
}
void QmlCommerce::account() {
auto ledger = DependencyManager::get<Ledger>();
ledger->account();

View file

@ -65,6 +65,7 @@ protected:
Q_INVOKABLE void history();
Q_INVOKABLE void generateKeyPair();
Q_INVOKABLE void reset();
Q_INVOKABLE void resetLocalWalletOnly();
Q_INVOKABLE void account();
Q_INVOKABLE void certificateInfo(const QString& certificateId);

View file

@ -321,6 +321,16 @@ Wallet::Wallet() {
auto accountManager = DependencyManager::get<AccountManager>();
connect(accountManager.data(), &AccountManager::usernameChanged, this, [&]() {
getWalletStatus();
_publicKeys.clear();
if (_securityImage) {
delete _securityImage;
}
_securityImage = nullptr;
// tell the provider we got nothing
updateImageProvider();
_passphrase->clear();
});
}

View file

@ -176,6 +176,10 @@ bool WindowScriptingInterface::isPointOnDesktopWindow(QVariant point) {
return offscreenUi->isPointOnDesktopWindow(point);
}
glm::vec2 WindowScriptingInterface::getDeviceSize() const {
return qApp->getDeviceSize();
}
/// Makes sure that the reticle is visible, use this in blocking forms that require a reticle and
/// might be in same thread as a script that sets the reticle to invisible
void WindowScriptingInterface::ensureReticleVisible() const {

View file

@ -12,6 +12,8 @@
#ifndef hifi_WindowScriptingInterface_h
#define hifi_WindowScriptingInterface_h
#include <glm/glm.hpp>
#include <QtCore/QObject>
#include <QtCore/QString>
#include <QtQuick/QQuickItem>
@ -73,6 +75,7 @@ public slots:
bool isPhysicsEnabled();
bool setDisplayTexture(const QString& name);
bool isPointOnDesktopWindow(QVariant point);
glm::vec2 getDeviceSize() const;
int openMessageBox(QString title, QString text, int buttons, int defaultButton);
void updateMessageBox(int id, QString title, QString text, int buttons, int defaultButton);

View file

@ -35,7 +35,8 @@ ModelOverlay::ModelOverlay(const ModelOverlay* modelOverlay) :
_modelTextures(QVariantMap()),
_url(modelOverlay->_url),
_updateModel(false),
_loadPriority(modelOverlay->getLoadPriority())
_scaleToFit(modelOverlay->_scaleToFit),
_loadPriority(modelOverlay->_loadPriority)
{
_model->init();
_model->setLoadingPriority(_loadPriority);

View file

@ -75,8 +75,8 @@ private:
QVariantMap _modelTextures;
QUrl _url;
bool _updateModel = { false };
bool _scaleToFit = { false };
bool _updateModel { false };
bool _scaleToFit { false };
float _loadPriority { 0.0f };
AnimationPointer _animation;
@ -87,7 +87,7 @@ private:
bool _animationRunning { false };
bool _animationLoop { false };
float _animationFirstFrame { 0.0f };
float _animationLastFrame = { 0.0f };
float _animationLastFrame { 0.0f };
bool _animationHold { false };
bool _animationAllowTranslation { false };
uint64_t _lastAnimated { 0 };

View file

@ -18,8 +18,9 @@
QString const Shape3DOverlay::TYPE = "shape";
Shape3DOverlay::Shape3DOverlay(const Shape3DOverlay* Shape3DOverlay) :
Volume3DOverlay(Shape3DOverlay)
Shape3DOverlay::Shape3DOverlay(const Shape3DOverlay* shape3DOverlay) :
Volume3DOverlay(shape3DOverlay),
_shape(shape3DOverlay->_shape)
{
}

View file

@ -23,7 +23,7 @@ public:
virtual QString getType() const override { return TYPE; }
Shape3DOverlay() {}
Shape3DOverlay(const Shape3DOverlay* Shape3DOverlay);
Shape3DOverlay(const Shape3DOverlay* shape3DOverlay);
virtual void render(RenderArgs* args) override;
virtual const render::ShapeKey getShapeKey() override;

View file

@ -14,7 +14,8 @@
#include <RegisteredMetaTypes.h>
Volume3DOverlay::Volume3DOverlay(const Volume3DOverlay* volume3DOverlay) :
Base3DOverlay(volume3DOverlay)
Base3DOverlay(volume3DOverlay),
_localBoundingBox(volume3DOverlay->_localBoundingBox)
{
}

View file

@ -2387,63 +2387,10 @@ void RayToAvatarIntersectionResultFromScriptValue(const QScriptValue& object, Ra
const float AvatarData::OUT_OF_VIEW_PENALTY = -10.0f;
float AvatarData::_avatarSortCoefficientSize { 0.5f };
float AvatarData::_avatarSortCoefficientSize { 1.0f };
float AvatarData::_avatarSortCoefficientCenter { 0.25 };
float AvatarData::_avatarSortCoefficientAge { 1.0f };
void AvatarData::sortAvatars(
QList<AvatarSharedPointer> avatarList,
const ViewFrustum& cameraView,
std::priority_queue<AvatarPriority>& sortedAvatarsOut,
std::function<uint64_t(AvatarSharedPointer)> getLastUpdated,
std::function<float(AvatarSharedPointer)> getBoundingRadius,
std::function<bool(AvatarSharedPointer)> shouldIgnore) {
PROFILE_RANGE(simulation, "sort");
uint64_t now = usecTimestampNow();
glm::vec3 frustumCenter = cameraView.getPosition();
const glm::vec3& forward = cameraView.getDirection();
for (int32_t i = 0; i < avatarList.size(); ++i) {
const auto& avatar = avatarList.at(i);
if (shouldIgnore(avatar)) {
continue;
}
// priority = weighted linear combination of:
// (a) apparentSize
// (b) proximity to center of view
// (c) time since last update
glm::vec3 avatarPosition = avatar->getWorldPosition();
glm::vec3 offset = avatarPosition - frustumCenter;
float distance = glm::length(offset) + 0.001f; // add 1mm to avoid divide by zero
// FIXME - AvatarData has something equivolent to this
float radius = getBoundingRadius(avatar);
float apparentSize = 2.0f * radius / distance;
float cosineAngle = glm::dot(offset, forward) / distance;
float age = (float)(now - getLastUpdated(avatar)) / (float)(USECS_PER_SECOND);
// NOTE: we are adding values of different units to get a single measure of "priority".
// Thus we multiply each component by a conversion "weight" that scales its units relative to the others.
// These weights are pure magic tuning and should be hard coded in the relation below,
// but are currently exposed for anyone who would like to explore fine tuning:
float priority = _avatarSortCoefficientSize * apparentSize
+ _avatarSortCoefficientCenter * cosineAngle
+ _avatarSortCoefficientAge * age;
// decrement priority of avatars outside keyhole
if (distance > cameraView.getCenterRadius()) {
if (!cameraView.sphereIntersectsFrustum(avatarPosition, radius)) {
priority += OUT_OF_VIEW_PENALTY;
}
}
sortedAvatarsOut.push(AvatarPriority(avatar, priority));
}
}
QScriptValue AvatarEntityMapToScriptValue(QScriptEngine* engine, const AvatarEntityMap& value) {
QScriptValue obj = engine->newObject();
for (auto entityID : value.keys()) {

View file

@ -629,14 +629,6 @@ public:
static const float OUT_OF_VIEW_PENALTY;
static void sortAvatars(
QList<AvatarSharedPointer> avatarList,
const ViewFrustum& cameraView,
std::priority_queue<AvatarPriority>& sortedAvatarsOut,
std::function<uint64_t(AvatarSharedPointer)> getLastUpdated,
std::function<float(AvatarSharedPointer)> getBoundingRadius,
std::function<bool(AvatarSharedPointer)> shouldIgnore);
// TODO: remove this HACK once we settle on optimal sort coefficients
// These coefficients exposed for fine tuning the sort priority for transfering new _jointData to the render pipeline.
static float _avatarSortCoefficientSize;

View file

@ -1605,6 +1605,48 @@ void EntityItem::setParentID(const QUuid& value) {
if (tree && !oldParentID.isNull()) {
tree->removeFromChildrenOfAvatars(getThisPointer());
}
uint32_t oldParentNoBootstrapping = 0;
uint32_t newParentNoBootstrapping = 0;
if (!value.isNull() && tree) {
EntityItemPointer entity = tree->findEntityByEntityItemID(value);
if (entity) {
newParentNoBootstrapping = entity->getDirtyFlags() & Simulation::NO_BOOTSTRAPPING;
}
}
if (!oldParentID.isNull() && tree) {
EntityItemPointer entity = tree->findEntityByEntityItemID(oldParentID);
if (entity) {
oldParentNoBootstrapping = entity->getDirtyFlags() & Simulation::NO_BOOTSTRAPPING;
}
}
if (!value.isNull() && (value == Physics::getSessionUUID() || value == AVATAR_SELF_ID)) {
newParentNoBootstrapping |= Simulation::NO_BOOTSTRAPPING;
}
if ((bool)(oldParentNoBootstrapping ^ newParentNoBootstrapping)) {
if ((bool)(newParentNoBootstrapping & Simulation::NO_BOOTSTRAPPING)) {
markDirtyFlags(Simulation::NO_BOOTSTRAPPING);
forEachDescendant([&](SpatiallyNestablePointer object) {
if (object->getNestableType() == NestableType::Entity) {
EntityItemPointer entity = std::static_pointer_cast<EntityItem>(object);
entity->markDirtyFlags(Simulation::DIRTY_COLLISION_GROUP | Simulation::NO_BOOTSTRAPPING);
}
});
} else {
clearDirtyFlags(Simulation::NO_BOOTSTRAPPING);
forEachDescendant([&](SpatiallyNestablePointer object) {
if (object->getNestableType() == NestableType::Entity) {
EntityItemPointer entity = std::static_pointer_cast<EntityItem>(object);
entity->markDirtyFlags(Simulation::DIRTY_COLLISION_GROUP);
entity->clearDirtyFlags(Simulation::NO_BOOTSTRAPPING);
}
});
}
}
SpatiallyNestable::setParentID(value);
// children are forced to be kinematic
// may need to not collide with own avatar
@ -1834,39 +1876,8 @@ void EntityItem::computeCollisionGroupAndFinalMask(int16_t& group, int16_t& mask
}
}
if (userMask & USER_COLLISION_GROUP_MY_AVATAR) {
bool iAmHoldingThis = false;
// if this entity is a descendant of MyAvatar, don't collide with MyAvatar. This avoids the
// "bootstrapping" problem where you can shoot yourself across the room by grabbing something
// and holding it against your own avatar.
if (isChildOfMyAvatar()) {
iAmHoldingThis = true;
}
// also, don't bootstrap our own avatar with a hold action
QList<EntityDynamicPointer> holdActions = getActionsOfType(DYNAMIC_TYPE_HOLD);
QList<EntityDynamicPointer>::const_iterator i = holdActions.begin();
while (i != holdActions.end()) {
EntityDynamicPointer action = *i;
if (action->isMine()) {
iAmHoldingThis = true;
break;
}
i++;
}
QList<EntityDynamicPointer> farGrabActions = getActionsOfType(DYNAMIC_TYPE_FAR_GRAB);
i = farGrabActions.begin();
while (i != farGrabActions.end()) {
EntityDynamicPointer action = *i;
if (action->isMine()) {
iAmHoldingThis = true;
break;
}
i++;
}
if (iAmHoldingThis) {
userMask &= ~USER_COLLISION_GROUP_MY_AVATAR;
}
if ((bool)(_dirtyFlags & Simulation::NO_BOOTSTRAPPING)) {
userMask &= ~USER_COLLISION_GROUP_MY_AVATAR;
}
mask = Physics::getDefaultCollisionMask(group) & (int16_t)(userMask);
}
@ -1961,7 +1972,20 @@ bool EntityItem::addActionInternal(EntitySimulationPointer simulation, EntityDyn
if (success) {
_allActionsDataCache = newDataCache;
_dirtyFlags |= Simulation::DIRTY_PHYSICS_ACTIVATION;
_dirtyFlags |= Simulation::DIRTY_COLLISION_GROUP; // may need to not collide with own avatar
auto actionType = action->getType();
if (actionType == DYNAMIC_TYPE_HOLD || actionType == DYNAMIC_TYPE_FAR_GRAB) {
if (!(bool)(_dirtyFlags & Simulation::NO_BOOTSTRAPPING)) {
_dirtyFlags |= Simulation::NO_BOOTSTRAPPING;
_dirtyFlags |= Simulation::DIRTY_COLLISION_GROUP; // may need to not collide with own avatar
forEachDescendant([&](SpatiallyNestablePointer child) {
if (child->getNestableType() == NestableType::Entity) {
EntityItemPointer entity = std::static_pointer_cast<EntityItem>(child);
entity->markDirtyFlags(Simulation::NO_BOOTSTRAPPING | Simulation::DIRTY_COLLISION_GROUP);
}
});
}
}
} else {
qCDebug(entities) << "EntityItem::addActionInternal -- serializeActions failed";
}
@ -2002,6 +2026,29 @@ bool EntityItem::removeAction(EntitySimulationPointer simulation, const QUuid& a
return success;
}
bool EntityItem::stillHasGrabActions() const {
QList<EntityDynamicPointer> holdActions = getActionsOfType(DYNAMIC_TYPE_HOLD);
QList<EntityDynamicPointer>::const_iterator i = holdActions.begin();
while (i != holdActions.end()) {
EntityDynamicPointer action = *i;
if (action->isMine()) {
return true;
}
i++;
}
QList<EntityDynamicPointer> farGrabActions = getActionsOfType(DYNAMIC_TYPE_FAR_GRAB);
i = farGrabActions.begin();
while (i != farGrabActions.end()) {
EntityDynamicPointer action = *i;
if (action->isMine()) {
return true;
}
i++;
}
return false;
}
bool EntityItem::removeActionInternal(const QUuid& actionID, EntitySimulationPointer simulation) {
_previouslyDeletedActions.insert(actionID, usecTimestampNow());
if (_objectActions.contains(actionID)) {
@ -2015,7 +2062,6 @@ bool EntityItem::removeActionInternal(const QUuid& actionID, EntitySimulationPoi
action->setOwnerEntity(nullptr);
action->setIsMine(false);
_objectActions.remove(actionID);
if (simulation) {
action->removeFromSimulation(simulation);
@ -2024,7 +2070,23 @@ bool EntityItem::removeActionInternal(const QUuid& actionID, EntitySimulationPoi
bool success = true;
serializeActions(success, _allActionsDataCache);
_dirtyFlags |= Simulation::DIRTY_PHYSICS_ACTIVATION;
_dirtyFlags |= Simulation::DIRTY_COLLISION_GROUP; // may need to not collide with own avatar
auto removedActionType = action->getType();
if ((removedActionType == DYNAMIC_TYPE_HOLD || removedActionType == DYNAMIC_TYPE_FAR_GRAB) && !stillHasGrabActions()) {
_dirtyFlags &= ~Simulation::NO_BOOTSTRAPPING;
_dirtyFlags |= Simulation::DIRTY_COLLISION_GROUP; // may need to not collide with own avatar
forEachDescendant([&](SpatiallyNestablePointer child) {
if (child->getNestableType() == NestableType::Entity) {
EntityItemPointer entity = std::static_pointer_cast<EntityItem>(child);
entity->markDirtyFlags(Simulation::DIRTY_COLLISION_GROUP);
entity->clearDirtyFlags(Simulation::NO_BOOTSTRAPPING);
}
});
} else {
// NO-OP: we assume NO_BOOTSTRAPPING bits and collision group are correct
// because they should have been set correctly when the action was added
// and/or when children were linked
}
_objectActions.remove(actionID);
setDynamicDataNeedsTransmit(true);
return success;
}

View file

@ -470,6 +470,7 @@ protected:
void setSimulated(bool simulated) { _simulated = simulated; }
const QByteArray getDynamicDataInternal() const;
bool stillHasGrabActions() const;
void setDynamicDataInternal(QByteArray dynamicData);
virtual void dimensionsChanged() override;

View file

@ -27,6 +27,7 @@ namespace Simulation {
const uint32_t DIRTY_PHYSICS_ACTIVATION = 0x0800; // should activate object in physics engine
const uint32_t DIRTY_SIMULATOR_ID = 0x1000; // the simulatorID has changed
const uint32_t DIRTY_SIMULATION_OWNERSHIP_PRIORITY = 0x2000; // our own bid priority has changed
const uint32_t NO_BOOTSTRAPPING = 0x4000;
const uint32_t DIRTY_TRANSFORM = DIRTY_POSITION | DIRTY_ROTATION;
const uint32_t DIRTY_VELOCITIES = DIRTY_LINEAR_VELOCITY | DIRTY_ANGULAR_VELOCITY;

View file

@ -1006,14 +1006,11 @@ NetworkTexturePointer TextureCache::getResourceTexture(QUrl resourceTextureUrl)
if (!_spectatorCameraNetworkTexture) {
_spectatorCameraNetworkTexture.reset(new NetworkTexture(resourceTextureUrl));
}
if (_spectatorCameraFramebuffer) {
texture = _spectatorCameraFramebuffer->getRenderBuffer(0);
if (texture) {
texture->setSource(SPECTATOR_CAMERA_FRAME_URL.toString().toStdString());
_spectatorCameraNetworkTexture->setImage(texture, texture->getWidth(), texture->getHeight());
return _spectatorCameraNetworkTexture;
}
if (!_spectatorCameraFramebuffer) {
getSpectatorCameraFramebuffer(); // initialize frame buffer
}
updateSpectatorCameraNetworkTexture();
return _spectatorCameraNetworkTexture;
}
// FIXME: Generalize this, DRY up this code
if (resourceTextureUrl == HMD_PREVIEW_FRAME_URL) {
@ -1052,7 +1049,18 @@ const gpu::FramebufferPointer& TextureCache::getSpectatorCameraFramebuffer(int w
// If we aren't taking a screenshot, we might need to resize or create the camera buffer
if (!_spectatorCameraFramebuffer || _spectatorCameraFramebuffer->getWidth() != width || _spectatorCameraFramebuffer->getHeight() != height) {
_spectatorCameraFramebuffer.reset(gpu::Framebuffer::create("spectatorCamera", gpu::Element::COLOR_SRGBA_32, width, height));
updateSpectatorCameraNetworkTexture();
emit spectatorCameraFramebufferReset();
}
return _spectatorCameraFramebuffer;
}
void TextureCache::updateSpectatorCameraNetworkTexture() {
if (_spectatorCameraFramebuffer && _spectatorCameraNetworkTexture) {
gpu::TexturePointer texture = _spectatorCameraFramebuffer->getRenderBuffer(0);
if (texture) {
texture->setSource(SPECTATOR_CAMERA_FRAME_URL.toString().toStdString());
_spectatorCameraNetworkTexture->setImage(texture, texture->getWidth(), texture->getHeight());
}
}
}

View file

@ -171,6 +171,7 @@ public:
const gpu::FramebufferPointer& getHmdPreviewFramebuffer(int width, int height);
const gpu::FramebufferPointer& getSpectatorCameraFramebuffer();
const gpu::FramebufferPointer& getSpectatorCameraFramebuffer(int width, int height);
void updateSpectatorCameraNetworkTexture();
static const int DEFAULT_SPECTATOR_CAM_WIDTH { 2048 };
static const int DEFAULT_SPECTATOR_CAM_HEIGHT { 1024 };

View file

@ -88,3 +88,56 @@ void UserActivityLoggerScriptingInterface::doLogAction(QString action, QJsonObje
Q_ARG(QString, action),
Q_ARG(QJsonObject, details));
}
void UserActivityLoggerScriptingInterface::commercePurchaseSuccess(QString marketplaceID, int cost, bool firstPurchaseOfThisItem) {
QJsonObject payload;
payload["marketplaceID"] = marketplaceID;
payload["cost"] = cost;
payload["firstPurchaseOfThisItem"] = firstPurchaseOfThisItem;
doLogAction("commercePurchaseSuccess", payload);
}
void UserActivityLoggerScriptingInterface::commercePurchaseFailure(QString marketplaceID, int cost, bool firstPurchaseOfThisItem, QString errorDetails) {
QJsonObject payload;
payload["marketplaceID"] = marketplaceID;
payload["cost"] = cost;
payload["firstPurchaseOfThisItem"] = firstPurchaseOfThisItem;
payload["errorDetails"] = errorDetails;
doLogAction("commercePurchaseFailure", payload);
}
void UserActivityLoggerScriptingInterface::commerceEntityRezzed(QString marketplaceID, QString source, QString type) {
QJsonObject payload;
payload["marketplaceID"] = marketplaceID;
payload["source"] = source;
payload["type"] = type;
doLogAction("commerceEntityRezzed", payload);
}
void UserActivityLoggerScriptingInterface::commerceWalletSetupStarted(int timestamp, QString setupAttemptID, int setupFlowVersion, QString referrer, QString currentDomain) {
QJsonObject payload;
payload["timestamp"] = timestamp;
payload["setupAttemptID"] = setupAttemptID;
payload["setupFlowVersion"] = setupFlowVersion;
payload["referrer"] = referrer;
payload["currentDomain"] = currentDomain;
doLogAction("commerceWalletSetupStarted", payload);
}
void UserActivityLoggerScriptingInterface::commerceWalletSetupProgress(int timestamp, QString setupAttemptID, int secondsElapsed, int currentStepNumber, QString currentStepName) {
QJsonObject payload;
payload["timestamp"] = timestamp;
payload["setupAttemptID"] = setupAttemptID;
payload["secondsElapsed"] = secondsElapsed;
payload["currentStepNumber"] = currentStepNumber;
payload["currentStepName"] = currentStepName;
doLogAction("commerceWalletSetupProgress", payload);
}
void UserActivityLoggerScriptingInterface::commerceWalletSetupFinished(int timestamp, QString setupAttemptID, int secondsToComplete) {
QJsonObject payload;
payload["timestamp"] = timestamp;
payload["setupAttemptID"] = setupAttemptID;
payload["secondsToComplete"] = secondsToComplete;
doLogAction("commerceWalletSetupFinished", payload);
}

View file

@ -33,6 +33,12 @@ public:
Q_INVOKABLE void bubbleToggled(bool newValue);
Q_INVOKABLE void bubbleActivated();
Q_INVOKABLE void logAction(QString action, QVariantMap details = QVariantMap{});
Q_INVOKABLE void commercePurchaseSuccess(QString marketplaceID, int cost, bool firstPurchaseOfThisItem);
Q_INVOKABLE void commercePurchaseFailure(QString marketplaceID, int cost, bool firstPurchaseOfThisItem, QString errorDetails);
Q_INVOKABLE void commerceEntityRezzed(QString marketplaceID, QString source, QString type);
Q_INVOKABLE void commerceWalletSetupStarted(int timestamp, QString setupAttemptID, int setupFlowVersion, QString referrer, QString currentDomain);
Q_INVOKABLE void commerceWalletSetupProgress(int timestamp, QString setupAttemptID, int secondsElapsed, int currentStepNumber, QString currentStepName);
Q_INVOKABLE void commerceWalletSetupFinished(int timestamp, QString setupAttemptID, int secondsToComplete);
private:
void doLogAction(QString action, QJsonObject details = {});
};

View file

@ -700,7 +700,7 @@ uint32_t EntityMotionState::getIncomingDirtyFlags() {
void EntityMotionState::clearIncomingDirtyFlags() {
assert(entityTreeIsLocked());
if (_body && _entity) {
_entity->clearDirtyFlags();
_entity->clearDirtyFlags(DIRTY_PHYSICS_FLAGS);
}
}

View file

@ -32,23 +32,23 @@ void Transaction::removeItem(ItemID id) {
}
void Transaction::addTransitionToItem(ItemID id, Transition::Type transition, ItemID boundId) {
_addedTransitions.emplace_back(TransitionAdd{ id, transition, boundId });
_addedTransitions.emplace_back(id, transition, boundId);
}
void Transaction::removeTransitionFromItem(ItemID id) {
_addedTransitions.emplace_back(TransitionAdd{ id, Transition::NONE, render::Item::INVALID_ITEM_ID });
_addedTransitions.emplace_back(id, Transition::NONE, render::Item::INVALID_ITEM_ID);
}
void Transaction::reApplyTransitionToItem(ItemID id) {
_reAppliedTransitions.emplace_back(TransitionReApply{ id });
_reAppliedTransitions.emplace_back(id);
}
void Transaction::queryTransitionOnItem(ItemID id, TransitionQueryFunc func) {
_queriedTransitions.emplace_back(TransitionQuery{ id, func });
_queriedTransitions.emplace_back(id, func);
}
void Transaction::updateItem(ItemID id, const UpdateFunctorPointer& functor) {
_updatedItems.emplace_back(Update{ id, functor });
_updatedItems.emplace_back(id, functor);
}
void Transaction::resetSelection(const Selection& selection) {
@ -56,28 +56,122 @@ void Transaction::resetSelection(const Selection& selection) {
}
void Transaction::resetSelectionHighlight(const std::string& selectionName, const HighlightStyle& style) {
_highlightResets.emplace_back(HighlightReset{ selectionName, style });
_highlightResets.emplace_back(selectionName, style );
}
void Transaction::removeHighlightFromSelection(const std::string& selectionName) {
_highlightRemoves.emplace_back(selectionName);
}
void Transaction::querySelectionHighlight(const std::string& selectionName, SelectionHighlightQueryFunc func) {
_highlightQueries.emplace_back(HighlightQuery{ selectionName, func });
void Transaction::querySelectionHighlight(const std::string& selectionName, const SelectionHighlightQueryFunc& func) {
_highlightQueries.emplace_back(selectionName, func);
}
void Transaction::reserve(const std::vector<Transaction>& transactionContainer) {
size_t resetItemsCount = 0;
size_t removedItemsCount = 0;
size_t updatedItemsCount = 0;
size_t resetSelectionsCount = 0;
size_t addedTransitionsCount = 0;
size_t queriedTransitionsCount = 0;
size_t reAppliedTransitionsCount = 0;
size_t highlightResetsCount = 0;
size_t highlightRemovesCount = 0;
size_t highlightQueriesCount = 0;
for (const auto& transaction : transactionContainer) {
resetItemsCount += transaction._resetItems.size();
removedItemsCount += transaction._removedItems.size();
updatedItemsCount += transaction._updatedItems.size();
resetSelectionsCount += transaction._resetSelections.size();
addedTransitionsCount += transaction._addedTransitions.size();
queriedTransitionsCount += transaction._queriedTransitions.size();
reAppliedTransitionsCount += transaction._reAppliedTransitions.size();
highlightResetsCount += transaction._highlightResets.size();
highlightRemovesCount += transaction._highlightRemoves.size();
highlightQueriesCount += transaction._highlightQueries.size();
}
_resetItems.reserve(resetItemsCount);
_removedItems.reserve(removedItemsCount);
_updatedItems.reserve(updatedItemsCount);
_resetSelections.reserve(resetSelectionsCount);
_addedTransitions.reserve(addedTransitionsCount);
_queriedTransitions.reserve(queriedTransitionsCount);
_reAppliedTransitions.reserve(reAppliedTransitionsCount);
_highlightResets.reserve(highlightResetsCount);
_highlightRemoves.reserve(highlightRemovesCount);
_highlightQueries.reserve(highlightQueriesCount);
}
void Transaction::merge(const std::vector<Transaction>& transactionContainer) {
reserve(transactionContainer);
for (const auto& transaction : transactionContainer) {
merge(transaction);
}
}
void Transaction::merge(std::vector<Transaction>&& transactionContainer) {
reserve(transactionContainer);
auto begin = std::make_move_iterator(transactionContainer.begin());
auto end = std::make_move_iterator(transactionContainer.end());
for (auto itr = begin; itr != end; ++itr) {
merge(*itr);
}
transactionContainer.clear();
}
template <typename T>
void moveElements(T& target, T& source) {
target.insert(target.end(), std::make_move_iterator(source.begin()), std::make_move_iterator(source.end()));
source.clear();
}
template <typename T>
void copyElements(T& target, const T& source) {
target.insert(target.end(), source.begin(), source.end());
}
void Transaction::merge(Transaction&& transaction) {
moveElements(_resetItems, transaction._resetItems);
moveElements(_removedItems, transaction._removedItems);
moveElements(_updatedItems, transaction._updatedItems);
moveElements(_resetSelections, transaction._resetSelections);
moveElements(_addedTransitions, transaction._addedTransitions);
moveElements(_queriedTransitions, transaction._queriedTransitions);
moveElements(_reAppliedTransitions, transaction._reAppliedTransitions);
moveElements(_highlightResets, transaction._highlightResets);
moveElements(_highlightRemoves, transaction._highlightRemoves);
moveElements(_highlightQueries, transaction._highlightQueries);
}
void Transaction::merge(const Transaction& transaction) {
_resetItems.insert(_resetItems.end(), transaction._resetItems.begin(), transaction._resetItems.end());
_removedItems.insert(_removedItems.end(), transaction._removedItems.begin(), transaction._removedItems.end());
_updatedItems.insert(_updatedItems.end(), transaction._updatedItems.begin(), transaction._updatedItems.end());
_resetSelections.insert(_resetSelections.end(), transaction._resetSelections.begin(), transaction._resetSelections.end());
_addedTransitions.insert(_addedTransitions.end(), transaction._addedTransitions.begin(), transaction._addedTransitions.end());
_queriedTransitions.insert(_queriedTransitions.end(), transaction._queriedTransitions.begin(), transaction._queriedTransitions.end());
_reAppliedTransitions.insert(_reAppliedTransitions.end(), transaction._reAppliedTransitions.begin(), transaction._reAppliedTransitions.end());
_highlightResets.insert(_highlightResets.end(), transaction._highlightResets.begin(), transaction._highlightResets.end());
_highlightRemoves.insert(_highlightRemoves.end(), transaction._highlightRemoves.begin(), transaction._highlightRemoves.end());
_highlightQueries.insert(_highlightQueries.end(), transaction._highlightQueries.begin(), transaction._highlightQueries.end());
copyElements(_resetItems, transaction._resetItems);
copyElements(_removedItems, transaction._removedItems);
copyElements(_updatedItems, transaction._updatedItems);
copyElements(_resetSelections, transaction._resetSelections);
copyElements(_addedTransitions, transaction._addedTransitions);
copyElements(_queriedTransitions, transaction._queriedTransitions);
copyElements(_reAppliedTransitions, transaction._reAppliedTransitions);
copyElements(_highlightResets, transaction._highlightResets);
copyElements(_highlightRemoves, transaction._highlightRemoves);
copyElements(_highlightQueries, transaction._highlightQueries);
}
void Transaction::clear() {
_resetItems.clear();
_removedItems.clear();
_updatedItems.clear();
_resetSelections.clear();
_addedTransitions.clear();
_queriedTransitions.clear();
_reAppliedTransitions.clear();
_highlightResets.clear();
_highlightRemoves.clear();
_highlightQueries.clear();
}
@ -102,54 +196,50 @@ bool Scene::isAllocatedID(const ItemID& id) const {
/// Enqueue change batch to the scene
void Scene::enqueueTransaction(const Transaction& transaction) {
_transactionQueueMutex.lock();
_transactionQueue.push(transaction);
_transactionQueueMutex.unlock();
std::unique_lock<std::mutex> lock(_transactionQueueMutex);
_transactionQueue.emplace_back(transaction);
}
void consolidateTransaction(TransactionQueue& queue, Transaction& singleBatch) {
while (!queue.empty()) {
const auto& transaction = queue.front();
singleBatch.merge(transaction);
queue.pop();
};
void Scene::enqueueTransaction(Transaction&& transaction) {
std::unique_lock<std::mutex> lock(_transactionQueueMutex);
_transactionQueue.emplace_back(std::move(transaction));
}
uint32_t Scene::enqueueFrame() {
PROFILE_RANGE(render, __FUNCTION__);
Transaction consolidatedTransaction;
TransactionQueue localTransactionQueue;
{
std::unique_lock<std::mutex> lock(_transactionQueueMutex);
consolidateTransaction(_transactionQueue, consolidatedTransaction);
localTransactionQueue.swap(_transactionQueue);
}
uint32_t frameNumber = 0;
Transaction consolidatedTransaction;
consolidatedTransaction.merge(std::move(localTransactionQueue));
{
std::unique_lock<std::mutex> lock(_transactionFramesMutex);
_transactionFrames.push_back(consolidatedTransaction);
_transactionFrameNumber++;
frameNumber = _transactionFrameNumber;
}
return frameNumber;
return ++_transactionFrameNumber;
}
void Scene::processTransactionQueue() {
PROFILE_RANGE(render, __FUNCTION__);
TransactionFrames queuedFrames;
static TransactionFrames queuedFrames;
{
// capture the queued frames and clear the queue
std::unique_lock<std::mutex> lock(_transactionFramesMutex);
queuedFrames = _transactionFrames;
_transactionFrames.clear();
queuedFrames.swap(_transactionFrames);
}
// go through the queue of frames and process them
for (auto& frame : queuedFrames) {
processTransactionFrame(frame);
}
queuedFrames.clear();
}
void Scene::processTransactionFrame(const Transaction& transaction) {

View file

@ -65,9 +65,14 @@ public:
void resetSelectionHighlight(const std::string& selectionName, const HighlightStyle& style = HighlightStyle());
void removeHighlightFromSelection(const std::string& selectionName);
void querySelectionHighlight(const std::string& selectionName, SelectionHighlightQueryFunc func);
void querySelectionHighlight(const std::string& selectionName, const SelectionHighlightQueryFunc& func);
void reserve(const std::vector<Transaction>& transactionContainer);
void merge(const std::vector<Transaction>& transactionContainer);
void merge(std::vector<Transaction>&& transactionContainer);
void merge(const Transaction& transaction);
void merge(Transaction&& transaction);
void clear();
// Checkers if there is work to do when processing the transaction
bool touchTransactions() const { return !_resetSelections.empty(); }
@ -107,7 +112,7 @@ protected:
HighlightRemoves _highlightRemoves;
HighlightQueries _highlightQueries;
};
typedef std::queue<Transaction> TransactionQueue;
typedef std::vector<Transaction> TransactionQueue;
// Scene is a container for Items
@ -133,6 +138,9 @@ public:
// Enqueue transaction to the scene
void enqueueTransaction(const Transaction& transaction);
// Enqueue transaction to the scene
void enqueueTransaction(Transaction&& transaction);
// Enqueue end of frame transactions boundary
uint32_t enqueueFrame();
@ -187,7 +195,7 @@ protected:
std::mutex _transactionFramesMutex;
using TransactionFrames = std::list<Transaction>;
using TransactionFrames = std::vector<Transaction>;
TransactionFrames _transactionFrames;
uint32_t _transactionFrameNumber{ 0 };

View file

@ -12,6 +12,9 @@
#define hifi_PrioritySortUtil_h
#include <glm/glm.hpp>
#include <queue>
#include "NumericalConstants.h"
#include "ViewFrustum.h"
/* PrioritySortUtil is a helper for sorting 3D things relative to a ViewFrustum. To use:
@ -32,11 +35,10 @@
(2) Make a PrioritySortUtil::PriorityQueue<Thing> and add them to the queue:
PrioritySortUtil::Prioritizer prioritizer(viewFrustum);
PrioritySortUtil::PriorityQueue<SortableWrapper> sortedThings(viewFrustum);
std::priority_queue< PrioritySortUtil::Sortable<Thing> > sortedThings;
for (thing in things) {
float priority = prioritizer.computePriority(PrioritySortUtil::PrioritizableThing(thing));
sortedThings.push(PrioritySortUtil::Sortable<Thing> entry(thing, priority));
sortedThings.push(SortableWrapper(thing));
}
(3) Loop over your priority queue and do timeboxed work:
@ -65,6 +67,7 @@ namespace PrioritySortUtil {
virtual uint64_t getTimestamp() const = 0;
void setPriority(float priority) { _priority = priority; }
float getPriority() const { return _priority; }
bool operator<(const Sortable& other) const { return _priority < other._priority; }
private:
float _priority { 0.0f };
@ -109,11 +112,19 @@ namespace PrioritySortUtil {
glm::vec3 position = thing.getPosition();
glm::vec3 offset = position - _view.getPosition();
float distance = glm::length(offset) + 0.001f; // add 1mm to avoid divide by zero
float radius = thing.getRadius();
const float MIN_RADIUS = 0.1f; // WORKAROUND for zero size objects (we still want them to sort by distance)
float radius = glm::min(thing.getRadius(), MIN_RADIUS);
float cosineAngle = (glm::dot(offset, _view.getDirection()) / distance);
float age = (float)(usecTimestampNow() - thing.getTimestamp());
float priority = _angularWeight * (radius / distance)
+ _centerWeight * (glm::dot(offset, _view.getDirection()) / distance)
+ _ageWeight * (float)(usecTimestampNow() - thing.getTimestamp());
// we modulatate "age" drift rate by the cosineAngle term to make periphrial objects sort forward
// at a reduced rate but we don't want the "age" term to go zero or negative so we clamp it
const float MIN_COSINE_ANGLE_FACTOR = 0.1f;
float cosineAngleFactor = glm::max(cosineAngle, MIN_COSINE_ANGLE_FACTOR);
float priority = _angularWeight * glm::max(radius, MIN_RADIUS) / distance
+ _centerWeight * cosineAngle
+ _ageWeight * cosineAngleFactor * age;
// decrement priority of things outside keyhole
if (distance - radius > _view.getCenterRadius()) {

View file

@ -121,63 +121,58 @@ uint64_t uvec2ToUint64(const uvec2& v) {
class AudioHandler : public QObject, QRunnable {
Q_OBJECT
public:
AudioHandler(QObject* container, const QString& deviceName, int runDelayMs = 0, QObject* parent = nullptr) : QObject(parent) {
_container = container;
AudioHandler(QSharedPointer<OffscreenQmlSurface> surface, const QString& deviceName, QObject* parent = nullptr) : QObject(parent) {
_newTargetDevice = deviceName;
_runDelayMs = runDelayMs;
_surface = surface;
setAutoDelete(true);
QThreadPool::globalInstance()->start(this);
if (deviceName.size() > 0) {
QThreadPool::globalInstance()->start(this);
}
}
virtual ~AudioHandler() {
qDebug() << "Audio Handler Destroyed";
}
void run() override {
if (_newTargetDevice.isEmpty()) {
return;
}
if (_runDelayMs > 0) {
QThread::msleep(_runDelayMs);
}
auto audioIO = DependencyManager::get<AudioClient>();
QString deviceName = audioIO->getActiveAudioDevice(QAudio::AudioOutput).deviceName();
for (auto player : _container->findChildren<QMediaPlayer*>()) {
auto mediaState = player->state();
QMediaService *svc = player->service();
if (nullptr == svc) {
return;
}
QAudioOutputSelectorControl *out = qobject_cast<QAudioOutputSelectorControl *>
(svc->requestControl(QAudioOutputSelectorControl_iid));
if (nullptr == out) {
return;
}
QString deviceOuput;
auto outputs = out->availableOutputs();
for (int i = 0; i < outputs.size(); i++) {
QString output = outputs[i];
QString description = out->outputDescription(output);
if (description == deviceName) {
deviceOuput = output;
break;
if (!_surface.isNull() && _surface->getRootItem() && !_surface->getCleaned()) {
for (auto player : _surface->getRootItem()->findChildren<QMediaPlayer*>()) {
auto mediaState = player->state();
QMediaService *svc = player->service();
if (nullptr == svc) {
return;
}
QAudioOutputSelectorControl *out = qobject_cast<QAudioOutputSelectorControl *>
(svc->requestControl(QAudioOutputSelectorControl_iid));
if (nullptr == out) {
return;
}
QString deviceOuput;
auto outputs = out->availableOutputs();
for (int i = 0; i < outputs.size(); i++) {
QString output = outputs[i];
QString description = out->outputDescription(output);
if (description == _newTargetDevice) {
deviceOuput = output;
break;
}
}
out->setActiveOutput(deviceOuput);
svc->releaseControl(out);
// if multimedia was paused, it will start playing automatically after changing audio device
// this will reset it back to a paused state
if (mediaState == QMediaPlayer::State::PausedState) {
player->pause();
}
else if (mediaState == QMediaPlayer::State::StoppedState) {
player->stop();
}
}
out->setActiveOutput(deviceOuput);
svc->releaseControl(out);
// if multimedia was paused, it will start playing automatically after changing audio device
// this will reset it back to a paused state
if (mediaState == QMediaPlayer::State::PausedState) {
player->pause();
} else if (mediaState == QMediaPlayer::State::StoppedState) {
player->stop();
}
}
qDebug() << "QML Audio changed to " << deviceName;
qDebug() << "QML Audio changed to " << _newTargetDevice;
}
private:
QString _newTargetDevice;
QObject* _container;
int _runDelayMs;
QSharedPointer<OffscreenQmlSurface> _surface;
};
class OffscreenTextures {
@ -502,6 +497,7 @@ QOpenGLContext* OffscreenQmlSurface::getSharedContext() {
}
void OffscreenQmlSurface::cleanup() {
_isCleaned = true;
_canvas->makeCurrent();
_renderControl->invalidate();
@ -600,6 +596,7 @@ OffscreenQmlSurface::OffscreenQmlSurface() {
OffscreenQmlSurface::~OffscreenQmlSurface() {
QObject::disconnect(&_updateTimer);
disconnectAudioOutputTimer();
QObject::disconnect(qApp);
cleanup();
@ -613,6 +610,15 @@ OffscreenQmlSurface::~OffscreenQmlSurface() {
void OffscreenQmlSurface::onAboutToQuit() {
_paused = true;
QObject::disconnect(&_updateTimer);
disconnectAudioOutputTimer();
}
void OffscreenQmlSurface::disconnectAudioOutputTimer() {
if (_audioOutputUpdateTimer.isActive()) {
_audioOutputUpdateTimer.stop();
}
QObject::disconnect(&_audioOutputUpdateTimer);
}
void OffscreenQmlSurface::create() {
@ -671,6 +677,14 @@ void OffscreenQmlSurface::create() {
}
});
// Setup the update of the QML media components with the current audio output device
QObject::connect(&_audioOutputUpdateTimer, &QTimer::timeout, this, [this]() {
new AudioHandler(sharedFromThis(), _currentAudioOutputDevice);
});
int waitForAudioQmlMs = 200;
_audioOutputUpdateTimer.setInterval(waitForAudioQmlMs);
_audioOutputUpdateTimer.setSingleShot(true);
// When Quick says there is a need to render, we will not render immediately. Instead,
// a timer with a small interval is used to get better performance.
QObject::connect(&_updateTimer, &QTimer::timeout, this, &OffscreenQmlSurface::updateQuick);
@ -699,10 +713,11 @@ void OffscreenQmlSurface::forceQmlAudioOutputDeviceUpdate() {
QMetaObject::invokeMethod(this, "forceQmlAudioOutputDeviceUpdate", Qt::QueuedConnection);
} else {
auto audioIO = DependencyManager::get<AudioClient>();
QString deviceName = audioIO->getActiveAudioDevice(QAudio::AudioOutput).deviceName();
int waitForAudioQmlMs = 500;
// The audio device need to be change using oth
new AudioHandler(_rootItem, deviceName, waitForAudioQmlMs);
_currentAudioOutputDevice = audioIO->getActiveAudioDevice(QAudio::AudioOutput).deviceName();
if (_audioOutputUpdateTimer.isActive()) {
_audioOutputUpdateTimer.stop();
}
_audioOutputUpdateTimer.start();
}
}

View file

@ -40,7 +40,7 @@ class QQuickItem;
using QmlContextCallback = std::function<void(QQmlContext*, QObject*)>;
class OffscreenQmlSurface : public QObject {
class OffscreenQmlSurface : public QObject, public QEnableSharedFromThis<OffscreenQmlSurface> {
Q_OBJECT
Q_PROPERTY(bool focusText READ isFocusText NOTIFY focusTextChanged)
public:
@ -75,6 +75,7 @@ public:
void pause();
void resume();
bool isPaused() const;
bool getCleaned() { return _isCleaned; }
void setBaseUrl(const QUrl& baseUrl);
QQuickItem* getRootItem();
@ -116,6 +117,7 @@ public slots:
void changeAudioOutputDevice(const QString& deviceName, bool isHtmlUpdate = false);
void forceHtmlAudioOutputDeviceUpdate();
void forceQmlAudioOutputDeviceUpdate();
signals:
void audioOutputDeviceChanged(const QString& deviceName);
@ -147,6 +149,7 @@ private:
void render();
void cleanup();
QJsonObject getGLContextData();
void disconnectAudioOutputTimer();
private slots:
void updateQuick();
@ -170,6 +173,9 @@ private:
uint64_t _lastRenderTime { 0 };
uvec2 _size;
QTimer _audioOutputUpdateTimer;
QString _currentAudioOutputDevice;
// Texture management
TextureAndFence _latestTextureAndFence { 0, 0 };
@ -177,6 +183,7 @@ private:
bool _polish { true };
bool _paused { true };
bool _focusText { false };
bool _isCleaned{ false };
uint8_t _maxFps { 60 };
MouseTranslator _mouseTranslator { [](const QPointF& p) { return p.toPoint(); } };
QWindow* _proxyWindow { nullptr };

View file

@ -62,7 +62,7 @@
this.pointingAtTablet = function(controllerData) {
var rayPick = controllerData.rayPicks[this.hand];
return (rayPick.objectID === HMD.tabletScreenID || rayPick.objectID === HMD.homeButtonID);
return (HMD.tabletScreenID && HMD.homeButtonID && (rayPick.objectID === HMD.tabletScreenID || rayPick.objectID === HMD.homeButtonID));
};
this.getOtherModule = function() {

View file

@ -81,7 +81,8 @@ Script.include("/~/system/libraries/cloneEntityUtils.js");
};
this.otherHandIsParent = function(props) {
return this.getOtherModule().thisHandIsParent(props);
var otherModule = this.getOtherModule();
return (otherModule.thisHandIsParent(props) && otherModule.grabbing);
};
this.startNearParentingGrabEntity = function (controllerData, targetProps) {

View file

@ -63,7 +63,7 @@ var toolBar = (function() {
y: -TOOLBAR_MARGIN_Y - toolHeight
});
browseDirectoryButton = toolBar.addTool({
imageURL: toolIconUrl + "directory-01.svg",
imageURL: toolIconUrl + "directory.svg",
subImage: {
x: 0,
y: Tool.IMAGE_WIDTH,

View file

@ -1415,6 +1415,9 @@ input#reset-to-natural-dimensions {
}
/* ----- Order of Menu items for Primitive ----- */
/* Entity Menu classes are specified by selected entity
within entityProperties.js
*/
#properties-list.ShapeMenu #general,
#properties-list.BoxMenu #general,
#properties-list.SphereMenu #general {
@ -1469,6 +1472,34 @@ input#reset-to-natural-dimensions {
display: none;
}
/* ----- ParticleEffectMenu ----- */
#properties-list.ParticleEffectMenu #general {
order: 1;
}
#properties-list.ParticleEffectMenu #collision-info {
order: 2;
}
#properties-list.ParticleEffectMenu #physical {
order: 3;
}
#properties-list.ParticleEffectMenu #spatial {
order: 4;
}
#properties-list.ParticleEffectMenu #behavior {
order: 5;
}
/* items to hide */
#properties-list.ParticleEffectMenu #base-color-section,
#properties-list.ParticleEffectMenu #hyperlink,
#properties-list.ParticleEffectMenu #light,
#properties-list.ParticleEffectMenu #model,
#properties-list.ParticleEffectMenu #shape-list,
#properties-list.ParticleEffectMenu #text,
#properties-list.ParticleEffectMenu #web,
#properties-list.ParticleEffectMenu #zone {
display: none;
}
/* ----- Order of Menu items for Light ----- */
#properties-list.LightMenu #general {
@ -1500,8 +1531,8 @@ input#reset-to-natural-dimensions {
display: none;
}
/* items to hide */
#properties-list.LightMenu .shape-group.shape-section.property.dropdown,
#properties-list.LightMenu color-section.color-control1 {
#properties-list.LightMenu #shape-list,
#properties-list.LightMenu #base-color-section {
display: none
}
@ -1536,8 +1567,8 @@ input#reset-to-natural-dimensions {
display: none;
}
/* items to hide */
#properties-list.ModelMenu .shape-group.shape-section.property.dropdown,
#properties-list.ModelMenu .color-section.color-control1 {
#properties-list.ModelMenu #shape-list,
#properties-list.ModelMenu #base-color-section {
display: none
}
@ -1572,8 +1603,8 @@ input#reset-to-natural-dimensions {
display: none;
}
/* items to hide */
#properties-list.ZoneMenu .shape-group.shape-section.property.dropdown,
#properties-list.ZoneMenu .color-section.color-control1 {
#properties-list.ZoneMenu #shape-list,
#properties-list.ZoneMenu #base-color-section {
display: none
}
@ -1608,8 +1639,8 @@ input#reset-to-natural-dimensions {
display: none;
}
/* items to hide */
#properties-list.WebMenu .shape-group.shape-section.property.dropdown,
#properties-list.WebMenu .color-section.color-control1 {
#properties-list.WebMenu #shape-list,
#properties-list.WebMenu #base-color-section {
display: none;
}
@ -1645,8 +1676,8 @@ input#reset-to-natural-dimensions {
display: none;
}
/* items to hide */
#properties-list.TextMenu .shape-group.shape-section.property.dropdown,
#properties-list.TextMenu .color-section.color-control1 {
#properties-list.TextMenu #shape-list,
#properties-list.TextMenu #base-color-section {
display: none
}

View file

@ -44,7 +44,7 @@
<fieldset id="general" class="major">
<div class="shape-group shape-section property dropdown">
<div class="shape-group shape-section property dropdown" id="shape-list">
<label for="property-shape">Shape</label>
<select name="SelectShape" id="property-shape">
<option value="Cube">Box</option>
@ -66,12 +66,19 @@
<label for="property-name">Name</label>
<input type="text" id="property-name">
</div>
<div class="physical-group color-section property color-control1">
<div class="color-picker" id="property-color-control1"></div>
<div class="physical-group color-section property rgb fstuple" id="base-color-section">
<div class="color-picker" id="property-color-control2"></div>
<label>Entity color</label>
<div class="tuple">
<div><input type="number" class="red" id="property-color-red"><label for="property-color-red">Red:</label></div>
<div><input type="number" class="green" id="property-color-green"><label for="property-color-green">Green:</label></div>
<div><input type="number" class="blue" id="property-color-blue"><label for="property-color-blue">Blue:</label></div>
</div>
</div>
</fieldset>
<fieldset id="collision-info">
<fieldset id="collision-info" class="major">
<legend class="section-header"> Collision<span>M</span> </legend>
<fieldset class="minor">
<div class="behavior-group property checkbox">
<input type="checkbox" id="property-collisionless">
@ -216,17 +223,6 @@
</div>
</fieldset>
</fieldset>
<fieldset class="minor">
<div class="physical-group color-section property rgb fstuple">
<div class="color-picker" id="property-color-control2"></div>
<label>Entity color</label>
<div class="tuple">
<div><input type="number" class="red" id="property-color-red"><label for="property-color-red">Red:</label></div>
<div><input type="number" class="green" id="property-color-green"><label for="property-color-green">Green:</label></div>
<div><input type="number" class="blue" id="property-color-blue"><label for="property-color-blue">Blue:</label></div>
</div>
</div>
</fieldset>
</fieldset>

File diff suppressed because it is too large Load diff

View file

@ -15,6 +15,9 @@ function setUpKeyboardControl() {
var KEYBOARD_HEIGHT = 200;
function raiseKeyboard() {
window.isKeyboardRaised = true;
window.isNumericKeyboard = this.type === "number";
if (lowerTimer !== null) {
clearTimeout(lowerTimer);
lowerTimer = null;
@ -35,6 +38,9 @@ function setUpKeyboardControl() {
}
function doLowerKeyboard() {
window.isKeyboardRaised = false;
window.isNumericKeyboard = false;
EventBridge.emitWebEvent("_LOWER_KEYBOARD");
lowerTimer = null;
isRaised = false;

View file

@ -110,8 +110,10 @@
var filterText; // Used for updating Purchases QML
function onScreenChanged(type, url) {
onMarketplaceScreen = type === "Web" && url.indexOf(MARKETPLACE_URL) !== -1;
onCommerceScreen = type === "QML" && (url.indexOf(MARKETPLACE_CHECKOUT_QML_PATH_BASE) !== -1 || url === MARKETPLACE_PURCHASES_QML_PATH || url.indexOf(MARKETPLACE_INSPECTIONCERTIFICATE_QML_PATH) !== -1);
wireEventBridge(onCommerceScreen);
onWalletScreen = url.indexOf(MARKETPLACE_WALLET_QML_PATH) !== -1;
onCommerceScreen = type === "QML" && (url.indexOf(MARKETPLACE_CHECKOUT_QML_PATH_BASE) !== -1 || url === MARKETPLACE_PURCHASES_QML_PATH
|| url.indexOf(MARKETPLACE_INSPECTIONCERTIFICATE_QML_PATH) !== -1);
wireEventBridge(onMarketplaceScreen || onCommerceScreen || onWalletScreen);
if (url === MARKETPLACE_PURCHASES_QML_PATH) {
tablet.sendToQml({
@ -122,7 +124,7 @@
}
// for toolbar mode: change button to active when window is first openend, false otherwise.
marketplaceButton.editProperties({ isActive: onMarketplaceScreen || onCommerceScreen });
marketplaceButton.editProperties({ isActive: (onMarketplaceScreen || onCommerceScreen) && !onWalletScreen });
if (type === "Web" && url.indexOf(MARKETPLACE_URL) !== -1) {
ContextOverlay.isInMarketplaceInspectionMode = true;
} else {
@ -322,6 +324,11 @@
} else if (parsedJsonMessage.type === "LOGIN") {
openLoginWindow();
} else if (parsedJsonMessage.type === "WALLET_SETUP") {
wireEventBridge(true);
tablet.sendToQml({
method: 'updateWalletReferrer',
referrer: "marketplace cta"
});
openWallet();
} else if (parsedJsonMessage.type === "MY_ITEMS") {
referrerURL = MARKETPLACE_URL_INITIAL;
@ -399,6 +406,7 @@
referrer: "purchases"
});
openWallet();
break;
case 'checkout_walletNotSetUp':
wireEventBridge(true);
tablet.sendToQml({