Merge pull request #10725 from highfidelity/RC-44

Beta Release 44 - Includes up to Developer Release 6702
This commit is contained in:
Chris Collins 2017-06-19 10:58:24 -07:00 committed by GitHub
commit 16822720cc
157 changed files with 3325 additions and 1408 deletions

View file

@ -166,11 +166,7 @@ void Agent::run() {
// Setup MessagesClient // Setup MessagesClient
auto messagesClient = DependencyManager::set<MessagesClient>(); auto messagesClient = DependencyManager::set<MessagesClient>();
QThread* messagesThread = new QThread; messagesClient->startThread();
messagesThread->setObjectName("Messages Client Thread");
messagesClient->moveToThread(messagesThread);
connect(messagesThread, &QThread::started, messagesClient.data(), &MessagesClient::init);
messagesThread->start();
// make sure we hear about connected nodes so we can grab an ATP script if a request is pending // make sure we hear about connected nodes so we can grab an ATP script if a request is pending
connect(nodeList.data(), &LimitedNodeList::nodeActivated, this, &Agent::nodeActivated); connect(nodeList.data(), &LimitedNodeList::nodeActivated, this, &Agent::nodeActivated);

View file

@ -69,17 +69,7 @@ AssignmentClient::AssignmentClient(Assignment::Type requestAssignmentType, QStri
DependencyManager::set<ResourceScriptingInterface>(); DependencyManager::set<ResourceScriptingInterface>();
DependencyManager::set<UserActivityLoggerScriptingInterface>(); DependencyManager::set<UserActivityLoggerScriptingInterface>();
// setup a thread for the NodeList and its PacketReceiver nodeList->startThread();
QThread* nodeThread = new QThread(this);
nodeThread->setObjectName("NodeList Thread");
nodeThread->start();
// make sure the node thread is given highest priority
nodeThread->setPriority(QThread::TimeCriticalPriority);
// put the NodeList on the node thread
nodeList->moveToThread(nodeThread);
// set the logging target to the the CHILD_TARGET_NAME // set the logging target to the the CHILD_TARGET_NAME
LogHandler::getInstance().setTargetName(ASSIGNMENT_CLIENT_TARGET_NAME); LogHandler::getInstance().setTargetName(ASSIGNMENT_CLIENT_TARGET_NAME);
@ -166,14 +156,8 @@ void AssignmentClient::stopAssignmentClient() {
} }
AssignmentClient::~AssignmentClient() { AssignmentClient::~AssignmentClient() {
QThread* nodeThread = DependencyManager::get<NodeList>()->thread();
// remove the NodeList from the DependencyManager // remove the NodeList from the DependencyManager
DependencyManager::destroy<NodeList>(); DependencyManager::destroy<NodeList>();
// ask the node thread to quit and wait until it is done
nodeThread->quit();
nodeThread->wait();
} }
void AssignmentClient::aboutToQuit() { void AssignmentClient::aboutToQuit() {

View file

@ -16,10 +16,10 @@
#include <mutex> #include <mutex>
#include <vector> #include <vector>
#include <tbb/concurrent_queue.h>
#include <QThread> #include <QThread>
#include <TBBHelpers.h>
#include "AudioMixerSlave.h" #include "AudioMixerSlave.h"
class AudioMixerSlavePool; class AudioMixerSlavePool;

View file

@ -132,7 +132,7 @@ void AvatarMixer::start() {
auto start = usecTimestampNow(); auto start = usecTimestampNow();
nodeList->nestedEach([&](NodeList::const_iterator cbegin, NodeList::const_iterator cend) { nodeList->nestedEach([&](NodeList::const_iterator cbegin, NodeList::const_iterator cend) {
std::for_each(cbegin, cend, [&](const SharedNodePointer& node) { std::for_each(cbegin, cend, [&](const SharedNodePointer& node) {
manageDisplayName(node); manageIdentityData(node);
++_sumListeners; ++_sumListeners;
}); });
}, &lockWait, &nodeTransform, &functor); }, &lockWait, &nodeTransform, &functor);
@ -183,8 +183,9 @@ void AvatarMixer::start() {
// NOTE: nodeData->getAvatar() might be side effected, must be called when access to node/nodeData // NOTE: nodeData->getAvatar() might be side effected, must be called when access to node/nodeData
// is guaranteed to not be accessed by other thread // is guaranteed to not be accessed by other thread
void AvatarMixer::manageDisplayName(const SharedNodePointer& node) { void AvatarMixer::manageIdentityData(const SharedNodePointer& node) {
AvatarMixerClientData* nodeData = reinterpret_cast<AvatarMixerClientData*>(node->getLinkedData()); AvatarMixerClientData* nodeData = reinterpret_cast<AvatarMixerClientData*>(node->getLinkedData());
bool sendIdentity = false;
if (nodeData && nodeData->getAvatarSessionDisplayNameMustChange()) { if (nodeData && nodeData->getAvatarSessionDisplayNameMustChange()) {
AvatarData& avatar = nodeData->getAvatar(); AvatarData& avatar = nodeData->getAvatar();
const QString& existingBaseDisplayName = nodeData->getBaseDisplayName(); const QString& existingBaseDisplayName = nodeData->getBaseDisplayName();
@ -210,9 +211,39 @@ void AvatarMixer::manageDisplayName(const SharedNodePointer& node) {
soFar.second++; // refcount soFar.second++; // refcount
nodeData->flagIdentityChange(); nodeData->flagIdentityChange();
nodeData->setAvatarSessionDisplayNameMustChange(false); nodeData->setAvatarSessionDisplayNameMustChange(false);
sendIdentityPacket(nodeData, node); // Tell node whose name changed about its new session display name. sendIdentity = true;
qCDebug(avatars) << "Giving session display name" << sessionDisplayName << "to node with ID" << node->getUUID(); qCDebug(avatars) << "Giving session display name" << sessionDisplayName << "to node with ID" << node->getUUID();
} }
if (nodeData && nodeData->getAvatarSkeletonModelUrlMustChange()) { // never true for an empty _avatarWhitelist
nodeData->setAvatarSkeletonModelUrlMustChange(false);
AvatarData& avatar = nodeData->getAvatar();
static const QUrl emptyURL("");
QUrl url = avatar.cannonicalSkeletonModelURL(emptyURL);
if (!isAvatarInWhitelist(url)) {
qCDebug(avatars) << "Forbidden avatar" << nodeData->getNodeID() << avatar.getSkeletonModelURL() << "replaced with" << (_replacementAvatar.isEmpty() ? "default" : _replacementAvatar);
avatar.setSkeletonModelURL(_replacementAvatar);
sendIdentity = true;
}
}
if (sendIdentity) {
sendIdentityPacket(nodeData, node); // Tell node whose name changed about its new session display name or avatar.
}
}
bool AvatarMixer::isAvatarInWhitelist(const QUrl& url) {
// The avatar is in the whitelist if:
// 1. The avatar's URL's host matches one of the hosts of the URLs in the whitelist AND
// 2. The avatar's URL's path starts with the path of that same URL in the whitelist
for (const auto& whiteListedPrefix : _avatarWhitelist) {
auto whiteListURL = QUrl::fromUserInput(whiteListedPrefix);
// check if this script URL matches the whitelist domain and, optionally, is beneath the path
if (url.host().compare(whiteListURL.host(), Qt::CaseInsensitive) == 0 &&
url.path().startsWith(whiteListURL.path(), Qt::CaseInsensitive)) {
return true;
}
}
return false;
} }
void AvatarMixer::throttle(std::chrono::microseconds duration, int frame) { void AvatarMixer::throttle(std::chrono::microseconds duration, int frame) {
@ -402,13 +433,17 @@ void AvatarMixer::handleAvatarIdentityPacket(QSharedPointer<ReceivedMessage> mes
AvatarData::parseAvatarIdentityPacket(message->getMessage(), identity); AvatarData::parseAvatarIdentityPacket(message->getMessage(), identity);
bool identityChanged = false; bool identityChanged = false;
bool displayNameChanged = false; bool displayNameChanged = false;
avatar.processAvatarIdentity(identity, identityChanged, displayNameChanged); bool skeletonModelUrlChanged = false;
avatar.processAvatarIdentity(identity, identityChanged, displayNameChanged, skeletonModelUrlChanged);
if (identityChanged) { if (identityChanged) {
QMutexLocker nodeDataLocker(&nodeData->getMutex()); QMutexLocker nodeDataLocker(&nodeData->getMutex());
nodeData->flagIdentityChange(); nodeData->flagIdentityChange();
if (displayNameChanged) { if (displayNameChanged) {
nodeData->setAvatarSessionDisplayNameMustChange(true); nodeData->setAvatarSessionDisplayNameMustChange(true);
} }
if (skeletonModelUrlChanged && !_avatarWhitelist.isEmpty()) {
nodeData->setAvatarSkeletonModelUrlMustChange(true);
}
} }
} }
} }
@ -764,4 +799,19 @@ void AvatarMixer::parseDomainServerSettings(const QJsonObject& domainSettings) {
qCDebug(avatars) << "This domain requires a minimum avatar scale of" << _domainMinimumScale qCDebug(avatars) << "This domain requires a minimum avatar scale of" << _domainMinimumScale
<< "and a maximum avatar scale of" << _domainMaximumScale; << "and a maximum avatar scale of" << _domainMaximumScale;
const QString AVATAR_WHITELIST_DEFAULT{ "" };
static const QString AVATAR_WHITELIST_OPTION = "avatar_whitelist";
_avatarWhitelist = domainSettings[AVATARS_SETTINGS_KEY].toObject()[AVATAR_WHITELIST_OPTION].toString(AVATAR_WHITELIST_DEFAULT).split(',', QString::KeepEmptyParts);
static const QString REPLACEMENT_AVATAR_OPTION = "replacement_avatar";
_replacementAvatar = domainSettings[AVATARS_SETTINGS_KEY].toObject()[REPLACEMENT_AVATAR_OPTION].toString(REPLACEMENT_AVATAR_DEFAULT);
if ((_avatarWhitelist.count() == 1) && _avatarWhitelist[0].isEmpty()) {
_avatarWhitelist.clear(); // KeepEmptyParts above will parse "," as ["", ""] (which is ok), but "" as [""] (which is not ok).
}
if (_avatarWhitelist.isEmpty()) {
qCDebug(avatars) << "All avatars are allowed.";
} else {
qCDebug(avatars) << "Avatars other than" << _avatarWhitelist << "will be replaced by" << (_replacementAvatar.isEmpty() ? "default" : _replacementAvatar);
}
} }

View file

@ -59,7 +59,12 @@ private:
void parseDomainServerSettings(const QJsonObject& domainSettings); void parseDomainServerSettings(const QJsonObject& domainSettings);
void sendIdentityPacket(AvatarMixerClientData* nodeData, const SharedNodePointer& destinationNode); void sendIdentityPacket(AvatarMixerClientData* nodeData, const SharedNodePointer& destinationNode);
void manageDisplayName(const SharedNodePointer& node); void manageIdentityData(const SharedNodePointer& node);
bool isAvatarInWhitelist(const QUrl& url);
const QString REPLACEMENT_AVATAR_DEFAULT{ "" };
QStringList _avatarWhitelist { };
QString _replacementAvatar { REPLACEMENT_AVATAR_DEFAULT };
p_high_resolution_clock::time_point _lastFrameTimestamp; p_high_resolution_clock::time_point _lastFrameTimestamp;

View file

@ -65,6 +65,8 @@ public:
void flagIdentityChange() { _identityChangeTimestamp = usecTimestampNow(); } void flagIdentityChange() { _identityChangeTimestamp = usecTimestampNow(); }
bool getAvatarSessionDisplayNameMustChange() const { return _avatarSessionDisplayNameMustChange; } bool getAvatarSessionDisplayNameMustChange() const { return _avatarSessionDisplayNameMustChange; }
void setAvatarSessionDisplayNameMustChange(bool set = true) { _avatarSessionDisplayNameMustChange = set; } void setAvatarSessionDisplayNameMustChange(bool set = true) { _avatarSessionDisplayNameMustChange = set; }
bool getAvatarSkeletonModelUrlMustChange() const { return _avatarSkeletonModelUrlMustChange; }
void setAvatarSkeletonModelUrlMustChange(bool set = true) { _avatarSkeletonModelUrlMustChange = set; }
void resetNumAvatarsSentLastFrame() { _numAvatarsSentLastFrame = 0; } void resetNumAvatarsSentLastFrame() { _numAvatarsSentLastFrame = 0; }
void incrementNumAvatarsSentLastFrame() { ++_numAvatarsSentLastFrame; } void incrementNumAvatarsSentLastFrame() { ++_numAvatarsSentLastFrame; }
@ -146,6 +148,7 @@ private:
uint64_t _identityChangeTimestamp; uint64_t _identityChangeTimestamp;
bool _avatarSessionDisplayNameMustChange{ true }; bool _avatarSessionDisplayNameMustChange{ true };
bool _avatarSkeletonModelUrlMustChange{ false };
int _numAvatarsSentLastFrame = 0; int _numAvatarsSentLastFrame = 0;
int _numFramesSinceAdjustment = 0; int _numFramesSinceAdjustment = 0;

View file

@ -16,10 +16,9 @@
#include <mutex> #include <mutex>
#include <vector> #include <vector>
#include <tbb/concurrent_queue.h>
#include <QThread> #include <QThread>
#include <TBBHelpers.h>
#include <NodeList.h> #include <NodeList.h>
#include "AvatarMixerSlave.h" #include "AvatarMixerSlave.h"

View file

@ -47,7 +47,7 @@ void OctreeInboundPacketProcessor::resetStats() {
_singleSenderStats.clear(); _singleSenderStats.clear();
} }
unsigned long OctreeInboundPacketProcessor::getMaxWait() const { uint32_t OctreeInboundPacketProcessor::getMaxWait() const {
// calculate time until next sendNackPackets() // calculate time until next sendNackPackets()
quint64 nextNackTime = _lastNackTime + TOO_LONG_SINCE_LAST_NACK; quint64 nextNackTime = _lastNackTime + TOO_LONG_SINCE_LAST_NACK;
quint64 now = usecTimestampNow(); quint64 now = usecTimestampNow();

View file

@ -80,7 +80,7 @@ protected:
virtual void processPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) override; virtual void processPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) override;
virtual unsigned long getMaxWait() const override; virtual uint32_t getMaxWait() const override;
virtual void preProcess() override; virtual void preProcess() override;
virtual void midProcess() override; virtual void midProcess() override;

View file

@ -236,11 +236,7 @@ void EntityScriptServer::run() {
// Setup MessagesClient // Setup MessagesClient
auto messagesClient = DependencyManager::set<MessagesClient>(); auto messagesClient = DependencyManager::set<MessagesClient>();
QThread* messagesThread = new QThread; messagesClient->startThread();
messagesThread->setObjectName("Messages Client Thread");
messagesClient->moveToThread(messagesThread);
connect(messagesThread, &QThread::started, messagesClient.data(), &MessagesClient::init);
messagesThread->start();
DomainHandler& domainHandler = DependencyManager::get<NodeList>()->getDomainHandler(); DomainHandler& domainHandler = DependencyManager::get<NodeList>()->getDomainHandler();
connect(&domainHandler, &DomainHandler::settingsReceived, this, &EntityScriptServer::handleSettings); connect(&domainHandler, &DomainHandler::settingsReceived, this, &EntityScriptServer::handleSettings);

View file

@ -866,6 +866,22 @@
"help": "Limits the scale of avatars in your domain. Cannot be greater than 1000.", "help": "Limits the scale of avatars in your domain. Cannot be greater than 1000.",
"placeholder": 3.0, "placeholder": 3.0,
"default": 3.0 "default": 3.0
},
{
"name": "avatar_whitelist",
"label": "Avatars Allowed from:",
"help": "Comma separated list of URLs (with optional paths) that avatar .fst files are allowed from. If someone attempts to use an avatar with a different domain, it will be rejected and the replacement avatar will be used. If left blank, any domain is allowed.",
"placeholder": "",
"default": "",
"advanced": true
},
{
"name": "replacement_avatar",
"label": "Replacement Avatar for disallowed avatars",
"help": "A URL for an avatar .fst to be used when someone tries to use an avatar that is not allowed. If left blank, the generic default avatar is used.",
"placeholder": "",
"default": "",
"advanced": true
} }
] ]
}, },

View file

@ -223,6 +223,14 @@ $(document).ready(function(){
// set focus to the first input in the new row // set focus to the first input in the new row
$target.closest('table').find('tr.inputs input:first').focus(); $target.closest('table').find('tr.inputs input:first').focus();
} }
var tableRows = sibling.parent();
var tableBody = tableRows.parent();
// if theres no more siblings, we should jump to a new row
if (sibling.next().length == 0 && tableRows.nextAll().length == 1) {
tableBody.find("." + Settings.ADD_ROW_BUTTON_CLASS).click();
}
} }
} else if ($target.is('input')) { } else if ($target.is('input')) {

View file

@ -37,20 +37,12 @@ RenderingClient::RenderingClient(QObject *parent, const QString& launchURLString
DependencyManager::set<AvatarHashMap>(); DependencyManager::set<AvatarHashMap>();
// get our audio client setup on its own thread // get our audio client setup on its own thread
QThread* audioThread = new QThread();
auto audioClient = DependencyManager::set<AudioClient>(); auto audioClient = DependencyManager::set<AudioClient>();
audioClient->setPositionGetter(getPositionForAudio); audioClient->setPositionGetter(getPositionForAudio);
audioClient->setOrientationGetter(getOrientationForAudio); audioClient->setOrientationGetter(getOrientationForAudio);
audioClient->startThread();
audioClient->moveToThread(audioThread);
connect(audioThread, &QThread::started, audioClient.data(), &AudioClient::start);
connect(audioClient.data(), &AudioClient::destroyed, audioThread, &QThread::quit);
connect(audioThread, &QThread::finished, audioThread, &QThread::deleteLater);
audioThread->start();
connect(&_avatarTimer, &QTimer::timeout, this, &RenderingClient::sendAvatarPacket); connect(&_avatarTimer, &QTimer::timeout, this, &RenderingClient::sendAvatarPacket);
_avatarTimer.setInterval(16); // 60 FPS _avatarTimer.setInterval(16); // 60 FPS
_avatarTimer.start(); _avatarTimer.start();

View file

@ -103,8 +103,8 @@
"rotationVar": "spine2Rotation", "rotationVar": "spine2Rotation",
"typeVar": "spine2Type", "typeVar": "spine2Type",
"weightVar": "spine2Weight", "weightVar": "spine2Weight",
"weight": 1.0, "weight": 2.0,
"flexCoefficients": [1.0, 0.5, 0.5] "flexCoefficients": [1.0, 0.5, 0.25]
}, },
{ {
"jointName": "Head", "jointName": "Head",
@ -113,7 +113,7 @@
"typeVar": "headType", "typeVar": "headType",
"weightVar": "headWeight", "weightVar": "headWeight",
"weight": 4.0, "weight": 4.0,
"flexCoefficients": [1, 0.05, 0.25, 0.25, 0.25] "flexCoefficients": [1, 0.5, 0.25, 0.2, 0.1]
}, },
{ {
"jointName": "LeftArm", "jointName": "LeftArm",

View file

@ -1,14 +1,16 @@
{ {
"RenderShadowTask": { "RenderMainView": {
"Enabled": { "RenderShadowTask": {
"enabled": true
}
},
"RenderDeferredTask": {
"AmbientOcclusion": {
"Enabled": { "Enabled": {
"enabled": true "enabled": true
} }
},
"RenderDeferredTask": {
"AmbientOcclusion": {
"Enabled": {
"enabled": true
}
}
} }
} }
} }

View file

@ -32,7 +32,7 @@ var EventBridge;
var webChannel = new QWebChannel(qt.webChannelTransport, function (channel) { var webChannel = new QWebChannel(qt.webChannelTransport, function (channel) {
// replace the TempEventBridge with the real one. // replace the TempEventBridge with the real one.
var tempEventBridge = EventBridge; var tempEventBridge = EventBridge;
EventBridge = channel.objects.eventBridgeWrapper.eventBridge; EventBridge = channel.objects.eventBridge;
tempEventBridge._callbacks.forEach(function (callback) { tempEventBridge._callbacks.forEach(function (callback) {
EventBridge.scriptEventReceived.connect(callback); EventBridge.scriptEventReceived.connect(callback);
}); });

View file

@ -21,8 +21,6 @@ ScrollingWindow {
property alias url: webview.url property alias url: webview.url
property alias webView: webview property alias webView: webview
property alias eventBridge: eventBridgeWrapper.eventBridge
signal loadingChanged(int status) signal loadingChanged(int status)
x: 100 x: 100
@ -210,17 +208,6 @@ ScrollingWindow {
url: "https://highfidelity.com/" url: "https://highfidelity.com/"
profile: FileTypeProfile; profile: FileTypeProfile;
property alias eventBridgeWrapper: eventBridgeWrapper
QtObject {
id: eventBridgeWrapper
WebChannel.id: "eventBridgeWrapper"
property var eventBridge;
}
webChannel.registeredObjects: [eventBridgeWrapper]
// Create a global EventBridge object for raiseAndLowerKeyboard. // Create a global EventBridge object for raiseAndLowerKeyboard.
WebEngineScript { WebEngineScript {
id: createGlobalEventBridge id: createGlobalEventBridge
@ -267,6 +254,8 @@ ScrollingWindow {
} }
Component.onCompleted: { Component.onCompleted: {
webChannel.registerObject("eventBridge", eventBridge);
webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper);
desktop.initWebviewProfileHandlers(webview.profile); desktop.initWebviewProfileHandlers(webview.profile);
} }
} }

View file

@ -26,15 +26,8 @@ Windows.ScrollingWindow {
// Don't destroy on close... otherwise the JS/C++ will have a dangling pointer // Don't destroy on close... otherwise the JS/C++ will have a dangling pointer
destroyOnCloseButton: false destroyOnCloseButton: false
property alias source: webview.url property alias source: webview.url
property alias eventBridge: eventBridgeWrapper.eventBridge;
property alias scriptUrl: webview.userScriptUrl property alias scriptUrl: webview.userScriptUrl
QtObject {
id: eventBridgeWrapper
WebChannel.id: "eventBridgeWrapper"
property var eventBridge;
}
// This is for JS/QML communication, which is unused in a WebWindow, // This is for JS/QML communication, which is unused in a WebWindow,
// but not having this here results in spurious warnings about a // but not having this here results in spurious warnings about a
// missing signal // missing signal
@ -70,7 +63,6 @@ Windows.ScrollingWindow {
url: "about:blank" url: "about:blank"
anchors.fill: parent anchors.fill: parent
focus: true focus: true
webChannel.registeredObjects: [eventBridgeWrapper]
property string userScriptUrl: "" property string userScriptUrl: ""
@ -107,6 +99,8 @@ Windows.ScrollingWindow {
} }
Component.onCompleted: { Component.onCompleted: {
webChannel.registerObject("eventBridge", eventBridge);
webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper);
eventBridge.webEventReceived.connect(onWebEventReceived); eventBridge.webEventReceived.connect(onWebEventReceived);
} }
} }

View file

@ -30,15 +30,6 @@ Windows.Window {
property bool keyboardRaised: false property bool keyboardRaised: false
property bool punctuationMode: false property bool punctuationMode: false
// JavaScript event bridge object in case QML content includes Web content.
property alias eventBridge: eventBridgeWrapper.eventBridge;
QtObject {
id: eventBridgeWrapper
WebChannel.id: "eventBridgeWrapper"
property var eventBridge;
}
onSourceChanged: { onSourceChanged: {
if (dynamicContent) { if (dynamicContent) {
dynamicContent.destroy(); dynamicContent.destroy();

View file

@ -18,7 +18,6 @@ Item {
property variant permissionsBar: {'securityOrigin':'none','feature':'none'} property variant permissionsBar: {'securityOrigin':'none','feature':'none'}
property alias url: webview.url property alias url: webview.url
property WebEngineView webView: webview property WebEngineView webView: webview
property alias eventBridge: eventBridgeWrapper.eventBridge
property bool canGoBack: webview.canGoBack property bool canGoBack: webview.canGoBack
property bool canGoForward: webview.canGoForward property bool canGoForward: webview.canGoForward
@ -32,12 +31,6 @@ Item {
webview.profile = profile; webview.profile = profile;
} }
QtObject {
id: eventBridgeWrapper
WebChannel.id: "eventBridgeWrapper"
property var eventBridge;
}
WebEngineView { WebEngineView {
id: webview id: webview
objectName: "webEngineView" objectName: "webEngineView"
@ -78,9 +71,10 @@ Item {
property string newUrl: "" property string newUrl: ""
webChannel.registeredObjects: [eventBridgeWrapper]
Component.onCompleted: { Component.onCompleted: {
webChannel.registerObject("eventBridge", eventBridge);
webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper);
// Ensure the JS from the web-engine makes it to our logging // Ensure the JS from the web-engine makes it to our logging
webview.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) { webview.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) {
console.log("Web Entity JS message: " + sourceID + " " + lineNumber + " " + message); console.log("Web Entity JS message: " + sourceID + " " + lineNumber + " " + message);

View file

@ -79,15 +79,11 @@ ScrollingWindow {
id: webView id: webView
anchors.fill: parent anchors.fill: parent
enabled: false enabled: false
property alias eventBridgeWrapper: eventBridgeWrapper Component.onCompleted: {
webChannel.registerObject("eventBridge", eventBridge);
QtObject { webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper);
id: eventBridgeWrapper
WebChannel.id: "eventBridgeWrapper"
property var eventBridge
} }
webChannel.registeredObjects: [eventBridgeWrapper]
onEnabledChanged: toolWindow.updateVisiblity() onEnabledChanged: toolWindow.updateVisiblity()
} }
} }
@ -251,12 +247,9 @@ ScrollingWindow {
tab.enabled = true; tab.enabled = true;
tab.originalUrl = properties.source; tab.originalUrl = properties.source;
var eventBridge = properties.eventBridge;
var result = tab.item; var result = tab.item;
result.enabled = true; result.enabled = true;
tabView.tabCount++; tabView.tabCount++;
result.eventBridgeWrapper.eventBridge = eventBridge;
result.url = properties.source; result.url = properties.source;
return result; return result;
} }

View file

@ -6,7 +6,6 @@ import "../controls-uit" as HiFiControls
Item { Item {
property alias url: root.url property alias url: root.url
property alias scriptURL: root.userScriptUrl property alias scriptURL: root.userScriptUrl
property alias eventBridge: eventBridgeWrapper.eventBridge
property alias canGoBack: root.canGoBack; property alias canGoBack: root.canGoBack;
property var goBack: root.goBack; property var goBack: root.goBack;
property alias urlTag: root.urlTag property alias urlTag: root.urlTag
@ -22,12 +21,6 @@ Item {
} }
*/ */
QtObject {
id: eventBridgeWrapper
WebChannel.id: "eventBridgeWrapper"
property var eventBridge;
}
property alias viewProfile: root.profile property alias viewProfile: root.profile
WebEngineView { WebEngineView {
@ -71,10 +64,11 @@ Item {
userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard, userScript ] userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard, userScript ]
property string newUrl: "" property string newUrl: ""
webChannel.registeredObjects: [eventBridgeWrapper]
Component.onCompleted: { Component.onCompleted: {
webChannel.registerObject("eventBridge", eventBridge);
webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper);
// Ensure the JS from the web-engine makes it to our logging // Ensure the JS from the web-engine makes it to our logging
root.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) { root.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) {
console.log("Web Entity JS message: " + sourceID + " " + lineNumber + " " + message); console.log("Web Entity JS message: " + sourceID + " " + lineNumber + " " + message);

View file

@ -17,7 +17,6 @@ Item {
property int headerHeight: 70 property int headerHeight: 70
property string url property string url
property string scriptURL property string scriptURL
property alias eventBridge: eventBridgeWrapper.eventBridge
property bool keyboardEnabled: false property bool keyboardEnabled: false
property bool keyboardRaised: false property bool keyboardRaised: false
property bool punctuationMode: false property bool punctuationMode: false
@ -135,12 +134,6 @@ Item {
loadUrl(url); loadUrl(url);
} }
QtObject {
id: eventBridgeWrapper
WebChannel.id: "eventBridgeWrapper"
property var eventBridge;
}
WebEngineView { WebEngineView {
id: webview id: webview
objectName: "webEngineView" objectName: "webEngineView"
@ -182,9 +175,9 @@ Item {
property string newUrl: "" property string newUrl: ""
webChannel.registeredObjects: [eventBridgeWrapper]
Component.onCompleted: { Component.onCompleted: {
webChannel.registerObject("eventBridge", eventBridge);
webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper);
// Ensure the JS from the web-engine makes it to our logging // Ensure the JS from the web-engine makes it to our logging
webview.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) { webview.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) {
console.log("Web Entity JS message: " + sourceID + " " + lineNumber + " " + message); console.log("Web Entity JS message: " + sourceID + " " + lineNumber + " " + message);

View file

@ -6,7 +6,6 @@ import "../controls-uit" as HiFiControls
Item { Item {
property alias url: root.url property alias url: root.url
property alias scriptURL: root.userScriptUrl property alias scriptURL: root.userScriptUrl
property alias eventBridge: eventBridgeWrapper.eventBridge
property alias canGoBack: root.canGoBack; property alias canGoBack: root.canGoBack;
property var goBack: root.goBack; property var goBack: root.goBack;
property alias urlTag: root.urlTag property alias urlTag: root.urlTag
@ -22,12 +21,6 @@ Item {
} }
*/ */
QtObject {
id: eventBridgeWrapper
WebChannel.id: "eventBridgeWrapper"
property var eventBridge;
}
property alias viewProfile: root.profile property alias viewProfile: root.profile
WebEngineView { WebEngineView {
@ -72,9 +65,9 @@ Item {
property string newUrl: "" property string newUrl: ""
webChannel.registeredObjects: [eventBridgeWrapper]
Component.onCompleted: { Component.onCompleted: {
webChannel.registerObject("eventBridge", eventBridge);
webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper);
// Ensure the JS from the web-engine makes it to our logging // Ensure the JS from the web-engine makes it to our logging
root.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) { root.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) {
console.log("Web Entity JS message: " + sourceID + " " + lineNumber + " " + message); console.log("Web Entity JS message: " + sourceID + " " + lineNumber + " " + message);

View file

@ -20,7 +20,6 @@ TabletModalWindow {
id: loginDialogRoot id: loginDialogRoot
objectName: "LoginDialog" objectName: "LoginDialog"
property var eventBridge;
signal sendToScript(var message); signal sendToScript(var message);
property bool isHMD: false property bool isHMD: false
property bool gotoPreviousApp: false; property bool gotoPreviousApp: false;

View file

@ -24,8 +24,6 @@ Window {
resizable: true resizable: true
modality: Qt.ApplicationModal modality: Qt.ApplicationModal
property alias eventBridge: eventBridgeWrapper.eventBridge
Item { Item {
anchors.fill: parent anchors.fill: parent
@ -45,16 +43,6 @@ Window {
bottom: keyboard.top bottom: keyboard.top
} }
property alias eventBridgeWrapper: eventBridgeWrapper
QtObject {
id: eventBridgeWrapper
WebChannel.id: "eventBridgeWrapper"
property var eventBridge;
}
webChannel.registeredObjects: [eventBridgeWrapper]
// Create a global EventBridge object for raiseAndLowerKeyboard. // Create a global EventBridge object for raiseAndLowerKeyboard.
WebEngineScript { WebEngineScript {
id: createGlobalEventBridge id: createGlobalEventBridge
@ -73,6 +61,10 @@ Window {
userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard ] userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard ]
Component.onCompleted: {
webChannel.registerObject("eventBridge", eventBridge);
webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper);
}
} }
Keyboard { Keyboard {

View file

@ -116,9 +116,7 @@ Preference {
Component { Component {
id: tabletAvatarBrowserBuilder; id: tabletAvatarBrowserBuilder;
TabletAvatarBrowser { TabletAvatarBrowser { }
eventBridge: tabletRoot.eventBridge
}
} }
} }

View file

@ -31,7 +31,6 @@ Rectangle {
HifiConstants { id: hifi; } HifiConstants { id: hifi; }
objectName: "AudioWindow" objectName: "AudioWindow"
property var eventBridge;
property string title: "Audio Options" property string title: "Audio Options"
signal sendToScript(var message); signal sendToScript(var message);

View file

@ -44,7 +44,6 @@ Rectangle {
property var activeTab: "nearbyTab"; property var activeTab: "nearbyTab";
property bool currentlyEditingDisplayName: false property bool currentlyEditingDisplayName: false
property bool punctuationMode: false; property bool punctuationMode: false;
property var eventBridge;
HifiConstants { id: hifi; } HifiConstants { id: hifi; }
@ -129,7 +128,7 @@ Rectangle {
pal.sendToScript({method: 'refreshNearby', params: params}); pal.sendToScript({method: 'refreshNearby', params: params});
} }
Item { Rectangle {
id: palTabContainer; id: palTabContainer;
// Anchors // Anchors
anchors { anchors {
@ -138,6 +137,7 @@ Rectangle {
left: parent.left; left: parent.left;
right: parent.right; right: parent.right;
} }
color: "white";
Rectangle { Rectangle {
id: tabSelectorContainer; id: tabSelectorContainer;
// Anchors // Anchors
@ -1043,7 +1043,6 @@ Rectangle {
} // Keyboard } // Keyboard
HifiControls.TabletWebView { HifiControls.TabletWebView {
eventBridge: pal.eventBridge;
id: userInfoViewer; id: userInfoViewer;
anchors { anchors {
top: parent.top; top: parent.top;

View file

@ -24,7 +24,6 @@ Rectangle {
property string title: "Asset Browser" property string title: "Asset Browser"
property bool keyboardRaised: false property bool keyboardRaised: false
property var eventBridge;
signal sendToScript(var message); signal sendToScript(var message);
property bool isHMD: false property bool isHMD: false

View file

@ -20,7 +20,6 @@ Rectangle {
id: root id: root
objectName: "DCConectionTiming" objectName: "DCConectionTiming"
property var eventBridge;
signal sendToScript(var message); signal sendToScript(var message);
property bool isHMD: false property bool isHMD: false

View file

@ -19,7 +19,6 @@ Rectangle {
id: root id: root
objectName: "DebugWindow" objectName: "DebugWindow"
property var eventBridge;
property var title: "Debug Window" property var title: "Debug Window"
property bool isHMD: false property bool isHMD: false

View file

@ -20,7 +20,6 @@ Rectangle {
id: root id: root
objectName: "EntityStatistics" objectName: "EntityStatistics"
property var eventBridge;
signal sendToScript(var message); signal sendToScript(var message);
property bool isHMD: false property bool isHMD: false

View file

@ -20,7 +20,6 @@ Rectangle {
id: root id: root
objectName: "LODTools" objectName: "LODTools"
property var eventBridge;
signal sendToScript(var message); signal sendToScript(var message);
property bool isHMD: false property bool isHMD: false

View file

@ -23,7 +23,6 @@ Rectangle {
property string title: "Running Scripts" property string title: "Running Scripts"
HifiConstants { id: hifi } HifiConstants { id: hifi }
signal sendToScript(var message); signal sendToScript(var message);
property var eventBridge;
property var scripts: ScriptDiscoveryService; property var scripts: ScriptDiscoveryService;
property var scriptsModel: scripts.scriptsModelFilter property var scriptsModel: scripts.scriptsModelFilter
property var runningScriptsModel: ListModel { } property var runningScriptsModel: ListModel { }

View file

@ -7,14 +7,12 @@ StackView {
objectName: "stack" objectName: "stack"
initialItem: Qt.resolvedUrl('EditTabView.qml') initialItem: Qt.resolvedUrl('EditTabView.qml')
property var eventBridge;
signal sendToScript(var message); signal sendToScript(var message);
HifiConstants { id: hifi } HifiConstants { id: hifi }
function pushSource(path) { function pushSource(path) {
editRoot.push(Qt.resolvedUrl(path)); editRoot.push(Qt.resolvedUrl(path));
editRoot.currentItem.eventBridge = editRoot.eventBridge;
editRoot.currentItem.sendToScript.connect(editRoot.sendToScript); editRoot.currentItem.sendToScript.connect(editRoot.sendToScript);
} }

View file

@ -181,7 +181,6 @@ TabView {
WebView { WebView {
id: entityListToolWebView id: entityListToolWebView
url: "../../../../../scripts/system/html/entityList.html" url: "../../../../../scripts/system/html/entityList.html"
eventBridge: editRoot.eventBridge
anchors.fill: parent anchors.fill: parent
enabled: true enabled: true
} }
@ -196,7 +195,6 @@ TabView {
WebView { WebView {
id: entityPropertiesWebView id: entityPropertiesWebView
url: "../../../../../scripts/system/html/entityProperties.html" url: "../../../../../scripts/system/html/entityProperties.html"
eventBridge: editRoot.eventBridge
anchors.fill: parent anchors.fill: parent
enabled: true enabled: true
} }
@ -211,7 +209,6 @@ TabView {
WebView { WebView {
id: gridControlsWebView id: gridControlsWebView
url: "../../../../../scripts/system/html/gridControls.html" url: "../../../../../scripts/system/html/gridControls.html"
eventBridge: editRoot.eventBridge
anchors.fill: parent anchors.fill: parent
enabled: true enabled: true
} }
@ -226,7 +223,6 @@ TabView {
WebView { WebView {
id: particleExplorerWebView id: particleExplorerWebView
url: "../../../../../scripts/system/particle_explorer/particleExplorer.html" url: "../../../../../scripts/system/particle_explorer/particleExplorer.html"
eventBridge: editRoot.eventBridge
anchors.fill: parent anchors.fill: parent
enabled: true enabled: true
} }
@ -289,7 +285,7 @@ TabView {
editTabView.currentIndex = id; editTabView.currentIndex = id;
} else { } else {
console.warn('Attempt to switch to invalid tab:', id); console.warn('Attempt to switch to invalid tab:', id);
} }
} else if (typeof id === 'string'){ } else if (typeof id === 'string'){
switch (id.toLowerCase()) { switch (id.toLowerCase()) {
case 'create': case 'create':

View file

@ -18,7 +18,6 @@ import "../../dialogs"
Rectangle { Rectangle {
id: inputRecorder id: inputRecorder
property var eventBridge;
HifiConstants { id: hifi } HifiConstants { id: hifi }
signal sendToScript(var message); signal sendToScript(var message);
color: hifi.colors.baseGray; color: hifi.colors.baseGray;

View file

@ -20,7 +20,6 @@ Rectangle {
// height: parent.height // height: parent.height
HifiConstants { id: hifi } HifiConstants { id: hifi }
color: hifi.colors.baseGray; color: hifi.colors.baseGray;
property var eventBridge;
signal sendToScript(var message); signal sendToScript(var message);
property bool keyboardEnabled: false property bool keyboardEnabled: false
property bool punctuationMode: false property bool punctuationMode: false

View file

@ -29,7 +29,6 @@ StackView {
initialItem: addressBarDialog initialItem: addressBarDialog
width: parent !== null ? parent.width : undefined width: parent !== null ? parent.width : undefined
height: parent !== null ? parent.height : undefined height: parent !== null ? parent.height : undefined
property var eventBridge;
property int cardWidth: 212; property int cardWidth: 212;
property int cardHeight: 152; property int cardHeight: 152;
property string metaverseBase: addressBarDialog.metaverseServerUrl + "/api/v1/"; property string metaverseBase: addressBarDialog.metaverseServerUrl + "/api/v1/";
@ -80,7 +79,6 @@ StackView {
var card = tabletWebView.createObject(); var card = tabletWebView.createObject();
card.url = addressBarDialog.metaverseServerUrl + targetString; card.url = addressBarDialog.metaverseServerUrl + targetString;
card.parentStackItem = root; card.parentStackItem = root;
card.eventBridge = root.eventBridge;
root.push(card); root.push(card);
return; return;
} }

View file

@ -25,7 +25,6 @@ Item {
property bool keyboardRaised: false property bool keyboardRaised: false
property bool punctuationMode: false property bool punctuationMode: false
property var eventBridge;
signal sendToScript(var message); signal sendToScript(var message);
anchors.fill: parent anchors.fill: parent

View file

@ -19,7 +19,6 @@ StackView {
objectName: "stack" objectName: "stack"
property string title: "Audio Settings" property string title: "Audio Settings"
property var eventBridge;
signal sendToScript(var message); signal sendToScript(var message);
function pushSource(path) { function pushSource(path) {

View file

@ -19,7 +19,6 @@ StackView {
objectName: "stack" objectName: "stack"
property string title: "Avatar Settings" property string title: "Avatar Settings"
property var eventBridge;
signal sendToScript(var message); signal sendToScript(var message);
function pushSource(path) { function pushSource(path) {

View file

@ -19,7 +19,6 @@ StackView {
objectName: "stack" objectName: "stack"
property string title: "General Settings" property string title: "General Settings"
property alias gotoPreviousApp: root.gotoPreviousApp; property alias gotoPreviousApp: root.gotoPreviousApp;
property var eventBridge;
signal sendToScript(var message); signal sendToScript(var message);
function pushSource(path) { function pushSource(path) {

View file

@ -19,7 +19,6 @@ StackView {
objectName: "stack" objectName: "stack"
property string title: "Graphics Settings" property string title: "Graphics Settings"
property var eventBridge;
signal sendToScript(var message); signal sendToScript(var message);
function pushSource(path) { function pushSource(path) {

View file

@ -19,7 +19,6 @@ StackView {
objectName: "stack" objectName: "stack"
property string title: "LOD Settings" property string title: "LOD Settings"
property var eventBridge;
signal sendToScript(var message); signal sendToScript(var message);
function pushSource(path) { function pushSource(path) {

View file

@ -21,7 +21,6 @@ FocusScope {
property var point: Qt.point(50, 50); property var point: Qt.point(50, 50);
TabletMenuStack { id: menuPopperUpper } TabletMenuStack { id: menuPopperUpper }
property string subMenu: "" property string subMenu: ""
property var eventBridge;
signal sendToScript(var message); signal sendToScript(var message);
Rectangle { Rectangle {

View file

@ -49,7 +49,6 @@ Item {
function pushSource(path) { function pushSource(path) {
d.push(Qt.resolvedUrl(path)); d.push(Qt.resolvedUrl(path));
d.currentItem.eventBridge = tabletMenu.eventBridge
d.currentItem.sendToScript.connect(tabletMenu.sendToScript); d.currentItem.sendToScript.connect(tabletMenu.sendToScript);
d.currentItem.focus = true; d.currentItem.focus = true;
d.currentItem.forceActiveFocus(); d.currentItem.forceActiveFocus();

View file

@ -19,7 +19,6 @@ StackView {
objectName: "stack" objectName: "stack"
property var title: "Networking Settings" property var title: "Networking Settings"
property var eventBridge;
signal sendToScript(var message); signal sendToScript(var message);
function pushSource(path) { function pushSource(path) {

View file

@ -8,7 +8,6 @@ Item {
id: tabletRoot id: tabletRoot
objectName: "tabletRoot" objectName: "tabletRoot"
property string username: "Unknown user" property string username: "Unknown user"
property var eventBridge;
property var rootMenu; property var rootMenu;
property var openModal: null; property var openModal: null;
property var openMessage: null; property var openMessage: null;
@ -111,7 +110,6 @@ Item {
function openBrowserWindow(request, profile) { function openBrowserWindow(request, profile) {
var component = Qt.createComponent("../../controls/TabletWebView.qml"); var component = Qt.createComponent("../../controls/TabletWebView.qml");
var newWindow = component.createObject(tabletRoot); var newWindow = component.createObject(tabletRoot);
newWindow.eventBridge = tabletRoot.eventBridge;
newWindow.remove = true; newWindow.remove = true;
newWindow.profile = profile; newWindow.profile = profile;
request.openIn(newWindow.webView); request.openIn(newWindow.webView);
@ -175,7 +173,7 @@ Item {
// Hook up callback for clara.io download from the marketplace. // Hook up callback for clara.io download from the marketplace.
Connections { Connections {
id: eventBridgeConnection id: eventBridgeConnection
target: null target: eventBridge
onWebEventReceived: { onWebEventReceived: {
if (message.slice(0, 17) === "CLARA.IO DOWNLOAD") { if (message.slice(0, 17) === "CLARA.IO DOWNLOAD") {
ApplicationInterface.addAssetToWorldFromURL(message.slice(18)); ApplicationInterface.addAssetToWorldFromURL(message.slice(18));
@ -184,10 +182,6 @@ Item {
} }
onLoaded: { onLoaded: {
if (loader.item.hasOwnProperty("eventBridge")) {
loader.item.eventBridge = eventBridge;
eventBridgeConnection.target = eventBridge
}
if (loader.item.hasOwnProperty("sendToScript")) { if (loader.item.hasOwnProperty("sendToScript")) {
loader.item.sendToScript.connect(tabletRoot.sendToScript); loader.item.sendToScript.connect(tabletRoot.sendToScript);
} }

View file

@ -18,7 +18,6 @@ Windows.ScrollingWindow {
id: tabletRoot id: tabletRoot
objectName: "tabletRoot" objectName: "tabletRoot"
property string username: "Unknown user" property string username: "Unknown user"
property var eventBridge;
property var rootMenu; property var rootMenu;
property string subMenu: "" property string subMenu: ""
@ -93,7 +92,7 @@ Windows.ScrollingWindow {
// Hook up callback for clara.io download from the marketplace. // Hook up callback for clara.io download from the marketplace.
Connections { Connections {
id: eventBridgeConnection id: eventBridgeConnection
target: null target: eventBridge
onWebEventReceived: { onWebEventReceived: {
if (message.slice(0, 17) === "CLARA.IO DOWNLOAD") { if (message.slice(0, 17) === "CLARA.IO DOWNLOAD") {
ApplicationInterface.addAssetToWorldFromURL(message.slice(18)); ApplicationInterface.addAssetToWorldFromURL(message.slice(18));
@ -102,10 +101,6 @@ Windows.ScrollingWindow {
} }
onLoaded: { onLoaded: {
if (loader.item.hasOwnProperty("eventBridge")) {
loader.item.eventBridge = eventBridge;
eventBridgeConnection.target = eventBridge
}
if (loader.item.hasOwnProperty("sendToScript")) { if (loader.item.hasOwnProperty("sendToScript")) {
loader.item.sendToScript.connect(tabletRoot.sendToScript); loader.item.sendToScript.connect(tabletRoot.sendToScript);
} }

View file

@ -27,8 +27,6 @@ Item {
property bool keyboardRaised: false property bool keyboardRaised: false
property bool punctuationMode: false property bool punctuationMode: false
property alias eventBridge: eventBridgeWrapper.eventBridge
anchors.fill: parent anchors.fill: parent
BaseWebView { BaseWebView {
@ -43,14 +41,6 @@ Item {
bottom: footer.top bottom: footer.top
} }
QtObject {
id: eventBridgeWrapper
WebChannel.id: "eventBridgeWrapper"
property var eventBridge;
}
webChannel.registeredObjects: [eventBridgeWrapper]
// Create a global EventBridge object for raiseAndLowerKeyboard. // Create a global EventBridge object for raiseAndLowerKeyboard.
WebEngineScript { WebEngineScript {
id: createGlobalEventBridge id: createGlobalEventBridge
@ -68,6 +58,11 @@ Item {
} }
userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard ] userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard ]
Component.onCompleted: {
webChannel.registerObject("eventBridge", eventBridge);
webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper);
}
} }
Rectangle { Rectangle {

View file

@ -114,6 +114,7 @@
#include <render/RenderFetchCullSortTask.h> #include <render/RenderFetchCullSortTask.h>
#include <RenderDeferredTask.h> #include <RenderDeferredTask.h>
#include <RenderForwardTask.h> #include <RenderForwardTask.h>
#include <RenderViewTask.h>
#include <ResourceCache.h> #include <ResourceCache.h>
#include <ResourceRequest.h> #include <ResourceRequest.h>
#include <SandboxUtils.h> #include <SandboxUtils.h>
@ -653,6 +654,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
Model::setAbstractViewStateInterface(this); // The model class will sometimes need to know view state details from us Model::setAbstractViewStateInterface(this); // The model class will sometimes need to know view state details from us
auto nodeList = DependencyManager::get<NodeList>(); auto nodeList = DependencyManager::get<NodeList>();
nodeList->startThread();
// Set up a watchdog thread to intentionally crash the application on deadlocks // Set up a watchdog thread to intentionally crash the application on deadlocks
_deadlockWatchdogThread = new DeadlockWatchdogThread(); _deadlockWatchdogThread = new DeadlockWatchdogThread();
@ -678,25 +680,11 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
updateHeartbeat(); updateHeartbeat();
// start the nodeThread so its event loop is running
QThread* nodeThread = new QThread(this);
nodeThread->setObjectName("NodeList Thread");
nodeThread->start();
// make sure the node thread is given highest priority
nodeThread->setPriority(QThread::TimeCriticalPriority);
// setup a timer for domain-server check ins // setup a timer for domain-server check ins
QTimer* domainCheckInTimer = new QTimer(nodeList.data()); QTimer* domainCheckInTimer = new QTimer(nodeList.data());
connect(domainCheckInTimer, &QTimer::timeout, nodeList.data(), &NodeList::sendDomainServerCheckIn); connect(domainCheckInTimer, &QTimer::timeout, nodeList.data(), &NodeList::sendDomainServerCheckIn);
domainCheckInTimer->start(DOMAIN_SERVER_CHECK_IN_MSECS); domainCheckInTimer->start(DOMAIN_SERVER_CHECK_IN_MSECS);
// put the NodeList and datagram processing on the node thread
nodeList->moveToThread(nodeThread);
// put the audio processing on a separate thread
QThread* audioThread = new QThread();
audioThread->setObjectName("Audio Thread");
auto audioIO = DependencyManager::get<AudioClient>(); auto audioIO = DependencyManager::get<AudioClient>();
audioIO->setPositionGetter([]{ audioIO->setPositionGetter([]{
@ -712,7 +700,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
return myAvatar ? myAvatar->getOrientationForAudio() : Quaternions::IDENTITY; return myAvatar ? myAvatar->getOrientationForAudio() : Quaternions::IDENTITY;
}); });
audioIO->moveToThread(audioThread);
recording::Frame::registerFrameHandler(AudioConstants::getAudioFrameName(), [=](recording::Frame::ConstPointer frame) { recording::Frame::registerFrameHandler(AudioConstants::getAudioFrameName(), [=](recording::Frame::ConstPointer frame) {
audioIO->handleRecordedAudioInput(frame->data); audioIO->handleRecordedAudioInput(frame->data);
}); });
@ -726,9 +713,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
}); });
auto audioScriptingInterface = DependencyManager::set<AudioScriptingInterface>(); auto audioScriptingInterface = DependencyManager::set<AudioScriptingInterface>();
connect(audioThread, &QThread::started, audioIO.data(), &AudioClient::start);
connect(audioIO.data(), &AudioClient::destroyed, audioThread, &QThread::quit);
connect(audioThread, &QThread::finished, audioThread, &QThread::deleteLater);
connect(audioIO.data(), &AudioClient::muteToggled, this, &Application::audioMuteToggled); connect(audioIO.data(), &AudioClient::muteToggled, this, &Application::audioMuteToggled);
connect(audioIO.data(), &AudioClient::mutedByMixer, audioScriptingInterface.data(), &AudioScriptingInterface::mutedByMixer); connect(audioIO.data(), &AudioClient::mutedByMixer, audioScriptingInterface.data(), &AudioScriptingInterface::mutedByMixer);
connect(audioIO.data(), &AudioClient::receivedFirstPacket, audioScriptingInterface.data(), &AudioScriptingInterface::receivedFirstPacket); connect(audioIO.data(), &AudioClient::receivedFirstPacket, audioScriptingInterface.data(), &AudioScriptingInterface::receivedFirstPacket);
@ -746,19 +730,14 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
} }
}); });
audioThread->start(); audioIO->startThread();
ResourceManager::init(); ResourceManager::init();
// Make sure we don't time out during slow operations at startup // Make sure we don't time out during slow operations at startup
updateHeartbeat(); updateHeartbeat();
// Setup MessagesClient // Setup MessagesClient
auto messagesClient = DependencyManager::get<MessagesClient>(); DependencyManager::get<MessagesClient>()->startThread();
QThread* messagesThread = new QThread;
messagesThread->setObjectName("Messages Client Thread");
messagesClient->moveToThread(messagesThread);
connect(messagesThread, &QThread::started, messagesClient.data(), &MessagesClient::init);
messagesThread->start();
const DomainHandler& domainHandler = nodeList->getDomainHandler(); const DomainHandler& domainHandler = nodeList->getDomainHandler();
@ -1678,12 +1657,7 @@ void Application::aboutToQuit() {
_autoSwitchDisplayModeSupportedHMDPlugin->endSession(); _autoSwitchDisplayModeSupportedHMDPlugin->endSession();
} }
// use the CloseEventSender via a QThread to send an event that says the user asked for the app to close // use the CloseEventSender via a QThread to send an event that says the user asked for the app to close
auto closeEventSender = DependencyManager::get<CloseEventSender>(); DependencyManager::get<CloseEventSender>()->startThread();
QThread* closureEventThread = new QThread(this);
closeEventSender->moveToThread(closureEventThread);
// sendQuitEventAsync will bail immediately if the UserActivityLogger is not enabled
connect(closureEventThread, &QThread::started, closeEventSender.data(), &CloseEventSender::sendQuitEventAsync);
closureEventThread->start();
// Hide Running Scripts dialog so that it gets destroyed in an orderly manner; prevents warnings at shutdown. // Hide Running Scripts dialog so that it gets destroyed in an orderly manner; prevents warnings at shutdown.
DependencyManager::get<OffscreenUi>()->hide("RunningScripts"); DependencyManager::get<OffscreenUi>()->hide("RunningScripts");
@ -1747,6 +1721,10 @@ void Application::cleanupBeforeQuit() {
// Cleanup all overlays after the scripts, as scripts might add more // Cleanup all overlays after the scripts, as scripts might add more
_overlays.cleanupAllOverlays(); _overlays.cleanupAllOverlays();
// The cleanup process enqueues the transactions but does not process them. Calling this here will force the actual
// removal of the items.
// See https://highfidelity.fogbugz.com/f/cases/5328
_main3DScene->processTransactionQueue();
// first stop all timers directly or by invokeMethod // first stop all timers directly or by invokeMethod
// depending on what thread they run in // depending on what thread they run in
@ -1771,6 +1749,8 @@ void Application::cleanupBeforeQuit() {
// stop QML // stop QML
DependencyManager::destroy<OffscreenUi>(); DependencyManager::destroy<OffscreenUi>();
DependencyManager::destroy<OffscreenQmlSurfaceCache>();
if (_snapshotSoundInjector != nullptr) { if (_snapshotSoundInjector != nullptr) {
_snapshotSoundInjector->stop(); _snapshotSoundInjector->stop();
} }
@ -1830,15 +1810,9 @@ Application::~Application() {
ResourceManager::cleanup(); ResourceManager::cleanup();
QThread* nodeThread = DependencyManager::get<NodeList>()->thread();
// remove the NodeList from the DependencyManager // remove the NodeList from the DependencyManager
DependencyManager::destroy<NodeList>(); DependencyManager::destroy<NodeList>();
// ask the node thread to quit and wait until it is done
nodeThread->quit();
nodeThread->wait();
Leapmotion::destroy(); Leapmotion::destroy();
if (auto steamClient = PluginManager::getInstance()->getSteamClientPlugin()) { if (auto steamClient = PluginManager::getInstance()->getSteamClientPlugin()) {
@ -1897,15 +1871,9 @@ void Application::initializeGL() {
// Set up the render engine // Set up the render engine
render::CullFunctor cullFunctor = LODManager::shouldRender; render::CullFunctor cullFunctor = LODManager::shouldRender;
_renderEngine->addJob<RenderShadowTask>("RenderShadowTask", cullFunctor);
const auto items = _renderEngine->addJob<RenderFetchCullSortTask>("FetchCullSort", cullFunctor);
assert(items.canCast<RenderFetchCullSortTask::Output>());
static const QString RENDER_FORWARD = "HIFI_RENDER_FORWARD"; static const QString RENDER_FORWARD = "HIFI_RENDER_FORWARD";
if (QProcessEnvironment::systemEnvironment().contains(RENDER_FORWARD)) { bool isDeferred = !QProcessEnvironment::systemEnvironment().contains(RENDER_FORWARD);
_renderEngine->addJob<RenderForwardTask>("Forward", items); _renderEngine->addJob<RenderViewTask>("RenderMainView", cullFunctor, isDeferred);
} else {
_renderEngine->addJob<RenderDeferredTask>("RenderDeferredTask", items);
}
_renderEngine->load(); _renderEngine->load();
_renderEngine->registerScene(_main3DScene); _renderEngine->registerScene(_main3DScene);
@ -5107,7 +5075,7 @@ namespace render {
template <> const ItemKey payloadGetKey(const WorldBoxRenderData::Pointer& stuff) { return ItemKey::Builder::opaqueShape(); } template <> const ItemKey payloadGetKey(const WorldBoxRenderData::Pointer& stuff) { return ItemKey::Builder::opaqueShape(); }
template <> const Item::Bound payloadGetBound(const WorldBoxRenderData::Pointer& stuff) { return Item::Bound(); } template <> const Item::Bound payloadGetBound(const WorldBoxRenderData::Pointer& stuff) { return Item::Bound(); }
template <> void payloadRender(const WorldBoxRenderData::Pointer& stuff, RenderArgs* args) { template <> void payloadRender(const WorldBoxRenderData::Pointer& stuff, RenderArgs* args) {
if (args->_renderMode != RenderArgs::MIRROR_RENDER_MODE && Menu::getInstance()->isOptionChecked(MenuOption::WorldAxes)) { if (Menu::getInstance()->isOptionChecked(MenuOption::WorldAxes)) {
PerformanceTimer perfTimer("worldBox"); PerformanceTimer perfTimer("worldBox");
auto& batch = *args->_batch; auto& batch = *args->_batch;
@ -5328,6 +5296,11 @@ void Application::nodeActivated(SharedNodePointer node) {
if (node->getType() == NodeType::AvatarMixer) { if (node->getType() == NodeType::AvatarMixer) {
// new avatar mixer, send off our identity packet on next update loop // new avatar mixer, send off our identity packet on next update loop
// Reset skeletonModelUrl if the last server modified our choice.
static const QUrl empty{};
if (getMyAvatar()->getFullAvatarURLFromPreferences() != getMyAvatar()->cannonicalSkeletonModelURL(empty)) {
getMyAvatar()->resetFullAvatarURL();
}
getMyAvatar()->markIdentityDataChanged(); getMyAvatar()->markIdentityDataChanged();
getMyAvatar()->resetLastSent(); getMyAvatar()->resetLastSent();
} }

View file

@ -21,15 +21,35 @@
#include "MainWindow.h" #include "MainWindow.h"
#include "Menu.h" #include "Menu.h"
#include "AvatarBookmarks.h" #include "AvatarBookmarks.h"
#include "InterfaceLogging.h"
#include <QtQuick/QQuickWindow> #include <QtQuick/QQuickWindow>
AvatarBookmarks::AvatarBookmarks() { AvatarBookmarks::AvatarBookmarks() {
_bookmarksFilename = QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/" + AVATARBOOKMARKS_FILENAME; _bookmarksFilename = PathUtils::getAppDataPath() + "/" + AVATARBOOKMARKS_FILENAME;
readFromFile(); readFromFile();
} }
void AvatarBookmarks::readFromFile() {
// migrate old avatarbookmarks.json, used to be in 'local' folder on windows
QString oldConfigPath = QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/" + AVATARBOOKMARKS_FILENAME;
QFile oldConfig(oldConfigPath);
// I imagine that in a year from now, this code for migrating (as well as the two lines above)
// may be removed since all bookmarks should have been migrated by then
// - Robbie Uvanni (6.8.2017)
if (oldConfig.exists()) {
if (QDir().rename(oldConfigPath, _bookmarksFilename)) {
qCDebug(interfaceapp) << "Successfully migrated" << AVATARBOOKMARKS_FILENAME;
} else {
qCDebug(interfaceapp) << "Failed to migrate" << AVATARBOOKMARKS_FILENAME;
}
}
Bookmarks::readFromFile();
}
void AvatarBookmarks::setupMenus(Menu* menubar, MenuWrapper* menu) { void AvatarBookmarks::setupMenus(Menu* menubar, MenuWrapper* menu) {
// Add menus/actions // Add menus/actions
auto bookmarkAction = menubar->addActionToQMenuAndActionHash(menu, MenuOption::BookmarkAvatar); auto bookmarkAction = menubar->addActionToQMenuAndActionHash(menu, MenuOption::BookmarkAvatar);

View file

@ -29,6 +29,7 @@ public slots:
protected: protected:
void addBookmarkToMenu(Menu* menubar, const QString& name, const QString& address) override; void addBookmarkToMenu(Menu* menubar, const QString& name, const QString& address) override;
void readFromFile();
private: private:
const QString AVATARBOOKMARKS_FILENAME = "avatarbookmarks.json"; const QString AVATARBOOKMARKS_FILENAME = "avatarbookmarks.json";

View file

@ -73,7 +73,7 @@ CrashHandler::Action CrashHandler::promptUserForAction(bool showCrashMessage) {
layout->addWidget(label); layout->addWidget(label);
QRadioButton* option1 = new QRadioButton("Reset all my settings"); QRadioButton* option1 = new QRadioButton("Reset all my settings");
QRadioButton* option2 = new QRadioButton("Reset my settings but retain avatar info."); QRadioButton* option2 = new QRadioButton("Reset my settings but keep essential info");
QRadioButton* option3 = new QRadioButton("Continue with my current settings"); QRadioButton* option3 = new QRadioButton("Continue with my current settings");
option3->setChecked(true); option3->setChecked(true);
layout->addWidget(option1); layout->addWidget(option1);
@ -95,7 +95,7 @@ CrashHandler::Action CrashHandler::promptUserForAction(bool showCrashMessage) {
return CrashHandler::DELETE_INTERFACE_INI; return CrashHandler::DELETE_INTERFACE_INI;
} }
if (option2->isChecked()) { if (option2->isChecked()) {
return CrashHandler::RETAIN_AVATAR_INFO; return CrashHandler::RETAIN_IMPORTANT_INFO;
} }
} }
@ -104,7 +104,7 @@ CrashHandler::Action CrashHandler::promptUserForAction(bool showCrashMessage) {
} }
void CrashHandler::handleCrash(CrashHandler::Action action) { void CrashHandler::handleCrash(CrashHandler::Action action) {
if (action != CrashHandler::DELETE_INTERFACE_INI && action != CrashHandler::RETAIN_AVATAR_INFO) { if (action != CrashHandler::DELETE_INTERFACE_INI && action != CrashHandler::RETAIN_IMPORTANT_INFO) {
// CrashHandler::DO_NOTHING or unexpected value // CrashHandler::DO_NOTHING or unexpected value
return; return;
} }
@ -116,12 +116,15 @@ void CrashHandler::handleCrash(CrashHandler::Action action) {
const QString DISPLAY_NAME_KEY = "displayName"; const QString DISPLAY_NAME_KEY = "displayName";
const QString FULL_AVATAR_URL_KEY = "fullAvatarURL"; const QString FULL_AVATAR_URL_KEY = "fullAvatarURL";
const QString FULL_AVATAR_MODEL_NAME_KEY = "fullAvatarModelName"; const QString FULL_AVATAR_MODEL_NAME_KEY = "fullAvatarModelName";
const QString TUTORIAL_COMPLETE_FLAG_KEY = "tutorialComplete";
QString displayName; QString displayName;
QUrl fullAvatarURL; QUrl fullAvatarURL;
QString fullAvatarModelName; QString fullAvatarModelName;
QUrl address; QUrl address;
bool tutorialComplete = false;
if (action == CrashHandler::RETAIN_AVATAR_INFO) { if (action == CrashHandler::RETAIN_IMPORTANT_INFO) {
// Read avatar info // Read avatar info
// Location and orientation // Location and orientation
@ -135,6 +138,9 @@ void CrashHandler::handleCrash(CrashHandler::Action action) {
fullAvatarURL = settings.value(FULL_AVATAR_URL_KEY).toUrl(); fullAvatarURL = settings.value(FULL_AVATAR_URL_KEY).toUrl();
fullAvatarModelName = settings.value(FULL_AVATAR_MODEL_NAME_KEY).toString(); fullAvatarModelName = settings.value(FULL_AVATAR_MODEL_NAME_KEY).toString();
settings.endGroup(); settings.endGroup();
// Tutorial complete
tutorialComplete = settings.value(TUTORIAL_COMPLETE_FLAG_KEY).toBool();
} }
// Delete Interface.ini // Delete Interface.ini
@ -143,7 +149,7 @@ void CrashHandler::handleCrash(CrashHandler::Action action) {
settingsFile.remove(); settingsFile.remove();
} }
if (action == CrashHandler::RETAIN_AVATAR_INFO) { if (action == CrashHandler::RETAIN_IMPORTANT_INFO) {
// Write avatar info // Write avatar info
// Location and orientation // Location and orientation
@ -157,6 +163,9 @@ void CrashHandler::handleCrash(CrashHandler::Action action) {
settings.setValue(FULL_AVATAR_URL_KEY, fullAvatarURL); settings.setValue(FULL_AVATAR_URL_KEY, fullAvatarURL);
settings.setValue(FULL_AVATAR_MODEL_NAME_KEY, fullAvatarModelName); settings.setValue(FULL_AVATAR_MODEL_NAME_KEY, fullAvatarModelName);
settings.endGroup(); settings.endGroup();
// Tutorial complete
settings.setValue(TUTORIAL_COMPLETE_FLAG_KEY, tutorialComplete);
} }
} }

View file

@ -22,7 +22,7 @@ public:
private: private:
enum Action { enum Action {
DELETE_INTERFACE_INI, DELETE_INTERFACE_INI,
RETAIN_AVATAR_INFO, RETAIN_IMPORTANT_INFO,
DO_NOTHING DO_NOTHING
}; };

View file

@ -794,6 +794,77 @@ controller::Pose MyAvatar::getRightHandTipPose() const {
return pose; return pose;
} }
glm::vec3 MyAvatar::worldToJointPoint(const glm::vec3& position, const int jointIndex) const {
glm::vec3 jointPos = getPosition();//default value if no or invalid joint specified
glm::quat jointRot = getRotation();//default value if no or invalid joint specified
if (jointIndex != -1) {
if (_skeletonModel->getJointPositionInWorldFrame(jointIndex, jointPos)) {
_skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRot);
} else {
qWarning() << "Invalid joint index specified: " << jointIndex;
}
}
glm::vec3 modelOffset = position - jointPos;
glm::vec3 jointSpacePosition = glm::inverse(jointRot) * modelOffset;
return jointSpacePosition;
}
glm::vec3 MyAvatar::worldToJointDirection(const glm::vec3& worldDir, const int jointIndex) const {
glm::quat jointRot = getRotation();//default value if no or invalid joint specified
if ((jointIndex != -1) && (!_skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRot))) {
qWarning() << "Invalid joint index specified: " << jointIndex;
}
glm::vec3 jointSpaceDir = glm::inverse(jointRot) * worldDir;
return jointSpaceDir;
}
glm::quat MyAvatar::worldToJointRotation(const glm::quat& worldRot, const int jointIndex) const {
glm::quat jointRot = getRotation();//default value if no or invalid joint specified
if ((jointIndex != -1) && (!_skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRot))) {
qWarning() << "Invalid joint index specified: " << jointIndex;
}
glm::quat jointSpaceRot = glm::inverse(jointRot) * worldRot;
return jointSpaceRot;
}
glm::vec3 MyAvatar::jointToWorldPoint(const glm::vec3& jointSpacePos, const int jointIndex) const {
glm::vec3 jointPos = getPosition();//default value if no or invalid joint specified
glm::quat jointRot = getRotation();//default value if no or invalid joint specified
if (jointIndex != -1) {
if (_skeletonModel->getJointPositionInWorldFrame(jointIndex, jointPos)) {
_skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRot);
} else {
qWarning() << "Invalid joint index specified: " << jointIndex;
}
}
glm::vec3 worldOffset = jointRot * jointSpacePos;
glm::vec3 worldPos = jointPos + worldOffset;
return worldPos;
}
glm::vec3 MyAvatar::jointToWorldDirection(const glm::vec3& jointSpaceDir, const int jointIndex) const {
glm::quat jointRot = getRotation();//default value if no or invalid joint specified
if ((jointIndex != -1) && (!_skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRot))) {
qWarning() << "Invalid joint index specified: " << jointIndex;
}
glm::vec3 worldDir = jointRot * jointSpaceDir;
return worldDir;
}
glm::quat MyAvatar::jointToWorldRotation(const glm::quat& jointSpaceRot, const int jointIndex) const {
glm::quat jointRot = getRotation();//default value if no or invalid joint specified
if ((jointIndex != -1) && (!_skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRot))) {
qWarning() << "Invalid joint index specified: " << jointIndex;
}
glm::quat worldRot = jointRot * jointSpaceRot;
return worldRot;
}
// virtual // virtual
void MyAvatar::render(RenderArgs* renderArgs) { void MyAvatar::render(RenderArgs* renderArgs) {
// don't render if we've been asked to disable local rendering // don't render if we've been asked to disable local rendering
@ -1890,6 +1961,32 @@ void MyAvatar::updateOrientation(float deltaTime) {
totalBodyYaw += (speedFactor * deltaAngle * (180.0f / PI)); totalBodyYaw += (speedFactor * deltaAngle * (180.0f / PI));
} }
// Use head/HMD roll to turn while walking or flying.
if (qApp->isHMDMode() && _hmdRollControlEnabled) {
// Turn with head roll.
const float MIN_CONTROL_SPEED = 0.01f;
float speed = glm::length(getVelocity());
if (speed >= MIN_CONTROL_SPEED) {
// Feather turn when stopping moving.
float speedFactor;
if (getDriveKey(TRANSLATE_Z) != 0.0f || _lastDrivenSpeed == 0.0f) {
_lastDrivenSpeed = speed;
speedFactor = 1.0f;
} else {
speedFactor = glm::min(speed / _lastDrivenSpeed, 1.0f);
}
float direction = glm::dot(getVelocity(), getRotation() * Vectors::UNIT_NEG_Z) > 0.0f ? 1.0f : -1.0f;
float rollAngle = glm::degrees(asinf(glm::dot(IDENTITY_UP, _hmdSensorOrientation * IDENTITY_RIGHT)));
float rollSign = rollAngle < 0.0f ? -1.0f : 1.0f;
rollAngle = fabsf(rollAngle);
rollAngle = rollAngle > _hmdRollControlDeadZone ? rollSign * (rollAngle - _hmdRollControlDeadZone) : 0.0f;
totalBodyYaw += speedFactor * direction * rollAngle * deltaTime * _hmdRollControlRate;
}
}
// update body orientation by movement inputs // update body orientation by movement inputs
glm::quat initialOrientation = getOrientationOutbound(); glm::quat initialOrientation = getOrientationOutbound();
setOrientation(getOrientation() * glm::quat(glm::radians(glm::vec3(0.0f, totalBodyYaw, 0.0f)))); setOrientation(getOrientation() * glm::quat(glm::radians(glm::vec3(0.0f, totalBodyYaw, 0.0f))));

View file

@ -132,6 +132,10 @@ class MyAvatar : public Avatar {
Q_PROPERTY(bool characterControllerEnabled READ getCharacterControllerEnabled WRITE setCharacterControllerEnabled) Q_PROPERTY(bool characterControllerEnabled READ getCharacterControllerEnabled WRITE setCharacterControllerEnabled)
Q_PROPERTY(bool useAdvancedMovementControls READ useAdvancedMovementControls WRITE setUseAdvancedMovementControls) Q_PROPERTY(bool useAdvancedMovementControls READ useAdvancedMovementControls WRITE setUseAdvancedMovementControls)
Q_PROPERTY(bool hmdRollControlEnabled READ getHMDRollControlEnabled WRITE setHMDRollControlEnabled)
Q_PROPERTY(float hmdRollControlDeadZone READ getHMDRollControlDeadZone WRITE setHMDRollControlDeadZone)
Q_PROPERTY(float hmdRollControlRate READ getHMDRollControlRate WRITE setHMDRollControlRate)
public: public:
enum DriveKeys { enum DriveKeys {
TRANSLATE_X = 0, TRANSLATE_X = 0,
@ -337,6 +341,13 @@ public:
void setUseAdvancedMovementControls(bool useAdvancedMovementControls) void setUseAdvancedMovementControls(bool useAdvancedMovementControls)
{ _useAdvancedMovementControls.set(useAdvancedMovementControls); } { _useAdvancedMovementControls.set(useAdvancedMovementControls); }
void setHMDRollControlEnabled(bool value) { _hmdRollControlEnabled = value; }
bool getHMDRollControlEnabled() const { return _hmdRollControlEnabled; }
void setHMDRollControlDeadZone(float value) { _hmdRollControlDeadZone = value; }
float getHMDRollControlDeadZone() const { return _hmdRollControlDeadZone; }
void setHMDRollControlRate(float value) { _hmdRollControlRate = value; }
float getHMDRollControlRate() const { return _hmdRollControlRate; }
// get/set avatar data // get/set avatar data
void saveData(); void saveData();
void loadData(); void loadData();
@ -378,6 +389,15 @@ public:
Q_INVOKABLE controller::Pose getLeftHandTipPose() const; Q_INVOKABLE controller::Pose getLeftHandTipPose() const;
Q_INVOKABLE controller::Pose getRightHandTipPose() const; Q_INVOKABLE controller::Pose getRightHandTipPose() const;
// world-space to avatar-space rigconversion functions
Q_INVOKABLE glm::vec3 worldToJointPoint(const glm::vec3& position, const int jointIndex = -1) const;
Q_INVOKABLE glm::vec3 worldToJointDirection(const glm::vec3& direction, const int jointIndex = -1) const;
Q_INVOKABLE glm::quat worldToJointRotation(const glm::quat& rotation, const int jointIndex = -1) const;
Q_INVOKABLE glm::vec3 jointToWorldPoint(const glm::vec3& position, const int jointIndex = -1) const;
Q_INVOKABLE glm::vec3 jointToWorldDirection(const glm::vec3& direction, const int jointIndex = -1) const;
Q_INVOKABLE glm::quat jointToWorldRotation(const glm::quat& rotation, const int jointIndex = -1) const;
AvatarWeakPointer getLookAtTargetAvatar() const { return _lookAtTargetAvatar; } AvatarWeakPointer getLookAtTargetAvatar() const { return _lookAtTargetAvatar; }
void updateLookAtTargetAvatar(); void updateLookAtTargetAvatar();
void clearLookAtTargetAvatar(); void clearLookAtTargetAvatar();
@ -678,6 +698,13 @@ private:
bool _useSnapTurn { true }; bool _useSnapTurn { true };
bool _clearOverlayWhenMoving { true }; bool _clearOverlayWhenMoving { true };
const float ROLL_CONTROL_DEAD_ZONE_DEFAULT = 8.0f; // deg
const float ROLL_CONTROL_RATE_DEFAULT = 2.5f; // deg/sec/deg
bool _hmdRollControlEnabled { true };
float _hmdRollControlDeadZone { ROLL_CONTROL_DEAD_ZONE_DEFAULT };
float _hmdRollControlRate { ROLL_CONTROL_RATE_DEFAULT };
float _lastDrivenSpeed { 0.0f };
// working copies -- see AvatarData for thread-safe _sensorToWorldMatrixCache, used for outward facing access // working copies -- see AvatarData for thread-safe _sensorToWorldMatrixCache, used for outward facing access
glm::mat4 _sensorToWorldMatrix { glm::mat4() }; glm::mat4 _sensorToWorldMatrix { glm::mat4() };

View file

@ -14,6 +14,7 @@
#include <QtCore/QJsonDocument> #include <QtCore/QJsonDocument>
#include <QtNetwork/QNetworkReply> #include <QtNetwork/QNetworkReply>
#include <ThreadHelpers.h>
#include <AccountManager.h> #include <AccountManager.h>
#include <NetworkAccessManager.h> #include <NetworkAccessManager.h>
#include <NetworkingConstants.h> #include <NetworkingConstants.h>
@ -87,4 +88,8 @@ bool CloseEventSender::hasTimedOutQuitEvent() {
&& QDateTime::currentMSecsSinceEpoch() - _quitEventStartTimestamp > CLOSURE_EVENT_TIMEOUT_MS; && QDateTime::currentMSecsSinceEpoch() - _quitEventStartTimestamp > CLOSURE_EVENT_TIMEOUT_MS;
} }
void CloseEventSender::startThread() {
moveToNewNamedThread(this, "CloseEvent Logger Thread", [this] {
sendQuitEventAsync();
});
}

View file

@ -24,6 +24,7 @@ class CloseEventSender : public QObject, public Dependency {
SINGLETON_DEPENDENCY SINGLETON_DEPENDENCY
public: public:
void startThread();
bool hasTimedOutQuitEvent(); bool hasTimedOutQuitEvent();
bool hasFinishedQuitEvent() { return _hasFinishedQuitEvent; } bool hasFinishedQuitEvent() { return _hasFinishedQuitEvent; }

View file

@ -48,15 +48,31 @@ AudioDeviceScriptingInterface::AudioDeviceScriptingInterface(): QAbstractListMod
SettingsScriptingInterface* settings = SettingsScriptingInterface::getInstance(); SettingsScriptingInterface* settings = SettingsScriptingInterface::getInstance();
const QString inDevice = settings->getValue("audio_input_device", _currentInputDevice).toString(); const QString inDevice = settings->getValue("audio_input_device", _currentInputDevice).toString();
if (inDevice != _currentInputDevice) { if (inDevice != _currentInputDevice) {
qCDebug(audioclient) << __FUNCTION__ << "about to call setInputDeviceAsync() device: [" << inDevice << "] _currentInputDevice:" << _currentInputDevice; // before using the old setting, check to make sure the device still exists....
setInputDeviceAsync(inDevice); bool inDeviceExists = DependencyManager::get<AudioClient>()->getNamedAudioDeviceForModeExists(QAudio::AudioInput, inDevice);
if (inDeviceExists) {
qCDebug(audioclient) << __FUNCTION__ << "about to call setInputDeviceAsync() device: [" << inDevice << "] _currentInputDevice:" << _currentInputDevice;
setInputDeviceAsync(inDevice);
} else {
qCDebug(audioclient) << __FUNCTION__ << "previously selected device no longer exists inDevice: [" << inDevice << "] keeping device _currentInputDevice:" << _currentInputDevice;
}
} }
// If the audio_output_device setting is not available, use the _currentOutputDevice // If the audio_output_device setting is not available, use the _currentOutputDevice
auto outDevice = settings->getValue("audio_output_device", _currentOutputDevice).toString(); auto outDevice = settings->getValue("audio_output_device", _currentOutputDevice).toString();
if (outDevice != _currentOutputDevice) { if (outDevice != _currentOutputDevice) {
qCDebug(audioclient) << __FUNCTION__ << "about to call setOutputDeviceAsync() outDevice: [" << outDevice << "] _currentOutputDevice:" << _currentOutputDevice; // before using the old setting, check to make sure the device still exists....
setOutputDeviceAsync(outDevice); bool outDeviceExists = DependencyManager::get<AudioClient>()->getNamedAudioDeviceForModeExists(QAudio::AudioOutput, outDevice);
if (outDeviceExists) {
qCDebug(audioclient) << __FUNCTION__ << "about to call setOutputDeviceAsync() outDevice: [" << outDevice << "] _currentOutputDevice:" << _currentOutputDevice;
setOutputDeviceAsync(outDevice);
} else {
qCDebug(audioclient) << __FUNCTION__ << "previously selected device no longer exists outDevice: [" << outDevice << "] keeping device _currentOutputDevice:" << _currentOutputDevice;
}
} }
} }

View file

@ -9,9 +9,12 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
// //
#include <QtConcurrent/QtConcurrentRun>
#include <ThreadHelpers.h>
#include <src/InterfaceLogging.h> #include <src/InterfaceLogging.h>
#include <src/ui/AvatarInputs.h> #include <src/ui/AvatarInputs.h>
#include <QtConcurrent/QtConcurrentRun>
#include "LimitlessVoiceRecognitionScriptingInterface.h" #include "LimitlessVoiceRecognitionScriptingInterface.h"
const float LimitlessVoiceRecognitionScriptingInterface::_audioLevelThreshold = 0.33f; const float LimitlessVoiceRecognitionScriptingInterface::_audioLevelThreshold = 0.33f;
@ -24,9 +27,7 @@ LimitlessVoiceRecognitionScriptingInterface::LimitlessVoiceRecognitionScriptingI
connect(&_voiceTimer, &QTimer::timeout, this, &LimitlessVoiceRecognitionScriptingInterface::voiceTimeout); connect(&_voiceTimer, &QTimer::timeout, this, &LimitlessVoiceRecognitionScriptingInterface::voiceTimeout);
connect(&_connection, &LimitlessConnection::onReceivedTranscription, this, [this](QString transcription){emit onReceivedTranscription(transcription);}); connect(&_connection, &LimitlessConnection::onReceivedTranscription, this, [this](QString transcription){emit onReceivedTranscription(transcription);});
connect(&_connection, &LimitlessConnection::onFinishedSpeaking, this, [this](QString transcription){emit onFinishedSpeaking(transcription);}); connect(&_connection, &LimitlessConnection::onFinishedSpeaking, this, [this](QString transcription){emit onFinishedSpeaking(transcription);});
_connection.moveToThread(&_connectionThread); moveToNewNamedThread(&_connection, "Limitless Connection");
_connectionThread.setObjectName("Limitless Connection");
_connectionThread.start();
} }
void LimitlessVoiceRecognitionScriptingInterface::update() { void LimitlessVoiceRecognitionScriptingInterface::update() {

View file

@ -41,7 +41,6 @@ private:
static const int _voiceTimeoutDuration; static const int _voiceTimeoutDuration;
QTimer _voiceTimer; QTimer _voiceTimer;
QThread _connectionThread;
LimitlessConnection _connection; LimitlessConnection _connection;
void voiceTimeout(); void voiceTimeout();

View file

@ -23,6 +23,7 @@
#include <QUrlQuery> #include <QUrlQuery>
#include <QXmlStreamReader> #include <QXmlStreamReader>
#include <ThreadHelpers.h>
#include <NetworkAccessManager.h> #include <NetworkAccessManager.h>
#include <SharedUtil.h> #include <SharedUtil.h>
@ -84,12 +85,7 @@ ModelsBrowser::ModelsBrowser(FSTReader::ModelType modelsType, QWidget* parent) :
_handler->connect(this, SIGNAL(destroyed()), SLOT(exit())); _handler->connect(this, SIGNAL(destroyed()), SLOT(exit()));
// Setup and launch update thread // Setup and launch update thread
QThread* thread = new QThread(); moveToNewNamedThread(_handler, "Models Browser");
thread->setObjectName("Models Browser");
thread->connect(_handler, SIGNAL(destroyed()), SLOT(quit()));
thread->connect(thread, SIGNAL(finished()), SLOT(deleteLater()));
_handler->moveToThread(thread);
thread->start();
emit startDownloading(); emit startDownloading();
// Initialize the view // Initialize the view

View file

@ -20,6 +20,8 @@
#include "ElbowConstraint.h" #include "ElbowConstraint.h"
#include "SwingTwistConstraint.h" #include "SwingTwistConstraint.h"
#include "AnimationLogging.h" #include "AnimationLogging.h"
#include "CubicHermiteSpline.h"
#include "AnimUtil.h"
AnimInverseKinematics::IKTargetVar::IKTargetVar(const QString& jointNameIn, const QString& positionVarIn, const QString& rotationVarIn, AnimInverseKinematics::IKTargetVar::IKTargetVar(const QString& jointNameIn, const QString& positionVarIn, const QString& rotationVarIn,
const QString& typeVarIn, const QString& weightVarIn, float weightIn, const std::vector<float>& flexCoefficientsIn) : const QString& typeVarIn, const QString& weightVarIn, float weightIn, const std::vector<float>& flexCoefficientsIn) :
@ -59,7 +61,8 @@ AnimInverseKinematics::AnimInverseKinematics(const QString& id) : AnimNode(AnimN
AnimInverseKinematics::~AnimInverseKinematics() { AnimInverseKinematics::~AnimInverseKinematics() {
clearConstraints(); clearConstraints();
_accumulators.clear(); _rotationAccumulators.clear();
_translationAccumulators.clear();
_targetVarVec.clear(); _targetVarVec.clear();
} }
@ -72,10 +75,12 @@ void AnimInverseKinematics::loadPoses(const AnimPoseVec& poses) {
assert(_skeleton && ((poses.size() == 0) || (_skeleton->getNumJoints() == (int)poses.size()))); assert(_skeleton && ((poses.size() == 0) || (_skeleton->getNumJoints() == (int)poses.size())));
if (_skeleton->getNumJoints() == (int)poses.size()) { if (_skeleton->getNumJoints() == (int)poses.size()) {
_relativePoses = poses; _relativePoses = poses;
_accumulators.resize(_relativePoses.size()); _rotationAccumulators.resize(_relativePoses.size());
_translationAccumulators.resize(_relativePoses.size());
} else { } else {
_relativePoses.clear(); _relativePoses.clear();
_accumulators.clear(); _rotationAccumulators.clear();
_translationAccumulators.clear();
} }
} }
@ -175,14 +180,17 @@ void AnimInverseKinematics::computeTargets(const AnimVariantMap& animVars, std::
} }
} }
void AnimInverseKinematics::solveWithCyclicCoordinateDescent(const AnimContext& context, const std::vector<IKTarget>& targets) { void AnimInverseKinematics::solve(const AnimContext& context, const std::vector<IKTarget>& targets) {
// compute absolute poses that correspond to relative target poses // compute absolute poses that correspond to relative target poses
AnimPoseVec absolutePoses; AnimPoseVec absolutePoses;
absolutePoses.resize(_relativePoses.size()); absolutePoses.resize(_relativePoses.size());
computeAbsolutePoses(absolutePoses); computeAbsolutePoses(absolutePoses);
// clear the accumulators before we start the IK solver // clear the accumulators before we start the IK solver
for (auto& accumulator: _accumulators) { for (auto& accumulator : _rotationAccumulators) {
accumulator.clearAndClean();
}
for (auto& accumulator : _translationAccumulators) {
accumulator.clearAndClean(); accumulator.clearAndClean();
} }
@ -197,14 +205,22 @@ void AnimInverseKinematics::solveWithCyclicCoordinateDescent(const AnimContext&
// solve all targets // solve all targets
for (auto& target: targets) { for (auto& target: targets) {
solveTargetWithCCD(context, target, absolutePoses, debug); if (target.getType() == IKTarget::Type::Spline) {
solveTargetWithSpline(context, target, absolutePoses, debug);
} else {
solveTargetWithCCD(context, target, absolutePoses, debug);
}
} }
// harvest accumulated rotations and apply the average // harvest accumulated rotations and apply the average
for (int i = 0; i < (int)_relativePoses.size(); ++i) { for (int i = 0; i < (int)_relativePoses.size(); ++i) {
if (_accumulators[i].size() > 0) { if (_rotationAccumulators[i].size() > 0) {
_relativePoses[i].rot() = _accumulators[i].getAverage(); _relativePoses[i].rot() = _rotationAccumulators[i].getAverage();
_accumulators[i].clear(); _rotationAccumulators[i].clear();
}
if (_translationAccumulators[i].size() > 0) {
_relativePoses[i].trans() = _translationAccumulators[i].getAverage();
_translationAccumulators[i].clear();
} }
} }
@ -236,7 +252,7 @@ void AnimInverseKinematics::solveWithCyclicCoordinateDescent(const AnimContext&
int parentIndex = _skeleton->getParentIndex(tipIndex); int parentIndex = _skeleton->getParentIndex(tipIndex);
// update rotationOnly targets that don't lie on the ik chain of other ik targets. // update rotationOnly targets that don't lie on the ik chain of other ik targets.
if (parentIndex != -1 && !_accumulators[tipIndex].isDirty() && target.getType() == IKTarget::Type::RotationOnly) { if (parentIndex != -1 && !_rotationAccumulators[tipIndex].isDirty() && target.getType() == IKTarget::Type::RotationOnly) {
const glm::quat& targetRotation = target.getRotation(); const glm::quat& targetRotation = target.getRotation();
// compute tip's new parent-relative rotation // compute tip's new parent-relative rotation
// Q = Qp * q --> q' = Qp^ * Q // Q = Qp * q --> q' = Qp^ * Q
@ -311,10 +327,13 @@ void AnimInverseKinematics::solveTargetWithCCD(const AnimContext& context, const
} }
// store the relative rotation change in the accumulator // store the relative rotation change in the accumulator
_accumulators[tipIndex].add(tipRelativeRotation, target.getWeight()); _rotationAccumulators[tipIndex].add(tipRelativeRotation, target.getWeight());
glm::vec3 tipRelativeTranslation = _relativePoses[target.getIndex()].trans();
_translationAccumulators[tipIndex].add(tipRelativeTranslation);
if (debug) { if (debug) {
debugJointMap[tipIndex] = DebugJoint(tipRelativeRotation, constrained); debugJointMap[tipIndex] = DebugJoint(tipRelativeRotation, tipRelativeTranslation, constrained);
} }
} }
@ -422,10 +441,13 @@ void AnimInverseKinematics::solveTargetWithCCD(const AnimContext& context, const
} }
// store the relative rotation change in the accumulator // store the relative rotation change in the accumulator
_accumulators[pivotIndex].add(newRot, target.getWeight()); _rotationAccumulators[pivotIndex].add(newRot, target.getWeight());
glm::vec3 newTrans = _relativePoses[pivotIndex].trans();
_translationAccumulators[pivotIndex].add(newTrans);
if (debug) { if (debug) {
debugJointMap[pivotIndex] = DebugJoint(newRot, constrained); debugJointMap[pivotIndex] = DebugJoint(newRot, newTrans, constrained);
} }
// keep track of tip's new transform as we descend towards root // keep track of tip's new transform as we descend towards root
@ -444,6 +466,187 @@ void AnimInverseKinematics::solveTargetWithCCD(const AnimContext& context, const
} }
} }
static CubicHermiteSplineFunctorWithArcLength computeSplineFromTipAndBase(const AnimPose& tipPose, const AnimPose& basePose, float baseGain = 1.0f, float tipGain = 1.0f) {
float linearDistance = glm::length(basePose.trans() - tipPose.trans());
glm::vec3 p0 = basePose.trans();
glm::vec3 m0 = baseGain * linearDistance * (basePose.rot() * Vectors::UNIT_Y);
glm::vec3 p1 = tipPose.trans();
glm::vec3 m1 = tipGain * linearDistance * (tipPose.rot() * Vectors::UNIT_Y);
return CubicHermiteSplineFunctorWithArcLength(p0, m0, p1, m1);
}
// pre-compute information about each joint influeced by this spline IK target.
void AnimInverseKinematics::computeSplineJointInfosForIKTarget(const AnimContext& context, const IKTarget& target) {
std::vector<SplineJointInfo> splineJointInfoVec;
// build spline between the default poses.
AnimPose tipPose = _skeleton->getAbsoluteDefaultPose(target.getIndex());
AnimPose basePose = _skeleton->getAbsoluteDefaultPose(_hipsIndex);
CubicHermiteSplineFunctorWithArcLength spline;
if (target.getIndex() == _headIndex) {
// set gain factors so that more curvature occurs near the tip of the spline.
const float HIPS_GAIN = 0.5f;
const float HEAD_GAIN = 1.0f;
spline = computeSplineFromTipAndBase(tipPose, basePose, HIPS_GAIN, HEAD_GAIN);
} else {
spline = computeSplineFromTipAndBase(tipPose, basePose);
}
// measure the total arc length along the spline
float totalArcLength = spline.arcLength(1.0f);
glm::vec3 baseToTip = tipPose.trans() - basePose.trans();
float baseToTipLength = glm::length(baseToTip);
glm::vec3 baseToTipNormal = baseToTip / baseToTipLength;
int index = target.getIndex();
int endIndex = _skeleton->getParentIndex(_hipsIndex);
while (index != endIndex) {
AnimPose defaultPose = _skeleton->getAbsoluteDefaultPose(index);
float ratio = glm::dot(defaultPose.trans() - basePose.trans(), baseToTipNormal) / baseToTipLength;
// compute offset from spline to the default pose.
float t = spline.arcLengthInverse(ratio * totalArcLength);
// compute the rotation by using the derivative of the spline as the y-axis, and the defaultPose x-axis
glm::vec3 y = glm::normalize(spline.d(t));
glm::vec3 x = defaultPose.rot() * Vectors::UNIT_X;
glm::vec3 u, v, w;
generateBasisVectors(y, x, v, u, w);
glm::mat3 m(u, v, glm::cross(u, v));
glm::quat rot = glm::normalize(glm::quat_cast(m));
AnimPose pose(glm::vec3(1.0f), rot, spline(t));
AnimPose offsetPose = pose.inverse() * defaultPose;
SplineJointInfo splineJointInfo = { index, ratio, offsetPose };
splineJointInfoVec.push_back(splineJointInfo);
index = _skeleton->getParentIndex(index);
}
_splineJointInfoMap[target.getIndex()] = splineJointInfoVec;
}
const std::vector<AnimInverseKinematics::SplineJointInfo>* AnimInverseKinematics::findOrCreateSplineJointInfo(const AnimContext& context, const IKTarget& target) {
// find or create splineJointInfo for this target
auto iter = _splineJointInfoMap.find(target.getIndex());
if (iter != _splineJointInfoMap.end()) {
return &(iter->second);
} else {
computeSplineJointInfosForIKTarget(context, target);
auto iter = _splineJointInfoMap.find(target.getIndex());
if (iter != _splineJointInfoMap.end()) {
return &(iter->second);
}
}
return nullptr;
}
void AnimInverseKinematics::solveTargetWithSpline(const AnimContext& context, const IKTarget& target, const AnimPoseVec& absolutePoses, bool debug) {
std::map<int, DebugJoint> debugJointMap;
const int baseIndex = _hipsIndex;
// build spline from tip to base
AnimPose tipPose = AnimPose(glm::vec3(1.0f), target.getRotation(), target.getTranslation());
AnimPose basePose = absolutePoses[baseIndex];
CubicHermiteSplineFunctorWithArcLength spline;
if (target.getIndex() == _headIndex) {
// set gain factors so that more curvature occurs near the tip of the spline.
const float HIPS_GAIN = 0.5f;
const float HEAD_GAIN = 1.0f;
spline = computeSplineFromTipAndBase(tipPose, basePose, HIPS_GAIN, HEAD_GAIN);
} else {
spline = computeSplineFromTipAndBase(tipPose, basePose);
}
float totalArcLength = spline.arcLength(1.0f);
// This prevents the rotation interpolation from rotating the wrong physical way (but correct mathematical way)
// when the head is arched backwards very far.
glm::quat halfRot = glm::normalize(glm::lerp(basePose.rot(), tipPose.rot(), 0.5f));
if (glm::dot(halfRot * Vectors::UNIT_Z, basePose.rot() * Vectors::UNIT_Z) < 0.0f) {
tipPose.rot() = -tipPose.rot();
}
// find or create splineJointInfo for this target
const std::vector<SplineJointInfo>* splineJointInfoVec = findOrCreateSplineJointInfo(context, target);
if (splineJointInfoVec && splineJointInfoVec->size() > 0) {
const int baseParentIndex = _skeleton->getParentIndex(baseIndex);
AnimPose parentAbsPose = (baseParentIndex >= 0) ? absolutePoses[baseParentIndex] : AnimPose();
// go thru splineJointInfoVec backwards (base to tip)
for (int i = (int)splineJointInfoVec->size() - 1; i >= 0; i--) {
const SplineJointInfo& splineJointInfo = (*splineJointInfoVec)[i];
float t = spline.arcLengthInverse(splineJointInfo.ratio * totalArcLength);
glm::vec3 trans = spline(t);
// for head splines, preform most twist toward the tip by using ease in function. t^2
float rotT = t;
if (target.getIndex() == _headIndex) {
rotT = t * t;
}
glm::quat twistRot = glm::normalize(glm::lerp(basePose.rot(), tipPose.rot(), rotT));
// compute the rotation by using the derivative of the spline as the y-axis, and the twistRot x-axis
glm::vec3 y = glm::normalize(spline.d(t));
glm::vec3 x = twistRot * Vectors::UNIT_X;
glm::vec3 u, v, w;
generateBasisVectors(y, x, v, u, w);
glm::mat3 m(u, v, glm::cross(u, v));
glm::quat rot = glm::normalize(glm::quat_cast(m));
AnimPose desiredAbsPose = AnimPose(glm::vec3(1.0f), rot, trans) * splineJointInfo.offsetPose;
// apply flex coefficent
AnimPose flexedAbsPose;
::blend(1, &absolutePoses[splineJointInfo.jointIndex], &desiredAbsPose, target.getFlexCoefficient(i), &flexedAbsPose);
AnimPose relPose = parentAbsPose.inverse() * flexedAbsPose;
_rotationAccumulators[splineJointInfo.jointIndex].add(relPose.rot(), target.getWeight());
bool constrained = false;
if (splineJointInfo.jointIndex != _hipsIndex) {
// constrain the amount the spine can stretch or compress
float length = glm::length(relPose.trans());
const float EPSILON = 0.0001f;
if (length > EPSILON) {
float defaultLength = glm::length(_skeleton->getRelativeDefaultPose(splineJointInfo.jointIndex).trans());
const float STRETCH_COMPRESS_PERCENTAGE = 0.15f;
const float MAX_LENGTH = defaultLength * (1.0f + STRETCH_COMPRESS_PERCENTAGE);
const float MIN_LENGTH = defaultLength * (1.0f - STRETCH_COMPRESS_PERCENTAGE);
if (length > MAX_LENGTH) {
relPose.trans() = (relPose.trans() / length) * MAX_LENGTH;
constrained = true;
} else if (length < MIN_LENGTH) {
relPose.trans() = (relPose.trans() / length) * MIN_LENGTH;
constrained = true;
}
} else {
relPose.trans() = glm::vec3(0.0f);
}
}
_translationAccumulators[splineJointInfo.jointIndex].add(relPose.trans(), target.getWeight());
if (debug) {
debugJointMap[splineJointInfo.jointIndex] = DebugJoint(relPose.rot(), relPose.trans(), constrained);
}
parentAbsPose = flexedAbsPose;
}
}
if (debug) {
debugDrawIKChain(debugJointMap, context);
}
}
//virtual //virtual
const AnimPoseVec& AnimInverseKinematics::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimNode::Triggers& triggersOut) { const AnimPoseVec& AnimInverseKinematics::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimNode::Triggers& triggersOut) {
// don't call this function, call overlay() instead // don't call this function, call overlay() instead
@ -453,14 +656,9 @@ const AnimPoseVec& AnimInverseKinematics::evaluate(const AnimVariantMap& animVar
//virtual //virtual
const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut, const AnimPoseVec& underPoses) { const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut, const AnimPoseVec& underPoses) {
// allows solutionSource to be overridden by an animVar // allows solutionSource to be overridden by an animVar
auto solutionSource = animVars.lookup(_solutionSourceVar, (int)_solutionSource); auto solutionSource = animVars.lookup(_solutionSourceVar, (int)_solutionSource);
if (context.getEnableDebugDrawIKConstraints()) {
debugDrawConstraints(context);
}
const float MAX_OVERLAY_DT = 1.0f / 30.0f; // what to clamp delta-time to in AnimInverseKinematics::overlay const float MAX_OVERLAY_DT = 1.0f / 30.0f; // what to clamp delta-time to in AnimInverseKinematics::overlay
if (dt > MAX_OVERLAY_DT) { if (dt > MAX_OVERLAY_DT) {
dt = MAX_OVERLAY_DT; dt = MAX_OVERLAY_DT;
@ -569,7 +767,7 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars
{ {
PROFILE_RANGE_EX(simulation_animation, "ik/ccd", 0xffff00ff, 0); PROFILE_RANGE_EX(simulation_animation, "ik/ccd", 0xffff00ff, 0);
solveWithCyclicCoordinateDescent(context, targets); solve(context, targets);
} }
if (_hipsTargetIndex < 0) { if (_hipsTargetIndex < 0) {
@ -579,6 +777,20 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars
_hipsOffset = Vectors::ZERO; _hipsOffset = Vectors::ZERO;
} }
} }
if (context.getEnableDebugDrawIKConstraints()) {
debugDrawConstraints(context);
}
}
if (_leftHandIndex > -1) {
_uncontrolledLeftHandPose = _skeleton->getAbsolutePose(_leftHandIndex, underPoses);
}
if (_rightHandIndex > -1) {
_uncontrolledRightHandPose = _skeleton->getAbsolutePose(_rightHandIndex, underPoses);
}
if (_hipsIndex > -1) {
_uncontrolledHipsPose = _skeleton->getAbsolutePose(_hipsIndex, underPoses);
} }
return _relativePoses; return _relativePoses;
@ -722,8 +934,10 @@ void AnimInverseKinematics::initConstraints() {
loadDefaultPoses(_skeleton->getRelativeBindPoses()); loadDefaultPoses(_skeleton->getRelativeBindPoses());
// compute corresponding absolute poses
int numJoints = (int)_defaultRelativePoses.size(); int numJoints = (int)_defaultRelativePoses.size();
/* KEEP THIS CODE for future experimentation
// compute corresponding absolute poses
AnimPoseVec absolutePoses; AnimPoseVec absolutePoses;
absolutePoses.resize(numJoints); absolutePoses.resize(numJoints);
for (int i = 0; i < numJoints; ++i) { for (int i = 0; i < numJoints; ++i) {
@ -734,6 +948,7 @@ void AnimInverseKinematics::initConstraints() {
absolutePoses[i] = absolutePoses[parentIndex] * _defaultRelativePoses[i]; absolutePoses[i] = absolutePoses[parentIndex] * _defaultRelativePoses[i];
} }
} }
*/
clearConstraints(); clearConstraints();
for (int i = 0; i < numJoints; ++i) { for (int i = 0; i < numJoints; ++i) {
@ -1045,7 +1260,10 @@ void AnimInverseKinematics::setSkeletonInternal(AnimSkeleton::ConstPointer skele
_maxTargetIndex = -1; _maxTargetIndex = -1;
for (auto& accumulator: _accumulators) { for (auto& accumulator: _rotationAccumulators) {
accumulator.clearAndClean();
}
for (auto& accumulator: _translationAccumulators) {
accumulator.clearAndClean(); accumulator.clearAndClean();
} }
@ -1061,12 +1279,21 @@ void AnimInverseKinematics::setSkeletonInternal(AnimSkeleton::ConstPointer skele
} else { } else {
_hipsParentIndex = -1; _hipsParentIndex = -1;
} }
_leftHandIndex = _skeleton->nameToJointIndex("LeftHand");
_rightHandIndex = _skeleton->nameToJointIndex("RightHand");
} else { } else {
clearConstraints(); clearConstraints();
_headIndex = -1; _headIndex = -1;
_hipsIndex = -1; _hipsIndex = -1;
_hipsParentIndex = -1; _hipsParentIndex = -1;
_leftHandIndex = -1;
_rightHandIndex = -1;
} }
_uncontrolledLeftHandPose = AnimPose();
_uncontrolledRightHandPose = AnimPose();
_uncontrolledHipsPose = AnimPose();
} }
static glm::vec3 sphericalToCartesian(float phi, float theta) { static glm::vec3 sphericalToCartesian(float phi, float theta) {
@ -1117,6 +1344,7 @@ void AnimInverseKinematics::debugDrawIKChain(std::map<int, DebugJoint>& debugJoi
// copy debug joint rotations into the relative poses // copy debug joint rotations into the relative poses
for (auto& debugJoint : debugJointMap) { for (auto& debugJoint : debugJointMap) {
poses[debugJoint.first].rot() = debugJoint.second.relRot; poses[debugJoint.first].rot() = debugJoint.second.relRot;
poses[debugJoint.first].trans() = debugJoint.second.relTrans;
} }
// convert relative poses to absolute // convert relative poses to absolute
@ -1282,7 +1510,7 @@ void AnimInverseKinematics::blendToPoses(const AnimPoseVec& targetPoses, const A
int numJoints = (int)_relativePoses.size(); int numJoints = (int)_relativePoses.size();
for (int i = 0; i < numJoints; ++i) { for (int i = 0; i < numJoints; ++i) {
float dotSign = copysignf(1.0f, glm::dot(_relativePoses[i].rot(), targetPoses[i].rot())); float dotSign = copysignf(1.0f, glm::dot(_relativePoses[i].rot(), targetPoses[i].rot()));
if (_accumulators[i].isDirty()) { if (_rotationAccumulators[i].isDirty()) {
// this joint is affected by IK --> blend toward the targetPoses rotation // this joint is affected by IK --> blend toward the targetPoses rotation
_relativePoses[i].rot() = glm::normalize(glm::lerp(_relativePoses[i].rot(), dotSign * targetPoses[i].rot(), blendFactor)); _relativePoses[i].rot() = glm::normalize(glm::lerp(_relativePoses[i].rot(), dotSign * targetPoses[i].rot(), blendFactor));
} else { } else {
@ -1316,3 +1544,46 @@ void AnimInverseKinematics::initRelativePosesFromSolutionSource(SolutionSource s
break; break;
} }
} }
void AnimInverseKinematics::debugDrawSpineSplines(const AnimContext& context, const std::vector<IKTarget>& targets) const {
for (auto& target : targets) {
if (target.getType() != IKTarget::Type::Spline) {
continue;
}
const int baseIndex = _hipsIndex;
// build spline
AnimPose tipPose = AnimPose(glm::vec3(1.0f), target.getRotation(), target.getTranslation());
AnimPose basePose = _skeleton->getAbsolutePose(baseIndex, _relativePoses);
CubicHermiteSplineFunctorWithArcLength spline;
if (target.getIndex() == _headIndex) {
// set gain factors so that more curvature occurs near the tip of the spline.
const float HIPS_GAIN = 0.5f;
const float HEAD_GAIN = 1.0f;
spline = computeSplineFromTipAndBase(tipPose, basePose, HIPS_GAIN, HEAD_GAIN);
} else {
spline = computeSplineFromTipAndBase(tipPose, basePose);
}
float totalArcLength = spline.arcLength(1.0f);
const glm::vec4 RED(1.0f, 0.0f, 0.0f, 1.0f);
const glm::vec4 WHITE(1.0f, 1.0f, 1.0f, 1.0f);
// draw red and white stripped spline, parameterized by arc length.
// i.e. each stripe should be the same length.
AnimPose geomToWorldPose = AnimPose(context.getRigToWorldMatrix() * context.getGeometryToRigMatrix());
const int NUM_SEGMENTS = 20;
const float dArcLength = totalArcLength / NUM_SEGMENTS;
float arcLength = 0.0f;
for (int i = 0; i < NUM_SEGMENTS; i++) {
float prevT = spline.arcLengthInverse(arcLength);
float nextT = spline.arcLengthInverse(arcLength + dArcLength);
DebugDraw::getInstance().drawRay(geomToWorldPose.xformPoint(spline(prevT)), geomToWorldPose.xformPoint(spline(nextT)), (i % 2) == 0 ? RED : WHITE);
arcLength += dArcLength;
}
}
}

View file

@ -19,6 +19,7 @@
#include "IKTarget.h" #include "IKTarget.h"
#include "RotationAccumulator.h" #include "RotationAccumulator.h"
#include "TranslationAccumulator.h"
class RotationConstraint; class RotationConstraint;
@ -56,23 +57,39 @@ public:
void setSolutionSource(SolutionSource solutionSource) { _solutionSource = solutionSource; } void setSolutionSource(SolutionSource solutionSource) { _solutionSource = solutionSource; }
void setSolutionSourceVar(const QString& solutionSourceVar) { _solutionSourceVar = solutionSourceVar; } void setSolutionSourceVar(const QString& solutionSourceVar) { _solutionSourceVar = solutionSourceVar; }
const AnimPose& getUncontrolledLeftHandPose() { return _uncontrolledLeftHandPose; }
const AnimPose& getUncontrolledRightHandPose() { return _uncontrolledRightHandPose; }
const AnimPose& getUncontrolledHipPose() { return _uncontrolledHipsPose; }
protected: protected:
void computeTargets(const AnimVariantMap& animVars, std::vector<IKTarget>& targets, const AnimPoseVec& underPoses); void computeTargets(const AnimVariantMap& animVars, std::vector<IKTarget>& targets, const AnimPoseVec& underPoses);
void solveWithCyclicCoordinateDescent(const AnimContext& context, const std::vector<IKTarget>& targets); void solve(const AnimContext& context, const std::vector<IKTarget>& targets);
void solveTargetWithCCD(const AnimContext& context, const IKTarget& target, const AnimPoseVec& absolutePoses, bool debug); void solveTargetWithCCD(const AnimContext& context, const IKTarget& target, const AnimPoseVec& absolutePoses, bool debug);
void solveTargetWithSpline(const AnimContext& context, const IKTarget& target, const AnimPoseVec& absolutePoses, bool debug);
virtual void setSkeletonInternal(AnimSkeleton::ConstPointer skeleton) override; virtual void setSkeletonInternal(AnimSkeleton::ConstPointer skeleton) override;
struct DebugJoint { struct DebugJoint {
DebugJoint() : relRot(), constrained(false) {} DebugJoint() : relRot(), constrained(false) {}
DebugJoint(const glm::quat& relRotIn, bool constrainedIn) : relRot(relRotIn), constrained(constrainedIn) {} DebugJoint(const glm::quat& relRotIn, const glm::vec3& relTransIn, bool constrainedIn) : relRot(relRotIn), relTrans(relTransIn), constrained(constrainedIn) {}
glm::quat relRot; glm::quat relRot;
glm::vec3 relTrans;
bool constrained; bool constrained;
}; };
void debugDrawIKChain(std::map<int, DebugJoint>& debugJointMap, const AnimContext& context) const; void debugDrawIKChain(std::map<int, DebugJoint>& debugJointMap, const AnimContext& context) const;
void debugDrawRelativePoses(const AnimContext& context) const; void debugDrawRelativePoses(const AnimContext& context) const;
void debugDrawConstraints(const AnimContext& context) const; void debugDrawConstraints(const AnimContext& context) const;
void debugDrawSpineSplines(const AnimContext& context, const std::vector<IKTarget>& targets) const;
void initRelativePosesFromSolutionSource(SolutionSource solutionSource, const AnimPoseVec& underPose); void initRelativePosesFromSolutionSource(SolutionSource solutionSource, const AnimPoseVec& underPose);
void blendToPoses(const AnimPoseVec& targetPoses, const AnimPoseVec& underPose, float blendFactor); void blendToPoses(const AnimPoseVec& targetPoses, const AnimPoseVec& underPose, float blendFactor);
// used to pre-compute information about each joint influeced by a spline IK target.
struct SplineJointInfo {
int jointIndex; // joint in the skeleton that this information pertains to.
float ratio; // percentage (0..1) along the spline for this joint.
AnimPose offsetPose; // local offset from the spline to the joint.
};
void computeSplineJointInfosForIKTarget(const AnimContext& context, const IKTarget& target);
const std::vector<SplineJointInfo>* findOrCreateSplineJointInfo(const AnimContext& context, const IKTarget& target);
// for AnimDebugDraw rendering // for AnimDebugDraw rendering
virtual const AnimPoseVec& getPosesInternal() const override { return _relativePoses; } virtual const AnimPoseVec& getPosesInternal() const override { return _relativePoses; }
@ -105,12 +122,15 @@ protected:
}; };
std::map<int, RotationConstraint*> _constraints; std::map<int, RotationConstraint*> _constraints;
std::vector<RotationAccumulator> _accumulators; std::vector<RotationAccumulator> _rotationAccumulators;
std::vector<TranslationAccumulator> _translationAccumulators;
std::vector<IKTargetVar> _targetVarVec; std::vector<IKTargetVar> _targetVarVec;
AnimPoseVec _defaultRelativePoses; // poses of the relaxed state AnimPoseVec _defaultRelativePoses; // poses of the relaxed state
AnimPoseVec _relativePoses; // current relative poses AnimPoseVec _relativePoses; // current relative poses
AnimPoseVec _limitCenterPoses; // relative AnimPoseVec _limitCenterPoses; // relative
std::map<int, std::vector<SplineJointInfo>> _splineJointInfoMap;
// experimental data for moving hips during IK // experimental data for moving hips during IK
glm::vec3 _hipsOffset { Vectors::ZERO }; glm::vec3 _hipsOffset { Vectors::ZERO };
float _maxHipsOffsetLength{ FLT_MAX }; float _maxHipsOffsetLength{ FLT_MAX };
@ -118,6 +138,8 @@ protected:
int _hipsIndex { -1 }; int _hipsIndex { -1 };
int _hipsParentIndex { -1 }; int _hipsParentIndex { -1 };
int _hipsTargetIndex { -1 }; int _hipsTargetIndex { -1 };
int _leftHandIndex { -1 };
int _rightHandIndex { -1 };
// _maxTargetIndex is tracked to help optimize the recalculation of absolute poses // _maxTargetIndex is tracked to help optimize the recalculation of absolute poses
// during the the cyclic coordinate descent algorithm // during the the cyclic coordinate descent algorithm
@ -127,6 +149,10 @@ protected:
bool _previousEnableDebugIKTargets { false }; bool _previousEnableDebugIKTargets { false };
SolutionSource _solutionSource { SolutionSource::RelaxToUnderPoses }; SolutionSource _solutionSource { SolutionSource::RelaxToUnderPoses };
QString _solutionSourceVar; QString _solutionSourceVar;
AnimPose _uncontrolledLeftHandPose { AnimPose() };
AnimPose _uncontrolledRightHandPose { AnimPose() };
AnimPose _uncontrolledHipsPose { AnimPose() };
}; };
#endif // hifi_AnimInverseKinematics_h #endif // hifi_AnimInverseKinematics_h

View file

@ -44,6 +44,9 @@ void IKTarget::setType(int type) {
case (int)Type::HipsRelativeRotationAndPosition: case (int)Type::HipsRelativeRotationAndPosition:
_type = Type::HipsRelativeRotationAndPosition; _type = Type::HipsRelativeRotationAndPosition;
break; break;
case (int)Type::Spline:
_type = Type::Spline;
break;
default: default:
_type = Type::Unknown; _type = Type::Unknown;
} }

View file

@ -21,6 +21,7 @@ public:
RotationOnly, RotationOnly,
HmdHead, HmdHead,
HipsRelativeRotationAndPosition, HipsRelativeRotationAndPosition,
Spline,
Unknown Unknown
}; };

View file

@ -28,6 +28,7 @@
#include "AnimClip.h" #include "AnimClip.h"
#include "AnimInverseKinematics.h" #include "AnimInverseKinematics.h"
#include "AnimSkeleton.h" #include "AnimSkeleton.h"
#include "AnimUtil.h"
#include "IKTarget.h" #include "IKTarget.h"
static bool isEqual(const glm::vec3& u, const glm::vec3& v) { static bool isEqual(const glm::vec3& u, const glm::vec3& v) {
@ -401,16 +402,6 @@ void Rig::setJointRotation(int index, bool valid, const glm::quat& rotation, flo
} }
} }
void Rig::restoreJointRotation(int index, float fraction, float priority) {
// AJT: DEAD CODE?
ASSERT(false);
}
void Rig::restoreJointTranslation(int index, float fraction, float priority) {
// AJT: DEAD CODE?
ASSERT(false);
}
bool Rig::getJointPositionInWorldFrame(int jointIndex, glm::vec3& position, glm::vec3 translation, glm::quat rotation) const { bool Rig::getJointPositionInWorldFrame(int jointIndex, glm::vec3& position, glm::vec3 translation, glm::quat rotation) const {
if (isIndexValid(jointIndex)) { if (isIndexValid(jointIndex)) {
position = (rotation * _internalPoseSet._absolutePoses[jointIndex].trans()) + translation; position = (rotation * _internalPoseSet._absolutePoses[jointIndex].trans()) + translation;
@ -1040,8 +1031,8 @@ void Rig::updateFromHeadParameters(const HeadParameters& params, float dt) {
_animVars.set("hipsType", (int)IKTarget::Type::Unknown); _animVars.set("hipsType", (int)IKTarget::Type::Unknown);
} }
if (params.spine2Enabled) { if (params.hipsEnabled && params.spine2Enabled) {
_animVars.set("spine2Type", (int)IKTarget::Type::RotationAndPosition); _animVars.set("spine2Type", (int)IKTarget::Type::Spline);
_animVars.set("spine2Position", extractTranslation(params.spine2Matrix)); _animVars.set("spine2Position", extractTranslation(params.spine2Matrix));
_animVars.set("spine2Rotation", glmExtractRotation(params.spine2Matrix)); _animVars.set("spine2Rotation", glmExtractRotation(params.spine2Matrix));
} else { } else {
@ -1051,7 +1042,7 @@ void Rig::updateFromHeadParameters(const HeadParameters& params, float dt) {
if (params.leftArmEnabled) { if (params.leftArmEnabled) {
_animVars.set("leftArmType", (int)IKTarget::Type::RotationAndPosition); _animVars.set("leftArmType", (int)IKTarget::Type::RotationAndPosition);
_animVars.set("leftArmPosition", params.leftArmPosition); _animVars.set("leftArmPosition", params.leftArmPosition);
_animVars.set("leftArmRotation", params.leftArmRotation); _animVars.set("leftArmRotation", params.leftArmRotation);
} else { } else {
_animVars.set("leftArmType", (int)IKTarget::Type::Unknown); _animVars.set("leftArmType", (int)IKTarget::Type::Unknown);
} }
@ -1101,9 +1092,9 @@ void Rig::updateHeadAnimVars(const HeadParameters& params) {
_animVars.set("headPosition", params.rigHeadPosition); _animVars.set("headPosition", params.rigHeadPosition);
_animVars.set("headRotation", params.rigHeadOrientation); _animVars.set("headRotation", params.rigHeadOrientation);
if (params.hipsEnabled) { if (params.hipsEnabled) {
// Since there is an explicit hips ik target, switch the head to use the more generic RotationAndPosition IK chain type. // Since there is an explicit hips ik target, switch the head to use the more flexible Spline IK chain type.
// this will allow the spine to bend more, ensuring that it can reach the head target position. // this will allow the spine to compress/expand and bend more natrually, ensuring that it can reach the head target position.
_animVars.set("headType", (int)IKTarget::Type::RotationAndPosition); _animVars.set("headType", (int)IKTarget::Type::Spline);
_animVars.unset("headWeight"); // use the default weight for this target. _animVars.unset("headWeight"); // use the default weight for this target.
} else { } else {
// When there is no hips IK target, use the HmdHead IK chain type. This will make the spine very stiff, // When there is no hips IK target, use the HmdHead IK chain type. This will make the spine very stiff,
@ -1172,9 +1163,32 @@ void Rig::updateFromHandAndFeetParameters(const HandAndFeetParameters& params, f
// TODO: add isHipsEnabled // TODO: add isHipsEnabled
bool bodySensorTrackingEnabled = params.isLeftFootEnabled || params.isRightFootEnabled; bool bodySensorTrackingEnabled = params.isLeftFootEnabled || params.isRightFootEnabled;
const float RELAX_DURATION = 0.6f;
const float CONTROL_DURATION = 0.4f;
const bool TO_CONTROLLED = true;
const bool FROM_CONTROLLED = false;
const bool LEFT_HAND = true;
const bool RIGHT_HAND = false;
if (params.isLeftEnabled) { if (params.isLeftEnabled) {
if (!_isLeftHandControlled) {
_leftHandControlTimeRemaining = CONTROL_DURATION;
_isLeftHandControlled = true;
}
glm::vec3 handPosition = params.leftPosition; glm::vec3 handPosition = params.leftPosition;
glm::quat handRotation = params.leftOrientation;
if (_leftHandControlTimeRemaining > 0.0f) {
// Move hand from non-controlled position to controlled position.
_leftHandControlTimeRemaining = std::max(_leftHandControlTimeRemaining - dt, 0.0f);
AnimPose handPose(Vectors::ONE, handRotation, handPosition);
if (transitionHandPose(_leftHandControlTimeRemaining, CONTROL_DURATION, handPose, LEFT_HAND, TO_CONTROLLED,
handPose)) {
handPosition = handPose.trans();
handRotation = handPose.rot();
}
}
if (!bodySensorTrackingEnabled) { if (!bodySensorTrackingEnabled) {
// prevent the hand IK targets from intersecting the body capsule // prevent the hand IK targets from intersecting the body capsule
@ -1185,17 +1199,52 @@ void Rig::updateFromHandAndFeetParameters(const HandAndFeetParameters& params, f
} }
_animVars.set("leftHandPosition", handPosition); _animVars.set("leftHandPosition", handPosition);
_animVars.set("leftHandRotation", params.leftOrientation); _animVars.set("leftHandRotation", handRotation);
_animVars.set("leftHandType", (int)IKTarget::Type::RotationAndPosition); _animVars.set("leftHandType", (int)IKTarget::Type::RotationAndPosition);
_lastLeftHandControlledPose = AnimPose(Vectors::ONE, handRotation, handPosition);
} else { } else {
_animVars.unset("leftHandPosition"); if (_isLeftHandControlled) {
_animVars.unset("leftHandRotation"); _leftHandRelaxTimeRemaining = RELAX_DURATION;
_animVars.set("leftHandType", (int)IKTarget::Type::HipsRelativeRotationAndPosition); _isLeftHandControlled = false;
}
if (_leftHandRelaxTimeRemaining > 0.0f) {
// Move hand from controlled position to non-controlled position.
_leftHandRelaxTimeRemaining = std::max(_leftHandRelaxTimeRemaining - dt, 0.0f);
AnimPose handPose;
if (transitionHandPose(_leftHandRelaxTimeRemaining, RELAX_DURATION, _lastLeftHandControlledPose, LEFT_HAND,
FROM_CONTROLLED, handPose)) {
_animVars.set("leftHandPosition", handPose.trans());
_animVars.set("leftHandRotation", handPose.rot());
_animVars.set("leftHandType", (int)IKTarget::Type::RotationAndPosition);
}
} else {
_animVars.unset("leftHandPosition");
_animVars.unset("leftHandRotation");
_animVars.set("leftHandType", (int)IKTarget::Type::HipsRelativeRotationAndPosition);
}
} }
if (params.isRightEnabled) { if (params.isRightEnabled) {
if (!_isRightHandControlled) {
_rightHandControlTimeRemaining = CONTROL_DURATION;
_isRightHandControlled = true;
}
glm::vec3 handPosition = params.rightPosition; glm::vec3 handPosition = params.rightPosition;
glm::quat handRotation = params.rightOrientation;
if (_rightHandControlTimeRemaining > 0.0f) {
// Move hand from non-controlled position to controlled position.
_rightHandControlTimeRemaining = std::max(_rightHandControlTimeRemaining - dt, 0.0f);
AnimPose handPose(Vectors::ONE, handRotation, handPosition);
if (transitionHandPose(_rightHandControlTimeRemaining, CONTROL_DURATION, handPose, RIGHT_HAND, TO_CONTROLLED,
handPose)) {
handPosition = handPose.trans();
handRotation = handPose.rot();
}
}
if (!bodySensorTrackingEnabled) { if (!bodySensorTrackingEnabled) {
// prevent the hand IK targets from intersecting the body capsule // prevent the hand IK targets from intersecting the body capsule
@ -1206,12 +1255,31 @@ void Rig::updateFromHandAndFeetParameters(const HandAndFeetParameters& params, f
} }
_animVars.set("rightHandPosition", handPosition); _animVars.set("rightHandPosition", handPosition);
_animVars.set("rightHandRotation", params.rightOrientation); _animVars.set("rightHandRotation", handRotation);
_animVars.set("rightHandType", (int)IKTarget::Type::RotationAndPosition); _animVars.set("rightHandType", (int)IKTarget::Type::RotationAndPosition);
_lastRightHandControlledPose = AnimPose(Vectors::ONE, handRotation, handPosition);
} else { } else {
_animVars.unset("rightHandPosition"); if (_isRightHandControlled) {
_animVars.unset("rightHandRotation"); _rightHandRelaxTimeRemaining = RELAX_DURATION;
_animVars.set("rightHandType", (int)IKTarget::Type::HipsRelativeRotationAndPosition); _isRightHandControlled = false;
}
if (_rightHandRelaxTimeRemaining > 0.0f) {
// Move hand from controlled position to non-controlled position.
_rightHandRelaxTimeRemaining = std::max(_rightHandRelaxTimeRemaining - dt, 0.0f);
AnimPose handPose;
if (transitionHandPose(_rightHandRelaxTimeRemaining, RELAX_DURATION, _lastRightHandControlledPose, RIGHT_HAND,
FROM_CONTROLLED, handPose)) {
_animVars.set("rightHandPosition", handPose.trans());
_animVars.set("rightHandRotation", handPose.rot());
_animVars.set("rightHandType", (int)IKTarget::Type::RotationAndPosition);
}
} else {
_animVars.unset("rightHandPosition");
_animVars.unset("rightHandRotation");
_animVars.set("rightHandType", (int)IKTarget::Type::HipsRelativeRotationAndPosition);
}
} }
if (params.isLeftFootEnabled) { if (params.isLeftFootEnabled) {
@ -1233,7 +1301,6 @@ void Rig::updateFromHandAndFeetParameters(const HandAndFeetParameters& params, f
_animVars.unset("rightFootRotation"); _animVars.unset("rightFootRotation");
_animVars.set("rightFootType", (int)IKTarget::Type::RotationAndPosition); _animVars.set("rightFootType", (int)IKTarget::Type::RotationAndPosition);
} }
} }
} }
@ -1510,4 +1577,24 @@ void Rig::computeAvatarBoundingCapsule(
localOffsetOut = rigCenter - (geometryToRig * rootPosition); localOffsetOut = rigCenter - (geometryToRig * rootPosition);
} }
bool Rig::transitionHandPose(float deltaTime, float totalDuration, AnimPose& controlledHandPose, bool isLeftHand,
bool isToControlled, AnimPose& returnHandPose) {
auto ikNode = getAnimInverseKinematicsNode();
if (ikNode) {
float alpha = 1.0f - deltaTime / totalDuration;
const AnimPose geometryToRigTransform(_geometryToRigTransform);
AnimPose uncontrolledHandPose;
if (isLeftHand) {
uncontrolledHandPose = geometryToRigTransform * ikNode->getUncontrolledLeftHandPose();
} else {
uncontrolledHandPose = geometryToRigTransform * ikNode->getUncontrolledRightHandPose();
}
if (isToControlled) {
::blend(1, &uncontrolledHandPose, &controlledHandPose, alpha, &returnHandPose);
} else {
::blend(1, &controlledHandPose, &uncontrolledHandPose, alpha, &returnHandPose);
}
return true;
}
return false;
}

View file

@ -134,10 +134,6 @@ public:
void setJointTranslation(int index, bool valid, const glm::vec3& translation, float priority); void setJointTranslation(int index, bool valid, const glm::vec3& translation, float priority);
void setJointRotation(int index, bool valid, const glm::quat& rotation, float priority); void setJointRotation(int index, bool valid, const glm::quat& rotation, float priority);
// legacy
void restoreJointRotation(int index, float fraction, float priority);
void restoreJointTranslation(int index, float fraction, float priority);
// if translation and rotation is identity, position will be in rig space // if translation and rotation is identity, position will be in rig space
bool getJointPositionInWorldFrame(int jointIndex, glm::vec3& position, bool getJointPositionInWorldFrame(int jointIndex, glm::vec3& position,
glm::vec3 translation, glm::quat rotation) const; glm::vec3 translation, glm::quat rotation) const;
@ -355,6 +351,18 @@ private:
QMap<int, StateHandler> _stateHandlers; QMap<int, StateHandler> _stateHandlers;
int _nextStateHandlerId { 0 }; int _nextStateHandlerId { 0 };
QMutex _stateMutex; QMutex _stateMutex;
bool transitionHandPose(float deltaTime, float totalDuration, AnimPose& controlledHandPose, bool isLeftHand,
bool isToControlled, AnimPose& returnHandPose);
bool _isLeftHandControlled { false };
bool _isRightHandControlled { false };
float _leftHandControlTimeRemaining { 0.0f };
float _rightHandControlTimeRemaining { 0.0f };
float _leftHandRelaxTimeRemaining { 0.0f };
float _rightHandRelaxTimeRemaining { 0.0f };
AnimPose _lastLeftHandControlledPose;
AnimPose _lastRightHandControlledPose;
}; };
#endif /* defined(__hifi__Rig__) */ #endif /* defined(__hifi__Rig__) */

View file

@ -1,5 +1,5 @@
// //
// RotationAccumulator.h // RotationAccumulator.cpp
// //
// Copyright 2015 High Fidelity, Inc. // Copyright 2015 High Fidelity, Inc.
// //
@ -27,7 +27,7 @@ void RotationAccumulator::clear() {
_numRotations = 0; _numRotations = 0;
} }
void RotationAccumulator::clearAndClean() { void RotationAccumulator::clearAndClean() {
clear(); clear();
_isDirty = false; _isDirty = false;
} }

View file

@ -0,0 +1,34 @@
//
// TranslationAccumulator.cpp
//
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "TranslationAccumulator.h"
void TranslationAccumulator::add(const glm::vec3& translation, float weight) {
_accum += weight * translation;
_totalWeight += weight;
_isDirty = true;
}
glm::vec3 TranslationAccumulator::getAverage() {
if (_totalWeight > 0.0f) {
return _accum / _totalWeight;
} else {
return glm::vec3();
}
}
void TranslationAccumulator::clear() {
_accum *= 0.0f;
_totalWeight = 0.0f;
}
void TranslationAccumulator::clearAndClean() {
clear();
_isDirty = false;
}

View file

@ -0,0 +1,42 @@
//
// TranslationAccumulator.h
//
// 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
//
#ifndef hifi_TranslationAccumulator_h
#define hifi_TranslationAccumulator_h
#include <glm/glm.hpp>
class TranslationAccumulator {
public:
TranslationAccumulator() : _accum(0.0f, 0.0f, 0.0f), _totalWeight(0), _isDirty(false) { }
int size() const { return _totalWeight > 0.0f; }
/// \param translation translation to add
/// \param weight contribution factor of this translation to total accumulation
void add(const glm::vec3& translation, float weight = 1.0f);
glm::vec3 getAverage();
/// \return true if any translation were accumulated
bool isDirty() const { return _isDirty; }
/// \brief clear accumulated translation but don't change _isDirty
void clear();
/// \brief clear accumulated translation and set _isDirty to false
void clearAndClean();
private:
glm::vec3 _accum;
float _totalWeight;
bool _isDirty;
};
#endif // hifi_TranslationAccumulator_h

View file

@ -31,10 +31,13 @@
#include <VersionHelpers.h> #include <VersionHelpers.h>
#endif #endif
#include <QtConcurrent/QtConcurrent>
#include <QtCore/QThreadPool>
#include <QtCore/QBuffer> #include <QtCore/QBuffer>
#include <QtMultimedia/QAudioInput> #include <QtMultimedia/QAudioInput>
#include <QtMultimedia/QAudioOutput> #include <QtMultimedia/QAudioOutput>
#include <ThreadHelpers.h>
#include <NodeList.h> #include <NodeList.h>
#include <plugins/CodecPlugin.h> #include <plugins/CodecPlugin.h>
#include <plugins/PluginManager.h> #include <plugins/PluginManager.h>
@ -76,59 +79,6 @@ using Mutex = std::mutex;
using Lock = std::unique_lock<Mutex>; using Lock = std::unique_lock<Mutex>;
static Mutex _deviceMutex; static Mutex _deviceMutex;
class BackgroundThread : public QThread {
public:
BackgroundThread(AudioClient* client) : QThread((QObject*)client), _client(client) {}
virtual void join() = 0;
protected:
AudioClient* _client;
};
// background thread continuously polling device changes
class CheckDevicesThread : public BackgroundThread {
public:
CheckDevicesThread(AudioClient* client) : BackgroundThread(client) {}
void join() override {
_shouldQuit = true;
std::unique_lock<std::mutex> lock(_joinMutex);
_joinCondition.wait(lock, [&]{ return !_isRunning; });
}
protected:
void run() override {
while (!_shouldQuit) {
_client->checkDevices();
const unsigned long DEVICE_CHECK_INTERVAL_MSECS = 2 * 1000;
QThread::msleep(DEVICE_CHECK_INTERVAL_MSECS);
}
std::lock_guard<std::mutex> lock(_joinMutex);
_isRunning = false;
_joinCondition.notify_one();
}
private:
std::atomic<bool> _shouldQuit { false };
bool _isRunning { true };
std::mutex _joinMutex;
std::condition_variable _joinCondition;
};
// background thread buffering local injectors
class LocalInjectorsThread : public BackgroundThread {
Q_OBJECT
public:
LocalInjectorsThread(AudioClient* client) : BackgroundThread(client) {}
void join() override { return; }
private slots:
void prepare() { _client->prepareLocalAudioInjectors(); }
};
#include "AudioClient.moc"
static void channelUpmix(int16_t* source, int16_t* dest, int numSamples, int numExtraChannels) { static void channelUpmix(int16_t* source, int16_t* dest, int numSamples, int numExtraChannels) {
for (int i = 0; i < numSamples/2; i++) { for (int i = 0; i < numSamples/2; i++) {
@ -226,16 +176,17 @@ AudioClient::AudioClient() :
_inputDevices = getDeviceNames(QAudio::AudioInput); _inputDevices = getDeviceNames(QAudio::AudioInput);
_outputDevices = getDeviceNames(QAudio::AudioOutput); _outputDevices = getDeviceNames(QAudio::AudioOutput);
// start a thread to detect any device changes
_checkDevicesThread = new CheckDevicesThread(this);
_checkDevicesThread->setObjectName("AudioClient CheckDevices Thread");
_checkDevicesThread->setPriority(QThread::LowPriority);
_checkDevicesThread->start();
// start a thread to process local injectors // start a thread to detect any device changes
_localInjectorsThread = new LocalInjectorsThread(this); _checkDevicesTimer = new QTimer(this);
_localInjectorsThread->setObjectName("AudioClient LocalInjectors Thread"); connect(_checkDevicesTimer, &QTimer::timeout, [this] {
_localInjectorsThread->start(); QtConcurrent::run(QThreadPool::globalInstance(), [this] {
checkDevices();
});
});
const unsigned long DEVICE_CHECK_INTERVAL_MSECS = 2 * 1000;
_checkDevicesTimer->start(DEVICE_CHECK_INTERVAL_MSECS);
configureReverb(); configureReverb();
@ -266,15 +217,7 @@ void AudioClient::cleanupBeforeQuit() {
stop(); stop();
if (_checkDevicesThread) { _checkDevicesTimer->stop();
static_cast<BackgroundThread*>(_checkDevicesThread)->join();
delete _checkDevicesThread;
}
if (_localInjectorsThread) {
static_cast<BackgroundThread*>(_localInjectorsThread)->join();
delete _localInjectorsThread;
}
} }
void AudioClient::handleMismatchAudioFormat(SharedNodePointer node, const QString& currentCodec, const QString& recievedCodec) { void AudioClient::handleMismatchAudioFormat(SharedNodePointer node, const QString& currentCodec, const QString& recievedCodec) {
@ -452,6 +395,11 @@ QAudioDeviceInfo defaultAudioDeviceForMode(QAudio::Mode mode) {
return (mode == QAudio::AudioInput) ? QAudioDeviceInfo::defaultInputDevice() : QAudioDeviceInfo::defaultOutputDevice(); return (mode == QAudio::AudioInput) ? QAudioDeviceInfo::defaultInputDevice() : QAudioDeviceInfo::defaultOutputDevice();
} }
bool AudioClient::getNamedAudioDeviceForModeExists(QAudio::Mode mode, const QString& deviceName) {
return (getNamedAudioDeviceForMode(mode, deviceName).deviceName() == deviceName);
}
// attempt to use the native sample rate and channel count // attempt to use the native sample rate and channel count
bool nativeFormatForAudioDevice(const QAudioDeviceInfo& audioDevice, bool nativeFormatForAudioDevice(const QAudioDeviceInfo& audioDevice,
QAudioFormat& audioFormat) { QAudioFormat& audioFormat) {
@ -1378,10 +1326,8 @@ bool AudioClient::outputLocalInjector(AudioInjector* injector) {
if (!_activeLocalAudioInjectors.contains(injector)) { if (!_activeLocalAudioInjectors.contains(injector)) {
qCDebug(audioclient) << "adding new injector"; qCDebug(audioclient) << "adding new injector";
_activeLocalAudioInjectors.append(injector); _activeLocalAudioInjectors.append(injector);
// move local buffer to the LocalAudioThread to avoid dataraces with AudioInjector (like stop()) // move local buffer to the LocalAudioThread to avoid dataraces with AudioInjector (like stop())
injectorBuffer->setParent(nullptr); injectorBuffer->setParent(nullptr);
injectorBuffer->moveToThread(_localInjectorsThread);
// update the flag // update the flag
_localInjectorsAvailable.exchange(true, std::memory_order_release); _localInjectorsAvailable.exchange(true, std::memory_order_release);
@ -1799,7 +1745,9 @@ qint64 AudioClient::AudioOutputIODevice::readData(char * data, qint64 maxSize) {
} }
// prepare injectors for the next callback // prepare injectors for the next callback
QMetaObject::invokeMethod(_audio->_localInjectorsThread, "prepare", Qt::QueuedConnection); QtConcurrent::run(QThreadPool::globalInstance(), [this] {
_audio->prepareLocalAudioInjectors();
});
int samplesPopped = std::max(networkSamplesPopped, injectorSamplesPopped); int samplesPopped = std::max(networkSamplesPopped, injectorSamplesPopped);
int framesPopped = samplesPopped / AudioConstants::STEREO; int framesPopped = samplesPopped / AudioConstants::STEREO;
@ -1872,3 +1820,8 @@ void AudioClient::setAvatarBoundingBoxParameters(glm::vec3 corner, glm::vec3 sca
avatarBoundingBoxCorner = corner; avatarBoundingBoxCorner = corner;
avatarBoundingBoxScale = scale; avatarBoundingBoxScale = scale;
} }
void AudioClient::startThread() {
moveToNewNamedThread(this, "Audio Thread", [this] { start(); });
}

View file

@ -104,6 +104,7 @@ public:
int _unfulfilledReads; int _unfulfilledReads;
}; };
void startThread();
void negotiateAudioFormat(); void negotiateAudioFormat();
void selectAudioFormat(const QString& selectedCodecName); void selectAudioFormat(const QString& selectedCodecName);
@ -148,6 +149,8 @@ public:
static const float CALLBACK_ACCELERATOR_RATIO; static const float CALLBACK_ACCELERATOR_RATIO;
bool getNamedAudioDeviceForModeExists(QAudio::Mode mode, const QString& deviceName);
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
static QString friendlyNameForAudioDevice(wchar_t* guid); static QString friendlyNameForAudioDevice(wchar_t* guid);
#endif #endif
@ -386,8 +389,7 @@ private:
RateCounter<> _silentInbound; RateCounter<> _silentInbound;
RateCounter<> _audioInbound; RateCounter<> _audioInbound;
QThread* _checkDevicesThread { nullptr }; QTimer* _checkDevicesTimer { nullptr };
QThread* _localInjectorsThread { nullptr };
}; };

View file

@ -1504,7 +1504,7 @@ QUrl AvatarData::cannonicalSkeletonModelURL(const QUrl& emptyURL) const {
return _skeletonModelURL.scheme() == "file" ? emptyURL : _skeletonModelURL; return _skeletonModelURL.scheme() == "file" ? emptyURL : _skeletonModelURL;
} }
void AvatarData::processAvatarIdentity(const Identity& identity, bool& identityChanged, bool& displayNameChanged) { void AvatarData::processAvatarIdentity(const Identity& identity, bool& identityChanged, bool& displayNameChanged, bool& skeletonModelUrlChanged) {
if (identity.sequenceId < _identitySequenceId) { if (identity.sequenceId < _identitySequenceId) {
qCDebug(avatars) << "Ignoring older identity packet for avatar" << getSessionUUID() qCDebug(avatars) << "Ignoring older identity packet for avatar" << getSessionUUID()
@ -1517,6 +1517,7 @@ void AvatarData::processAvatarIdentity(const Identity& identity, bool& identityC
if (_firstSkeletonCheck || (identity.skeletonModelURL != cannonicalSkeletonModelURL(emptyURL))) { if (_firstSkeletonCheck || (identity.skeletonModelURL != cannonicalSkeletonModelURL(emptyURL))) {
setSkeletonModelURL(identity.skeletonModelURL); setSkeletonModelURL(identity.skeletonModelURL);
identityChanged = true; identityChanged = true;
skeletonModelUrlChanged = true;
if (_firstSkeletonCheck) { if (_firstSkeletonCheck) {
displayNameChanged = true; displayNameChanged = true;
} }

View file

@ -368,6 +368,7 @@ public:
virtual ~AvatarData(); virtual ~AvatarData();
static const QUrl& defaultFullAvatarModelUrl(); static const QUrl& defaultFullAvatarModelUrl();
QUrl cannonicalSkeletonModelURL(const QUrl& empty) const;
virtual bool isMyAvatar() const { return false; } virtual bool isMyAvatar() const { return false; }
@ -536,9 +537,8 @@ public:
static void parseAvatarIdentityPacket(const QByteArray& data, Identity& identityOut); static void parseAvatarIdentityPacket(const QByteArray& data, Identity& identityOut);
// identityChanged returns true if identity has changed, false otherwise. // identityChanged returns true if identity has changed, false otherwise. Similarly for displayNameChanged and skeletonModelUrlChange.
// displayNameChanged returns true if displayName has changed, false otherwise. void processAvatarIdentity(const Identity& identity, bool& identityChanged, bool& displayNameChanged, bool& skeletonModelUrlChanged);
void processAvatarIdentity(const Identity& identity, bool& identityChanged, bool& displayNameChanged);
QByteArray identityByteArray() const; QByteArray identityByteArray() const;
@ -697,7 +697,6 @@ protected:
QVector<AttachmentData> _attachmentData; QVector<AttachmentData> _attachmentData;
QString _displayName; QString _displayName;
QString _sessionDisplayName { }; QString _sessionDisplayName { };
QUrl cannonicalSkeletonModelURL(const QUrl& empty) const;
QHash<QString, int> _jointIndices; ///< 1-based, since zero is returned for missing keys QHash<QString, int> _jointIndices; ///< 1-based, since zero is returned for missing keys
QStringList _jointNames; ///< in order of depth-first traversal QStringList _jointNames; ///< in order of depth-first traversal

View file

@ -148,8 +148,9 @@ void AvatarHashMap::processAvatarIdentityPacket(QSharedPointer<ReceivedMessage>
auto avatar = newOrExistingAvatar(identity.uuid, sendingNode); auto avatar = newOrExistingAvatar(identity.uuid, sendingNode);
bool identityChanged = false; bool identityChanged = false;
bool displayNameChanged = false; bool displayNameChanged = false;
bool skeletonModelUrlChanged = false;
// In this case, the "sendingNode" is the Avatar Mixer. // In this case, the "sendingNode" is the Avatar Mixer.
avatar->processAvatarIdentity(identity, identityChanged, displayNameChanged); avatar->processAvatarIdentity(identity, identityChanged, displayNameChanged, skeletonModelUrlChanged);
} }
} }

View file

@ -221,6 +221,12 @@ void HmdDisplayPlugin::internalPresent() {
float shiftLeftBy = getLeftCenterPixel() - (sourceSize.x / 2); float shiftLeftBy = getLeftCenterPixel() - (sourceSize.x / 2);
float newWidth = sourceSize.x - shiftLeftBy; float newWidth = sourceSize.x - shiftLeftBy;
// Experimentally adjusted the region presented in preview to avoid seeing the masked pixels and recenter the center...
static float SCALE_WIDTH = 0.9f;
static float SCALE_OFFSET = 2.0f;
newWidth *= SCALE_WIDTH;
shiftLeftBy *= SCALE_OFFSET;
const unsigned int RATIO_Y = 9; const unsigned int RATIO_Y = 9;
const unsigned int RATIO_X = 16; const unsigned int RATIO_X = 16;
glm::uvec2 originalClippedSize { newWidth, newWidth * RATIO_Y / RATIO_X }; glm::uvec2 originalClippedSize { newWidth, newWidth * RATIO_Y / RATIO_X };

View file

@ -460,17 +460,11 @@ FBXLight extractLight(const FBXNode& object) {
} }
QByteArray fileOnUrl(const QByteArray& filepath, const QString& url) { QByteArray fileOnUrl(const QByteArray& filepath, const QString& url) {
QString path = QFileInfo(url).path(); // in order to match the behaviour when loading models from remote URLs
QByteArray filename = filepath; // we assume that all external textures are right beside the loaded model
QFileInfo checkFile(path + "/" + filepath); // ignoring any relative paths or absolute paths inside of models
// check if the file exists at the RelativeFilename return filepath.mid(filepath.lastIndexOf('/') + 1);
if (!(checkFile.exists() && checkFile.isFile())) {
// if not, assume it is in the fbx directory
filename = filename.mid(filename.lastIndexOf('/') + 1);
}
return filename;
} }
FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QString& url) { FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QString& url) {

View file

@ -277,6 +277,23 @@ QString getEventBridgeJavascript() {
return javaScriptToInject; return javaScriptToInject;
} }
class EventBridgeWrapper : public QObject {
Q_OBJECT
Q_PROPERTY(QObject* eventBridge READ getEventBridge CONSTANT);
public:
EventBridgeWrapper(QObject* eventBridge, QObject* parent = nullptr) : QObject(parent), _eventBridge(eventBridge) {
}
QObject* getEventBridge() {
return _eventBridge;
}
private:
QObject* _eventBridge;
};
QQmlEngine* acquireEngine(QQuickWindow* window) { QQmlEngine* acquireEngine(QQuickWindow* window) {
Q_ASSERT(QThread::currentThread() == qApp->thread()); Q_ASSERT(QThread::currentThread() == qApp->thread());
@ -430,7 +447,6 @@ OffscreenQmlSurface::~OffscreenQmlSurface() {
_canvas->deleteLater(); _canvas->deleteLater();
_rootItem->deleteLater(); _rootItem->deleteLater();
_qmlComponent->deleteLater();
_quickWindow->deleteLater(); _quickWindow->deleteLater();
releaseEngine(); releaseEngine();
} }
@ -473,11 +489,12 @@ void OffscreenQmlSurface::create(QOpenGLContext* shareContext) {
_qmlContext = new QQmlContext(qmlEngine->rootContext()); _qmlContext = new QQmlContext(qmlEngine->rootContext());
_qmlContext->setContextProperty("offscreenWindow", QVariant::fromValue(getWindow())); _qmlContext->setContextProperty("offscreenWindow", QVariant::fromValue(getWindow()));
_qmlContext->setContextProperty("globalEventBridge", this); _qmlContext->setContextProperty("eventBridge", this);
_qmlContext->setContextProperty("webEntity", this); _qmlContext->setContextProperty("webEntity", this);
_qmlComponent = new QQmlComponent(qmlEngine); // FIXME Compatibility mechanism for existing HTML and JS that uses eventBridgeWrapper
// Find a way to flag older scripts using this mechanism and wanr that this is deprecated
_qmlContext->setContextProperty("eventBridgeWrapper", new EventBridgeWrapper(this, _qmlContext));
if (!_canvas->makeCurrent()) { if (!_canvas->makeCurrent()) {
qWarning("Failed to make context current for QML Renderer"); qWarning("Failed to make context current for QML Renderer");
@ -577,71 +594,79 @@ void OffscreenQmlSurface::setBaseUrl(const QUrl& baseUrl) {
_qmlContext->setBaseUrl(baseUrl); _qmlContext->setBaseUrl(baseUrl);
} }
QObject* OffscreenQmlSurface::load(const QUrl& qmlSource, std::function<void(QQmlContext*, QObject*)> f) { QObject* OffscreenQmlSurface::load(const QUrl& qmlSource, bool createNewContext, std::function<void(QQmlContext*, QObject*)> f) {
// Synchronous loading may take a while; restart the deadlock timer // Synchronous loading may take a while; restart the deadlock timer
QMetaObject::invokeMethod(qApp, "updateHeartbeat", Qt::DirectConnection); QMetaObject::invokeMethod(qApp, "updateHeartbeat", Qt::DirectConnection);
if ((qmlSource.isRelative() && !qmlSource.isEmpty()) || qmlSource.scheme() == QLatin1String("file")) { QQmlContext* targetContext = _qmlContext;
_qmlComponent->loadUrl(_qmlContext->resolvedUrl(qmlSource), QQmlComponent::PreferSynchronous); if (_rootItem && createNewContext) {
} else { targetContext = new QQmlContext(targetContext);
_qmlComponent->loadUrl(qmlSource, QQmlComponent::PreferSynchronous);
} }
QUrl finalQmlSource = qmlSource;
if ((qmlSource.isRelative() && !qmlSource.isEmpty()) || qmlSource.scheme() == QLatin1String("file")) {
finalQmlSource = _qmlContext->resolvedUrl(qmlSource);
}
if (_qmlComponent->isLoading()) { auto qmlComponent = new QQmlComponent(_qmlContext->engine(), finalQmlSource, QQmlComponent::PreferSynchronous);
connect(_qmlComponent, &QQmlComponent::statusChanged, this, if (qmlComponent->isLoading()) {
[this, f](QQmlComponent::Status){ connect(qmlComponent, &QQmlComponent::statusChanged, this,
finishQmlLoad(f); [this, qmlComponent, targetContext, f](QQmlComponent::Status) {
}); finishQmlLoad(qmlComponent, targetContext, f);
});
return nullptr; return nullptr;
} }
return finishQmlLoad(f); return finishQmlLoad(qmlComponent, targetContext, f);
}
QObject* OffscreenQmlSurface::loadInNewContext(const QUrl& qmlSource, std::function<void(QQmlContext*, QObject*)> f) {
return load(qmlSource, true, f);
}
QObject* OffscreenQmlSurface::load(const QUrl& qmlSource, std::function<void(QQmlContext*, QObject*)> f) {
return load(qmlSource, false, f);
} }
void OffscreenQmlSurface::clearCache() { void OffscreenQmlSurface::clearCache() {
_qmlContext->engine()->clearComponentCache(); _qmlContext->engine()->clearComponentCache();
} }
QObject* OffscreenQmlSurface::finishQmlLoad(std::function<void(QQmlContext*, QObject*)> f) { QObject* OffscreenQmlSurface::finishQmlLoad(QQmlComponent* qmlComponent, QQmlContext* qmlContext, std::function<void(QQmlContext*, QObject*)> f) {
#if 0 disconnect(qmlComponent, &QQmlComponent::statusChanged, this, 0);
if (!_rootItem) { if (qmlComponent->isError()) {
QQmlComponent component(_qmlContext->engine()); for (const auto& error : qmlComponent->errors()) {
component.setData(R"QML( qCWarning(glLogging) << error.url() << error.line() << error;
import QtQuick 2.0
import QtWebChannel 1.0
Item { Component.onCompleted: globalEventBridge.WebChannel.id = "globalEventBridge"; }
)QML", QUrl());
QObject *helper = component.create(_qmlContext);
qDebug() << "Created helper";
}
#endif
disconnect(_qmlComponent, &QQmlComponent::statusChanged, this, 0);
if (_qmlComponent->isError()) {
QList<QQmlError> errorList = _qmlComponent->errors();
foreach(const QQmlError& error, errorList) {
qWarning() << error.url() << error.line() << error;
} }
qmlComponent->deleteLater();
return nullptr; return nullptr;
} }
QObject* newObject = _qmlComponent->beginCreate(_qmlContext); QObject* newObject = qmlComponent->beginCreate(qmlContext);
if (_qmlComponent->isError()) { if (qmlComponent->isError()) {
QList<QQmlError> errorList = _qmlComponent->errors(); for (const auto& error : qmlComponent->errors()) {
foreach(const QQmlError& error, errorList)
qCWarning(glLogging) << error.url() << error.line() << error; qCWarning(glLogging) << error.url() << error.line() << error;
}
if (!_rootItem) { if (!_rootItem) {
qFatal("Unable to finish loading QML root"); qFatal("Unable to finish loading QML root");
} }
qmlComponent->deleteLater();
return nullptr; return nullptr;
} }
_qmlContext->engine()->setObjectOwnership(this, QQmlEngine::CppOwnership); qmlContext->engine()->setObjectOwnership(this, QQmlEngine::CppOwnership);
newObject->setProperty("eventBridge", QVariant::fromValue(this)); f(qmlContext, newObject);
f(_qmlContext, newObject); QObject* eventBridge = qmlContext->contextProperty("eventBridge").value<QObject*>();
_qmlComponent->completeCreate(); if (qmlContext != _qmlContext && eventBridge && eventBridge != this) {
// FIXME Compatibility mechanism for existing HTML and JS that uses eventBridgeWrapper
// Find a way to flag older scripts using this mechanism and wanr that this is deprecated
qmlContext->setContextProperty("eventBridgeWrapper", new EventBridgeWrapper(eventBridge, qmlContext));
}
qmlComponent->completeCreate();
qmlComponent->deleteLater();
// All quick items should be focusable // All quick items should be focusable

View file

@ -48,6 +48,8 @@ public:
void resize(const QSize& size, bool forceResize = false); void resize(const QSize& size, bool forceResize = false);
QSize size() const; QSize size() const;
Q_INVOKABLE QObject* load(const QUrl& qmlSource, bool createNewContext, std::function<void(QQmlContext*, QObject*)> f = [](QQmlContext*, QObject*) {});
Q_INVOKABLE QObject* loadInNewContext(const QUrl& qmlSource, std::function<void(QQmlContext*, QObject*)> f = [](QQmlContext*, QObject*) {});
Q_INVOKABLE QObject* load(const QUrl& qmlSource, std::function<void(QQmlContext*, QObject*)> f = [](QQmlContext*, QObject*) {}); Q_INVOKABLE QObject* load(const QUrl& qmlSource, std::function<void(QQmlContext*, QObject*)> f = [](QQmlContext*, QObject*) {});
Q_INVOKABLE QObject* load(const QString& qmlSourceFile, std::function<void(QQmlContext*, QObject*)> f = [](QQmlContext*, QObject*) {}) { Q_INVOKABLE QObject* load(const QString& qmlSourceFile, std::function<void(QQmlContext*, QObject*)> f = [](QQmlContext*, QObject*) {}) {
return load(QUrl(qmlSourceFile), f); return load(QUrl(qmlSourceFile), f);
@ -118,7 +120,7 @@ protected:
void setFocusText(bool newFocusText); void setFocusText(bool newFocusText);
private: private:
QObject* finishQmlLoad(std::function<void(QQmlContext*, QObject*)> f); QObject* finishQmlLoad(QQmlComponent* qmlComponent, QQmlContext* qmlContext, std::function<void(QQmlContext*, QObject*)> f);
QPointF mapWindowToUi(const QPointF& sourcePosition, QObject* sourceObject); QPointF mapWindowToUi(const QPointF& sourcePosition, QObject* sourceObject);
void setupFbo(); void setupFbo();
bool allowNewFrame(uint8_t fps); bool allowNewFrame(uint8_t fps);
@ -134,7 +136,6 @@ private:
QQuickWindow* _quickWindow { nullptr }; QQuickWindow* _quickWindow { nullptr };
QMyQuickRenderControl* _renderControl{ nullptr }; QMyQuickRenderControl* _renderControl{ nullptr };
QQmlContext* _qmlContext { nullptr }; QQmlContext* _qmlContext { nullptr };
QQmlComponent* _qmlComponent { nullptr };
QQuickItem* _rootItem { nullptr }; QQuickItem* _rootItem { nullptr };
OffscreenGLCanvas* _canvas { nullptr }; OffscreenGLCanvas* _canvas { nullptr };

View file

@ -63,11 +63,17 @@ void GLBackend::do_clearFramebuffer(const Batch& batch, size_t paramOffset) {
int useScissor = batch._params[paramOffset + 0]._int; int useScissor = batch._params[paramOffset + 0]._int;
GLuint glmask = 0; GLuint glmask = 0;
bool restoreStencilMask = false;
uint8_t cacheStencilMask = 0xFF;
if (masks & Framebuffer::BUFFER_STENCIL) { if (masks & Framebuffer::BUFFER_STENCIL) {
glClearStencil(stencil); glClearStencil(stencil);
glmask |= GL_STENCIL_BUFFER_BIT; glmask |= GL_STENCIL_BUFFER_BIT;
// TODO: we will probably need to also check the write mask of stencil like we do
// for depth buffer, but as would say a famous Fez owner "We'll cross that bridge when we come to it" cacheStencilMask = _pipeline._stateCache.stencilActivation.getWriteMaskFront();
if (cacheStencilMask != 0xFF) {
restoreStencilMask = true;
glStencilMask( 0xFF);
}
} }
bool restoreDepthMask = false; bool restoreDepthMask = false;
@ -121,6 +127,11 @@ void GLBackend::do_clearFramebuffer(const Batch& batch, size_t paramOffset) {
glDisable(GL_SCISSOR_TEST); glDisable(GL_SCISSOR_TEST);
} }
// Restore Stencil write mask
if (restoreStencilMask) {
glStencilMask(cacheStencilMask);
}
// Restore write mask meaning turn back off // Restore write mask meaning turn back off
if (restoreDepthMask) { if (restoreDepthMask) {
glDepthMask(GL_FALSE); glDepthMask(GL_FALSE);

View file

@ -22,8 +22,6 @@
#include <QtCore/QUrl> #include <QtCore/QUrl>
#include <QtNetwork/QHostInfo> #include <QtNetwork/QHostInfo>
#include <tbb/parallel_for.h>
#include <LogHandler.h> #include <LogHandler.h>
#include <shared/NetworkUtils.h> #include <shared/NetworkUtils.h>
#include <NumericalConstants.h> #include <NumericalConstants.h>
@ -586,7 +584,7 @@ SharedNodePointer LimitedNodeList::addOrUpdateNode(const QUuid& uuid, NodeType_t
return matchingNode; return matchingNode;
} else { } else {
// we didn't have this node, so add them // we didn't have this node, so add them
Node* newNode = new Node(uuid, nodeType, publicSocket, localSocket, permissions, connectionSecret, this); Node* newNode = new Node(uuid, nodeType, publicSocket, localSocket, permissions, connectionSecret);
if (nodeType == NodeType::AudioMixer) { if (nodeType == NodeType::AudioMixer) {
LimitedNodeList::flagTimeForConnectionStep(LimitedNodeList::AddedAudioMixer); LimitedNodeList::flagTimeForConnectionStep(LimitedNodeList::AddedAudioMixer);
@ -619,24 +617,28 @@ SharedNodePointer LimitedNodeList::addOrUpdateNode(const QUuid& uuid, NodeType_t
} }
// insert the new node and release our read lock // insert the new node and release our read lock
_nodeHash.insert(UUIDNodePair(newNode->getUUID(), newNodePointer)); _nodeHash.emplace(newNode->getUUID(), newNodePointer);
readLocker.unlock(); readLocker.unlock();
qCDebug(networking) << "Added" << *newNode; qCDebug(networking) << "Added" << *newNode;
auto weakPtr = newNodePointer.toWeakRef(); // We don't want the lambdas to hold a strong ref
emit nodeAdded(newNodePointer); emit nodeAdded(newNodePointer);
if (newNodePointer->getActiveSocket()) { if (newNodePointer->getActiveSocket()) {
emit nodeActivated(newNodePointer); emit nodeActivated(newNodePointer);
} else { } else {
connect(newNodePointer.data(), &NetworkPeer::socketActivated, this, [=] { connect(newNodePointer.data(), &NetworkPeer::socketActivated, this, [this, weakPtr] {
emit nodeActivated(newNodePointer); auto sharedPtr = weakPtr.lock();
disconnect(newNodePointer.data(), &NetworkPeer::socketActivated, this, 0); if (sharedPtr) {
emit nodeActivated(sharedPtr);
disconnect(sharedPtr.data(), &NetworkPeer::socketActivated, this, 0);
}
}); });
} }
// Signal when a socket changes, so we can start the hole punch over. // Signal when a socket changes, so we can start the hole punch over.
auto weakPtr = newNodePointer.toWeakRef(); // We don't want the lambda to hold a strong ref connect(newNodePointer.data(), &NetworkPeer::socketUpdated, this, [this, weakPtr] {
connect(newNodePointer.data(), &NetworkPeer::socketUpdated, this, [=] {
emit nodeSocketUpdated(weakPtr); emit nodeSocketUpdated(weakPtr);
}); });

View file

@ -31,7 +31,7 @@
#include <QtNetwork/QUdpSocket> #include <QtNetwork/QUdpSocket>
#include <QtNetwork/QHostAddress> #include <QtNetwork/QHostAddress>
#include <tbb/concurrent_unordered_map.h> #include <TBBHelpers.h>
#include <DependencyManager.h> #include <DependencyManager.h>
#include <SharedUtil.h> #include <SharedUtil.h>
@ -68,9 +68,8 @@ const QString USERNAME_UUID_REPLACEMENT_STATS_KEY = "$username";
const QString LOCAL_SOCKET_CHANGE_STAT = "LocalSocketChanges"; const QString LOCAL_SOCKET_CHANGE_STAT = "LocalSocketChanges";
using namespace tbb;
typedef std::pair<QUuid, SharedNodePointer> UUIDNodePair; typedef std::pair<QUuid, SharedNodePointer> UUIDNodePair;
typedef concurrent_unordered_map<QUuid, SharedNodePointer, UUIDHasher> NodeHash; typedef tbb::concurrent_unordered_map<QUuid, SharedNodePointer, UUIDHasher> NodeHash;
typedef quint8 PingType_t; typedef quint8 PingType_t;
namespace PingType { namespace PingType {

View file

@ -16,6 +16,8 @@
#include <QtCore/QBuffer> #include <QtCore/QBuffer>
#include <QtCore/QThread> #include <QtCore/QThread>
#include <ThreadHelpers.h>
#include "NetworkLogging.h" #include "NetworkLogging.h"
#include "NodeList.h" #include "NodeList.h"
#include "PacketReceiver.h" #include "PacketReceiver.h"
@ -30,12 +32,6 @@ MessagesClient::MessagesClient() {
connect(nodeList.data(), &LimitedNodeList::nodeActivated, this, &MessagesClient::handleNodeActivated); connect(nodeList.data(), &LimitedNodeList::nodeActivated, this, &MessagesClient::handleNodeActivated);
} }
void MessagesClient::init() {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "init", Qt::BlockingQueuedConnection);
}
}
void MessagesClient::decodeMessagesPacket(QSharedPointer<ReceivedMessage> receivedMessage, QString& channel, void MessagesClient::decodeMessagesPacket(QSharedPointer<ReceivedMessage> receivedMessage, QString& channel,
bool& isText, QString& message, QByteArray& data, QUuid& senderID) { bool& isText, QString& message, QByteArray& data, QUuid& senderID) {
quint16 channelLength; quint16 channelLength;
@ -185,3 +181,7 @@ void MessagesClient::handleNodeActivated(SharedNodePointer node) {
} }
} }
} }
void MessagesClient::startThread() {
moveToNewNamedThread(this, "Messages Client Thread");
}

View file

@ -28,7 +28,7 @@ class MessagesClient : public QObject, public Dependency {
public: public:
MessagesClient(); MessagesClient();
Q_INVOKABLE void init(); void startThread();
Q_INVOKABLE void sendMessage(QString channel, QString message, bool localOnly = false); Q_INVOKABLE void sendMessage(QString channel, QString message, bool localOnly = false);
Q_INVOKABLE void sendLocalMessage(QString channel, QString message); Q_INVOKABLE void sendLocalMessage(QString channel, QString message);

View file

@ -24,7 +24,7 @@
#include <QReadLocker> #include <QReadLocker>
#include <UUIDHasher.h> #include <UUIDHasher.h>
#include <tbb/concurrent_unordered_set.h> #include <TBBHelpers.h>
#include "HifiSockAddr.h" #include "HifiSockAddr.h"
#include "NetworkPeer.h" #include "NetworkPeer.h"
@ -40,7 +40,7 @@ public:
Node(const QUuid& uuid, NodeType_t type, Node(const QUuid& uuid, NodeType_t type,
const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket, const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket,
const NodePermissions& permissions, const QUuid& connectionSecret = QUuid(), const NodePermissions& permissions, const QUuid& connectionSecret = QUuid(),
QObject* parent = 0); QObject* parent = nullptr);
bool operator==(const Node& otherNode) const { return _uuid == otherNode._uuid; } bool operator==(const Node& otherNode) const { return _uuid == otherNode._uuid; }
bool operator!=(const Node& otherNode) const { return !(*this == otherNode); } bool operator!=(const Node& otherNode) const { return !(*this == otherNode); }

View file

@ -20,6 +20,7 @@
#include <QtNetwork/QHostInfo> #include <QtNetwork/QHostInfo>
#include <QtNetwork/QNetworkInterface> #include <QtNetwork/QNetworkInterface>
#include <ThreadHelpers.h>
#include <LogHandler.h> #include <LogHandler.h>
#include <UUID.h> #include <UUID.h>
@ -1115,3 +1116,8 @@ void NodeList::setRequestsDomainListData(bool isRequesting) {
}); });
_requestsDomainListData = isRequesting; _requestsDomainListData = isRequesting;
} }
void NodeList::startThread() {
moveToNewNamedThread(this, "NodeList Thread", QThread::TimeCriticalPriority);
}

View file

@ -20,7 +20,7 @@
#include <unistd.h> // not on windows, not needed for mac or windows #include <unistd.h> // not on windows, not needed for mac or windows
#endif #endif
#include <tbb/concurrent_unordered_set.h> #include <TBBHelpers.h>
#include <QtCore/QElapsedTimer> #include <QtCore/QElapsedTimer>
#include <QtCore/QMutex> #include <QtCore/QMutex>
@ -52,6 +52,7 @@ class NodeList : public LimitedNodeList {
SINGLETON_DEPENDENCY SINGLETON_DEPENDENCY
public: public:
void startThread();
NodeType_t getOwnerType() const { return _ownerType.load(); } NodeType_t getOwnerType() const { return _ownerType.load(); }
void setOwnerType(NodeType_t ownerType) { _ownerType.store(ownerType); } void setOwnerType(NodeType_t ownerType) { _ownerType.store(ownerType); }

View file

@ -20,6 +20,8 @@
class ReceivedPacketProcessor : public GenericThread { class ReceivedPacketProcessor : public GenericThread {
Q_OBJECT Q_OBJECT
public: public:
static const uint64_t MAX_WAIT_TIME { 100 }; // Max wait time in ms
ReceivedPacketProcessor(); ReceivedPacketProcessor();
/// Add packet from network receive thread to the processing queue. /// Add packet from network receive thread to the processing queue.
@ -63,8 +65,8 @@ protected:
/// Implements generic processing behavior for this thread. /// Implements generic processing behavior for this thread.
virtual bool process() override; virtual bool process() override;
/// Determines the timeout of the wait when there are no packets to process. Default value means no timeout /// Determines the timeout of the wait when there are no packets to process. Default value is 100ms to allow for regular event processing.
virtual unsigned long getMaxWait() const { return ULONG_MAX; } virtual uint32_t getMaxWait() const { return MAX_WAIT_TIME; }
/// Override to do work before the packets processing loop. Default does nothing. /// Override to do work before the packets processing loop. Default does nothing.
virtual void preProcess() { } virtual void preProcess() { }

View file

@ -20,6 +20,10 @@
#include <DependencyManager.h> #include <DependencyManager.h>
#include "AddressManager.h" #include "AddressManager.h"
UserActivityLogger::UserActivityLogger() {
_timer.start();
}
UserActivityLogger& UserActivityLogger::getInstance() { UserActivityLogger& UserActivityLogger::getInstance() {
static UserActivityLogger sharedInstance; static UserActivityLogger sharedInstance;
return sharedInstance; return sharedInstance;
@ -42,6 +46,12 @@ void UserActivityLogger::logAction(QString action, QJsonObject details, JSONCall
actionPart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data; name=\"action_name\""); actionPart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data; name=\"action_name\"");
actionPart.setBody(QByteArray().append(action)); actionPart.setBody(QByteArray().append(action));
multipart->append(actionPart); multipart->append(actionPart);
// Log the local-time that this event was logged
QHttpPart elapsedPart;
elapsedPart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data; name=\"elapsed_ms\"");
elapsedPart.setBody(QString::number(_timer.elapsed()).toLocal8Bit());
multipart->append(elapsedPart);
// If there are action details, add them to the multipart // If there are action details, add them to the multipart
if (!details.isEmpty()) { if (!details.isEmpty()) {

View file

@ -18,6 +18,7 @@
#include <QString> #include <QString>
#include <QJsonObject> #include <QJsonObject>
#include <QNetworkReply> #include <QNetworkReply>
#include <QElapsedTimer>
#include <SettingHandle.h> #include <SettingHandle.h>
#include "AddressManager.h" #include "AddressManager.h"
@ -51,8 +52,10 @@ private slots:
void requestError(QNetworkReply& errorReply); void requestError(QNetworkReply& errorReply);
private: private:
UserActivityLogger() {}; UserActivityLogger();
Setting::Handle<bool> _disabled { "UserActivityLoggerDisabled", false }; Setting::Handle<bool> _disabled { "UserActivityLoggerDisabled", false };
QElapsedTimer _timer;
}; };
#endif // hifi_UserActivityLogger_h #endif // hifi_UserActivityLogger_h

View file

@ -35,14 +35,13 @@ void JurisdictionListener::nodeKilled(SharedNodePointer node) {
} }
bool JurisdictionListener::queueJurisdictionRequest() { bool JurisdictionListener::queueJurisdictionRequest() {
auto packet = NLPacket::create(PacketType::JurisdictionRequest, 0);
auto nodeList = DependencyManager::get<NodeList>(); auto nodeList = DependencyManager::get<NodeList>();
int nodeCount = 0; int nodeCount = 0;
nodeList->eachNode([&](const SharedNodePointer& node) { nodeList->eachNode([&](const SharedNodePointer& node) {
if (node->getType() == getNodeType() && node->getActiveSocket()) { if (node->getType() == getNodeType() && node->getActiveSocket()) {
auto packet = NLPacket::create(PacketType::JurisdictionRequest, 0);
_packetSender.queuePacketForSending(node, std::move(packet)); _packetSender.queuePacketForSending(node, std::move(packet));
nodeCount++; nodeCount++;
} }

View file

@ -41,8 +41,6 @@ bool JurisdictionSender::process() {
// call our ReceivedPacketProcessor base class process so we'll get any pending packets // call our ReceivedPacketProcessor base class process so we'll get any pending packets
if (continueProcessing && (continueProcessing = ReceivedPacketProcessor::process())) { if (continueProcessing && (continueProcessing = ReceivedPacketProcessor::process())) {
auto packet = (_jurisdictionMap) ? _jurisdictionMap->packIntoPacket()
: JurisdictionMap::packEmptyJurisdictionIntoMessage(getNodeType());
int nodeCount = 0; int nodeCount = 0;
lockRequestingNodes(); lockRequestingNodes();
@ -53,6 +51,8 @@ bool JurisdictionSender::process() {
SharedNodePointer node = DependencyManager::get<NodeList>()->nodeWithUUID(nodeUUID); SharedNodePointer node = DependencyManager::get<NodeList>()->nodeWithUUID(nodeUUID);
if (node && node->getActiveSocket()) { if (node && node->getActiveSocket()) {
auto packet = (_jurisdictionMap) ? _jurisdictionMap->packIntoPacket()
: JurisdictionMap::packEmptyJurisdictionIntoMessage(getNodeType());
_packetSender.queuePacketForSending(node, std::move(packet)); _packetSender.queuePacketForSending(node, std::move(packet));
nodeCount++; nodeCount++;
} }

View file

@ -19,7 +19,6 @@
#include "AntialiasingEffect.h" #include "AntialiasingEffect.h"
#include "StencilMaskPass.h" #include "StencilMaskPass.h"
#include "TextureCache.h" #include "TextureCache.h"
#include "FramebufferCache.h"
#include "DependencyManager.h" #include "DependencyManager.h"
#include "ViewFrustum.h" #include "ViewFrustum.h"
#include "GeometryCache.h" #include "GeometryCache.h"
@ -40,9 +39,9 @@ Antialiasing::~Antialiasing() {
} }
} }
const gpu::PipelinePointer& Antialiasing::getAntialiasingPipeline() { const gpu::PipelinePointer& Antialiasing::getAntialiasingPipeline(RenderArgs* args) {
int width = DependencyManager::get<FramebufferCache>()->getFrameBufferSize().width(); int width = args->_viewport.z;
int height = DependencyManager::get<FramebufferCache>()->getFrameBufferSize().height(); int height = args->_viewport.w;
if (_antialiasingBuffer && _antialiasingBuffer->getSize() != uvec2(width, height)) { if (_antialiasingBuffer && _antialiasingBuffer->getSize() != uvec2(width, height)) {
_antialiasingBuffer.reset(); _antialiasingBuffer.reset();
@ -51,7 +50,7 @@ const gpu::PipelinePointer& Antialiasing::getAntialiasingPipeline() {
if (!_antialiasingBuffer) { if (!_antialiasingBuffer) {
// Link the antialiasing FBO to texture // Link the antialiasing FBO to texture
_antialiasingBuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("antialiasing")); _antialiasingBuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("antialiasing"));
auto format = gpu::Element::COLOR_SRGBA_32; // DependencyManager::get<FramebufferCache>()->getLightingTexture()->getTexelFormat(); auto format = gpu::Element::COLOR_SRGBA_32;
auto defaultSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_POINT); auto defaultSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_POINT);
_antialiasingTexture = gpu::Texture::createRenderBuffer(format, width, height, gpu::Texture::SINGLE_MIP, defaultSampler); _antialiasingTexture = gpu::Texture::createRenderBuffer(format, width, height, gpu::Texture::SINGLE_MIP, defaultSampler);
_antialiasingBuffer->setRenderBuffer(0, _antialiasingTexture); _antialiasingBuffer->setRenderBuffer(0, _antialiasingTexture);
@ -110,19 +109,13 @@ void Antialiasing::run(const render::RenderContextPointer& renderContext, const
RenderArgs* args = renderContext->args; RenderArgs* args = renderContext->args;
if (args->_renderMode == RenderArgs::MIRROR_RENDER_MODE) {
return;
}
gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { gpu::doInBatch(args->_context, [&](gpu::Batch& batch) {
batch.enableStereo(false); batch.enableStereo(false);
batch.setViewportTransform(args->_viewport); batch.setViewportTransform(args->_viewport);
// FIXME: NEED to simplify that code to avoid all the GeometryCahce call, this is purely pixel manipulation // FIXME: NEED to simplify that code to avoid all the GeometryCahce call, this is purely pixel manipulation
auto framebufferCache = DependencyManager::get<FramebufferCache>(); float fbWidth = renderContext->args->_viewport.z;
QSize framebufferSize = framebufferCache->getFrameBufferSize(); float fbHeight = renderContext->args->_viewport.w;
float fbWidth = framebufferSize.width();
float fbHeight = framebufferSize.height();
// float sMin = args->_viewport.x / fbWidth; // float sMin = args->_viewport.x / fbWidth;
// float sWidth = args->_viewport.z / fbWidth; // float sWidth = args->_viewport.z / fbWidth;
// float tMin = args->_viewport.y / fbHeight; // float tMin = args->_viewport.y / fbHeight;
@ -137,10 +130,10 @@ void Antialiasing::run(const render::RenderContextPointer& renderContext, const
batch.setModelTransform(Transform()); batch.setModelTransform(Transform());
// FXAA step // FXAA step
getAntialiasingPipeline(); auto pipeline = getAntialiasingPipeline(renderContext->args);
batch.setResourceTexture(0, sourceBuffer->getRenderBuffer(0)); batch.setResourceTexture(0, sourceBuffer->getRenderBuffer(0));
batch.setFramebuffer(_antialiasingBuffer); batch.setFramebuffer(_antialiasingBuffer);
batch.setPipeline(getAntialiasingPipeline()); batch.setPipeline(pipeline);
// initialize the view-space unpacking uniforms using frustum data // initialize the view-space unpacking uniforms using frustum data
float left, right, bottom, top, nearVal, farVal; float left, right, bottom, top, nearVal, farVal;

View file

@ -33,7 +33,7 @@ public:
void configure(const Config& config) {} void configure(const Config& config) {}
void run(const render::RenderContextPointer& renderContext, const gpu::FramebufferPointer& sourceBuffer); void run(const render::RenderContextPointer& renderContext, const gpu::FramebufferPointer& sourceBuffer);
const gpu::PipelinePointer& getAntialiasingPipeline(); const gpu::PipelinePointer& getAntialiasingPipeline(RenderArgs* args);
const gpu::PipelinePointer& getBlendPipeline(); const gpu::PipelinePointer& getBlendPipeline();
private: private:

View file

@ -19,7 +19,6 @@
#include <ViewFrustum.h> #include <ViewFrustum.h>
#include "GeometryCache.h" #include "GeometryCache.h"
#include "FramebufferCache.h"
#include "TextureCache.h" #include "TextureCache.h"
#include "DeferredLightingEffect.h" #include "DeferredLightingEffect.h"
@ -410,7 +409,6 @@ void DebugDeferredBuffer::run(const RenderContextPointer& renderContext, const I
batch.setViewportTransform(args->_viewport); batch.setViewportTransform(args->_viewport);
const auto geometryBuffer = DependencyManager::get<GeometryCache>(); const auto geometryBuffer = DependencyManager::get<GeometryCache>();
const auto framebufferCache = DependencyManager::get<FramebufferCache>();
const auto textureCache = DependencyManager::get<TextureCache>(); const auto textureCache = DependencyManager::get<TextureCache>();
glm::mat4 projMat; glm::mat4 projMat;

View file

@ -418,10 +418,7 @@ model::MeshPointer DeferredLightingEffect::getSpotLightMesh() {
} }
void PreparePrimaryFramebuffer::run(const RenderContextPointer& renderContext, gpu::FramebufferPointer& primaryFramebuffer) { void PreparePrimaryFramebuffer::run(const RenderContextPointer& renderContext, gpu::FramebufferPointer& primaryFramebuffer) {
glm::uvec2 frameSize(renderContext->args->_viewport.z, renderContext->args->_viewport.w);
auto framebufferCache = DependencyManager::get<FramebufferCache>();
auto framebufferSize = framebufferCache->getFrameBufferSize();
glm::uvec2 frameSize(framebufferSize.width(), framebufferSize.height());
// Resizing framebuffers instead of re-building them seems to cause issues with threaded // Resizing framebuffers instead of re-building them seems to cause issues with threaded
// rendering // rendering
@ -504,10 +501,7 @@ void RenderDeferredSetup::run(const render::RenderContextPointer& renderContext,
{ {
// Framebuffer copy operations cannot function as multipass stereo operations. // Framebuffer copy operations cannot function as multipass stereo operations.
batch.enableStereo(false); batch.enableStereo(false);
// perform deferred lighting, rendering to free fbo
auto framebufferCache = DependencyManager::get<FramebufferCache>();
auto textureCache = DependencyManager::get<TextureCache>(); auto textureCache = DependencyManager::get<TextureCache>();
auto deferredLightingEffect = DependencyManager::get<DeferredLightingEffect>(); auto deferredLightingEffect = DependencyManager::get<DeferredLightingEffect>();

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