mirror of
https://github.com/overte-org/overte.git
synced 2025-04-22 13:53:26 +02:00
Merge remote-tracking branch 'upstream/master' into switch
This commit is contained in:
commit
59e7db2d6b
94 changed files with 2140 additions and 910 deletions
CMakeLists.txt
assignment-client/src/avatars
AvatarMixer.cppAvatarMixer.hAvatarMixerClientData.cppAvatarMixerSlave.cppMixerAvatar.cppMixerAvatar.h
cmake
domain-server/resources
hifi_singleton.pyinterface
resources
images
meshes
qml/hifi/audio
src
libraries
avatars-renderer/src/avatars-renderer
avatars/src
controllers/src/controllers
display-plugins/src/display-plugins
entities-renderer/src
entities/src
fbx/src
input-plugins/src/input-plugins
midi/src
networking/src/udt
qml/src/qml/impl
render-utils/src
render/src/render
shaders/src/shaders
shared/src
ui/src
plugins
prebuild.pyscripts/system
tools/ci-scripts
|
@ -10,7 +10,14 @@ endif()
|
|||
include("${CMAKE_CURRENT_SOURCE_DIR}/cmake/macros/TargetPython.cmake")
|
||||
target_python()
|
||||
|
||||
if (HIFI_ANDROID )
|
||||
# set our OS X deployment target
|
||||
# (needs to be set before first project() call and before prebuild.py)
|
||||
# Will affect VCPKG dependencies
|
||||
if (APPLE)
|
||||
set(ENV{MACOSX_DEPLOYMENT_TARGET} 10.9)
|
||||
endif()
|
||||
|
||||
if (HIFI_ANDROID)
|
||||
execute_process(
|
||||
COMMAND ${HIFI_PYTHON_EXEC} ${CMAKE_CURRENT_SOURCE_DIR}/prebuild.py --android ${HIFI_ANDROID_APP} --build-root ${CMAKE_BINARY_DIR}
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
|
|
|
@ -82,6 +82,7 @@ AvatarMixer::AvatarMixer(ReceivedMessage& message) :
|
|||
packetReceiver.registerListener(PacketType::BulkAvatarTraitsAck, this, "queueIncomingPacket");
|
||||
packetReceiver.registerListenerForTypes({ PacketType::OctreeStats, PacketType::EntityData, PacketType::EntityErase },
|
||||
this, "handleOctreePacket");
|
||||
packetReceiver.registerListener(PacketType::ChallengeOwnership, this, "handleChallengeOwnership");
|
||||
|
||||
packetReceiver.registerListenerForTypes({
|
||||
PacketType::ReplicatedAvatarIdentity,
|
||||
|
@ -367,10 +368,13 @@ void AvatarMixer::manageIdentityData(const SharedNodePointer& node) {
|
|||
return;
|
||||
}
|
||||
|
||||
bool sendIdentity = false;
|
||||
if (nodeData && nodeData->getAvatarSessionDisplayNameMustChange()) {
|
||||
AvatarData& avatar = nodeData->getAvatar();
|
||||
const QString& existingBaseDisplayName = nodeData->getAvatar().getSessionDisplayName();
|
||||
MixerAvatar& avatar = nodeData->getAvatar();
|
||||
bool sendIdentity = avatar.needsIdentityUpdate();
|
||||
if (sendIdentity) {
|
||||
nodeData->flagIdentityChange();
|
||||
}
|
||||
if (nodeData->getAvatarSessionDisplayNameMustChange()) {
|
||||
const QString& existingBaseDisplayName = avatar.getSessionDisplayName();
|
||||
if (!existingBaseDisplayName.isEmpty()) {
|
||||
SessionDisplayName existingDisplayName { existingBaseDisplayName };
|
||||
|
||||
|
@ -414,10 +418,11 @@ void AvatarMixer::manageIdentityData(const SharedNodePointer& node) {
|
|||
sendIdentityPacket(nodeData, node); // Tell node whose name changed about its new session display name or avatar.
|
||||
// since this packet includes a change to either the skeleton model URL or the display name
|
||||
// it needs a new sequence number
|
||||
nodeData->getAvatar().pushIdentitySequenceNumber();
|
||||
avatar.pushIdentitySequenceNumber();
|
||||
|
||||
// tell node whose name changed about its new session display name or avatar.
|
||||
sendIdentityPacket(nodeData, node);
|
||||
avatar.setNeedsIdentityUpdate(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1123,6 +1128,16 @@ void AvatarMixer::entityChange() {
|
|||
_dirtyHeroStatus = true;
|
||||
}
|
||||
|
||||
void AvatarMixer::handleChallengeOwnership(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
|
||||
if (senderNode->getType() == NodeType::Agent && senderNode->getLinkedData()) {
|
||||
auto clientData = static_cast<AvatarMixerClientData*>(senderNode->getLinkedData());
|
||||
auto avatar = clientData->getAvatarSharedPointer();
|
||||
if (avatar) {
|
||||
avatar->handleChallengeResponse(message.data());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AvatarMixer::aboutToFinish() {
|
||||
DependencyManager::destroy<ResourceManager>();
|
||||
DependencyManager::destroy<ResourceCacheSharedItems>();
|
||||
|
|
|
@ -65,6 +65,7 @@ private slots:
|
|||
void domainSettingsRequestComplete();
|
||||
void handlePacketVersionMismatch(PacketType type, const HifiSockAddr& senderSockAddr, const QUuid& senderUUID);
|
||||
void handleOctreePacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
|
||||
void handleChallengeOwnership(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
|
||||
void start();
|
||||
|
||||
private:
|
||||
|
|
|
@ -81,6 +81,10 @@ int AvatarMixerClientData::processPackets(const SlaveSharedData& slaveSharedData
|
|||
}
|
||||
assert(_packetQueue.empty());
|
||||
|
||||
if (_avatar) {
|
||||
_avatar->processCertifyEvents();
|
||||
}
|
||||
|
||||
return packetsProcessed;
|
||||
}
|
||||
|
||||
|
@ -200,6 +204,7 @@ void AvatarMixerClientData::processSetTraitsMessage(ReceivedMessage& message,
|
|||
if (traitType == AvatarTraits::SkeletonModelURL) {
|
||||
// special handling for skeleton model URL, since we need to make sure it is in the whitelist
|
||||
checkSkeletonURLAgainstWhitelist(slaveSharedData, sendingNode, packetTraitVersion);
|
||||
_avatar->fetchAvatarFST();
|
||||
}
|
||||
|
||||
anyTraitsChanged = true;
|
||||
|
|
|
@ -157,6 +157,11 @@ qint64 AvatarMixerSlave::addChangedTraitsToBulkPacket(AvatarMixerClientData* lis
|
|||
++simpleReceivedIt;
|
||||
}
|
||||
|
||||
if (bytesWritten > 0 && sendingAvatar->isCertifyFailed()) {
|
||||
// Resend identity packet if certification failed:
|
||||
sendingAvatar->setNeedsIdentityUpdate();
|
||||
}
|
||||
|
||||
// enumerate the received instanced trait versions
|
||||
auto instancedReceivedIt = lastReceivedVersions.instancedCBegin();
|
||||
while (instancedReceivedIt != lastReceivedVersions.instancedCEnd()) {
|
||||
|
|
345
assignment-client/src/avatars/MixerAvatar.cpp
Normal file
345
assignment-client/src/avatars/MixerAvatar.cpp
Normal file
|
@ -0,0 +1,345 @@
|
|||
//
|
||||
// MixerAvatar.cpp
|
||||
// assignment-client/src/avatars
|
||||
//
|
||||
// Created by Simon Walton April 2019
|
||||
// Copyright 2019 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 "MixerAvatar.h"
|
||||
|
||||
#include <QRegularExpression>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QNetworkReply>
|
||||
#include <QCryptographicHash>
|
||||
#include <QApplication>
|
||||
|
||||
#include <ResourceManager.h>
|
||||
#include <NetworkAccessManager.h>
|
||||
#include <NetworkingConstants.h>
|
||||
#include <EntityItem.h>
|
||||
#include <EntityItemProperties.h>
|
||||
#include "ClientTraitsHandler.h"
|
||||
#include "AvatarLogging.h"
|
||||
|
||||
void MixerAvatar::fetchAvatarFST() {
|
||||
_verifyState = nonCertified;
|
||||
|
||||
_pendingEvent = false;
|
||||
|
||||
QUrl avatarURL = getSkeletonModelURL();
|
||||
if (avatarURL.isEmpty() || avatarURL.isLocalFile() || avatarURL.scheme() == "qrc") {
|
||||
// Not network FST.
|
||||
return;
|
||||
}
|
||||
_certificateIdFromURL.clear();
|
||||
_certificateIdFromFST.clear();
|
||||
_marketplaceIdFromURL.clear();
|
||||
_marketplaceIdFromFST.clear();
|
||||
auto resourceManager = DependencyManager::get<ResourceManager>();
|
||||
|
||||
// Match UUID + (optionally) URL cert
|
||||
static const QRegularExpression marketIdRegex{
|
||||
"^https://.*?highfidelity\\.com/api/.*?/commerce/entity_edition/([-0-9a-z]{36})(.*?certificate_id=([\\w/+%]+)|.*).*$"
|
||||
};
|
||||
auto marketIdMatch = marketIdRegex.match(avatarURL.toDisplayString());
|
||||
if (marketIdMatch.hasMatch()) {
|
||||
QMutexLocker certifyLocker(&_avatarCertifyLock);
|
||||
_marketplaceIdFromURL = marketIdMatch.captured(1);
|
||||
if (marketIdMatch.lastCapturedIndex() == 3) {
|
||||
_certificateIdFromURL = QUrl::fromPercentEncoding(marketIdMatch.captured(3).toUtf8());
|
||||
}
|
||||
}
|
||||
|
||||
ResourceRequest* fstRequest = resourceManager->createResourceRequest(this, avatarURL);
|
||||
if (fstRequest) {
|
||||
QMutexLocker certifyLocker(&_avatarCertifyLock);
|
||||
|
||||
_avatarRequest = fstRequest;
|
||||
_verifyState = requestingFST;
|
||||
connect(fstRequest, &ResourceRequest::finished, this, &MixerAvatar::fstRequestComplete);
|
||||
fstRequest->send();
|
||||
} else {
|
||||
qCDebug(avatars) << "Couldn't create FST request for" << avatarURL;
|
||||
_verifyState = error;
|
||||
}
|
||||
_needsIdentityUpdate = true;
|
||||
}
|
||||
|
||||
void MixerAvatar::fstRequestComplete() {
|
||||
ResourceRequest* fstRequest = static_cast<ResourceRequest*>(QObject::sender());
|
||||
QMutexLocker certifyLocker(&_avatarCertifyLock);
|
||||
if (fstRequest == _avatarRequest) {
|
||||
auto result = fstRequest->getResult();
|
||||
if (result != ResourceRequest::Success) {
|
||||
_verifyState = error;
|
||||
qCDebug(avatars) << "FST request for" << fstRequest->getUrl() << "failed:" << result;
|
||||
} else {
|
||||
_avatarFSTContents = fstRequest->getData();
|
||||
_verifyState = receivedFST;
|
||||
_pendingEvent = true;
|
||||
}
|
||||
_avatarRequest->deleteLater();
|
||||
_avatarRequest = nullptr;
|
||||
} else {
|
||||
qCDebug(avatars) << "Incorrect request for" << getDisplayName();
|
||||
}
|
||||
}
|
||||
|
||||
bool MixerAvatar::generateFSTHash() {
|
||||
if (_avatarFSTContents.length() == 0) {
|
||||
return false;
|
||||
}
|
||||
QByteArray hashJson = canonicalJson(_avatarFSTContents);
|
||||
QCryptographicHash fstHash(QCryptographicHash::Sha256);
|
||||
fstHash.addData(hashJson);
|
||||
_certificateHash = fstHash.result();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MixerAvatar::validateFSTHash(const QString& publicKey) {
|
||||
// Guess we should refactor this stuff into a Authorization namespace ...
|
||||
return EntityItemProperties::verifySignature(publicKey, _certificateHash,
|
||||
QByteArray::fromBase64(_certificateIdFromFST.toUtf8()));
|
||||
}
|
||||
|
||||
QByteArray MixerAvatar::canonicalJson(const QString fstFile) {
|
||||
QStringList fstLines = fstFile.split("\n", QString::SkipEmptyParts);
|
||||
static const QString fstKeywordsReg {
|
||||
"(marketplaceID|itemDescription|itemCategories|itemArtist|itemLicenseUrl|limitedRun|itemName|"
|
||||
"filename|texdir|script|editionNumber|certificateID)"
|
||||
};
|
||||
QRegularExpression fstLineRegExp { QString("^\\s*") + fstKeywordsReg + "\\s*=\\s*(\\S.*)$" };
|
||||
QStringListIterator fstLineIter(fstLines);
|
||||
|
||||
QJsonObject certifiedItems;
|
||||
QStringList scripts;
|
||||
while (fstLineIter.hasNext()) {
|
||||
auto line = fstLineIter.next();
|
||||
auto lineMatch = fstLineRegExp.match(line);
|
||||
if (lineMatch.hasMatch()) {
|
||||
QString key = lineMatch.captured(1);
|
||||
if (key == "certificateID") {
|
||||
_certificateIdFromFST = lineMatch.captured(2);
|
||||
} else if (key == "itemDescription") {
|
||||
// Item description can be multiline - intermediate lines end in <CR>
|
||||
QString itemDesc = lineMatch.captured(2);
|
||||
while (itemDesc.endsWith('\r') && fstLineIter.hasNext()) {
|
||||
itemDesc += '\n' + fstLineIter.next();
|
||||
}
|
||||
certifiedItems[key] = QJsonValue(itemDesc);
|
||||
} else if (key == "limitedRun" || key == "editionNumber") {
|
||||
double value = lineMatch.captured(2).toDouble();
|
||||
if (value != 0.0) {
|
||||
certifiedItems[key] = QJsonValue(value);
|
||||
}
|
||||
} else if (key == "script") {
|
||||
scripts.append(lineMatch.captured(2).trimmed());
|
||||
} else {
|
||||
certifiedItems[key] = QJsonValue(lineMatch.captured(2));
|
||||
if (key == "marketplaceID") {
|
||||
_marketplaceIdFromFST = lineMatch.captured(2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!scripts.empty()) {
|
||||
scripts.sort();
|
||||
certifiedItems["script"] = QJsonArray::fromStringList(scripts);
|
||||
}
|
||||
|
||||
QJsonDocument jsonDocCertifiedItems(certifiedItems);
|
||||
//Example working form:
|
||||
//return R"({"editionNumber":34,"filename":"http://mpassets.highfidelity.com/7f142fde-541a-4902-b33a-25fa89dfba21-v1/Bridger/Hifi_Toon_Male_3.fbx","itemArtist":"EgyMax",
|
||||
//"itemCategories":"Avatars","itemDescription":"This is my first avatar. I hope you like it. More will come","itemName":"Bridger","limitedRun":-1,
|
||||
//"marketplaceID":"7f142fde-541a-4902-b33a-25fa89dfba21","texdir":"http://mpassets.highfidelity.com/7f142fde-541a-4902-b33a-25fa89dfba21-v1/Bridger/textures"})";
|
||||
return jsonDocCertifiedItems.toJson(QJsonDocument::Compact);
|
||||
}
|
||||
|
||||
void MixerAvatar::ownerRequestComplete() {
|
||||
QMutexLocker certifyLocker(&_avatarCertifyLock);
|
||||
QNetworkReply* networkReply = static_cast<QNetworkReply*>(QObject::sender());
|
||||
|
||||
if (networkReply->error() == QNetworkReply::NoError) {
|
||||
_dynamicMarketResponse = networkReply->readAll();
|
||||
_verifyState = ownerResponse;
|
||||
_pendingEvent = true;
|
||||
} else {
|
||||
auto jsonData = QJsonDocument::fromJson(networkReply->readAll())["data"];
|
||||
if (!jsonData.isUndefined() && !jsonData.toObject()["message"].isUndefined()) {
|
||||
qCDebug(avatars) << "Owner lookup failed for" << getDisplayName() << ":"
|
||||
<< jsonData.toObject()["message"].toString();
|
||||
_verifyState = error;
|
||||
_pendingEvent = false;
|
||||
}
|
||||
}
|
||||
networkReply->deleteLater();
|
||||
}
|
||||
|
||||
void MixerAvatar::processCertifyEvents() {
|
||||
if (!_pendingEvent) {
|
||||
return;
|
||||
}
|
||||
|
||||
QMutexLocker certifyLocker(&_avatarCertifyLock);
|
||||
switch (_verifyState) {
|
||||
|
||||
case receivedFST:
|
||||
{
|
||||
generateFSTHash();
|
||||
if (_certificateIdFromFST.length() != 0) {
|
||||
QString& marketplacePublicKey = EntityItem::_marketplacePublicKey;
|
||||
bool staticVerification = validateFSTHash(marketplacePublicKey);
|
||||
_verifyState = staticVerification ? staticValidation : verificationFailed;
|
||||
|
||||
if (_verifyState == staticValidation) {
|
||||
static const QString POP_MARKETPLACE_API { "/api/v1/commerce/proof_of_purchase_status/transfer" };
|
||||
auto& networkAccessManager = NetworkAccessManager::getInstance();
|
||||
QNetworkRequest networkRequest;
|
||||
networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
|
||||
networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
QUrl requestURL = NetworkingConstants::METAVERSE_SERVER_URL();
|
||||
requestURL.setPath(POP_MARKETPLACE_API);
|
||||
networkRequest.setUrl(requestURL);
|
||||
|
||||
QJsonObject request;
|
||||
request["certificate_id"] = _certificateIdFromFST;
|
||||
_verifyState = requestingOwner;
|
||||
QNetworkReply* networkReply = networkAccessManager.put(networkRequest, QJsonDocument(request).toJson());
|
||||
connect(networkReply, &QNetworkReply::finished, this, &MixerAvatar::ownerRequestComplete);
|
||||
} else {
|
||||
_needsIdentityUpdate = true;
|
||||
_pendingEvent = false;
|
||||
qCDebug(avatars) << "Avatar" << getDisplayName() << "FAILED static certification";
|
||||
}
|
||||
} else { // FST doesn't have a certificate, so noncertified rather than failed:
|
||||
_pendingEvent = false;
|
||||
_verifyState = nonCertified;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case ownerResponse:
|
||||
{
|
||||
QJsonDocument responseJson = QJsonDocument::fromJson(_dynamicMarketResponse.toUtf8());
|
||||
QString ownerPublicKey;
|
||||
bool ownerValid = false;
|
||||
if (responseJson["status"].toString() == "success") {
|
||||
QJsonValue jsonData = responseJson["data"];
|
||||
if (jsonData.isObject()) {
|
||||
auto ownerJson = jsonData["transfer_recipient_key"];
|
||||
if (ownerJson.isString()) {
|
||||
ownerPublicKey = ownerJson.toString();
|
||||
}
|
||||
auto transferStatusJson = jsonData["transfer_status"];
|
||||
if (transferStatusJson.isArray() && transferStatusJson.toArray()[0].toString() == "confirmed") {
|
||||
ownerValid = true;
|
||||
}
|
||||
}
|
||||
if (ownerValid && !ownerPublicKey.isEmpty()) {
|
||||
if (ownerPublicKey.startsWith("-----BEGIN ")){
|
||||
_ownerPublicKey = ownerPublicKey;
|
||||
} else {
|
||||
_ownerPublicKey = "-----BEGIN PUBLIC KEY-----\n"
|
||||
+ ownerPublicKey
|
||||
+ "\n-----END PUBLIC KEY-----\n";
|
||||
}
|
||||
sendOwnerChallenge();
|
||||
_verifyState = challengeClient;
|
||||
} else {
|
||||
_verifyState = error;
|
||||
}
|
||||
} else {
|
||||
qCDebug(avatars) << "Get owner status failed for " << getDisplayName() << _marketplaceIdFromURL <<
|
||||
"message:" << responseJson["message"].toString();
|
||||
_verifyState = error;
|
||||
}
|
||||
_pendingEvent = false;
|
||||
break;
|
||||
}
|
||||
|
||||
case challengeResponse:
|
||||
{
|
||||
if (_challengeResponse.length() < 8) {
|
||||
_verifyState = error;
|
||||
_pendingEvent = false;
|
||||
break;
|
||||
}
|
||||
|
||||
int avatarIDLength;
|
||||
int signedNonceLength;
|
||||
{
|
||||
QDataStream responseStream(_challengeResponse);
|
||||
responseStream.setByteOrder(QDataStream::LittleEndian);
|
||||
responseStream >> avatarIDLength >> signedNonceLength;
|
||||
}
|
||||
QByteArray avatarID(_challengeResponse.data() + 2 * sizeof(int), avatarIDLength);
|
||||
QByteArray signedNonce(_challengeResponse.data() + 2 * sizeof(int) + avatarIDLength, signedNonceLength);
|
||||
|
||||
bool challengeResult = EntityItemProperties::verifySignature(_ownerPublicKey, _challengeNonceHash,
|
||||
QByteArray::fromBase64(signedNonce));
|
||||
_verifyState = challengeResult ? verificationSucceeded : verificationFailed;
|
||||
_needsIdentityUpdate = true;
|
||||
if (_verifyState == verificationFailed) {
|
||||
qCDebug(avatars) << "Dynamic verification FAILED for " << getDisplayName() << getSessionUUID();
|
||||
} else {
|
||||
qCDebug(avatars) << "Dynamic verification SUCCEEDED for " << getDisplayName() << getSessionUUID();
|
||||
}
|
||||
_pendingEvent = false;
|
||||
break;
|
||||
}
|
||||
|
||||
case requestingOwner:
|
||||
{ // Qt networking done on this thread:
|
||||
QCoreApplication::processEvents();
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
qCDebug(avatars) << "Unexpected verify state" << _verifyState;
|
||||
break;
|
||||
|
||||
} // close switch
|
||||
}
|
||||
|
||||
void MixerAvatar::sendOwnerChallenge() {
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
QByteArray avatarID = ("{" + _marketplaceIdFromFST + "}").toUtf8();
|
||||
QByteArray nonce = QUuid::createUuid().toByteArray();
|
||||
|
||||
auto challengeOwnershipPacket = NLPacket::create(PacketType::ChallengeOwnership,
|
||||
2 * sizeof(int) + nonce.length() + avatarID.length(), true);
|
||||
challengeOwnershipPacket->writePrimitive(avatarID.length());
|
||||
challengeOwnershipPacket->writePrimitive(nonce.length());
|
||||
challengeOwnershipPacket->write(avatarID);
|
||||
challengeOwnershipPacket->write(nonce);
|
||||
|
||||
nodeList->sendPacket(std::move(challengeOwnershipPacket), *(nodeList->nodeWithUUID(getSessionUUID())) );
|
||||
QCryptographicHash nonceHash(QCryptographicHash::Sha256);
|
||||
nonceHash.addData(nonce);
|
||||
_challengeNonceHash = nonceHash.result();
|
||||
|
||||
static constexpr int CHALLENGE_TIMEOUT_MS = 10 * 1000; // 10 s
|
||||
_challengeTimeout.setInterval(CHALLENGE_TIMEOUT_MS);
|
||||
_challengeTimeout.connect(&_challengeTimeout, &QTimer::timeout, [this]() {
|
||||
_verifyState = verificationFailed;
|
||||
_needsIdentityUpdate = true;
|
||||
});
|
||||
}
|
||||
|
||||
void MixerAvatar::handleChallengeResponse(ReceivedMessage* response) {
|
||||
QByteArray avatarID;
|
||||
QByteArray encryptedNonce;
|
||||
QMutexLocker certifyLocker(&_avatarCertifyLock);
|
||||
if (_verifyState == challengeClient) {
|
||||
_challengeTimeout.stop();
|
||||
_challengeResponse = response->readAll();
|
||||
_verifyState = challengeResponse;
|
||||
_pendingEvent = true;
|
||||
}
|
||||
}
|
|
@ -17,14 +17,55 @@
|
|||
|
||||
#include <AvatarData.h>
|
||||
|
||||
class ResourceRequest;
|
||||
|
||||
class MixerAvatar : public AvatarData {
|
||||
public:
|
||||
bool getNeedsHeroCheck() const { return _needsHeroCheck; }
|
||||
void setNeedsHeroCheck(bool needsHeroCheck = true)
|
||||
{ _needsHeroCheck = needsHeroCheck; }
|
||||
void setNeedsHeroCheck(bool needsHeroCheck = true) { _needsHeroCheck = needsHeroCheck; }
|
||||
|
||||
void fetchAvatarFST();
|
||||
virtual bool isCertifyFailed() const override { return _verifyState == verificationFailed; }
|
||||
bool needsIdentityUpdate() const { return _needsIdentityUpdate; }
|
||||
void setNeedsIdentityUpdate(bool value = true) { _needsIdentityUpdate = value; }
|
||||
|
||||
void processCertifyEvents();
|
||||
void handleChallengeResponse(ReceivedMessage* response);
|
||||
|
||||
private:
|
||||
bool _needsHeroCheck { false };
|
||||
|
||||
// Avatar certification/verification:
|
||||
enum VerifyState { nonCertified, requestingFST, receivedFST, staticValidation, requestingOwner, ownerResponse,
|
||||
challengeClient, challengeResponse, verified, verificationFailed, verificationSucceeded, error };
|
||||
Q_ENUM(VerifyState);
|
||||
VerifyState _verifyState { nonCertified };
|
||||
std::atomic<bool> _pendingEvent { false };
|
||||
QMutex _avatarCertifyLock;
|
||||
ResourceRequest* _avatarRequest { nullptr };
|
||||
QString _marketplaceIdFromURL;
|
||||
QString _marketplaceIdFromFST;
|
||||
QByteArray _avatarFSTContents;
|
||||
QByteArray _certificateHash;
|
||||
QString _certificateIdFromURL;
|
||||
QString _certificateIdFromFST;
|
||||
QString _dynamicMarketResponse;
|
||||
QString _ownerPublicKey;
|
||||
QByteArray _challengeNonceHash;
|
||||
QByteArray _challengeResponse;
|
||||
QTimer _challengeTimeout;
|
||||
bool _needsIdentityUpdate { false };
|
||||
|
||||
bool generateFSTHash();
|
||||
bool validateFSTHash(const QString& publicKey);
|
||||
QByteArray canonicalJson(const QString fstFile);
|
||||
void sendOwnerChallenge();
|
||||
|
||||
static const QString VERIFY_FAIL_MODEL;
|
||||
|
||||
private slots:
|
||||
void fstRequestComplete();
|
||||
void ownerRequestComplete();
|
||||
};
|
||||
|
||||
using MixerAvatarSharedPointer = std::shared_ptr<MixerAvatar>;
|
||||
|
|
|
@ -88,11 +88,9 @@ if (APPLE)
|
|||
exec_program(sw_vers ARGS -productVersion OUTPUT_VARIABLE OSX_VERSION)
|
||||
string(REGEX MATCH "^[0-9]+\\.[0-9]+" OSX_VERSION ${OSX_VERSION})
|
||||
message(STATUS "Detected OS X version = ${OSX_VERSION}")
|
||||
message(STATUS "OS X deployment target = ${CMAKE_OSX_DEPLOYMENT_TARGET}")
|
||||
|
||||
set(OSX_SDK "${OSX_VERSION}" CACHE String "OS X SDK version to look for inside Xcode bundle or at OSX_SDK_PATH")
|
||||
|
||||
# set our OS X deployment target
|
||||
set(CMAKE_OSX_DEPLOYMENT_TARGET 10.9)
|
||||
set(OSX_SDK "${OSX_VERSION}" CACHE STRING "OS X SDK version to look for inside Xcode bundle or at OSX_SDK_PATH")
|
||||
|
||||
# find the SDK path for the desired SDK
|
||||
find_path(
|
||||
|
|
22
cmake/externals/LibOVR/CMakeLists.txt
vendored
22
cmake/externals/LibOVR/CMakeLists.txt
vendored
|
@ -27,12 +27,12 @@ if (WIN32)
|
|||
ExternalProject_Get_Property(${EXTERNAL_NAME} SOURCE_DIR)
|
||||
ExternalProject_Get_Property(${EXTERNAL_NAME} INSTALL_DIR)
|
||||
set(LIBOVR_DIR ${INSTALL_DIR})
|
||||
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${LIBOVR_DIR}/Include CACHE TYPE INTERNAL)
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG ${LIBOVR_DIR}/Lib/LibOVRd.lib CACHE TYPE INTERNAL)
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${LIBOVR_DIR}/Lib/LibOVR.lib CACHE TYPE INTERNAL)
|
||||
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${LIBOVR_DIR}/Include CACHE STRING INTERNAL)
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG ${LIBOVR_DIR}/Lib/LibOVRd.lib CACHE STRING INTERNAL)
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${LIBOVR_DIR}/Lib/LibOVR.lib CACHE STRING INTERNAL)
|
||||
include(SelectLibraryConfigurations)
|
||||
select_library_configurations(LIBOVR)
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${${EXTERNAL_NAME_UPPER}_LIBRARIES} CACHE TYPE INTERNAL)
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${${EXTERNAL_NAME_UPPER}_LIBRARIES} CACHE STRING INTERNAL)
|
||||
|
||||
elseif(APPLE)
|
||||
|
||||
|
@ -50,11 +50,11 @@ elseif(APPLE)
|
|||
# In theory we should use the Headers path inside the framework, as seen here
|
||||
# but unfortunately Oculus doesn't seem to have figured out automated testing
|
||||
# so they released a framework with missing headers.
|
||||
#set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/LibOVR/Lib/Mac/Release/LibOVR.framework/Headers/ CACHE TYPE INTERNAL)
|
||||
#set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/LibOVR/Lib/Mac/Release/LibOVR.framework/Headers/ CACHE STRING INTERNAL)
|
||||
|
||||
# Work around the broken framework by using a different path for the headers.
|
||||
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/LibOVR/Include CACHE TYPE INTERNAL)
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${SOURCE_DIR}/LibOVR/Lib/Mac/Release/LibOVR.framework/LibOVR CACHE TYPE INTERNAL)
|
||||
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/LibOVR/Include CACHE STRING INTERNAL)
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${SOURCE_DIR}/LibOVR/Lib/Mac/Release/LibOVR.framework/LibOVR CACHE STRING INTERNAL)
|
||||
|
||||
|
||||
|
||||
|
@ -74,8 +74,8 @@ elseif(NOT ANDROID)
|
|||
ExternalProject_Get_Property(${EXTERNAL_NAME} SOURCE_DIR)
|
||||
ExternalProject_Get_Property(${EXTERNAL_NAME} INSTALL_DIR)
|
||||
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${INSTALL_DIR}/lib/libovr.a CACHE TYPE INTERNAL)
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG "" CACHE TYPE INTERNAL)
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${INSTALL_DIR}/lib/libovr.a CACHE STRING INTERNAL)
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG "" CACHE STRING INTERNAL)
|
||||
|
||||
find_package(Threads REQUIRED)
|
||||
find_package(X11 REQUIRED)
|
||||
|
@ -89,8 +89,8 @@ elseif(NOT ANDROID)
|
|||
|
||||
select_library_configurations(${EXTERNAL_NAME_UPPER})
|
||||
|
||||
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/LibOVR/Include ${SOURCE_DIR}/LibOVR/Src CACHE TYPE INTERNAL)
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${${EXTERNAL_NAME_UPPER}_LIBRARY} ${${EXTERNAL_NAME_UPPER}_LIBRARY_EXTRAS} CACHE TYPE INTERNAL)
|
||||
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/LibOVR/Include ${SOURCE_DIR}/LibOVR/Src CACHE STRING INTERNAL)
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${${EXTERNAL_NAME_UPPER}_LIBRARY} ${${EXTERNAL_NAME_UPPER}_LIBRARY_EXTRAS} CACHE STRING INTERNAL)
|
||||
endif()
|
||||
|
||||
# Hide this external target (for ide users)
|
||||
|
|
|
@ -20,12 +20,12 @@ if (WIN32)
|
|||
ExternalProject_Get_Property(${EXTERNAL_NAME} SOURCE_DIR)
|
||||
|
||||
if ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8")
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${SOURCE_DIR}/Windows/LibOVRPlatform64_1.lib CACHE TYPE INTERNAL)
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${SOURCE_DIR}/Windows/LibOVRPlatform64_1.lib CACHE STRING INTERNAL)
|
||||
else()
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${SOURCE_DIR}/Windows/LibOVRPlatform32_1.lib CACHE TYPE INTERNAL)
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${SOURCE_DIR}/Windows/LibOVRPlatform32_1.lib CACHE STRING INTERNAL)
|
||||
endif()
|
||||
|
||||
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/Include CACHE TYPE INTERNAL)
|
||||
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/Include CACHE STRING INTERNAL)
|
||||
endif ()
|
||||
|
||||
# Hide this external target (for ide users)
|
||||
|
|
|
@ -36,10 +36,10 @@ set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals")
|
|||
|
||||
ExternalProject_Get_Property(${EXTERNAL_NAME} SOURCE_DIR)
|
||||
|
||||
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/include CACHE TYPE INTERNAL)
|
||||
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/include CACHE STRING INTERNAL)
|
||||
|
||||
if (WIN32)
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${SOURCE_DIR}/Release/audio.lib CACHE TYPE INTERNAL)
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${SOURCE_DIR}/Release/audio.lib CACHE STRING INTERNAL)
|
||||
else()
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${SOURCE_DIR}/Release/libaudio.a CACHE TYPE INTERNAL)
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${SOURCE_DIR}/Release/libaudio.a CACHE STRING INTERNAL)
|
||||
endif()
|
||||
|
|
12
cmake/externals/neuron/CMakeLists.txt
vendored
12
cmake/externals/neuron/CMakeLists.txt
vendored
|
@ -21,9 +21,9 @@ set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals")
|
|||
|
||||
# set include dir
|
||||
if(WIN32)
|
||||
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS "${SOURCE_DIR}/NeuronDataReader_Windows/include" CACHE TYPE INTERNAL)
|
||||
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS "${SOURCE_DIR}/NeuronDataReader_Windows/include" CACHE STRING INTERNAL)
|
||||
elseif(APPLE)
|
||||
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS "${SOURCE_DIR}/NeuronDataReader_Mac/include" CACHE TYPE INTERNAL)
|
||||
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS "${SOURCE_DIR}/NeuronDataReader_Mac/include" CACHE STRING INTERNAL)
|
||||
else()
|
||||
# Unsupported
|
||||
endif()
|
||||
|
@ -37,16 +37,16 @@ if(WIN32)
|
|||
endif()
|
||||
|
||||
set(${EXTERNAL_NAME_UPPER}_LIB_PATH "${SOURCE_DIR}/NeuronDataReader_Windows/lib/${ARCH_DIR}")
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE "${${EXTERNAL_NAME_UPPER}_LIB_PATH}/NeuronDataReader.lib" CACHE TYPE INTERNAL)
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARIES "${${EXTERNAL_NAME_UPPER}_LIB_PATH}/NeuronDataReader.lib" CACHE TYPE INTERNAL)
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE "${${EXTERNAL_NAME_UPPER}_LIB_PATH}/NeuronDataReader.lib" CACHE STRING INTERNAL)
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARIES "${${EXTERNAL_NAME_UPPER}_LIB_PATH}/NeuronDataReader.lib" CACHE STRING INTERNAL)
|
||||
|
||||
add_paths_to_fixup_libs("${${EXTERNAL_NAME_UPPER}_LIB_PATH}")
|
||||
|
||||
elseif(APPLE)
|
||||
|
||||
set(${EXTERNAL_NAME_UPPER}_LIB_PATH "${SOURCE_DIR}/NeuronDataReader_Mac/dylib")
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE "${${EXTERNAL_NAME_UPPER}_LIB_PATH}/NeuronDataReader.dylib" CACHE TYPE INTERNAL)
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARIES "${${EXTERNAL_NAME_UPPER}_LIB_PATH}/NeuronDataReader.dylib" CACHE TYPE INTERNAL)
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE "${${EXTERNAL_NAME_UPPER}_LIB_PATH}/NeuronDataReader.dylib" CACHE STRING INTERNAL)
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARIES "${${EXTERNAL_NAME_UPPER}_LIB_PATH}/NeuronDataReader.dylib" CACHE STRING INTERNAL)
|
||||
|
||||
add_paths_to_fixup_libs("${${EXTERNAL_NAME_UPPER}_LIB_PATH}")
|
||||
|
||||
|
|
6
cmake/externals/sixense/CMakeLists.txt
vendored
6
cmake/externals/sixense/CMakeLists.txt
vendored
|
@ -30,7 +30,7 @@ set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals")
|
|||
|
||||
ExternalProject_Get_Property(${EXTERNAL_NAME} SOURCE_DIR)
|
||||
|
||||
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/include CACHE TYPE INTERNAL)
|
||||
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/include CACHE STRING INTERNAL)
|
||||
|
||||
if (WIN32)
|
||||
|
||||
|
@ -52,7 +52,7 @@ if (WIN32)
|
|||
set(${EXTERNAL_NAME_UPPER}_LIB_PATH "${SOURCE_DIR}/lib/${ARCH_DIR}/release_dll")
|
||||
endif()
|
||||
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE "${${EXTERNAL_NAME_UPPER}_LIB_PATH}/sixense${ARCH_SUFFIX}.lib" CACHE TYPE INTERNAL)
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE "${${EXTERNAL_NAME_UPPER}_LIB_PATH}/sixense${ARCH_SUFFIX}.lib" CACHE STRING INTERNAL)
|
||||
add_paths_to_fixup_libs("${${EXTERNAL_NAME_UPPER}_DLL_PATH}")
|
||||
|
||||
elseif(APPLE)
|
||||
|
@ -62,7 +62,7 @@ elseif(APPLE)
|
|||
elseif(NOT ANDROID)
|
||||
|
||||
# FIXME need to account for different architectures
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${SOURCE_DIR}/lib/linux_x64/release/libsixense_x64.so CACHE TYPE INTERNAL)
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${SOURCE_DIR}/lib/linux_x64/release/libsixense_x64.so CACHE STRING INTERNAL)
|
||||
|
||||
endif()
|
||||
|
||||
|
|
8
cmake/externals/steamworks/CMakeLists.txt
vendored
8
cmake/externals/steamworks/CMakeLists.txt
vendored
|
@ -21,7 +21,7 @@ set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals")
|
|||
|
||||
ExternalProject_Get_Property(${EXTERNAL_NAME} SOURCE_DIR)
|
||||
|
||||
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/public CACHE TYPE INTERNAL)
|
||||
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/public CACHE STRING INTERNAL)
|
||||
|
||||
if (WIN32)
|
||||
|
||||
|
@ -36,12 +36,12 @@ if (WIN32)
|
|||
set(${EXTERNAL_NAME_UPPER}_DLL_PATH ${ARCH_DIR})
|
||||
set(${EXTERNAL_NAME_UPPER}_LIB_PATH ${ARCH_DIR})
|
||||
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE "${${EXTERNAL_NAME_UPPER}_LIB_PATH}/steam_api${ARCH_SUFFIX}.lib" CACHE TYPE INTERNAL)
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE "${${EXTERNAL_NAME_UPPER}_LIB_PATH}/steam_api${ARCH_SUFFIX}.lib" CACHE STRING INTERNAL)
|
||||
add_paths_to_fixup_libs("${${EXTERNAL_NAME_UPPER}_DLL_PATH}")
|
||||
|
||||
elseif(APPLE)
|
||||
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${SOURCE_DIR}/redistributable_bin/osx32/libsteam_api.dylib CACHE TYPE INTERNAL)
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${SOURCE_DIR}/redistributable_bin/osx32/libsteam_api.dylib CACHE STRING INTERNAL)
|
||||
|
||||
set(_STEAMWORKS_LIB_DIR "${SOURCE_DIR}/redistributable_bin/osx32")
|
||||
ExternalProject_Add_Step(
|
||||
|
@ -57,6 +57,6 @@ elseif(APPLE)
|
|||
elseif(NOT ANDROID)
|
||||
|
||||
# FIXME need to account for different architectures
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${SOURCE_DIR}/redistributable_bin/linux64/libsteam_api.so CACHE TYPE INTERNAL)
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${SOURCE_DIR}/redistributable_bin/linux64/libsteam_api.so CACHE STRING INTERNAL)
|
||||
|
||||
endif()
|
||||
|
|
2
cmake/externals/tbb/CMakeLists.txt
vendored
2
cmake/externals/tbb/CMakeLists.txt
vendored
|
@ -102,6 +102,6 @@ if (DEFINED _TBB_LIB_DIR)
|
|||
endif ()
|
||||
|
||||
if (DEFINED ${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE)
|
||||
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/include CACHE TYPE "List of tbb include directories")
|
||||
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/include CACHE STRING "List of tbb include directories")
|
||||
endif ()
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
macro(TARGET_BREAKPAD)
|
||||
if (ANDROID)
|
||||
set(INSTALL_DIR ${HIFI_ANDROID_PRECOMPILED}/breakpad)
|
||||
set(BREAKPAD_INCLUDE_DIRS "${INSTALL_DIR}/include" CACHE TYPE INTERNAL)
|
||||
set(BREAKPAD_INCLUDE_DIRS "${INSTALL_DIR}/include" CACHE STRING INTERNAL)
|
||||
set(LIB_DIR ${INSTALL_DIR}/lib)
|
||||
list(APPEND BREAKPAD_LIBRARIES ${LIB_DIR}/libbreakpad_client.a)
|
||||
target_include_directories(${TARGET_NAME} SYSTEM PRIVATE ${BREAKPAD_INCLUDE_DIRS})
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
macro(TARGET_BULLET)
|
||||
if (ANDROID)
|
||||
set(INSTALL_DIR ${HIFI_ANDROID_PRECOMPILED}/bullet)
|
||||
set(BULLET_INCLUDE_DIRS "${INSTALL_DIR}/include/bullet" CACHE TYPE INTERNAL)
|
||||
set(BULLET_INCLUDE_DIRS "${INSTALL_DIR}/include/bullet" CACHE STRING INTERNAL)
|
||||
|
||||
set(LIB_DIR ${INSTALL_DIR}/lib)
|
||||
list(APPEND BULLET_LIBRARIES ${LIB_DIR}/libBulletDynamics.a)
|
||||
|
|
|
@ -3,7 +3,7 @@ macro(TARGET_DRACO)
|
|||
find_library(LIBPATH ${LIB} PATHS )
|
||||
if (ANDROID)
|
||||
set(INSTALL_DIR ${HIFI_ANDROID_PRECOMPILED}/draco)
|
||||
set(DRACO_INCLUDE_DIRS "${INSTALL_DIR}/include" CACHE TYPE INTERNAL)
|
||||
set(DRACO_INCLUDE_DIRS "${INSTALL_DIR}/include" CACHE STRING INTERNAL)
|
||||
set(LIB_DIR ${INSTALL_DIR}/lib)
|
||||
list(APPEND DRACO_LIBRARIES ${LIB_DIR}/libdraco.a)
|
||||
list(APPEND DRACO_LIBRARIES ${LIB_DIR}/libdracodec.a)
|
||||
|
@ -21,4 +21,4 @@ macro(TARGET_DRACO)
|
|||
select_library_configurations(DRACO)
|
||||
target_link_libraries(${TARGET_NAME} ${DRACO_LIBRARY})
|
||||
endif()
|
||||
endmacro()
|
||||
endmacro()
|
||||
|
|
|
@ -9,9 +9,9 @@ macro(TARGET_HIFIAUDIOCODEC)
|
|||
if (ANDROID)
|
||||
set(HIFIAC_INSTALL_DIR ${HIFI_ANDROID_PRECOMPILED}/hifiAC/codecSDK)
|
||||
set(HIFIAC_LIB_DIR "${HIFIAC_INSTALL_DIR}/Release")
|
||||
set(HIFIAC_INCLUDE_DIRS "${HIFIAC_INSTALL_DIR}/include" CACHE TYPE INTERNAL)
|
||||
set(HIFIAC_INCLUDE_DIRS "${HIFIAC_INSTALL_DIR}/include" CACHE STRING INTERNAL)
|
||||
list(APPEND HIFIAC_LIBS "${HIFIAC_LIB_DIR}/libaudio.a")
|
||||
set(HIFIAC_LIBRARIES ${HIFIAC_LIBS} CACHE TYPE INTERNAL)
|
||||
set(HIFIAC_LIBRARIES ${HIFIAC_LIBS} CACHE STRING INTERNAL)
|
||||
else()
|
||||
add_dependency_external_projects(hifiAudioCodec)
|
||||
target_include_directories(${TARGET_NAME} PRIVATE ${HIFIAUDIOCODEC_INCLUDE_DIRS})
|
||||
|
|
|
@ -9,12 +9,12 @@ macro(TARGET_NVTT)
|
|||
if (ANDROID)
|
||||
set(NVTT_INSTALL_DIR ${HIFI_ANDROID_PRECOMPILED}/nvtt)
|
||||
set(NVTT_LIB_DIR "${NVTT_INSTALL_DIR}/lib")
|
||||
set(NVTT_INCLUDE_DIRS "${NVTT_INSTALL_DIR}/include" CACHE TYPE INTERNAL)
|
||||
set(NVTT_INCLUDE_DIRS "${NVTT_INSTALL_DIR}/include" CACHE STRING INTERNAL)
|
||||
list(APPEND NVTT_LIBS "${NVTT_LIB_DIR}/libnvcore.so")
|
||||
list(APPEND NVTT_LIBS "${NVTT_LIB_DIR}/libnvmath.so")
|
||||
list(APPEND NVTT_LIBS "${NVTT_LIB_DIR}/libnvimage.so")
|
||||
list(APPEND NVTT_LIBS "${NVTT_LIB_DIR}/libnvtt.so")
|
||||
set(NVTT_LIBRARIES ${NVTT_LIBS} CACHE TYPE INTERNAL)
|
||||
set(NVTT_LIBRARIES ${NVTT_LIBS} CACHE STRING INTERNAL)
|
||||
target_include_directories(${TARGET_NAME} PRIVATE ${NVTT_INCLUDE_DIRS})
|
||||
else()
|
||||
find_library(NVTT_LIBRARY_RELEASE nvtt PATHS ${VCPKG_INSTALL_ROOT}/lib NO_DEFAULT_PATH)
|
||||
|
|
|
@ -8,8 +8,8 @@
|
|||
macro(TARGET_OPENSSL)
|
||||
if (ANDROID)
|
||||
set(OPENSSL_INSTALL_DIR ${HIFI_ANDROID_PRECOMPILED}/openssl)
|
||||
set(OPENSSL_INCLUDE_DIR "${OPENSSL_INSTALL_DIR}/include" CACHE TYPE INTERNAL)
|
||||
set(OPENSSL_LIBRARIES "${OPENSSL_INSTALL_DIR}/lib/libcrypto.a;${OPENSSL_INSTALL_DIR}/lib/libssl.a" CACHE TYPE INTERNAL)
|
||||
set(OPENSSL_INCLUDE_DIR "${OPENSSL_INSTALL_DIR}/include" CACHE STRING INTERNAL)
|
||||
set(OPENSSL_LIBRARIES "${OPENSSL_INSTALL_DIR}/lib/libcrypto.a;${OPENSSL_INSTALL_DIR}/lib/libssl.a" CACHE STRING INTERNAL)
|
||||
else()
|
||||
# using VCPKG for OpenSSL
|
||||
find_package(OpenSSL REQUIRED)
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
macro(TARGET_POLYVOX)
|
||||
if (ANDROID)
|
||||
set(INSTALL_DIR ${HIFI_ANDROID_PRECOMPILED}/polyvox)
|
||||
set(POLYVOX_INCLUDE_DIRS "${INSTALL_DIR}/include" CACHE TYPE INTERNAL)
|
||||
set(POLYVOX_INCLUDE_DIRS "${INSTALL_DIR}/include" CACHE STRING INTERNAL)
|
||||
set(LIB_DIR ${INSTALL_DIR}/lib)
|
||||
list(APPEND POLYVOX_LIBRARIES ${LIB_DIR}/libPolyVoxUtil.so)
|
||||
list(APPEND POLYVOX_LIBRARIES ${LIB_DIR}/Release/libPolyVoxCore.so)
|
||||
|
|
|
@ -1307,8 +1307,8 @@
|
|||
"name": "connection_rate",
|
||||
"label": "Connection Rate",
|
||||
"help": "Number of new agents that can connect to the mixer every second",
|
||||
"placeholder": "50",
|
||||
"default": "50",
|
||||
"placeholder": "10000000",
|
||||
"default": "10000000",
|
||||
"advanced": true
|
||||
},
|
||||
{
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import json
|
||||
import logging
|
||||
import os
|
||||
import platform
|
||||
import time
|
||||
|
@ -7,6 +9,15 @@ try:
|
|||
except ImportError:
|
||||
fcntl = None
|
||||
|
||||
try:
|
||||
import msvcrt
|
||||
except ImportError:
|
||||
msvcrt = None
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# Used to ensure only one instance of the script runs at a time
|
||||
class Singleton:
|
||||
def __init__(self, path):
|
||||
|
@ -33,7 +44,10 @@ class Singleton:
|
|||
else:
|
||||
self.fh.close()
|
||||
self.fh = None
|
||||
print("Couldn't aquire lock, retrying in 10 seconds")
|
||||
# print is horked here so write directly to stdout.
|
||||
with open(1, mode="w", closefd=False) as _stdout:
|
||||
_stdout.write("Couldn't aquire lock, retrying in 10 seconds\n")
|
||||
_stdout.flush()
|
||||
time.sleep(10)
|
||||
return self
|
||||
|
||||
|
@ -43,4 +57,104 @@ class Singleton:
|
|||
else:
|
||||
fcntl.lockf(self.fh, fcntl.LOCK_UN)
|
||||
self.fh.close()
|
||||
os.unlink(self.path)
|
||||
os.unlink(self.path)
|
||||
|
||||
|
||||
class FLock:
|
||||
"""
|
||||
File locking context manager
|
||||
|
||||
>> with FLock("/tmp/foo.lock"):
|
||||
>> do_something_that_must_be_synced()
|
||||
|
||||
The lock file must stick around forever. The author is not aware of a no cross platform way to clean it up w/o introducting race conditions.
|
||||
"""
|
||||
def __init__(self, path):
|
||||
self.fh = os.open(path, os.O_CREAT | os.O_RDWR)
|
||||
self.path = path
|
||||
|
||||
def _lock_posix(self):
|
||||
try:
|
||||
fcntl.lockf(self.fh, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
||||
except BlockingIOError:
|
||||
# Windows sleeps for 10 seconds before giving up on a lock.
|
||||
# Lets mimic that behavior.
|
||||
time.sleep(10)
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
def _lock_windows(self):
|
||||
try:
|
||||
msvcrt.locking(self.fh, msvcrt.LK_LOCK, 1)
|
||||
except OSError:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
if fcntl is not None:
|
||||
_lock = _lock_posix
|
||||
elif msvcrt is not None:
|
||||
_lock = _lock_windows
|
||||
else:
|
||||
raise RuntimeError("No locking library found")
|
||||
|
||||
def read_stats(self):
|
||||
data = {}
|
||||
with open(self.fh, mode="r", closefd=False) as stats_file:
|
||||
stats_file.seek(0)
|
||||
try:
|
||||
data = json.loads(stats_file.read())
|
||||
except json.decoder.JSONDecodeError:
|
||||
logger.warning("couldn't decode json in lock file")
|
||||
except PermissionError:
|
||||
# Can't read a locked file on Windows :(
|
||||
pass
|
||||
|
||||
lock_age = time.time() - os.fstat(self.fh).st_mtime
|
||||
if lock_age > 0:
|
||||
data["Age"] = "%0.2f" % lock_age
|
||||
|
||||
with open(1, mode="w", closefd=False) as _stdout:
|
||||
_stdout.write("Lock stats:\n")
|
||||
for key, value in sorted(data.items()):
|
||||
_stdout.write("* %s: %s\n" % (key, value))
|
||||
_stdout.flush()
|
||||
|
||||
def write_stats(self):
|
||||
stats = {
|
||||
"Owner PID": os.getpid(),
|
||||
}
|
||||
flock_env_vars = os.getenv("FLOCK_ENV_VARS")
|
||||
if flock_env_vars:
|
||||
for env_var_name in flock_env_vars.split(":"):
|
||||
stats[env_var_name] = os.getenv(env_var_name)
|
||||
|
||||
with open(self.fh, mode="w", closefd=False) as stats_file:
|
||||
stats_file.truncate()
|
||||
return stats_file.write(json.dumps(stats, indent=2))
|
||||
|
||||
def __enter__(self):
|
||||
while not self._lock():
|
||||
try:
|
||||
self.read_stats()
|
||||
except (IOError, ValueError) as exc:
|
||||
logger.exception("couldn't read stats")
|
||||
time.sleep(3.33) # don't hammer the file
|
||||
|
||||
self.write_stats()
|
||||
|
||||
return self
|
||||
|
||||
def __exit__(self, type, value, traceback):
|
||||
os.close(self.fh)
|
||||
# WARNING: `os.close` gives up the lock on `fh` then we attempt the `os.unlink`. On posix platforms this can lead to us deleting a lock file that another process owns. This step is required to maintain compatablity with Singleton. When and if FLock is completely rolled out to the build fleet this unlink should be removed.
|
||||
try:
|
||||
os.unlink(self.path)
|
||||
except (FileNotFoundError, PermissionError):
|
||||
logger.exception("couldn't unlink lock file")
|
||||
|
||||
|
||||
if os.getenv("USE_FLOCK_CLS") is not None:
|
||||
logger.warning("Using FLock locker")
|
||||
Singleton = FLock
|
||||
|
|
BIN
interface/resources/images/AvatarTheftBanner.png
Normal file
BIN
interface/resources/images/AvatarTheftBanner.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 48 KiB |
BIN
interface/resources/meshes/mannequin/man_stolen.fbx
Normal file
BIN
interface/resources/meshes/mannequin/man_stolen.fbx
Normal file
Binary file not shown.
86
interface/resources/meshes/verifyFailed.fst
Normal file
86
interface/resources/meshes/verifyFailed.fst
Normal file
|
@ -0,0 +1,86 @@
|
|||
name = mannequin2
|
||||
type = body+head
|
||||
scale = 1
|
||||
filename = mannequin/man_stolen.fbx
|
||||
texdir = textures
|
||||
joint = jointEyeLeft = LeftEye
|
||||
joint = jointRightHand = RightHand
|
||||
joint = jointHead = Head
|
||||
joint = jointEyeRight = RightEye
|
||||
joint = jointNeck = Neck
|
||||
joint = jointLeftHand = LeftHand
|
||||
joint = jointLean = Spine
|
||||
joint = jointRoot = Hips
|
||||
freeJoint = LeftArm
|
||||
freeJoint = LeftForeArm
|
||||
freeJoint = RightArm
|
||||
freeJoint = RightForeArm
|
||||
jointIndex = RightHandPinky2 = 19
|
||||
jointIndex = LeftHandPinky3 = 44
|
||||
jointIndex = RightToeBase = 9
|
||||
jointIndex = LeftHandRing4 = 49
|
||||
jointIndex = LeftHandPinky1 = 42
|
||||
jointIndex = LeftHandRing1 = 46
|
||||
jointIndex = LeftLeg = 2
|
||||
jointIndex = RightHandIndex4 = 29
|
||||
jointIndex = LeftHandRing3 = 48
|
||||
jointIndex = RightShoulder = 14
|
||||
jointIndex = RightArm = 15
|
||||
jointIndex = Neck = 62
|
||||
jointIndex = RightHandMiddle2 = 35
|
||||
jointIndex = HeadTop_End = 66
|
||||
jointIndex = LeftHandRing2 = 47
|
||||
jointIndex = RightHandThumb1 = 30
|
||||
jointIndex = RightHandRing3 = 24
|
||||
jointIndex = LeftHandIndex3 = 52
|
||||
jointIndex = LeftForeArm = 40
|
||||
jointIndex = face = 68
|
||||
jointIndex = LeftToe_End = 5
|
||||
jointIndex = RightHandThumb3 = 32
|
||||
jointIndex = RightEye = 65
|
||||
jointIndex = Spine = 11
|
||||
jointIndex = LeftEye = 64
|
||||
jointIndex = LeftToeBase = 4
|
||||
jointIndex = LeftHandIndex4 = 53
|
||||
jointIndex = RightHandPinky4 = 21
|
||||
jointIndex = RightHandMiddle1 = 34
|
||||
jointIndex = Spine1 = 12
|
||||
jointIndex = LeftHandIndex2 = 51
|
||||
jointIndex = RightToe_End = 10
|
||||
jointIndex = RightHand = 17
|
||||
jointIndex = LeftUpLeg = 1
|
||||
jointIndex = RightHandRing1 = 22
|
||||
jointIndex = RightUpLeg = 6
|
||||
jointIndex = RightHandMiddle4 = 37
|
||||
jointIndex = Head = 63
|
||||
jointIndex = RightHandMiddle3 = 36
|
||||
jointIndex = RightHandIndex1 = 26
|
||||
jointIndex = LeftHandMiddle4 = 61
|
||||
jointIndex = LeftHandPinky4 = 45
|
||||
jointIndex = Hips = 0
|
||||
jointIndex = body = 67
|
||||
jointIndex = RightHandThumb2 = 31
|
||||
jointIndex = LeftHandThumb2 = 55
|
||||
jointIndex = RightHandThumb4 = 33
|
||||
jointIndex = RightHandPinky3 = 20
|
||||
jointIndex = LeftHandPinky2 = 43
|
||||
jointIndex = LeftShoulder = 38
|
||||
jointIndex = RightHandIndex3 = 28
|
||||
jointIndex = LeftHandThumb4 = 57
|
||||
jointIndex = RightLeg = 7
|
||||
jointIndex = RightHandIndex2 = 27
|
||||
jointIndex = LeftHandMiddle3 = 60
|
||||
jointIndex = RightHandRing4 = 25
|
||||
jointIndex = LeftHandThumb1 = 54
|
||||
jointIndex = LeftArm = 39
|
||||
jointIndex = LeftHandThumb3 = 56
|
||||
jointIndex = LeftHandMiddle1 = 58
|
||||
jointIndex = RightHandPinky1 = 18
|
||||
jointIndex = Spine2 = 13
|
||||
jointIndex = RightHandRing2 = 23
|
||||
jointIndex = RightForeArm = 16
|
||||
jointIndex = LeftHandIndex1 = 50
|
||||
jointIndex = RightFoot = 8
|
||||
jointIndex = LeftHandMiddle2 = 59
|
||||
jointIndex = LeftHand = 41
|
||||
jointIndex = LeftFoot = 3
|
|
@ -24,6 +24,20 @@ Rectangle {
|
|||
property var pushingToTalk: AudioScriptingInterface.pushingToTalk;
|
||||
readonly property var userSpeakingLevel: 0.4;
|
||||
property bool gated: false;
|
||||
|
||||
Timer {
|
||||
// used to hold the muted warning.
|
||||
id: mutedTimer
|
||||
|
||||
interval: 2000;
|
||||
running: false;
|
||||
repeat: false;
|
||||
property bool isRunning: false;
|
||||
onTriggered: {
|
||||
isRunning = false;
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
AudioScriptingInterface.noiseGateOpened.connect(function() { gated = false; });
|
||||
AudioScriptingInterface.noiseGateClosed.connect(function() { gated = true; });
|
||||
|
@ -54,7 +68,17 @@ Rectangle {
|
|||
opacity: 0.7;
|
||||
|
||||
onLevelChanged: {
|
||||
var rectOpacity = (muted && (level >= userSpeakingLevel)) ? 1.0 : 0.7;
|
||||
var mutedAndSpeaking = (muted && (level >= userSpeakingLevel));
|
||||
if (!mutedTimer.isRunning && !pushToTalk) {
|
||||
if (mutedAndSpeaking) {
|
||||
mutedTimer.start();
|
||||
mutedTimer.isRunning = true;
|
||||
statusText.text = "MUTED";
|
||||
} else {
|
||||
statusText.text = "";
|
||||
}
|
||||
}
|
||||
var rectOpacity = mutedAndSpeaking ? 1.0 : 0.7;
|
||||
if (pushToTalk && !pushingToTalk) {
|
||||
rectOpacity = (mouseArea.containsMouse) ? 1.0 : 0.7;
|
||||
} else if (mouseArea.containsMouse && rectOpacity != 1.0) {
|
||||
|
@ -63,6 +87,10 @@ Rectangle {
|
|||
micBar.opacity = rectOpacity;
|
||||
}
|
||||
|
||||
onPushToTalkChanged: {
|
||||
statusText.text = pushToTalk ? HMD.active ? "PTT" : "PTT-(T)" : "";
|
||||
}
|
||||
|
||||
color: "#00000000";
|
||||
border {
|
||||
width: mouseArea.containsMouse || mouseArea.containsPress ? 2 : 0;
|
||||
|
@ -190,7 +218,6 @@ Rectangle {
|
|||
color: pushToTalk ? (pushingToTalk ? colors.unmutedColor : colors.mutedColor) : (level >= userSpeakingLevel && muted) ? colors.mutedColor : colors.unmutedColor;
|
||||
font.bold: true
|
||||
|
||||
text: pushToTalk ? (HMD.active ? "PTT" : "PTT-(T)") : (muted ? "MUTED" : "MUTE");
|
||||
size: 12;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -136,7 +136,6 @@
|
|||
#include <SoundCacheScriptingInterface.h>
|
||||
#include <ui/TabletScriptingInterface.h>
|
||||
#include <ui/ToolbarScriptingInterface.h>
|
||||
#include <InteractiveWindow.h>
|
||||
#include <Tooltip.h>
|
||||
#include <udt/PacketHeaders.h>
|
||||
#include <UserActivityLogger.h>
|
||||
|
@ -213,6 +212,7 @@
|
|||
#include "ui/UpdateDialog.h"
|
||||
#include "ui/DomainConnectionModel.h"
|
||||
#include "ui/Keyboard.h"
|
||||
#include "ui/InteractiveWindow.h"
|
||||
#include "Util.h"
|
||||
#include "InterfaceParentFinder.h"
|
||||
#include "ui/OctreeStatsProvider.h"
|
||||
|
@ -374,7 +374,12 @@ const std::vector<std::pair<QString, Application::AcceptURLMethod>> Application:
|
|||
class DeadlockWatchdogThread : public QThread {
|
||||
public:
|
||||
static const unsigned long HEARTBEAT_UPDATE_INTERVAL_SECS = 1;
|
||||
// TODO: go back to 2 min across the board, after figuring out the issues with mac
|
||||
#if defined(Q_OS_MAC)
|
||||
static const unsigned long MAX_HEARTBEAT_AGE_USECS = 600 * USECS_PER_SECOND; // 10 mins with no checkin probably a deadlock, right now, on MAC
|
||||
#else
|
||||
static const unsigned long MAX_HEARTBEAT_AGE_USECS = 120 * USECS_PER_SECOND; // 2 mins with no checkin probably a deadlock
|
||||
#endif
|
||||
static const int WARNING_ELAPSED_HEARTBEAT = 500 * USECS_PER_MSEC; // warn if elapsed heartbeat average is large
|
||||
static const int HEARTBEAT_SAMPLES = 100000; // ~5 seconds worth of samples
|
||||
|
||||
|
@ -554,7 +559,10 @@ public:
|
|||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Attempting to close MIDI interfaces of a hot-unplugged device can result in audio-driver deadlock.
|
||||
// Detecting MIDI devices that have been added/removed after starting Inteface has been disabled.
|
||||
// https://support.microsoft.com/en-us/help/4460006/midi-device-app-hangs-when-former-midi-api-is-used
|
||||
#if 0
|
||||
if (message->message == WM_DEVICECHANGE) {
|
||||
const float MIN_DELTA_SECONDS = 2.0f; // de-bounce signal
|
||||
static float lastTriggerTime = 0.0f;
|
||||
|
@ -564,6 +572,7 @@ public:
|
|||
Midi::USBchanged(); // re-scan the MIDI bus
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -661,9 +670,10 @@ private:
|
|||
|
||||
/**jsdoc
|
||||
* <p>The <code>Controller.Hardware.Application</code> object has properties representing Interface's state. The property
|
||||
* values are integer IDs, uniquely identifying each output. <em>Read-only.</em> These can be mapped to actions or functions or
|
||||
* <code>Controller.Standard</code> items in a {@link RouteObject} mapping (e.g., using the {@link RouteObject#when} method).
|
||||
* Each data value is either <code>1.0</code> for "true" or <code>0.0</code> for "false".</p>
|
||||
* values are integer IDs, uniquely identifying each output. <em>Read-only.</em></p>
|
||||
* <p>These states can be mapped to actions or functions or <code>Controller.Standard</code> items in a {@link RouteObject}
|
||||
* mapping (e.g., using the {@link RouteObject#when} method). Each data value is either <code>1.0</code> for "true" or
|
||||
* <code>0.0</code> for "false".</p>
|
||||
* <table>
|
||||
* <thead>
|
||||
* <tr><th>Property</th><th>Type</th><th>Data</th><th>Description</th></tr>
|
||||
|
@ -677,13 +687,17 @@ private:
|
|||
* <tr><td><code>CameraIndependent</code></td><td>number</td><td>number</td><td>The camera is in independent mode.</td></tr>
|
||||
* <tr><td><code>CameraEntity</code></td><td>number</td><td>number</td><td>The camera is in entity mode.</td></tr>
|
||||
* <tr><td><code>InHMD</code></td><td>number</td><td>number</td><td>The user is in HMD mode.</td></tr>
|
||||
* <tr><td><code>AdvancedMovement</code></td><td>number</td><td>number</td><td>Advanced movement controls are enabled.
|
||||
* </td></tr>
|
||||
* <tr><td><code>AdvancedMovement</code></td><td>number</td><td>number</td><td>Advanced movement (walking) controls are
|
||||
* enabled.</td></tr>
|
||||
* <tr><td><code>StrafeEnabled</code></td><td>number</td><td>number</td><td>Strafing is enabled</td></tr>
|
||||
* <tr><td><code>LeftHandDominant</code></td><td>number</td><td>number</td><td>Dominant hand set to left.</td></tr>
|
||||
* <tr><td><code>RightHandDominant</code></td><td>number</td><td>number</td><td>Dominant hand set to right.</td></tr>
|
||||
* <tr><td><code>SnapTurn</code></td><td>number</td><td>number</td><td>Snap turn is enabled.</td></tr>
|
||||
* <tr><td><code>Grounded</code></td><td>number</td><td>number</td><td>The user's avatar is on the ground.</td></tr>
|
||||
* <tr><td><code>NavigationFocused</code></td><td>number</td><td>number</td><td><em>Not used.</em></td></tr>
|
||||
* <tr><td><code>PlatformWindows</code></td><td>number</td><td>number</td><td>The operating system is Windows.</td></tr>
|
||||
* <tr><td><code>PlatformMac</code></td><td>number</td><td>number</td><td>The operating system is Mac.</td></tr>
|
||||
* <tr><td><code>PlatformAndroid</code></td><td>number</td><td>number</td><td>The operating system is Android.</td></tr>
|
||||
* </tbody>
|
||||
* </table>
|
||||
* @typedef {object} Controller.Hardware-Application
|
||||
|
@ -1052,6 +1066,14 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
}
|
||||
}
|
||||
|
||||
{
|
||||
// identify gpu as early as possible to help identify OpenGL initialization errors.
|
||||
auto gpuIdent = GPUIdent::getInstance();
|
||||
setCrashAnnotation("gpu_name", gpuIdent->getName().toStdString());
|
||||
setCrashAnnotation("gpu_driver", gpuIdent->getDriver().toStdString());
|
||||
setCrashAnnotation("gpu_memory", std::to_string(gpuIdent->getMemory()));
|
||||
}
|
||||
|
||||
// make sure the debug draw singleton is initialized on the main thread.
|
||||
DebugDraw::getInstance().removeMarker("");
|
||||
|
||||
|
@ -1327,7 +1349,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
setCrashAnnotation("avatar", avatarURL.toString().toStdString());
|
||||
});
|
||||
|
||||
|
||||
// Inititalize sample before registering
|
||||
_sampleSound = DependencyManager::get<SoundCache>()->getSound(PathUtils::resourcesUrl("sounds/sample.wav"));
|
||||
|
||||
|
@ -1418,6 +1439,10 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
initializeDisplayPlugins();
|
||||
qCDebug(interfaceapp, "Initialized Display");
|
||||
|
||||
if (_displayPlugin && !_displayPlugin->isHmd()) {
|
||||
_preferredCursor.set(Cursor::Manager::getIconName(Cursor::Icon::SYSTEM));
|
||||
showCursor(Cursor::Manager::lookupIcon(_preferredCursor.get()));
|
||||
}
|
||||
// An audio device changed signal received before the display plugins are set up will cause a crash,
|
||||
// so we defer the setup of the `scripting::Audio` class until this point
|
||||
{
|
||||
|
@ -1437,8 +1462,11 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
audioScriptingInterface->environmentMuted();
|
||||
}
|
||||
});
|
||||
connect(this, &Application::activeDisplayPluginChanged,
|
||||
reinterpret_cast<scripting::Audio*>(audioScriptingInterface.data()), &scripting::Audio::onContextChanged);
|
||||
QSharedPointer<scripting::Audio> scriptingAudioSharedPointer = qSharedPointerDynamicCast<scripting::Audio>(DependencyManager::get<AudioScriptingInterface>());
|
||||
if (scriptingAudioSharedPointer) {
|
||||
connect(this, &Application::activeDisplayPluginChanged,
|
||||
scriptingAudioSharedPointer.data(), &scripting::Audio::onContextChanged);
|
||||
}
|
||||
}
|
||||
|
||||
// Create the rendering engine. This can be slow on some machines due to lots of
|
||||
|
@ -1631,7 +1659,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
connect(userInputMapper.data(), &UserInputMapper::actionEvent, [this](int action, float state) {
|
||||
using namespace controller;
|
||||
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
|
||||
auto audioScriptingInterface = reinterpret_cast<scripting::Audio*>(DependencyManager::get<AudioScriptingInterface>().data());
|
||||
QSharedPointer<scripting::Audio> audioScriptingInterface = qSharedPointerDynamicCast<scripting::Audio>(DependencyManager::get<AudioScriptingInterface>());
|
||||
{
|
||||
auto actionEnum = static_cast<Action>(action);
|
||||
int key = Qt::Key_unknown;
|
||||
|
@ -1639,10 +1667,12 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
bool navAxis = false;
|
||||
switch (actionEnum) {
|
||||
case Action::TOGGLE_PUSHTOTALK:
|
||||
if (state > 0.0f) {
|
||||
audioScriptingInterface->setPushingToTalk(true);
|
||||
} else if (state <= 0.0f) {
|
||||
audioScriptingInterface->setPushingToTalk(false);
|
||||
if (audioScriptingInterface) {
|
||||
if (state > 0.0f) {
|
||||
audioScriptingInterface->setPushingToTalk(true);
|
||||
} else if (state <= 0.0f) {
|
||||
audioScriptingInterface->setPushingToTalk(false);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
|
@ -2764,14 +2794,14 @@ Application::~Application() {
|
|||
avatarManager->handleProcessedPhysicsTransaction(transaction);
|
||||
|
||||
avatarManager->deleteAllAvatars();
|
||||
|
||||
|
||||
auto myCharacterController = getMyAvatar()->getCharacterController();
|
||||
myCharacterController->clearDetailedMotionStates();
|
||||
|
||||
|
||||
myCharacterController->buildPhysicsTransaction(transaction);
|
||||
_physicsEngine->processTransaction(transaction);
|
||||
myCharacterController->handleProcessedPhysicsTransaction(transaction);
|
||||
|
||||
|
||||
_physicsEngine->setCharacterController(nullptr);
|
||||
|
||||
// the _shapeManager should have zero references
|
||||
|
@ -2903,7 +2933,7 @@ void Application::initializeGL() {
|
|||
|
||||
#if !defined(DISABLE_QML)
|
||||
QStringList chromiumFlags;
|
||||
// Bug 21993: disable microphone and camera input
|
||||
// Bug 21993: disable microphone and camera input
|
||||
chromiumFlags << "--use-fake-device-for-media-stream";
|
||||
// Disable signed distance field font rendering on ATI/AMD GPUs, due to
|
||||
// https://highfidelity.manuscript.com/f/cases/13677/Text-showing-up-white-on-Marketplace-app
|
||||
|
@ -3374,6 +3404,7 @@ void Application::setupQmlSurface(QQmlContext* surfaceContext, bool setAdditiona
|
|||
surfaceContext->setContextProperty("KeyboardScriptingInterface", DependencyManager::get<KeyboardScriptingInterface>().data());
|
||||
|
||||
if (setAdditionalContextProperties) {
|
||||
qDebug() << "setting additional context properties!";
|
||||
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
|
||||
auto flags = tabletScriptingInterface->getFlags();
|
||||
|
||||
|
@ -3588,7 +3619,14 @@ void Application::setPreferAvatarFingerOverStylus(bool value) {
|
|||
|
||||
void Application::setPreferredCursor(const QString& cursorName) {
|
||||
qCDebug(interfaceapp) << "setPreferredCursor" << cursorName;
|
||||
_preferredCursor.set(cursorName.isEmpty() ? DEFAULT_CURSOR_NAME : cursorName);
|
||||
|
||||
if (_displayPlugin && _displayPlugin->isHmd()) {
|
||||
_preferredCursor.set(cursorName.isEmpty() ? DEFAULT_CURSOR_NAME : cursorName);
|
||||
}
|
||||
else {
|
||||
_preferredCursor.set(cursorName.isEmpty() ? Cursor::Manager::getIconName(Cursor::Icon::SYSTEM) : cursorName);
|
||||
}
|
||||
|
||||
showCursor(Cursor::Manager::lookupIcon(_preferredCursor.get()));
|
||||
}
|
||||
|
||||
|
@ -3992,8 +4030,8 @@ bool Application::notify(QObject * object, QEvent * event) {
|
|||
if (thread() == QThread::currentThread()) {
|
||||
PROFILE_RANGE_IF_LONGER(app, "notify", 2)
|
||||
return QApplication::notify(object, event);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return QApplication::notify(object, event);
|
||||
}
|
||||
|
||||
|
@ -4054,9 +4092,6 @@ bool Application::event(QEvent* event) {
|
|||
case QEvent::KeyRelease:
|
||||
keyReleaseEvent(static_cast<QKeyEvent*>(event));
|
||||
return true;
|
||||
case QEvent::FocusIn:
|
||||
focusInEvent(static_cast<QFocusEvent*>(event));
|
||||
return true;
|
||||
case QEvent::FocusOut:
|
||||
focusOutEvent(static_cast<QFocusEvent*>(event));
|
||||
return true;
|
||||
|
@ -4099,6 +4134,11 @@ bool Application::eventFilter(QObject* object, QEvent* event) {
|
|||
return true;
|
||||
}
|
||||
|
||||
auto eventType = event->type();
|
||||
if (eventType == QEvent::KeyPress || eventType == QEvent::KeyRelease || eventType == QEvent::MouseMove) {
|
||||
getRefreshRateManager().resetInactiveTimer();
|
||||
}
|
||||
|
||||
if (event->type() == QEvent::Leave) {
|
||||
getApplicationCompositor().handleLeaveEvent();
|
||||
}
|
||||
|
@ -4288,6 +4328,10 @@ void Application::keyPressEvent(QKeyEvent* event) {
|
|||
if (isMeta) {
|
||||
auto audioClient = DependencyManager::get<AudioClient>();
|
||||
audioClient->setMuted(!audioClient->isMuted());
|
||||
QSharedPointer<scripting::Audio> audioScriptingInterface = qSharedPointerDynamicCast<scripting::Audio>(DependencyManager::get<AudioScriptingInterface>());
|
||||
if (audioScriptingInterface && audioScriptingInterface->getPTT()) {
|
||||
audioScriptingInterface->setPushingToTalk(!audioClient->isMuted());
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
|
@ -4410,13 +4454,6 @@ void Application::keyReleaseEvent(QKeyEvent* event) {
|
|||
|
||||
}
|
||||
|
||||
void Application::focusInEvent(QFocusEvent* event) {
|
||||
if (!_aboutToQuit && _startUpFinished) {
|
||||
getRefreshRateManager().setRefreshRateRegime(RefreshRateManager::RefreshRateRegime::RUNNING);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Application::focusOutEvent(QFocusEvent* event) {
|
||||
auto inputPlugins = PluginManager::getInstance()->getInputPlugins();
|
||||
foreach(auto inputPlugin, inputPlugins) {
|
||||
|
@ -4424,10 +4461,6 @@ void Application::focusOutEvent(QFocusEvent* event) {
|
|||
inputPlugin->pluginFocusOutEvent();
|
||||
}
|
||||
}
|
||||
|
||||
if (!_aboutToQuit && _startUpFinished) {
|
||||
getRefreshRateManager().setRefreshRateRegime(RefreshRateManager::RefreshRateRegime::UNFOCUS);
|
||||
}
|
||||
// FIXME spacemouse code still needs cleanup
|
||||
#if 0
|
||||
//SpacemouseDevice::getInstance().focusOutEvent();
|
||||
|
@ -5038,7 +5071,7 @@ void Application::idle() {
|
|||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
checkChangeCursor();
|
||||
|
||||
#if !defined(DISABLE_QML)
|
||||
|
@ -5349,8 +5382,10 @@ void Application::loadSettings() {
|
|||
}
|
||||
}
|
||||
|
||||
auto audioScriptingInterface = reinterpret_cast<scripting::Audio*>(DependencyManager::get<AudioScriptingInterface>().data());
|
||||
audioScriptingInterface->loadData();
|
||||
QSharedPointer<scripting::Audio> audioScriptingInterface = qSharedPointerDynamicCast<scripting::Audio>(DependencyManager::get<AudioScriptingInterface>());
|
||||
if (audioScriptingInterface) {
|
||||
audioScriptingInterface->loadData();
|
||||
}
|
||||
|
||||
getMyAvatar()->loadData();
|
||||
_settingsLoaded = true;
|
||||
|
@ -5361,8 +5396,10 @@ void Application::saveSettings() const {
|
|||
DependencyManager::get<AudioClient>()->saveSettings();
|
||||
DependencyManager::get<LODManager>()->saveSettings();
|
||||
|
||||
auto audioScriptingInterface = reinterpret_cast<scripting::Audio*>(DependencyManager::get<AudioScriptingInterface>().data());
|
||||
audioScriptingInterface->saveData();
|
||||
QSharedPointer<scripting::Audio> audioScriptingInterface = qSharedPointerDynamicCast<scripting::Audio>(DependencyManager::get<AudioScriptingInterface>());
|
||||
if (audioScriptingInterface) {
|
||||
audioScriptingInterface->saveData();
|
||||
}
|
||||
|
||||
Menu::getInstance()->saveSettings();
|
||||
getMyAvatar()->saveData();
|
||||
|
@ -5596,7 +5633,7 @@ void Application::resumeAfterLoginDialogActionTaken() {
|
|||
_myCamera.setMode(_previousCameraMode);
|
||||
cameraModeChanged();
|
||||
_startUpFinished = true;
|
||||
getRefreshRateManager().setRefreshRateRegime(RefreshRateManager::RefreshRateRegime::RUNNING);
|
||||
getRefreshRateManager().setRefreshRateRegime(RefreshRateManager::RefreshRateRegime::FOCUS_ACTIVE);
|
||||
}
|
||||
|
||||
void Application::loadAvatarScripts(const QVector<QString>& urls) {
|
||||
|
@ -5801,7 +5838,8 @@ void Application::cameraModeChanged() {
|
|||
Menu::getInstance()->setIsOptionChecked(MenuOption::FullscreenMirror, true);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
// we don't have menu items for the others, so just leave it alone.
|
||||
return;
|
||||
}
|
||||
cameraMenuChanged();
|
||||
}
|
||||
|
@ -8453,28 +8491,30 @@ bool Application::takeSnapshotOperators(std::queue<SnapshotOperator>& snapshotOp
|
|||
|
||||
void Application::takeSnapshot(bool notify, bool includeAnimated, float aspectRatio, const QString& filename) {
|
||||
addSnapshotOperator(std::make_tuple([notify, includeAnimated, aspectRatio, filename](const QImage& snapshot) {
|
||||
QString path = DependencyManager::get<Snapshot>()->saveSnapshot(snapshot, filename, TestScriptingInterface::getInstance()->getTestResultsLocation());
|
||||
qApp->postLambdaEvent([snapshot, notify, includeAnimated, aspectRatio, filename] {
|
||||
QString path = DependencyManager::get<Snapshot>()->saveSnapshot(snapshot, filename, TestScriptingInterface::getInstance()->getTestResultsLocation());
|
||||
|
||||
// If we're not doing an animated snapshot as well...
|
||||
if (!includeAnimated) {
|
||||
if (!path.isEmpty()) {
|
||||
// Tell the dependency manager that the capture of the still snapshot has taken place.
|
||||
emit DependencyManager::get<WindowScriptingInterface>()->stillSnapshotTaken(path, notify);
|
||||
}
|
||||
} else if (!SnapshotAnimated::isAlreadyTakingSnapshotAnimated()) {
|
||||
qApp->postLambdaEvent([path, aspectRatio] {
|
||||
// If we're not doing an animated snapshot as well...
|
||||
if (!includeAnimated) {
|
||||
if (!path.isEmpty()) {
|
||||
// Tell the dependency manager that the capture of the still snapshot has taken place.
|
||||
emit DependencyManager::get<WindowScriptingInterface>()->stillSnapshotTaken(path, notify);
|
||||
}
|
||||
} else if (!SnapshotAnimated::isAlreadyTakingSnapshotAnimated()) {
|
||||
// Get an animated GIF snapshot and save it
|
||||
SnapshotAnimated::saveSnapshotAnimated(path, aspectRatio, DependencyManager::get<WindowScriptingInterface>());
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}, aspectRatio, true));
|
||||
}
|
||||
|
||||
void Application::takeSecondaryCameraSnapshot(const bool& notify, const QString& filename) {
|
||||
addSnapshotOperator(std::make_tuple([notify, filename](const QImage& snapshot) {
|
||||
QString snapshotPath = DependencyManager::get<Snapshot>()->saveSnapshot(snapshot, filename, TestScriptingInterface::getInstance()->getTestResultsLocation());
|
||||
qApp->postLambdaEvent([snapshot, notify, filename] {
|
||||
QString snapshotPath = DependencyManager::get<Snapshot>()->saveSnapshot(snapshot, filename, TestScriptingInterface::getInstance()->getTestResultsLocation());
|
||||
|
||||
emit DependencyManager::get<WindowScriptingInterface>()->stillSnapshotTaken(snapshotPath, notify);
|
||||
emit DependencyManager::get<WindowScriptingInterface>()->stillSnapshotTaken(snapshotPath, notify);
|
||||
});
|
||||
}, 0.0f, false));
|
||||
}
|
||||
|
||||
|
@ -8539,11 +8579,20 @@ void Application::activeChanged(Qt::ApplicationState state) {
|
|||
switch (state) {
|
||||
case Qt::ApplicationActive:
|
||||
_isForeground = true;
|
||||
if (!_aboutToQuit && _startUpFinished) {
|
||||
getRefreshRateManager().setRefreshRateRegime(RefreshRateManager::RefreshRateRegime::FOCUS_ACTIVE);
|
||||
}
|
||||
break;
|
||||
|
||||
case Qt::ApplicationSuspended:
|
||||
break;
|
||||
case Qt::ApplicationHidden:
|
||||
break;
|
||||
case Qt::ApplicationInactive:
|
||||
if (!_aboutToQuit && _startUpFinished) {
|
||||
getRefreshRateManager().setRefreshRateRegime(RefreshRateManager::RefreshRateRegime::UNFOCUS);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
_isForeground = false;
|
||||
break;
|
||||
|
@ -8874,7 +8923,7 @@ void Application::setDisplayPlugin(DisplayPluginPointer newDisplayPlugin) {
|
|||
RefreshRateManager& refreshRateManager = getRefreshRateManager();
|
||||
refreshRateManager.setRefreshRateOperator(OpenGLDisplayPlugin::getRefreshRateOperator());
|
||||
bool isHmd = newDisplayPlugin->isHmd();
|
||||
RefreshRateManager::UXMode uxMode = isHmd ? RefreshRateManager::UXMode::HMD :
|
||||
RefreshRateManager::UXMode uxMode = isHmd ? RefreshRateManager::UXMode::VR :
|
||||
RefreshRateManager::UXMode::DESKTOP;
|
||||
|
||||
refreshRateManager.setUXMode(uxMode);
|
||||
|
|
|
@ -20,37 +20,40 @@
|
|||
|
||||
#include <display-plugins/hmd/HmdDisplayPlugin.h>
|
||||
|
||||
static const int HMD_TARGET_RATE = 90;
|
||||
static const int VR_TARGET_RATE = 90;
|
||||
|
||||
static const std::array<std::string, RefreshRateManager::RefreshRateProfile::PROFILE_NUM> REFRESH_RATE_PROFILE_TO_STRING =
|
||||
{ { "Eco", "Interactive", "Realtime" } };
|
||||
|
||||
static const std::array<std::string, RefreshRateManager::RefreshRateRegime::REGIME_NUM> REFRESH_RATE_REGIME_TO_STRING =
|
||||
{ { "Running", "Unfocus", "Minimized", "StartUp", "ShutDown" } };
|
||||
{ { "FocusActive", "FocusInactive", "Unfocus", "Minimized", "StartUp", "ShutDown" } };
|
||||
|
||||
static const std::array<std::string, RefreshRateManager::UXMode::UX_NUM> UX_MODE_TO_STRING =
|
||||
{ { "Desktop", "HMD" } };
|
||||
{ { "Desktop", "VR" } };
|
||||
|
||||
static const std::map<std::string, RefreshRateManager::RefreshRateProfile> REFRESH_RATE_PROFILE_FROM_STRING =
|
||||
{ { "Eco", RefreshRateManager::RefreshRateProfile::ECO },
|
||||
{ "Interactive", RefreshRateManager::RefreshRateProfile::INTERACTIVE },
|
||||
{ "Realtime", RefreshRateManager::RefreshRateProfile::REALTIME } };
|
||||
|
||||
static const std::array<int, RefreshRateManager::RefreshRateProfile::PROFILE_NUM> RUNNING_REGIME_PROFILES =
|
||||
{ { 5, 20, 60 } };
|
||||
|
||||
static const std::array<int, RefreshRateManager::RefreshRateProfile::PROFILE_NUM> UNFOCUS_REGIME_PROFILES =
|
||||
{ { 5, 5, 10 } };
|
||||
// Porfile regimes are:
|
||||
// { { "FocusActive", "FocusInactive", "Unfocus", "Minimized", "StartUp", "ShutDown" } }
|
||||
|
||||
static const std::array<int, RefreshRateManager::RefreshRateProfile::PROFILE_NUM> MINIMIZED_REGIME_PROFILE =
|
||||
{ { 2, 2, 2 } };
|
||||
static const std::array<int, RefreshRateManager::RefreshRateRegime::REGIME_NUM> ECO_PROFILE =
|
||||
{ { 20, 10, 5, 2, 30, 30 } };
|
||||
|
||||
static const std::array<int, RefreshRateManager::RefreshRateProfile::PROFILE_NUM> START_AND_SHUTDOWN_REGIME_PROFILES =
|
||||
{ { 30, 30, 30 } };
|
||||
static const std::array<int, RefreshRateManager::RefreshRateRegime::REGIME_NUM> INTERACTIVE_PROFILE =
|
||||
{ { 30, 20, 10, 2, 30, 30 } };
|
||||
|
||||
static const std::array<std::array<int, RefreshRateManager::RefreshRateProfile::PROFILE_NUM>, RefreshRateManager::RefreshRateRegime::REGIME_NUM> REFRESH_RATE_REGIMES =
|
||||
{ { RUNNING_REGIME_PROFILES, UNFOCUS_REGIME_PROFILES, MINIMIZED_REGIME_PROFILE,
|
||||
START_AND_SHUTDOWN_REGIME_PROFILES, START_AND_SHUTDOWN_REGIME_PROFILES } };
|
||||
static const std::array<int, RefreshRateManager::RefreshRateRegime::REGIME_NUM> REALTIME_PROFILE =
|
||||
{ { 60, 60, 10, 2, 30, 30} };
|
||||
|
||||
static const std::array<std::array<int, RefreshRateManager::RefreshRateRegime::REGIME_NUM>, RefreshRateManager::RefreshRateProfile::PROFILE_NUM> REFRESH_RATE_PROFILES =
|
||||
{ { ECO_PROFILE, INTERACTIVE_PROFILE, REALTIME_PROFILE } };
|
||||
|
||||
|
||||
static const int INACTIVE_TIMER_LIMIT = 3000;
|
||||
|
||||
|
||||
std::string RefreshRateManager::refreshRateProfileToString(RefreshRateManager::RefreshRateProfile refreshRateProfile) {
|
||||
|
@ -70,14 +73,36 @@ std::string RefreshRateManager::uxModeToString(RefreshRateManager::RefreshRateMa
|
|||
}
|
||||
|
||||
RefreshRateManager::RefreshRateManager() {
|
||||
_refreshRateProfile = (RefreshRateManager::RefreshRateProfile) _refreshRateMode.get();
|
||||
_refreshRateProfile = (RefreshRateManager::RefreshRateProfile) _refreshRateProfileSetting.get();
|
||||
_inactiveTimer->setInterval(INACTIVE_TIMER_LIMIT);
|
||||
_inactiveTimer->setSingleShot(true);
|
||||
QObject::connect(_inactiveTimer.get(), &QTimer::timeout, [&] {
|
||||
toggleInactive();
|
||||
});
|
||||
}
|
||||
|
||||
void RefreshRateManager::resetInactiveTimer() {
|
||||
if (_uxMode == RefreshRateManager::UXMode::DESKTOP) {
|
||||
auto regime = getRefreshRateRegime();
|
||||
if (regime == RefreshRateRegime::FOCUS_ACTIVE || regime == RefreshRateRegime::FOCUS_INACTIVE) {
|
||||
_inactiveTimer->start();
|
||||
setRefreshRateRegime(RefreshRateManager::RefreshRateRegime::FOCUS_ACTIVE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RefreshRateManager::toggleInactive() {
|
||||
if (_uxMode == RefreshRateManager::UXMode::DESKTOP &&
|
||||
getRefreshRateRegime() == RefreshRateManager::RefreshRateRegime::FOCUS_ACTIVE) {
|
||||
setRefreshRateRegime(RefreshRateManager::RefreshRateRegime::FOCUS_INACTIVE);
|
||||
}
|
||||
}
|
||||
|
||||
void RefreshRateManager::setRefreshRateProfile(RefreshRateManager::RefreshRateProfile refreshRateProfile) {
|
||||
if (_refreshRateProfile != refreshRateProfile) {
|
||||
_refreshRateModeLock.withWriteLock([&] {
|
||||
_refreshRateProfileSettingLock.withWriteLock([&] {
|
||||
_refreshRateProfile = refreshRateProfile;
|
||||
_refreshRateMode.set((int) refreshRateProfile);
|
||||
_refreshRateProfileSetting.set((int) refreshRateProfile);
|
||||
});
|
||||
updateRefreshRateController();
|
||||
}
|
||||
|
@ -86,9 +111,9 @@ void RefreshRateManager::setRefreshRateProfile(RefreshRateManager::RefreshRatePr
|
|||
RefreshRateManager::RefreshRateProfile RefreshRateManager::getRefreshRateProfile() const {
|
||||
RefreshRateManager::RefreshRateProfile profile = RefreshRateManager::RefreshRateProfile::REALTIME;
|
||||
|
||||
if (getUXMode() != RefreshRateManager::UXMode::HMD) {
|
||||
profile =(RefreshRateManager::RefreshRateProfile) _refreshRateModeLock.resultWithReadLock<int>([&] {
|
||||
return _refreshRateMode.get();
|
||||
if (getUXMode() != RefreshRateManager::UXMode::VR) {
|
||||
profile =(RefreshRateManager::RefreshRateProfile) _refreshRateProfileSettingLock.resultWithReadLock<int>([&] {
|
||||
return _refreshRateProfileSetting.get();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -96,8 +121,11 @@ RefreshRateManager::RefreshRateProfile RefreshRateManager::getRefreshRateProfile
|
|||
}
|
||||
|
||||
RefreshRateManager::RefreshRateRegime RefreshRateManager::getRefreshRateRegime() const {
|
||||
return getUXMode() == RefreshRateManager::UXMode::HMD ? RefreshRateManager::RefreshRateRegime::RUNNING :
|
||||
_refreshRateRegime;
|
||||
if (getUXMode() == RefreshRateManager::UXMode::VR) {
|
||||
return RefreshRateManager::RefreshRateRegime::FOCUS_ACTIVE;
|
||||
} else {
|
||||
return _refreshRateRegime;
|
||||
}
|
||||
}
|
||||
|
||||
void RefreshRateManager::setRefreshRateRegime(RefreshRateManager::RefreshRateRegime refreshRateRegime) {
|
||||
|
@ -119,31 +147,12 @@ void RefreshRateManager::updateRefreshRateController() const {
|
|||
if (_refreshRateOperator) {
|
||||
int targetRefreshRate;
|
||||
if (_uxMode == RefreshRateManager::UXMode::DESKTOP) {
|
||||
if (_refreshRateRegime == RefreshRateManager::RefreshRateRegime::RUNNING &&
|
||||
_refreshRateProfile == RefreshRateManager::RefreshRateProfile::INTERACTIVE) {
|
||||
targetRefreshRate = getInteractiveRefreshRate();
|
||||
} else {
|
||||
targetRefreshRate = REFRESH_RATE_REGIMES[_refreshRateRegime][_refreshRateProfile];
|
||||
}
|
||||
targetRefreshRate = REFRESH_RATE_PROFILES[_refreshRateProfile][_refreshRateRegime];
|
||||
} else {
|
||||
targetRefreshRate = HMD_TARGET_RATE;
|
||||
targetRefreshRate = VR_TARGET_RATE;
|
||||
}
|
||||
|
||||
_refreshRateOperator(targetRefreshRate);
|
||||
_activeRefreshRate = targetRefreshRate;
|
||||
}
|
||||
}
|
||||
|
||||
void RefreshRateManager::setInteractiveRefreshRate(int refreshRate) {
|
||||
_refreshRateLock.withWriteLock([&] {
|
||||
_interactiveRefreshRate.set(refreshRate);
|
||||
});
|
||||
updateRefreshRateController();
|
||||
}
|
||||
|
||||
|
||||
int RefreshRateManager::getInteractiveRefreshRate() const {
|
||||
return _refreshRateLock.resultWithReadLock<int>([&] {
|
||||
return _interactiveRefreshRate.get();
|
||||
});
|
||||
}
|
||||
|
|
|
@ -15,6 +15,8 @@
|
|||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#include <QTimer>
|
||||
|
||||
#include <SettingHandle.h>
|
||||
#include <shared/ReadWriteLockable.h>
|
||||
|
||||
|
@ -28,7 +30,8 @@ public:
|
|||
};
|
||||
|
||||
enum RefreshRateRegime {
|
||||
RUNNING = 0,
|
||||
FOCUS_ACTIVE = 0,
|
||||
FOCUS_INACTIVE,
|
||||
UNFOCUS,
|
||||
MINIMIZED,
|
||||
STARTUP,
|
||||
|
@ -38,7 +41,7 @@ public:
|
|||
|
||||
enum UXMode {
|
||||
DESKTOP = 0,
|
||||
HMD,
|
||||
VR,
|
||||
UX_NUM
|
||||
};
|
||||
|
||||
|
@ -57,8 +60,9 @@ public:
|
|||
void setRefreshRateOperator(std::function<void(int)> refreshRateOperator) { _refreshRateOperator = refreshRateOperator; }
|
||||
int getActiveRefreshRate() const { return _activeRefreshRate; }
|
||||
void updateRefreshRateController() const;
|
||||
void setInteractiveRefreshRate(int refreshRate);
|
||||
int getInteractiveRefreshRate() const;
|
||||
|
||||
void resetInactiveTimer();
|
||||
void toggleInactive();
|
||||
|
||||
static std::string refreshRateProfileToString(RefreshRateProfile refreshRateProfile);
|
||||
static RefreshRateProfile refreshRateProfileFromString(std::string refreshRateProfile);
|
||||
|
@ -66,18 +70,17 @@ public:
|
|||
static std::string refreshRateRegimeToString(RefreshRateRegime refreshRateRegime);
|
||||
|
||||
private:
|
||||
mutable ReadWriteLockable _refreshRateLock;
|
||||
mutable ReadWriteLockable _refreshRateModeLock;
|
||||
|
||||
mutable int _activeRefreshRate { 20 };
|
||||
RefreshRateProfile _refreshRateProfile { RefreshRateProfile::INTERACTIVE};
|
||||
RefreshRateRegime _refreshRateRegime { RefreshRateRegime::STARTUP };
|
||||
UXMode _uxMode;
|
||||
|
||||
Setting::Handle<int> _interactiveRefreshRate { "interactiveRefreshRate", 20};
|
||||
Setting::Handle<int> _refreshRateMode { "refreshRateProfile", INTERACTIVE };
|
||||
mutable ReadWriteLockable _refreshRateProfileSettingLock;
|
||||
Setting::Handle<int> _refreshRateProfileSetting { "refreshRateProfile", RefreshRateProfile::INTERACTIVE };
|
||||
|
||||
std::function<void(int)> _refreshRateOperator { nullptr };
|
||||
|
||||
std::shared_ptr<QTimer> _inactiveTimer { std::make_shared<QTimer>() };
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -46,6 +46,7 @@
|
|||
#include "MyAvatar.h"
|
||||
#include "DebugDraw.h"
|
||||
#include "SceneScriptingInterface.h"
|
||||
#include "ui/AvatarCertifyBanner.h"
|
||||
|
||||
// 50 times per second - target is 45hz, but this helps account for any small deviations
|
||||
// in the update loop - this also results in ~30hz when in desktop mode which is essentially
|
||||
|
@ -178,6 +179,13 @@ void AvatarManager::updateMyAvatar(float deltaTime) {
|
|||
_lastSendAvatarDataTime = now;
|
||||
_myAvatarSendRate.increment();
|
||||
}
|
||||
|
||||
static AvatarCertifyBanner theftBanner;
|
||||
if (_myAvatar->isCertifyFailed()) {
|
||||
theftBanner.show(_myAvatar->getSessionUUID());
|
||||
} else {
|
||||
theftBanner.clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
#include <EntityItemID.h>
|
||||
|
||||
/**jsdoc
|
||||
* The Clipboard API enables you to export and import entities to and from JSON files.
|
||||
* The <code>Clipboard</code> API enables you to export and import entities to and from JSON files.
|
||||
*
|
||||
* @namespace Clipboard
|
||||
*
|
||||
|
@ -33,56 +33,92 @@ public:
|
|||
|
||||
public:
|
||||
/**jsdoc
|
||||
* Compute the extents of the contents held in the clipboard.
|
||||
* Gets the extents of the entities held in the clipboard.
|
||||
* @function Clipboard.getContentsDimensions
|
||||
* @returns {Vec3} The extents of the contents held in the clipboard.
|
||||
* @returns {Vec3} The extents of the content held in the clipboard.
|
||||
* @example <caption>Import entities to the clipboard and report their overall dimensions.</caption>
|
||||
* var filename = Window.browse("Import entities to clipboard", "", "*.json");
|
||||
* if (filename) {
|
||||
* if (Clipboard.importEntities(filename)) {
|
||||
* print("Clipboard dimensions: " + JSON.stringify(Clipboard.getContentsDimensions()));
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
Q_INVOKABLE glm::vec3 getContentsDimensions();
|
||||
|
||||
/**jsdoc
|
||||
* Compute the largest dimension of the extents of the contents held in the clipboard.
|
||||
* Gets the largest dimension of the extents of the entities held in the clipboard.
|
||||
* @function Clipboard.getClipboardContentsLargestDimension
|
||||
* @returns {number} The largest dimension computed.
|
||||
* @returns {number} The largest dimension of the extents of the content held in the clipboard.
|
||||
*/
|
||||
Q_INVOKABLE float getClipboardContentsLargestDimension();
|
||||
|
||||
/**jsdoc
|
||||
* Import entities from a JSON file containing entity data into the clipboard.
|
||||
* You can generate a JSON file using {@link Clipboard.exportEntities}.
|
||||
* Imports entities from a JSON file into the clipboard.
|
||||
* @function Clipboard.importEntities
|
||||
* @param {string} filename Path and name of file to import.
|
||||
* @param {boolean} does the ResourceRequestObserver observe this request?
|
||||
* @param {number} optional internal id of object causing this import.
|
||||
* @param {string} filename - The path and name of the JSON file to import.
|
||||
* @param {boolean} [isObservable=true] - <code>true</code> if the {@link ResourceRequestObserver} can observe this
|
||||
* request, <code>false</code> if it can't.
|
||||
* @param {number} [callerID=-1] - An integer ID that is passed through to the {@link ResourceRequestObserver}.
|
||||
* @returns {boolean} <code>true</code> if the import was successful, otherwise <code>false</code>.
|
||||
* @example <caption>Import entities and paste into the domain.</caption>
|
||||
* var filename = Window.browse("Import entities to clipboard", "", "*.json");
|
||||
* if (filename) {
|
||||
* if (Clipboard.importEntities(filename)) {
|
||||
* pastedEntities = Clipboard.pasteEntities(Vec3.sum(MyAvatar.position,
|
||||
* Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -3 })));
|
||||
* print("Entities pasted: " + JSON.stringify(pastedEntities));
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
Q_INVOKABLE bool importEntities(const QString& filename, const bool isObservable = true, const qint64 callerId = -1);
|
||||
|
||||
/**jsdoc
|
||||
* Export the entities specified to a JSON file.
|
||||
* Exports specified entities to a JSON file.
|
||||
* @function Clipboard.exportEntities
|
||||
* @param {string} filename Path and name of the file to export the entities to. Should have the extension ".json".
|
||||
* @param {Uuid[]} entityIDs Array of IDs of the entities to export.
|
||||
* @returns {boolean} <code>true</code> if the export was successful, otherwise <code>false</code>.
|
||||
* @param {string} filename - Path and name of the file to export the entities to. Should have the extension ".json".
|
||||
* @param {Uuid[]} entityIDs - The IDs of the entities to export.
|
||||
* @returns {boolean} <code>true</code> if entities were found and the file was written, otherwise <code>false</code>.
|
||||
* @example <caption>Create and export a cube and a sphere.</caption>
|
||||
* // Create entities.
|
||||
* var box = Entities.addEntity({
|
||||
* type: "Box",
|
||||
* position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: -0.2, y: 0, z: -3 })),
|
||||
* lifetime: 300 // Delete after 5 minutes.
|
||||
* });
|
||||
* var sphere = Entities.addEntity({
|
||||
* type: "Sphere",
|
||||
* position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0.2, y: 0, z: -3 })),
|
||||
* lifetime: 300 // Delete after 5 minutes.
|
||||
* });
|
||||
*
|
||||
* // Export entities.
|
||||
* var filename = Window.save("Export entities to JSON file", Paths.resources, "*.json");
|
||||
* if (filename) {
|
||||
* Clipboard.exportEntities(filename, [box, sphere]);
|
||||
* }
|
||||
*/
|
||||
Q_INVOKABLE bool exportEntities(const QString& filename, const QVector<QUuid>& entityIDs);
|
||||
|
||||
/**jsdoc
|
||||
* Export the entities with centers within a cube to a JSON file.
|
||||
* Exports all entities that have centers within a cube to a JSON file.
|
||||
* @function Clipboard.exportEntities
|
||||
* @param {string} filename Path and name of the file to export the entities to. Should have the extension ".json".
|
||||
* @param {number} x X-coordinate of the cube center.
|
||||
* @param {number} y Y-coordinate of the cube center.
|
||||
* @param {number} z Z-coordinate of the cube center.
|
||||
* @param {number} scale Half dimension of the cube.
|
||||
* @returns {boolean} <code>true</code> if the export was successful, otherwise <code>false</code>.
|
||||
* @variation 0
|
||||
* @param {string} filename - Path and name of the file to export the entities to. Should have the extension ".json".
|
||||
* @param {number} x - X-coordinate of the cube center.
|
||||
* @param {number} y - Y-coordinate of the cube center.
|
||||
* @param {number} z - Z-coordinate of the cube center.
|
||||
* @param {number} scale - Half dimension of the cube.
|
||||
* @returns {boolean} <code>true</code> if entities were found and the file was written, otherwise <code>false</code>.
|
||||
*/
|
||||
Q_INVOKABLE bool exportEntities(const QString& filename, float x, float y, float z, float scale);
|
||||
|
||||
/**jsdoc
|
||||
* Paste the contents of the clipboard into the world.
|
||||
* Pastes the contents of the clipboard into the domain.
|
||||
* @function Clipboard.pasteEntities
|
||||
* @param {Vec3} position Position to paste the clipboard contents at.
|
||||
* @returns {Uuid[]} Array of entity IDs for the new entities that were created as a result of the paste operation.
|
||||
* @param {Vec3} position - The position to paste the clipboard contents at.
|
||||
* @returns {Uuid[]} The IDs of the new entities that were created as a result of the paste operation. If entities couldn't
|
||||
* be created then an empty array is returned.
|
||||
*/
|
||||
Q_INVOKABLE QVector<EntityItemID> pasteEntities(glm::vec3 position);
|
||||
};
|
||||
|
|
|
@ -26,18 +26,20 @@ class ScriptEngine;
|
|||
|
||||
|
||||
/**jsdoc
|
||||
* The Controller API provides facilities to interact with computer and controller hardware.
|
||||
* The <code>Controller</code> API provides facilities to interact with computer and controller hardware.
|
||||
*
|
||||
* <h5>Functions</h5>
|
||||
* <h3>Facilities</h3>
|
||||
*
|
||||
* <p>Properties</p>
|
||||
* <h4>Properties</h4>
|
||||
* <p>Get <code>Controller</code> property trees.</p>
|
||||
* <ul>
|
||||
* <li>{@link Controller.getActions|getActions}</li>
|
||||
* <li>{@link Controller.getHardware|getHardware}</li>
|
||||
* <li>{@link Controller.getStandard|getStandard}</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>Mappings</p>
|
||||
* <h4>Mappings</h4>
|
||||
* <p>Create and enable or disable <code>Controller</code> mappings.</p>
|
||||
* <ul>
|
||||
* <li>{@link Controller.disableMapping|disableMapping}</li>
|
||||
* <li>{@link Controller.enableMapping|enableMapping}</li>
|
||||
|
@ -46,7 +48,8 @@ class ScriptEngine;
|
|||
* <li>{@link Controller.parseMapping|parseMapping}</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>Input, Hardware, and Action Reflection</p>
|
||||
* <h4>Input, Hardware, and Action Reflection</h4>
|
||||
* <p>Information on the devices and actions available.</p>
|
||||
* <ul>
|
||||
* <li>{@link Controller.findAction|findAction}</li>
|
||||
* <li>{@link Controller.findDevice|findDevice}</li>
|
||||
|
@ -55,16 +58,20 @@ class ScriptEngine;
|
|||
* <li>{@link Controller.getAvailableInputs|getAvailableInputs}</li>
|
||||
* <li>{@link Controller.getDeviceName|getDeviceName}</li>
|
||||
* <li>{@link Controller.getDeviceNames|getDeviceNames}</li>
|
||||
* <li>{@link Controller.getRunningInputDevices|getRunningInputDevices}</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>Input, Hardware, and Action Events</p>
|
||||
* <h4>Input, Hardware, and Action Signals</h4>
|
||||
* <p>Notifications of device and action events.</p>
|
||||
* <ul>
|
||||
* <li>{@link Controller.actionEvent|actionEvent}</li>
|
||||
* <li>{@link Controller.hardwareChanged|hardwareChanged}</li>
|
||||
* <li>{@link Controller.inputDeviceRunningChanged|inputDeviceRunningChanged}</li>
|
||||
* <li>{@link Controller.inputEvent|inputEvent}</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>Mouse, Keyboard, and Touch Events</p>
|
||||
* <h4>Mouse, Keyboard, and Touch Signals</h4>
|
||||
* <p>Notifications of mouse, keyboard, and touch events.</p>
|
||||
* <ul>
|
||||
* <li>{@link Controller.keyPressEvent|keyPressEvent}</li>
|
||||
* <li>{@link Controller.keyReleaseEvent|keyReleaseEvent}</li>
|
||||
|
@ -78,29 +85,32 @@ class ScriptEngine;
|
|||
* <li>{@link Controller.wheelEvent|wheelEvent}</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>Control Capturing</p>
|
||||
* <h4>Control Capturing</h4>
|
||||
* <p>Disable and enable the processing of mouse and touch events.</p>
|
||||
* <ul>
|
||||
* <li>{@link Controller.captureMouseEvents|captureMouseEvents}</li>
|
||||
* <li>{@link Controller.captureTouchEvents|captureTouchEvents}</li>
|
||||
* <li>{@link Controller.captureWheelEvents|captureWheelEvents}</li>
|
||||
* <li>{@link Controller.captureTouchEvents|captureTouchEvents}</li>
|
||||
* <li>{@link Controller.releaseMouseEvents|releaseMouseEvents}</li>
|
||||
* <li>{@link Controller.releaseTouchEvents|releaseTouchEvents}</li>
|
||||
* <li>{@link Controller.releaseWheelEvents|releaseWheelEvents}</li>
|
||||
* <li>{@link Controller.releaseTouchEvents|releaseTouchEvents}</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>Action Capturing</p>
|
||||
* <h4>Action Capturing</h4>
|
||||
* <p>Disable and enable controller actions.</p>
|
||||
* <ul>
|
||||
* <li>{@link Controller.captureActionEvents|captureActionEvents}</li>
|
||||
* <li>{@link Controller.captureEntityClickEvents|captureEntityClickEvents}</li>
|
||||
* <li>{@link Controller.captureJoystick|captureJoystick}</li>
|
||||
* <li>{@link Controller.captureKeyEvents|captureKeyEvents}</li>
|
||||
* <li>{@link Controller.captureJoystick|captureJoystick}</li>
|
||||
* <li>{@link Controller.captureEntityClickEvents|captureEntityClickEvents}</li>
|
||||
* <li>{@link Controller.releaseActionEvents|releaseActionEvents}</li>
|
||||
* <li>{@link Controller.releaseEntityClickEvents|releaseEntityClickEvents}</li>
|
||||
* <li>{@link Controller.releaseJoystick|releaseJoystick}</li>
|
||||
* <li>{@link Controller.releaseKeyEvents|releaseKeyEvents}</li>
|
||||
* <li>{@link Controller.releaseJoystick|releaseJoystick}</li>
|
||||
* <li>{@link Controller.releaseEntityClickEvents|releaseEntityClickEvents}</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>Controller and Action Values</p>
|
||||
* <h4>Controller and Action Values</h4>
|
||||
* <p>Get the current value of controller outputs and actions.</p>
|
||||
* <ul>
|
||||
* <li>{@link Controller.getValue|getValue}</li>
|
||||
* <li>{@link Controller.getAxisValue|getAxisValue}</li>
|
||||
|
@ -108,7 +118,8 @@ class ScriptEngine;
|
|||
* <li>{@link Controller.getActionValue|getActionValue}</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>Haptics</p>
|
||||
* <h4>Haptics</h4>
|
||||
* <p>Trigger haptic pulses.</p>
|
||||
* <ul>
|
||||
* <li>{@link Controller.triggerHapticPulse|triggerHapticPulse}</li>
|
||||
* <li>{@link Controller.triggerHapticPulseOnDevice|triggerHapticPulseOnDevice}</li>
|
||||
|
@ -116,20 +127,23 @@ class ScriptEngine;
|
|||
* <li>{@link Controller.triggerShortHapticPulseOnDevice|triggerShortHapticPulseOnDevice}</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>Display Information</p>
|
||||
* <h4>Display Information</h4>
|
||||
* <p>Get information on the display.</p>
|
||||
* <ul>
|
||||
* <li>{@link Controller.getViewportDimensions|getViewportDimensions}</li>
|
||||
* <li>{@link Controller.getRecommendedHUDRect|getRecommendedHUDRect}</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>Virtual Game Pad</p>
|
||||
* <h4>Virtual Game Pad</h4>
|
||||
* <p>Use the virtual game pad which is available on some devices.</p>
|
||||
* <ul>
|
||||
* <li>{@link Controller.setVPadEnabled|setVPadEnabled}</li>
|
||||
* <li>{@link Controller.setVPadHidden|setVPadHidden}</li>
|
||||
* <li>{@link Controller.setVPadExtraBottomMargin|setVPadExtraBottomMargin}</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>Input Recordings</p>
|
||||
* <h4>Input Recordings</h4>
|
||||
* <p>Create and play input recordings.</p>
|
||||
* <ul>
|
||||
* <li>{@link Controller.startInputRecording|startInputRecording}</li>
|
||||
* <li>{@link Controller.stopInputRecording|stopInputRecording}</li>
|
||||
|
@ -140,10 +154,10 @@ class ScriptEngine;
|
|||
* <li>{@link Controller.stopInputPlayback|stopInputPlayback}</li>
|
||||
* </ul>
|
||||
*
|
||||
* <h5>Entity Methods:</h5>
|
||||
* <h3>Entity Methods</h3>
|
||||
*
|
||||
* <p>The default scripts implement hand controller actions that use {@link Entities.callEntityMethod} to call entity script
|
||||
* methods, if present in the entity being interacted with.</p>
|
||||
* methods, if present, in the entity being interacted with.</p>
|
||||
*
|
||||
* <table>
|
||||
* <thead>
|
||||
|
@ -203,7 +217,7 @@ class ScriptEngine;
|
|||
*
|
||||
* @property {Controller.Actions} Actions - Predefined actions on Interface and the user's avatar. These can be used as end
|
||||
* points in a {@link RouteObject} mapping. A synonym for <code>Controller.Hardware.Actions</code>.
|
||||
* <em>Read-only.</em><br />
|
||||
* <em>Read-only.</em><br /><br />
|
||||
* Default mappings are provided from the <code>Controller.Hardware.Keyboard</code> and <code>Controller.Standard</code> to
|
||||
* actions in
|
||||
* <a href="https://github.com/highfidelity/hifi/blob/master/interface/resources/controllers/keyboardMouse.json">
|
||||
|
@ -217,7 +231,7 @@ class ScriptEngine;
|
|||
* controller outputs. <em>Read-only.</em>
|
||||
*
|
||||
* @property {Controller.Standard} Standard - Standard controller outputs that can be mapped to <code>Actions</code> or
|
||||
* functions in a {@link RouteObject} mapping. <em>Read-only.</em><br />
|
||||
* functions in a {@link RouteObject} mapping. <em>Read-only.</em><br /><br />
|
||||
* Each hardware device has a mapping from its outputs to <code>Controller.Standard</code> items, specified in a JSON file.
|
||||
* For example, <a href="https://github.com/highfidelity/hifi/blob/master/interface/resources/controllers/leapmotion.json">
|
||||
* leapmotion.json</a> and
|
||||
|
@ -253,7 +267,7 @@ public:
|
|||
public slots:
|
||||
|
||||
/**jsdoc
|
||||
* Disable default Interface actions for a particular key event.
|
||||
* Disables default Interface actions for a particular key event.
|
||||
* @function Controller.captureKeyEvents
|
||||
* @param {KeyEvent} event - Details of the key event to be captured. The <code>key</code> property must be specified. The
|
||||
* <code>text</code> property is ignored. The other properties default to <code>false</code>.
|
||||
|
@ -272,7 +286,7 @@ public slots:
|
|||
virtual void captureKeyEvents(const KeyEvent& event);
|
||||
|
||||
/**jsdoc
|
||||
* Re-enable default Interface actions for a particular key event that has been disabled using
|
||||
* Re-enables default Interface actions for a particular key event that has been disabled using
|
||||
* {@link Controller.captureKeyEvents|captureKeyEvents}.
|
||||
* @function Controller.releaseKeyEvents
|
||||
* @param {KeyEvent} event - Details of the key event to release from capture. The <code>key</code> property must be
|
||||
|
@ -281,7 +295,7 @@ public slots:
|
|||
virtual void releaseKeyEvents(const KeyEvent& event);
|
||||
|
||||
/**jsdoc
|
||||
* Disable default Interface actions for a joystick.
|
||||
* Disables default Interface actions for a joystick.
|
||||
* @function Controller.captureJoystick
|
||||
* @param {number} joystickID - The integer ID of the joystick.
|
||||
* @deprecated This function is deprecated and will be removed. It no longer has any effect.
|
||||
|
@ -289,7 +303,7 @@ public slots:
|
|||
virtual void captureJoystick(int joystickIndex);
|
||||
|
||||
/**jsdoc
|
||||
* Re-enable default Interface actions for a joystick that has been disabled using
|
||||
* Re-enables default Interface actions for a joystick that has been disabled using
|
||||
* {@link Controller.captureJoystick|captureJoystick}.
|
||||
* @function Controller.releaseJoystick
|
||||
* @param {number} joystickID - The integer ID of the joystick.
|
||||
|
@ -298,7 +312,7 @@ public slots:
|
|||
virtual void releaseJoystick(int joystickIndex);
|
||||
|
||||
/**jsdoc
|
||||
* Disable {@link Entities.mousePressOnEntity} and {@link Entities.mouseDoublePressOnEntity} events on entities.
|
||||
* Disables {@link Entities.mousePressOnEntity} and {@link Entities.mouseDoublePressOnEntity} events on entities.
|
||||
* @function Controller.captureEntityClickEvents
|
||||
* @example <caption>Disable entity click events for a short period.</caption>
|
||||
* Entities.mousePressOnEntity.connect(function (entityID, event) {
|
||||
|
@ -316,7 +330,7 @@ public slots:
|
|||
virtual void captureEntityClickEvents();
|
||||
|
||||
/**jsdoc
|
||||
* Re-enable {@link Entities.mousePressOnEntity} and {@link Entities.mouseDoublePressOnEntity} events on entities that were
|
||||
* Re-enables {@link Entities.mousePressOnEntity} and {@link Entities.mouseDoublePressOnEntity} events on entities that were
|
||||
* disabled using {@link Controller.captureEntityClickEvents|captureEntityClickEvents}.
|
||||
* @function Controller.releaseEntityClickEvents
|
||||
*/
|
||||
|
@ -324,14 +338,14 @@ public slots:
|
|||
|
||||
|
||||
/**jsdoc
|
||||
* Get the dimensions of the Interface window's interior if in desktop mode or the HUD surface if in HMD mode.
|
||||
* Gets the dimensions of the Interface window's interior if in desktop mode or the HUD surface if in HMD mode.
|
||||
* @function Controller.getViewportDimensions
|
||||
* @returns {Vec2} The dimensions of the Interface window interior if in desktop mode or HUD surface if in HMD mode.
|
||||
*/
|
||||
virtual glm::vec2 getViewportDimensions() const;
|
||||
|
||||
/**jsdoc
|
||||
* Get the recommended area to position UI on the HUD surface if in HMD mode or Interface's window interior if in desktop
|
||||
* Gets the recommended area to position UI on the HUD surface if in HMD mode or Interface's window interior if in desktop
|
||||
* mode.
|
||||
* @function Controller.getRecommendedHUDRect
|
||||
* @returns {Rect} The recommended area in which to position UI.
|
||||
|
|
|
@ -22,6 +22,13 @@
|
|||
#include <DependencyManager.h>
|
||||
#include <OffscreenUi.h>
|
||||
|
||||
static const QVariantMap DOCK_AREA {
|
||||
{ "TOP", DockArea::TOP },
|
||||
{ "BOTTOM", DockArea::BOTTOM },
|
||||
{ "LEFT", DockArea::LEFT },
|
||||
{ "RIGHT", DockArea::RIGHT }
|
||||
};
|
||||
|
||||
int DesktopScriptingInterface::getWidth() {
|
||||
QSize size = qApp->getWindow()->windowHandle()->screen()->virtualSize();
|
||||
return size.width();
|
||||
|
@ -39,6 +46,10 @@ QVariantMap DesktopScriptingInterface::getPresentationMode() {
|
|||
return presentationModes;
|
||||
}
|
||||
|
||||
QVariantMap DesktopScriptingInterface::getDockArea() {
|
||||
return DOCK_AREA;
|
||||
}
|
||||
|
||||
void DesktopScriptingInterface::setHUDAlpha(float alpha) {
|
||||
qApp->getApplicationCompositor().setAlpha(alpha);
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
#include <DependencyManager.h>
|
||||
|
||||
#include "InteractiveWindow.h"
|
||||
#include "ui/InteractiveWindow.h"
|
||||
|
||||
/**jsdoc
|
||||
* @namespace Desktop
|
||||
|
@ -37,10 +37,11 @@ class DesktopScriptingInterface : public QObject, public Dependency {
|
|||
Q_PROPERTY(int height READ getHeight) // Physical height of screen(s) including task bars and system menus
|
||||
|
||||
Q_PROPERTY(QVariantMap PresentationMode READ getPresentationMode CONSTANT FINAL)
|
||||
Q_PROPERTY(QVariantMap DockArea READ getDockArea CONSTANT FINAL)
|
||||
Q_PROPERTY(int ALWAYS_ON_TOP READ flagAlwaysOnTop CONSTANT FINAL)
|
||||
Q_PROPERTY(int CLOSE_BUTTON_HIDES READ flagCloseButtonHides CONSTANT FINAL)
|
||||
|
||||
public:
|
||||
public:
|
||||
Q_INVOKABLE void setHUDAlpha(float alpha);
|
||||
Q_INVOKABLE void show(const QString& path, const QString& title);
|
||||
|
||||
|
@ -54,6 +55,8 @@ private:
|
|||
static int flagAlwaysOnTop() { return AlwaysOnTop; }
|
||||
static int flagCloseButtonHides() { return CloseButtonHides; }
|
||||
|
||||
static QVariantMap getDockArea();
|
||||
|
||||
Q_INVOKABLE static QVariantMap getPresentationMode();
|
||||
};
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
|
||||
//
|
||||
// HMDScriptingInterface.h
|
||||
// interface/src/scripting
|
||||
//
|
||||
|
@ -25,7 +25,7 @@ class QScriptEngine;
|
|||
#include <QReadWriteLock>
|
||||
|
||||
/**jsdoc
|
||||
* The HMD API provides access to the HMD used in VR display mode.
|
||||
* The <code>HMD</code> API provides access to the HMD used in VR display mode.
|
||||
*
|
||||
* @namespace HMD
|
||||
*
|
||||
|
@ -87,7 +87,7 @@ class HMDScriptingInterface : public AbstractHMDScriptingInterface, public Depen
|
|||
public:
|
||||
|
||||
/**jsdoc
|
||||
* Calculate the intersection of a ray with the HUD overlay.
|
||||
* Calculates the intersection of a ray with the HUD overlay.
|
||||
* @function HMD.calculateRayUICollisionPoint
|
||||
* @param {Vec3} position - The origin of the ray.
|
||||
* @param {Vec3} direction - The direction of the ray.
|
||||
|
@ -115,7 +115,7 @@ public:
|
|||
glm::vec3 calculateParabolaUICollisionPoint(const glm::vec3& position, const glm::vec3& velocity, const glm::vec3& acceleration, float& parabolicDistance) const;
|
||||
|
||||
/**jsdoc
|
||||
* Get the 2D HUD overlay coordinates of a 3D point on the HUD overlay.
|
||||
* Gets the 2D HUD overlay coordinates of a 3D point on the HUD overlay.
|
||||
* 2D HUD overlay coordinates are pixels with the origin at the top left of the overlay.
|
||||
* @function HMD.overlayFromWorldPoint
|
||||
* @param {Vec3} position - The point on the HUD overlay in world coordinates.
|
||||
|
@ -141,7 +141,7 @@ public:
|
|||
Q_INVOKABLE glm::vec2 overlayFromWorldPoint(const glm::vec3& position) const;
|
||||
|
||||
/**jsdoc
|
||||
* Get the 3D world coordinates of a 2D point on the HUD overlay.
|
||||
* Gets the 3D world coordinates of a 2D point on the HUD overlay.
|
||||
* 2D HUD overlay coordinates are pixels with the origin at the top left of the overlay.
|
||||
* @function HMD.worldPointFromOverlay
|
||||
* @param {Vec2} coordinates - The point on the HUD overlay in HUD coordinates.
|
||||
|
@ -150,7 +150,7 @@ public:
|
|||
Q_INVOKABLE glm::vec3 worldPointFromOverlay(const glm::vec2& overlay) const;
|
||||
|
||||
/**jsdoc
|
||||
* Get the 2D point on the HUD overlay represented by given spherical coordinates.
|
||||
* Gets the 2D point on the HUD overlay represented by given spherical coordinates.
|
||||
* 2D HUD overlay coordinates are pixels with the origin at the top left of the overlay.
|
||||
* Spherical coordinates are polar coordinates in radians with <code>{ x: 0, y: 0 }</code> being the center of the HUD
|
||||
* overlay.
|
||||
|
@ -161,7 +161,7 @@ public:
|
|||
Q_INVOKABLE glm::vec2 sphericalToOverlay(const glm::vec2 & sphericalPos) const;
|
||||
|
||||
/**jsdoc
|
||||
* Get the spherical coordinates of a 2D point on the HUD overlay.
|
||||
* Gets the spherical coordinates of a 2D point on the HUD overlay.
|
||||
* 2D HUD overlay coordinates are pixels with the origin at the top left of the overlay.
|
||||
* Spherical coordinates are polar coordinates in radians with <code>{ x: 0, y: 0 }</code> being the center of the HUD
|
||||
* overlay.
|
||||
|
@ -172,21 +172,21 @@ public:
|
|||
Q_INVOKABLE glm::vec2 overlayToSpherical(const glm::vec2 & overlayPos) const;
|
||||
|
||||
/**jsdoc
|
||||
* Recenter the HMD HUD to the current HMD position and orientation.
|
||||
* Recenters the HMD HUD to the current HMD position and orientation.
|
||||
* @function HMD.centerUI
|
||||
*/
|
||||
Q_INVOKABLE void centerUI();
|
||||
|
||||
|
||||
/**jsdoc
|
||||
* Get the name of the HMD audio input device.
|
||||
* Gets the name of the HMD audio input device.
|
||||
* @function HMD.preferredAudioInput
|
||||
* @returns {string} The name of the HMD audio input device if in HMD mode, otherwise an empty string.
|
||||
*/
|
||||
Q_INVOKABLE QString preferredAudioInput() const;
|
||||
|
||||
/**jsdoc
|
||||
* Get the name of the HMD audio output device.
|
||||
* Gets the name of the HMD audio output device.
|
||||
* @function HMD.preferredAudioOutput
|
||||
* @returns {string} The name of the HMD audio output device if in HMD mode, otherwise an empty string.
|
||||
*/
|
||||
|
@ -194,10 +194,10 @@ public:
|
|||
|
||||
|
||||
/**jsdoc
|
||||
* Check whether there is an HMD available.
|
||||
* Checks whether there is an HMD available.
|
||||
* @function HMD.isHMDAvailable
|
||||
* @param {string} [name=""] - The name of the HMD to check for, e.g., <code>"Oculus Rift"</code>. The name is the same as
|
||||
* may be displayed in Interface's "Display" menu. If no name is specified then any HMD matches.
|
||||
* may be displayed in Interface's "Display" menu. If no name is specified, then any HMD matches.
|
||||
* @returns {boolean} <code>true</code> if an HMD of the specified <code>name</code> is available, otherwise
|
||||
* <code>false</code>.
|
||||
* @example <caption>Report on HMD availability.</caption>
|
||||
|
@ -208,10 +208,10 @@ public:
|
|||
Q_INVOKABLE bool isHMDAvailable(const QString& name = "");
|
||||
|
||||
/**jsdoc
|
||||
* Check whether there is an HMD head controller available.
|
||||
* Checks whether there is an HMD head controller available.
|
||||
* @function HMD.isHeadControllerAvailable
|
||||
* @param {string} [name=""] - The name of the HMD head controller to check for, e.g., <code>"Oculus"</code>. If no name is
|
||||
* specified then any HMD head controller matches.
|
||||
* specified, then any HMD head controller matches.
|
||||
* @returns {boolean} <code>true</code> if an HMD head controller of the specified <code>name</code> is available,
|
||||
* otherwise <code>false</code>.
|
||||
* @example <caption>Report HMD head controller availability.</caption>
|
||||
|
@ -222,10 +222,10 @@ public:
|
|||
Q_INVOKABLE bool isHeadControllerAvailable(const QString& name = "");
|
||||
|
||||
/**jsdoc
|
||||
* Check whether there are HMD hand controllers available.
|
||||
* Checks whether there are HMD hand controllers available.
|
||||
* @function HMD.isHandControllerAvailable
|
||||
* @param {string} [name=""] - The name of the HMD hand controller to check for, e.g., <code>"Oculus"</code>. If no name is
|
||||
* specified then any HMD hand controller matches.
|
||||
* specified, then any HMD hand controller matches.
|
||||
* @returns {boolean} <code>true</code> if an HMD hand controller of the specified <code>name</code> is available,
|
||||
* otherwise <code>false</code>.
|
||||
* @example <caption>Report HMD hand controller availability.</caption>
|
||||
|
@ -236,7 +236,7 @@ public:
|
|||
Q_INVOKABLE bool isHandControllerAvailable(const QString& name = "");
|
||||
|
||||
/**jsdoc
|
||||
* Check whether there are specific HMD controllers available.
|
||||
* Checks whether there are specific HMD controllers available.
|
||||
* @function HMD.isSubdeviceContainingNameAvailable
|
||||
* @param {string} name - The name of the HMD controller to check for, e.g., <code>"OculusTouch"</code>.
|
||||
* @returns {boolean} <code>true</code> if an HMD controller with a name containing the specified <code>name</code> is
|
||||
|
@ -248,7 +248,7 @@ public:
|
|||
Q_INVOKABLE bool isSubdeviceContainingNameAvailable(const QString& name);
|
||||
|
||||
/**jsdoc
|
||||
* Signal that models of the HMD hand controllers being used should be displayed. The models are displayed at their actual,
|
||||
* Signals that models of the HMD hand controllers being used should be displayed. The models are displayed at their actual,
|
||||
* real-world locations.
|
||||
* @function HMD.requestShowHandControllers
|
||||
* @example <caption>Show your hand controllers for 10 seconds.</caption>
|
||||
|
@ -260,14 +260,14 @@ public:
|
|||
Q_INVOKABLE void requestShowHandControllers();
|
||||
|
||||
/**jsdoc
|
||||
* Signal that it is no longer necessary to display models of the HMD hand controllers being used. If no other scripts
|
||||
* Signals that it is no longer necessary to display models of the HMD hand controllers being used. If no other scripts
|
||||
* want the models displayed then they are no longer displayed.
|
||||
* @function HMD.requestHideHandControllers
|
||||
*/
|
||||
Q_INVOKABLE void requestHideHandControllers();
|
||||
|
||||
/**jsdoc
|
||||
* Check whether any script wants models of the HMD hand controllers displayed. Requests are made and canceled using
|
||||
* Checks whether any script wants models of the HMD hand controllers displayed. Requests are made and canceled using
|
||||
* {@link HMD.requestShowHandControllers|requestShowHandControllers} and
|
||||
* {@link HMD.requestHideHandControllers|requestHideHandControllers}.
|
||||
* @function HMD.shouldShowHandControllers
|
||||
|
@ -292,8 +292,8 @@ public:
|
|||
|
||||
|
||||
/**jsdoc
|
||||
* Suppress the activation of the HMD-provided keyboard, if any. Successful calls should be balanced with a call to
|
||||
* {@link HMD.unspressKeyboard|unspressKeyboard} within a reasonable amount of time.
|
||||
* Suppresses the activation of the HMD-provided keyboard, if any. Successful calls should be balanced with a call to
|
||||
* {@link HMD.unsuppressKeyboard|unsuppressKeyboard} within a reasonable amount of time.
|
||||
* @function HMD.suppressKeyboard
|
||||
* @returns {boolean} <code>true</code> if the current HMD provides a keyboard and it was successfully suppressed (e.g., it
|
||||
* isn't being displayed), otherwise <code>false</code>.
|
||||
|
@ -307,14 +307,14 @@ public:
|
|||
Q_INVOKABLE bool suppressKeyboard();
|
||||
|
||||
/**jsdoc
|
||||
* Unsuppress the activation of the HMD-provided keyboard, if any.
|
||||
* Unsuppresses the activation of the HMD-provided keyboard, if any.
|
||||
* @function HMD.unsuppressKeyboard
|
||||
*/
|
||||
/// Enable the keyboard following a suppressKeyboard call
|
||||
Q_INVOKABLE void unsuppressKeyboard();
|
||||
|
||||
/**jsdoc
|
||||
* Check whether the HMD-provided keyboard, if any, is visible.
|
||||
* Checks whether the HMD-provided keyboard, if any, is visible.
|
||||
* @function HMD.isKeyboardVisible
|
||||
* @returns {boolean} <code>true</code> if the current HMD provides a keyboard and it is visible, otherwise
|
||||
* <code>false</code>.
|
||||
|
@ -377,7 +377,19 @@ signals:
|
|||
|
||||
public:
|
||||
HMDScriptingInterface();
|
||||
|
||||
/**jsdoc
|
||||
* Gets the position on the HUD overlay that your HMD is looking at, in HUD coordinates.
|
||||
* @function HMD.getHUDLookAtPosition2D
|
||||
* @returns {Vec2} The position on the HUD overlay that your HMD is looking at, in pixels.
|
||||
*/
|
||||
static QScriptValue getHUDLookAtPosition2D(QScriptContext* context, QScriptEngine* engine);
|
||||
|
||||
/**jsdoc
|
||||
* Gets the position on the HUD overlay that your HMD is looking at, in world coordinates.
|
||||
* @function HMD.getHUDLookAtPosition3D
|
||||
* @returns {Vec3} The position on the HUD overlay the your HMD is looking at, in world coordinates.
|
||||
*/
|
||||
static QScriptValue getHUDLookAtPosition3D(QScriptContext* context, QScriptEngine* engine);
|
||||
|
||||
bool isMounted() const override;
|
||||
|
|
76
interface/src/ui/AvatarCertifyBanner.cpp
Normal file
76
interface/src/ui/AvatarCertifyBanner.cpp
Normal file
|
@ -0,0 +1,76 @@
|
|||
//
|
||||
// AvatarCertifyBanner.h
|
||||
// interface/src/ui
|
||||
//
|
||||
// Created by Simon Walton, April 2019
|
||||
// Copyright 2019 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 "AvatarCertifyBanner.h"
|
||||
|
||||
#include <QtGui/QDesktopServices>
|
||||
|
||||
#include "ui/TabletScriptingInterface.h"
|
||||
#include "EntityTreeRenderer.h"
|
||||
|
||||
namespace {
|
||||
const QUrl AVATAR_THEFT_BANNER_IMAGE = PathUtils::resourcesUrl("images/AvatarTheftBanner.png");
|
||||
const QString AVATAR_THEFT_BANNER_SCRIPT { "/system/clickToAvatarApp.js" };
|
||||
}
|
||||
|
||||
AvatarCertifyBanner::AvatarCertifyBanner(QQuickItem* parent) {
|
||||
}
|
||||
|
||||
void AvatarCertifyBanner::show(const QUuid& avatarID) {
|
||||
if (!_active) {
|
||||
auto entityTreeRenderer = DependencyManager::get<EntityTreeRenderer>();
|
||||
EntityTreePointer entityTree = entityTreeRenderer->getTree();
|
||||
if (!entityTree) {
|
||||
return;
|
||||
}
|
||||
const bool tabletShown = DependencyManager::get<TabletScriptingInterface>()->property("tabletShown").toBool();
|
||||
const auto& position = tabletShown ? glm::vec3(0.0f, 0.0f, -1.8f) : glm::vec3(0.0f, 0.0f, -0.7f);
|
||||
const float scaleFactor = tabletShown ? 2.6f : 1.0f;
|
||||
|
||||
EntityItemProperties entityProperties;
|
||||
entityProperties.setType(EntityTypes::Image);
|
||||
entityProperties.setEntityHostType(entity::HostType::LOCAL);
|
||||
entityProperties.setImageURL(AVATAR_THEFT_BANNER_IMAGE.toString());
|
||||
entityProperties.setName("hifi-avatar-notification-banner");
|
||||
entityProperties.setParentID(avatarID);
|
||||
entityProperties.setParentJointIndex(CAMERA_MATRIX_INDEX);
|
||||
entityProperties.setLocalPosition(position);
|
||||
entityProperties.setDimensions(glm::vec3(1.0f, 1.0f, 0.3f) * scaleFactor);
|
||||
entityProperties.setRenderLayer(tabletShown ? RenderLayer::WORLD : RenderLayer::FRONT);
|
||||
entityProperties.getGrab().setGrabbable(false);
|
||||
QString scriptPath = QUrl(PathUtils::defaultScriptsLocation("")).toString() + AVATAR_THEFT_BANNER_SCRIPT;
|
||||
entityProperties.setScript(scriptPath);
|
||||
entityProperties.setVisible(true);
|
||||
|
||||
entityTree->withWriteLock([&] {
|
||||
auto entityTreeItem = entityTree->addEntity(_bannerID, entityProperties);
|
||||
entityTreeItem->setLocalPosition(position);
|
||||
});
|
||||
|
||||
_active = true;
|
||||
}
|
||||
}
|
||||
|
||||
void AvatarCertifyBanner::clear() {
|
||||
if (_active) {
|
||||
auto entityTreeRenderer = DependencyManager::get<EntityTreeRenderer>();
|
||||
EntityTreePointer entityTree = entityTreeRenderer->getTree();
|
||||
if (!entityTree) {
|
||||
return;
|
||||
}
|
||||
|
||||
entityTree->withWriteLock([&] {
|
||||
entityTree->deleteEntity(_bannerID);
|
||||
});
|
||||
|
||||
_active = false;
|
||||
}
|
||||
}
|
34
interface/src/ui/AvatarCertifyBanner.h
Normal file
34
interface/src/ui/AvatarCertifyBanner.h
Normal file
|
@ -0,0 +1,34 @@
|
|||
//
|
||||
// AvatarCertifyBanner.h
|
||||
// interface/src/ui
|
||||
//
|
||||
// Created by Simon Walton, April 2019
|
||||
// Copyright 2019 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_AvatarCertifyBanner_h
|
||||
#define hifi_AvatarCertifyBanner_h
|
||||
|
||||
#include <QUuid>
|
||||
#include "OffscreenQmlElement.h"
|
||||
#include "EntityItemID.h"
|
||||
|
||||
class EntityItemID;
|
||||
|
||||
class AvatarCertifyBanner : QObject {
|
||||
Q_OBJECT
|
||||
HIFI_QML_DECL
|
||||
public:
|
||||
AvatarCertifyBanner(QQuickItem* parent = nullptr);
|
||||
void show(const QUuid& avatarID);
|
||||
void clear();
|
||||
|
||||
private:
|
||||
const EntityItemID _bannerID { QUuid::createUuid() };
|
||||
bool _active { false };
|
||||
};
|
||||
|
||||
#endif // hifi_AvatarCertifyBanner_h
|
|
@ -11,12 +11,15 @@
|
|||
|
||||
#include "InteractiveWindow.h"
|
||||
|
||||
#include "Application.h"
|
||||
#include <QtQml/QQmlContext>
|
||||
#include <QtCore/QThread>
|
||||
#include <QtGui/QGuiApplication>
|
||||
#include <QtQuick/QQuickWindow>
|
||||
#include <QQuickView>
|
||||
|
||||
#include <DependencyManager.h>
|
||||
#include <DockWidget.h>
|
||||
#include <RegisteredMetaTypes.h>
|
||||
|
||||
#include "OffscreenUi.h"
|
||||
|
@ -40,9 +43,17 @@ static const char* const VISIBLE_PROPERTY = "visible";
|
|||
static const char* const INTERACTIVE_WINDOW_VISIBLE_PROPERTY = "interactiveWindowVisible";
|
||||
static const char* const EVENT_BRIDGE_PROPERTY = "eventBridge";
|
||||
static const char* const PRESENTATION_MODE_PROPERTY = "presentationMode";
|
||||
static const char* const DOCKED_PROPERTY = "presentationWindowInfo";
|
||||
static const char* const DOCK_AREA_PROPERTY = "dockArea";
|
||||
|
||||
static const QStringList KNOWN_SCHEMES = QStringList() << "http" << "https" << "file" << "about" << "atp" << "qrc";
|
||||
|
||||
static const int DEFAULT_HEIGHT = 60;
|
||||
|
||||
static void dockWidgetDeleter(DockWidget* dockWidget) {
|
||||
dockWidget->deleteLater();
|
||||
}
|
||||
|
||||
void registerInteractiveWindowMetaType(QScriptEngine* engine) {
|
||||
qScriptRegisterMetaType(engine, interactiveWindowPointerToScriptValue, interactiveWindowPointerFromScriptValue);
|
||||
}
|
||||
|
@ -58,55 +69,116 @@ void interactiveWindowPointerFromScriptValue(const QScriptValue& object, Interac
|
|||
}
|
||||
|
||||
InteractiveWindow::InteractiveWindow(const QString& sourceUrl, const QVariantMap& properties) {
|
||||
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
||||
bool docked = false;
|
||||
InteractiveWindowPresentationMode presentationMode = InteractiveWindowPresentationMode::Native;
|
||||
|
||||
if (properties.contains(PRESENTATION_MODE_PROPERTY)) {
|
||||
presentationMode = (InteractiveWindowPresentationMode) properties[PRESENTATION_MODE_PROPERTY].toInt();
|
||||
}
|
||||
|
||||
if (properties.contains(DOCKED_PROPERTY) && presentationMode == InteractiveWindowPresentationMode::Native) {
|
||||
QVariantMap nativeWindowInfo = properties[DOCKED_PROPERTY].toMap();
|
||||
Qt::DockWidgetArea dockArea = Qt::TopDockWidgetArea;
|
||||
QString title;
|
||||
QSize windowSize(DEFAULT_HEIGHT, DEFAULT_HEIGHT);
|
||||
|
||||
// Build the event bridge and wrapper on the main thread
|
||||
offscreenUi->loadInNewContext(CONTENT_WINDOW_QML, [&](QQmlContext* context, QObject* object) {
|
||||
_qmlWindow = object;
|
||||
context->setContextProperty(EVENT_BRIDGE_PROPERTY, this);
|
||||
if (properties.contains(FLAGS_PROPERTY)) {
|
||||
object->setProperty(FLAGS_PROPERTY, properties[FLAGS_PROPERTY].toUInt());
|
||||
}
|
||||
if (properties.contains(PRESENTATION_MODE_PROPERTY)) {
|
||||
object->setProperty(PRESENTATION_MODE_PROPERTY, properties[PRESENTATION_MODE_PROPERTY].toInt());
|
||||
}
|
||||
if (properties.contains(TITLE_PROPERTY)) {
|
||||
object->setProperty(TITLE_PROPERTY, properties[TITLE_PROPERTY].toString());
|
||||
title = properties[TITLE_PROPERTY].toString();
|
||||
}
|
||||
if (properties.contains(SIZE_PROPERTY)) {
|
||||
const auto size = vec2FromVariant(properties[SIZE_PROPERTY]);
|
||||
object->setProperty(INTERACTIVE_WINDOW_SIZE_PROPERTY, QSize(size.x, size.y));
|
||||
}
|
||||
if (properties.contains(POSITION_PROPERTY)) {
|
||||
const auto position = vec2FromVariant(properties[POSITION_PROPERTY]);
|
||||
object->setProperty(INTERACTIVE_WINDOW_POSITION_PROPERTY, QPointF(position.x, position.y));
|
||||
}
|
||||
if (properties.contains(VISIBLE_PROPERTY)) {
|
||||
object->setProperty(VISIBLE_PROPERTY, properties[INTERACTIVE_WINDOW_VISIBLE_PROPERTY].toBool());
|
||||
windowSize = QSize(size.x, size.y);
|
||||
}
|
||||
|
||||
connect(object, SIGNAL(sendToScript(QVariant)), this, SLOT(qmlToScript(const QVariant&)), Qt::QueuedConnection);
|
||||
connect(object, SIGNAL(interactiveWindowPositionChanged()), this, SIGNAL(positionChanged()), Qt::QueuedConnection);
|
||||
connect(object, SIGNAL(interactiveWindowSizeChanged()), this, SIGNAL(sizeChanged()), Qt::QueuedConnection);
|
||||
connect(object, SIGNAL(interactiveWindowVisibleChanged()), this, SIGNAL(visibleChanged()), Qt::QueuedConnection);
|
||||
connect(object, SIGNAL(presentationModeChanged()), this, SIGNAL(presentationModeChanged()), Qt::QueuedConnection);
|
||||
connect(object, SIGNAL(titleChanged()), this, SIGNAL(titleChanged()), Qt::QueuedConnection);
|
||||
connect(object, SIGNAL(windowClosed()), this, SIGNAL(closed()), Qt::QueuedConnection);
|
||||
connect(object, SIGNAL(selfDestruct()), this, SLOT(close()), Qt::QueuedConnection);
|
||||
auto mainWindow = qApp->getWindow();
|
||||
_dockWidget = std::shared_ptr<DockWidget>(new DockWidget(title, mainWindow), dockWidgetDeleter);
|
||||
|
||||
if (nativeWindowInfo.contains(DOCK_AREA_PROPERTY)) {
|
||||
DockArea dockedArea = (DockArea) nativeWindowInfo[DOCK_AREA_PROPERTY].toInt();
|
||||
switch (dockedArea) {
|
||||
case DockArea::TOP:
|
||||
dockArea = Qt::TopDockWidgetArea;
|
||||
_dockWidget->setFixedHeight(windowSize.height());
|
||||
break;
|
||||
case DockArea::BOTTOM:
|
||||
dockArea = Qt::BottomDockWidgetArea;
|
||||
_dockWidget->setFixedHeight(windowSize.height());
|
||||
break;
|
||||
case DockArea::LEFT:
|
||||
dockArea = Qt::LeftDockWidgetArea;
|
||||
_dockWidget->setFixedWidth(windowSize.width());
|
||||
break;
|
||||
case DockArea::RIGHT:
|
||||
dockArea = Qt::RightDockWidgetArea;
|
||||
_dockWidget->setFixedWidth(windowSize.width());
|
||||
break;
|
||||
|
||||
default:
|
||||
_dockWidget->setFixedHeight(DEFAULT_HEIGHT);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto quickView = _dockWidget->getQuickView();
|
||||
QObject::connect(quickView.get(), &QQuickView::statusChanged, [&, this] (QQuickView::Status status) {
|
||||
if (status == QQuickView::Ready) {
|
||||
QQuickItem* rootItem = _dockWidget->getRootItem();
|
||||
QObject::connect(rootItem, SIGNAL(sendToScript(QVariant)), this, SLOT(qmlToScript(const QVariant&)), Qt::QueuedConnection);
|
||||
}
|
||||
});
|
||||
_dockWidget->setSource(QUrl(sourceUrl));
|
||||
mainWindow->addDockWidget(dockArea, _dockWidget.get());
|
||||
_dockedWindow = docked;
|
||||
} else {
|
||||
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
||||
// Build the event bridge and wrapper on the main thread
|
||||
offscreenUi->loadInNewContext(CONTENT_WINDOW_QML, [&](QQmlContext* context, QObject* object) {
|
||||
_qmlWindow = object;
|
||||
context->setContextProperty(EVENT_BRIDGE_PROPERTY, this);
|
||||
if (properties.contains(FLAGS_PROPERTY)) {
|
||||
object->setProperty(FLAGS_PROPERTY, properties[FLAGS_PROPERTY].toUInt());
|
||||
}
|
||||
if (properties.contains(PRESENTATION_MODE_PROPERTY)) {
|
||||
object->setProperty(PRESENTATION_MODE_PROPERTY, properties[PRESENTATION_MODE_PROPERTY].toInt());
|
||||
}
|
||||
if (properties.contains(TITLE_PROPERTY)) {
|
||||
object->setProperty(TITLE_PROPERTY, properties[TITLE_PROPERTY].toString());
|
||||
}
|
||||
if (properties.contains(SIZE_PROPERTY)) {
|
||||
const auto size = vec2FromVariant(properties[SIZE_PROPERTY]);
|
||||
object->setProperty(INTERACTIVE_WINDOW_SIZE_PROPERTY, QSize(size.x, size.y));
|
||||
}
|
||||
if (properties.contains(POSITION_PROPERTY)) {
|
||||
const auto position = vec2FromVariant(properties[POSITION_PROPERTY]);
|
||||
object->setProperty(INTERACTIVE_WINDOW_POSITION_PROPERTY, QPointF(position.x, position.y));
|
||||
}
|
||||
if (properties.contains(VISIBLE_PROPERTY)) {
|
||||
object->setProperty(VISIBLE_PROPERTY, properties[INTERACTIVE_WINDOW_VISIBLE_PROPERTY].toBool());
|
||||
}
|
||||
|
||||
connect(object, SIGNAL(sendToScript(QVariant)), this, SLOT(qmlToScript(const QVariant&)), Qt::QueuedConnection);
|
||||
connect(object, SIGNAL(interactiveWindowPositionChanged()), this, SIGNAL(positionChanged()), Qt::QueuedConnection);
|
||||
connect(object, SIGNAL(interactiveWindowSizeChanged()), this, SIGNAL(sizeChanged()), Qt::QueuedConnection);
|
||||
connect(object, SIGNAL(interactiveWindowVisibleChanged()), this, SIGNAL(visibleChanged()), Qt::QueuedConnection);
|
||||
connect(object, SIGNAL(presentationModeChanged()), this, SIGNAL(presentationModeChanged()), Qt::QueuedConnection);
|
||||
connect(object, SIGNAL(titleChanged()), this, SIGNAL(titleChanged()), Qt::QueuedConnection);
|
||||
connect(object, SIGNAL(windowClosed()), this, SIGNAL(closed()), Qt::QueuedConnection);
|
||||
connect(object, SIGNAL(selfDestruct()), this, SLOT(close()), Qt::QueuedConnection);
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
connect(object, SIGNAL(nativeWindowChanged()), this, SLOT(parentNativeWindowToMainWindow()), Qt::QueuedConnection);
|
||||
connect(object, SIGNAL(interactiveWindowVisibleChanged()), this, SLOT(parentNativeWindowToMainWindow()), Qt::QueuedConnection);
|
||||
connect(object, SIGNAL(presentationModeChanged()), this, SLOT(parentNativeWindowToMainWindow()), Qt::QueuedConnection);
|
||||
connect(object, SIGNAL(nativeWindowChanged()), this, SLOT(parentNativeWindowToMainWindow()), Qt::QueuedConnection);
|
||||
connect(object, SIGNAL(interactiveWindowVisibleChanged()), this, SLOT(parentNativeWindowToMainWindow()), Qt::QueuedConnection);
|
||||
connect(object, SIGNAL(presentationModeChanged()), this, SLOT(parentNativeWindowToMainWindow()), Qt::QueuedConnection);
|
||||
#endif
|
||||
|
||||
QUrl sourceURL{ sourceUrl };
|
||||
// If the passed URL doesn't correspond to a known scheme, assume it's a local file path
|
||||
if (!KNOWN_SCHEMES.contains(sourceURL.scheme(), Qt::CaseInsensitive)) {
|
||||
sourceURL = QUrl::fromLocalFile(sourceURL.toString()).toString();
|
||||
}
|
||||
object->setProperty(SOURCE_PROPERTY, sourceURL);
|
||||
});
|
||||
QUrl sourceURL{ sourceUrl };
|
||||
// If the passed URL doesn't correspond to a known scheme, assume it's a local file path
|
||||
if (!KNOWN_SCHEMES.contains(sourceURL.scheme(), Qt::CaseInsensitive)) {
|
||||
sourceURL = QUrl::fromLocalFile(sourceURL.toString()).toString();
|
||||
}
|
||||
object->setProperty(SOURCE_PROPERTY, sourceURL);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
InteractiveWindow::~InteractiveWindow() {
|
||||
|
@ -115,7 +187,14 @@ InteractiveWindow::~InteractiveWindow() {
|
|||
|
||||
void InteractiveWindow::sendToQml(const QVariant& message) {
|
||||
// Forward messages received from the script on to QML
|
||||
QMetaObject::invokeMethod(_qmlWindow, "fromScript", Qt::QueuedConnection, Q_ARG(QVariant, message));
|
||||
if (_dockedWindow) {
|
||||
QQuickItem* rootItem = _dockWidget->getRootItem();
|
||||
if (rootItem) {
|
||||
QMetaObject::invokeMethod(_qmlWindow, "fromScript", Qt::QueuedConnection, Q_ARG(QVariant, message));
|
||||
}
|
||||
} else {
|
||||
QMetaObject::invokeMethod(_qmlWindow, "fromScript", Qt::QueuedConnection, Q_ARG(QVariant, message));
|
||||
}
|
||||
}
|
||||
|
||||
void InteractiveWindow::emitScriptEvent(const QVariant& scriptMessage) {
|
||||
|
@ -143,6 +222,9 @@ void InteractiveWindow::close() {
|
|||
if (_qmlWindow) {
|
||||
_qmlWindow->deleteLater();
|
||||
}
|
||||
|
||||
qApp->getWindow()->removeDockWidget(_dockWidget.get());
|
||||
_dockWidget = nullptr;
|
||||
_qmlWindow = nullptr;
|
||||
}
|
||||
|
|
@ -36,6 +36,14 @@ namespace InteractiveWindowEnums {
|
|||
Native
|
||||
};
|
||||
Q_ENUM_NS(InteractiveWindowPresentationMode);
|
||||
|
||||
enum DockArea {
|
||||
TOP,
|
||||
BOTTOM,
|
||||
LEFT,
|
||||
RIGHT
|
||||
};
|
||||
Q_ENUM_NS(DockArea);
|
||||
}
|
||||
|
||||
using namespace InteractiveWindowEnums;
|
||||
|
@ -52,8 +60,10 @@ using namespace InteractiveWindowEnums;
|
|||
* @property {Vec2} size
|
||||
* @property {boolean} visible
|
||||
* @property {Desktop.PresentationMode} presentationMode
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
class DockWidget;
|
||||
class InteractiveWindow : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
|
@ -65,7 +75,6 @@ class InteractiveWindow : public QObject {
|
|||
|
||||
public:
|
||||
InteractiveWindow(const QString& sourceUrl, const QVariantMap& properties);
|
||||
|
||||
~InteractiveWindow();
|
||||
|
||||
private:
|
||||
|
@ -194,7 +203,9 @@ protected slots:
|
|||
void qmlToScript(const QVariant& message);
|
||||
|
||||
private:
|
||||
bool _dockedWindow { false };
|
||||
QPointer<QObject> _qmlWindow;
|
||||
std::shared_ptr<DockWidget> _dockWidget { nullptr };
|
||||
};
|
||||
|
||||
typedef InteractiveWindow* InteractiveWindowPointer;
|
|
@ -1428,7 +1428,7 @@ int Avatar::getJointIndex(const QString& name) const {
|
|||
|
||||
withValidJointIndicesCache([&]() {
|
||||
if (_modelJointIndicesCache.contains(name)) {
|
||||
result = _modelJointIndicesCache[name] - 1;
|
||||
result = _modelJointIndicesCache.value(name) - 1;
|
||||
}
|
||||
});
|
||||
return result;
|
||||
|
@ -1439,9 +1439,7 @@ QStringList Avatar::getJointNames() const {
|
|||
withValidJointIndicesCache([&]() {
|
||||
// find out how large the vector needs to be
|
||||
int maxJointIndex = -1;
|
||||
QHashIterator<QString, int> k(_modelJointIndicesCache);
|
||||
while (k.hasNext()) {
|
||||
k.next();
|
||||
for (auto k = _modelJointIndicesCache.constBegin(); k != _modelJointIndicesCache.constEnd(); k++) {
|
||||
int index = k.value();
|
||||
if (index > maxJointIndex) {
|
||||
maxJointIndex = index;
|
||||
|
@ -1450,9 +1448,7 @@ QStringList Avatar::getJointNames() const {
|
|||
// iterate through the hash and put joint names
|
||||
// into the vector at their indices
|
||||
QVector<QString> resultVector(maxJointIndex+1);
|
||||
QHashIterator<QString, int> i(_modelJointIndicesCache);
|
||||
while (i.hasNext()) {
|
||||
i.next();
|
||||
for (auto i = _modelJointIndicesCache.constBegin(); i != _modelJointIndicesCache.constEnd(); i++) {
|
||||
int index = i.value();
|
||||
resultVector[index] = i.key();
|
||||
}
|
||||
|
|
|
@ -164,7 +164,7 @@ void SkeletonModel::simulate(float deltaTime, bool fullUpdate) {
|
|||
Parent::simulate(deltaTime, fullUpdate);
|
||||
}
|
||||
|
||||
// FIXME: This texture loading logic should probably live in Avatar, to mirror RenderableModelEntityItem and ModelOverlay,
|
||||
// FIXME: This texture loading logic should probably live in Avatar, to mirror RenderableModelEntityItem,
|
||||
// but Avatars don't get updates in the same way
|
||||
if (!_texturesLoaded && getGeometry() && getGeometry()->areTexturesLoaded()) {
|
||||
_texturesLoaded = true;
|
||||
|
|
|
@ -1955,8 +1955,7 @@ void AvatarData::processAvatarIdentity(QDataStream& packetStream, bool& identity
|
|||
>> identity.attachmentData
|
||||
>> identity.displayName
|
||||
>> identity.sessionDisplayName
|
||||
>> identity.isReplicated
|
||||
>> identity.lookAtSnappingEnabled
|
||||
>> identity.identityFlags
|
||||
;
|
||||
|
||||
if (incomingSequenceNumber > _identitySequenceNumber) {
|
||||
|
@ -1971,8 +1970,22 @@ void AvatarData::processAvatarIdentity(QDataStream& packetStream, bool& identity
|
|||
}
|
||||
maybeUpdateSessionDisplayNameFromTransport(identity.sessionDisplayName);
|
||||
|
||||
if (identity.isReplicated != _isReplicated) {
|
||||
_isReplicated = identity.isReplicated;
|
||||
bool flagValue;
|
||||
flagValue = identity.identityFlags.testFlag(AvatarDataPacket::IdentityFlag::isReplicated);
|
||||
if ( flagValue != _isReplicated) {
|
||||
_isReplicated = flagValue;
|
||||
identityChanged = true;
|
||||
}
|
||||
|
||||
flagValue = identity.identityFlags.testFlag(AvatarDataPacket::IdentityFlag::lookAtSnapping);
|
||||
if ( flagValue != _lookAtSnappingEnabled) {
|
||||
setProperty("lookAtSnappingEnabled", flagValue);
|
||||
identityChanged = true;
|
||||
}
|
||||
|
||||
flagValue = identity.identityFlags.testFlag(AvatarDataPacket::IdentityFlag::verificationFailed);
|
||||
if (flagValue != _verificationFailed) {
|
||||
_verificationFailed = flagValue;
|
||||
identityChanged = true;
|
||||
}
|
||||
|
||||
|
@ -1981,11 +1994,6 @@ void AvatarData::processAvatarIdentity(QDataStream& packetStream, bool& identity
|
|||
identityChanged = true;
|
||||
}
|
||||
|
||||
if (identity.lookAtSnappingEnabled != _lookAtSnappingEnabled) {
|
||||
setProperty("lookAtSnappingEnabled", identity.lookAtSnappingEnabled);
|
||||
identityChanged = true;
|
||||
}
|
||||
|
||||
#ifdef WANT_DEBUG
|
||||
qCDebug(avatars) << __FUNCTION__
|
||||
<< "identity.uuid:" << identity.uuid
|
||||
|
@ -2195,17 +2203,27 @@ void AvatarData::prepareResetTraitInstances() {
|
|||
QByteArray AvatarData::identityByteArray(bool setIsReplicated) const {
|
||||
QByteArray identityData;
|
||||
QDataStream identityStream(&identityData, QIODevice::Append);
|
||||
using namespace AvatarDataPacket;
|
||||
|
||||
// when mixers send identity packets to agents, they simply forward along the last incoming sequence number they received
|
||||
// whereas agents send a fresh outgoing sequence number when identity data has changed
|
||||
IdentityFlags identityFlags = IdentityFlag::none;
|
||||
if (_isReplicated || setIsReplicated) {
|
||||
identityFlags.setFlag(IdentityFlag::isReplicated);
|
||||
}
|
||||
if (_lookAtSnappingEnabled) {
|
||||
identityFlags.setFlag(IdentityFlag::lookAtSnapping);
|
||||
}
|
||||
if (isCertifyFailed()) {
|
||||
identityFlags.setFlag(IdentityFlag::verificationFailed);
|
||||
}
|
||||
|
||||
identityStream << getSessionUUID()
|
||||
<< (udt::SequenceNumber::Type) _identitySequenceNumber
|
||||
<< _attachmentData
|
||||
<< _displayName
|
||||
<< getSessionDisplayNameForTransport() // depends on _sessionDisplayName
|
||||
<< (_isReplicated || setIsReplicated)
|
||||
<< _lookAtSnappingEnabled;
|
||||
<< identityFlags;
|
||||
|
||||
return identityData;
|
||||
}
|
||||
|
|
|
@ -378,6 +378,10 @@ namespace AvatarDataPacket {
|
|||
|
||||
static const size_t MIN_BULK_PACKET_SIZE = NUM_BYTES_RFC4122_UUID + HEADER_SIZE;
|
||||
|
||||
// AvatarIdentity packet:
|
||||
enum class IdentityFlag: quint32 {none, isReplicated = 0x1, lookAtSnapping = 0x2, verificationFailed = 0x4};
|
||||
Q_DECLARE_FLAGS(IdentityFlags, IdentityFlag)
|
||||
|
||||
struct SendStatus {
|
||||
HasFlags itemFlags { 0 };
|
||||
bool sendUUID { false };
|
||||
|
@ -1182,6 +1186,7 @@ public:
|
|||
QString sessionDisplayName;
|
||||
bool isReplicated;
|
||||
bool lookAtSnappingEnabled;
|
||||
AvatarDataPacket::IdentityFlags identityFlags;
|
||||
};
|
||||
|
||||
// identityChanged returns true if identity has changed, false otherwise.
|
||||
|
@ -1213,6 +1218,7 @@ public:
|
|||
_sessionDisplayName = sessionDisplayName;
|
||||
markIdentityDataChanged();
|
||||
}
|
||||
virtual bool isCertifyFailed() const { return _verificationFailed; }
|
||||
|
||||
/**jsdoc
|
||||
* Gets information about the models currently attached to your avatar.
|
||||
|
@ -1694,6 +1700,7 @@ protected:
|
|||
QString _displayName;
|
||||
QString _sessionDisplayName { };
|
||||
bool _lookAtSnappingEnabled { true };
|
||||
bool _verificationFailed { false };
|
||||
|
||||
quint64 _errorLogExpiry; ///< time in future when to log an error
|
||||
|
||||
|
|
|
@ -23,6 +23,8 @@
|
|||
|
||||
#include "Profile.h"
|
||||
|
||||
static const QString VERIFY_FAIL_MODEL { "/meshes/verifyFailed.fst" };
|
||||
|
||||
void AvatarReplicas::addReplica(const QUuid& parentID, AvatarSharedPointer replica) {
|
||||
if (parentID == QUuid()) {
|
||||
return;
|
||||
|
@ -324,6 +326,10 @@ void AvatarHashMap::processAvatarIdentityPacket(QSharedPointer<ReceivedMessage>
|
|||
bool displayNameChanged = false;
|
||||
// In this case, the "sendingNode" is the Avatar Mixer.
|
||||
avatar->processAvatarIdentity(avatarIdentityStream, identityChanged, displayNameChanged);
|
||||
if (avatar->isCertifyFailed() && identityUUID != EMPTY) {
|
||||
qCDebug(avatars) << "Avatar" << avatar->getSessionDisplayName() << "marked as VERIFY-FAILED";
|
||||
avatar->setSkeletonModelURL(PathUtils::resourcesUrl(VERIFY_FAIL_MODEL));
|
||||
}
|
||||
_replicas.processAvatarIdentity(identityUUID, message->getMessage(), identityChanged, displayNameChanged);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,9 +35,9 @@ namespace controller {
|
|||
|
||||
/**jsdoc
|
||||
* <p>The <code>Controller.Actions</code> object has properties representing predefined actions on the user's avatar and
|
||||
* Interface. The property values are integer IDs, uniquely identifying each action. <em>Read-only.</em> These can be used
|
||||
* as end points in the routes of a {@link MappingObject}. The data routed to each action is either a number or a
|
||||
* {@link Pose}.</p>
|
||||
* Interface. The property values are integer IDs, uniquely identifying each action. <em>Read-only.</em></p>
|
||||
* <p>These actions can be used as end points in the routes of a {@link MappingObject}. The data item routed to each action
|
||||
* is either a number or a {@link Pose}.</p>
|
||||
*
|
||||
* <table>
|
||||
* <thead>
|
||||
|
@ -178,7 +178,7 @@ namespace controller {
|
|||
* person view.</td></tr>
|
||||
* <tr><td><code>CycleCamera</code></td><td>number</td><td>number</td><td>Cycle the camera view from first person, to
|
||||
* third person, to full screen mirror, then back to first person and repeat.</td></tr>
|
||||
* <tr><td><code>ContextMenu</code></td><td>number</td><td>number</td><td>Show / hide the tablet.</td></tr>
|
||||
* <tr><td><code>ContextMenu</code></td><td>number</td><td>number</td><td>Show/hide the tablet.</td></tr>
|
||||
* <tr><td><code>ToggleMute</code></td><td>number</td><td>number</td><td>Toggle the microphone mute.</td></tr>
|
||||
* <tr><td><code>TogglePushToTalk</code></td><td>number</td><td>number</td><td>Toggle push to talk.</td></tr>
|
||||
* <tr><td><code>ToggleOverlay</code></td><td>number</td><td>number</td><td>Toggle the display of overlays.</td></tr>
|
||||
|
@ -238,71 +238,49 @@ namespace controller {
|
|||
* <tr><td><code>LEFT_HAND</code></td><td>number</td><td>{@link Pose}</td><td><span class="important">Deprecated: This
|
||||
* action is deprecated and will be removed. Use <code>LeftHand</code> instead.</span></td></tr>
|
||||
* <tr><td><code>RIGHT_HAND</code></td><td>number</td><td>{@link Pose}</td><td><span class="important">Deprecated: This
|
||||
* action is deprecated and will be removed. Use
|
||||
* <code>RightHand</code> instead.</span></td></tr>
|
||||
* action is deprecated and will be removed. Use <code>RightHand</code> instead.</span></td></tr>
|
||||
* <tr><td><code>BOOM_IN</code></td><td>number</td><td>number</td><td><span class="important">Deprecated: This
|
||||
* action is deprecated and will be removed. Use
|
||||
* <code>BoomIn</code> instead.</span></td></tr>
|
||||
* action is deprecated and will be removed. Use <code>BoomIn</code> instead.</span></td></tr>
|
||||
* <tr><td><code>BOOM_OUT</code></td><td>number</td><td>number</td><td><span class="important">Deprecated: This
|
||||
* action is deprecated and will be removed. Use
|
||||
* <code>BoomOut</code> instead.</span></td></tr>
|
||||
* action is deprecated and will be removed. Use <code>BoomOut</code> instead.</span></td></tr>
|
||||
* <tr><td><code>CONTEXT_MENU</code></td><td>number</td><td>number</td><td><span class="important">Deprecated: This
|
||||
* action is deprecated and will be removed. Use
|
||||
* <code>ContextMenu</code> instead.</span></td></tr>
|
||||
* action is deprecated and will be removed. Use <code>ContextMenu</code> instead.</span></td></tr>
|
||||
* <tr><td><code>TOGGLE_MUTE</code></td><td>number</td><td>number</td><td><span class="important">Deprecated: This
|
||||
* action is deprecated and will be removed. Use
|
||||
* <code>ToggleMute</code> instead.</span></td></tr>
|
||||
* action is deprecated and will be removed. Use <code>ToggleMute</code> instead.</span></td></tr>
|
||||
* <tr><td><code>TOGGLE_PUSHTOTALK</code></td><td>number</td><td>number</td><td><span class="important">Deprecated: This
|
||||
* action is deprecated and will be removed. Use
|
||||
* <code>TogglePushToTalk</code> instead.</span></td></tr>
|
||||
* action is deprecated and will be removed. Use <code>TogglePushToTalk</code> instead.</span></td></tr>
|
||||
* <tr><td><code>SPRINT</code></td><td>number</td><td>number</td><td><span class="important">Deprecated: This
|
||||
* action is deprecated and will be removed. Use
|
||||
* <code>Sprint</code> instead.</span></td></tr>
|
||||
* action is deprecated and will be removed. Use <code>Sprint</code> instead.</span></td></tr>
|
||||
* <tr><td><code>LONGITUDINAL_BACKWARD</code></td><td>number</td><td>number</td><td><span class="important">Deprecated: This
|
||||
* action is deprecated and will be removed. Use
|
||||
* <code>Backward</code> instead.</span></td></tr>
|
||||
* action is deprecated and will be removed. Use <code>Backward</code> instead.</span></td></tr>
|
||||
* <tr><td><code>LONGITUDINAL_FORWARD</code></td><td>number</td><td>number</td><td><span class="important">Deprecated: This
|
||||
* action is deprecated and will be removed. Use
|
||||
* <code>Forward</code> instead.</span></td></tr>
|
||||
* action is deprecated and will be removed. Use <code>Forward</code> instead.</span></td></tr>
|
||||
* <tr><td><code>LATERAL_LEFT</code></td><td>number</td><td>number</td><td><span class="important">Deprecated: This
|
||||
* action is deprecated and will be removed. Use
|
||||
* <code>StrafeLeft</code> instead.</span></td></tr>
|
||||
* action is deprecated and will be removed. Use <code>StrafeLeft</code> instead.</span></td></tr>
|
||||
* <tr><td><code>LATERAL_RIGHT</code></td><td>number</td><td>number</td><td><span class="important">Deprecated: This
|
||||
* action is deprecated and will be removed. Use
|
||||
* <code>StrafeRight</code> instead.</span></td></tr>
|
||||
* action is deprecated and will be removed. Use <code>StrafeRight</code> instead.</span></td></tr>
|
||||
* <tr><td><code>VERTICAL_UP</code></td><td>number</td><td>number</td><td><span class="important">Deprecated: This
|
||||
* action is deprecated and will be removed. Use
|
||||
* <code>Up</code> instead.</span></td></tr>
|
||||
* action is deprecated and will be removed. Use <code>Up</code> instead.</span></td></tr>
|
||||
* <tr><td><code>VERTICAL_DOWN</code></td><td>number</td><td>number</td><td><span class="important">Deprecated: This
|
||||
* action is deprecated and will be removed. Use
|
||||
* <code>Down</code> instead.</span></td></tr>
|
||||
* action is deprecated and will be removed. Use <code>Down</code> instead.</span></td></tr>
|
||||
* <tr><td><code>PITCH_DOWN</code></td><td>number</td><td>number</td><td><span class="important">Deprecated: This
|
||||
* action is deprecated and will be removed. Use
|
||||
* <code>PitchDown</code> instead.</span></td></tr>
|
||||
* action is deprecated and will be removed. Use <code>PitchDown</code> instead.</span></td></tr>
|
||||
* <tr><td><code>PITCH_UP</code></td><td>number</td><td>number</td><td><span class="important">Deprecated: This
|
||||
* action is deprecated and will be removed. Use
|
||||
* <code>PitchUp</code> instead.</span></td></tr>
|
||||
* action is deprecated and will be removed. Use <code>PitchUp</code> instead.</span></td></tr>
|
||||
* <tr><td><code>YAW_LEFT</code></td><td>number</td><td>number</td><td><span class="important">Deprecated: This
|
||||
* action is deprecated and will be removed. Use
|
||||
* <code>YawLeft</code> instead.</span></td></tr>
|
||||
* action is deprecated and will be removed. Use <code>YawLeft</code> instead.</span></td></tr>
|
||||
* <tr><td><code>YAW_RIGHT</code></td><td>number</td><td>number</td><td><span class="important">Deprecated: This
|
||||
* action is deprecated and will be removed. Use
|
||||
* <code>YawRight</code> instead.</span></td></tr>
|
||||
* action is deprecated and will be removed. Use <code>YawRight</code> instead.</span></td></tr>
|
||||
* <tr><td><code>LEFT_HAND_CLICK</code></td><td>number</td><td>number</td><td><span class="important">Deprecated: This
|
||||
* action is deprecated and will be removed. Use
|
||||
* <code>LeftHandClick</code> instead.</span></td></tr>
|
||||
* action is deprecated and will be removed. Use <code>LeftHandClick</code> instead.</span></td></tr>
|
||||
* <tr><td><code>RIGHT_HAND_CLICK</code></td><td>number</td><td>number</td><td><span class="important">Deprecated: This
|
||||
* action is deprecated and will be removed. Use
|
||||
* <code>RightHandClick</code> instead.</span></td></tr>
|
||||
* action is deprecated and will be removed. Use <code>RightHandClick</code> instead.</span></td></tr>
|
||||
* <tr><td><code>SHIFT</code></td><td>number</td><td>number</td><td><span class="important">Deprecated: This
|
||||
* action is deprecated and will be removed. Use
|
||||
* <code>Shift</code> instead.</span></td></tr>
|
||||
* action is deprecated and will be removed. Use <code>Shift</code> instead.</span></td></tr>
|
||||
* <tr><td><code>ACTION1</code></td><td>number</td><td>number</td><td><span class="important">Deprecated: This
|
||||
* action is deprecated and will be removed. Use
|
||||
* <code>PrimaryAction</code> instead.</span></td></tr>
|
||||
* action is deprecated and will be removed. Use <code>PrimaryAction</code> instead.</span></td></tr>
|
||||
* <tr><td><code>ACTION2</code></td><td>number</td><td>number</td><td><span class="important">Deprecated: This
|
||||
* action is deprecated and will be removed. Use
|
||||
* <code>SecondaryAction</code> instead.</span></td></tr>
|
||||
* action is deprecated and will be removed. Use <code>SecondaryAction</code> instead.</span></td></tr>
|
||||
*
|
||||
* <tr><td colSpan=4><strong>Deprecated Trackers</strong></td>
|
||||
* <tr><td><code>TrackedObject00</code></td><td>number</td><td>{@link Pose}</td><td><span class="important">Deprecated:
|
||||
|
|
|
@ -54,10 +54,9 @@ enum Hand {
|
|||
|
||||
/**jsdoc
|
||||
* <p>The <code>Controller.Hardware</code> object has properties representing standard and hardware-specific controller and
|
||||
* computer outputs, plus predefined actions on Interface and the user's avatar. <em>Read-only.</em> The outputs can be mapped
|
||||
* to actions or functions in a {@link RouteObject} mapping. Additionally, hardware-specific controller outputs can be mapped
|
||||
* to standard controller outputs.
|
||||
*
|
||||
* computer outputs, plus predefined actions on Interface and the user's avatar. <em>Read-only.</em></p>
|
||||
* <p>The outputs can be mapped to actions or functions in a {@link RouteObject} mapping. Additionally, hardware-specific
|
||||
* controller outputs can be mapped to standard controller outputs.
|
||||
* <p>Controllers typically implement a subset of the {@link Controller.Standard} controls, plus they may implement some extras.
|
||||
* Some common controllers are included in the table. You can see the outputs provided by these and others by
|
||||
* viewing their {@link Controller.MappingJSON|MappingJSON} files at
|
||||
|
|
|
@ -227,6 +227,7 @@ namespace controller {
|
|||
}
|
||||
|
||||
QObject* ScriptingInterface::loadMapping(const QString& jsonUrl) {
|
||||
// FIXME: Implement. https://highfidelity.manuscript.com/f/cases/14188/Implement-Controller-loadMappping
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
|
|
@ -73,7 +73,7 @@ namespace controller {
|
|||
virtual ~ScriptingInterface() {};
|
||||
|
||||
/**jsdoc
|
||||
* Get a list of all available actions.
|
||||
* Gets a list of all available actions.
|
||||
* @function Controller.getAllActions
|
||||
* @returns {Action[]} All available actions.
|
||||
* @deprecated This function is deprecated and will be removed. It no longer works.
|
||||
|
@ -82,7 +82,7 @@ namespace controller {
|
|||
Q_INVOKABLE QVector<Action> getAllActions();
|
||||
|
||||
/**jsdoc
|
||||
* Get a list of all available inputs for a hardware device.
|
||||
* Gets a list of all available inputs for a hardware device.
|
||||
* @function Controller.getAvailableInputs
|
||||
* @param {number} deviceID - Integer ID of the hardware device.
|
||||
* @returns {NamedPair[]} All available inputs for the device.
|
||||
|
@ -92,7 +92,7 @@ namespace controller {
|
|||
Q_INVOKABLE QVector<Input::NamedPair> getAvailableInputs(unsigned int device);
|
||||
|
||||
/**jsdoc
|
||||
* Find the name of a particular controller from its device ID.
|
||||
* Finds the name of a particular controller from its device ID.
|
||||
* @function Controller.getDeviceName
|
||||
* @param {number} deviceID - The integer ID of the device.
|
||||
* @returns {string} The name of the device if found, otherwise <code>"unknown"</code>.
|
||||
|
@ -106,7 +106,7 @@ namespace controller {
|
|||
Q_INVOKABLE QString getDeviceName(unsigned int device);
|
||||
|
||||
/**jsdoc
|
||||
* Get the current value of an action.
|
||||
* Gets the current value of an action.
|
||||
* @function Controller.getActionValue
|
||||
* @param {number} actionID - The integer ID of the action.
|
||||
* @returns {number} The current value of the action.
|
||||
|
@ -121,7 +121,7 @@ namespace controller {
|
|||
Q_INVOKABLE float getActionValue(int action);
|
||||
|
||||
/**jsdoc
|
||||
* Find the ID of a specific controller from its device name.
|
||||
* Finds the ID of a specific controller from its device name.
|
||||
* @function Controller.findDevice
|
||||
* @param {string} deviceName - The name of the device to find.
|
||||
* @returns {number} The integer ID of the device if available, otherwise <code>65535</code>.
|
||||
|
@ -132,7 +132,7 @@ namespace controller {
|
|||
Q_INVOKABLE int findDevice(QString name);
|
||||
|
||||
/**jsdoc
|
||||
* Get the names of all currently available controller devices plus "Actions", "Application", and "Standard".
|
||||
* Gets the names of all currently available controller devices plus "Actions", "Application", and "Standard".
|
||||
* @function Controller.getDeviceNames
|
||||
* @returns {string[]} An array of device names.
|
||||
* @example <caption>Get the names of all currently available controller devices.</caption>
|
||||
|
@ -143,7 +143,7 @@ namespace controller {
|
|||
Q_INVOKABLE QVector<QString> getDeviceNames();
|
||||
|
||||
/**jsdoc
|
||||
* Find the ID of an action from its name.
|
||||
* Finds the ID of an action from its name.
|
||||
* @function Controller.findAction
|
||||
* @param {string} actionName - The name of the action: one of the {@link Controller.Actions} property names.
|
||||
* @returns {number} The integer ID of the action if found, otherwise <code>4095</code>. Note that this value is not
|
||||
|
@ -156,7 +156,7 @@ namespace controller {
|
|||
Q_INVOKABLE int findAction(QString actionName);
|
||||
|
||||
/**jsdoc
|
||||
* Get the names of all actions available as properties of {@link Controller.Actions}.
|
||||
* Gets the names of all actions available as properties of {@link Controller.Actions}.
|
||||
* @function Controller.getActionNames
|
||||
* @returns {string[]} An array of action names.
|
||||
* @example <caption>Get the names of all actions.</caption>
|
||||
|
@ -167,7 +167,7 @@ namespace controller {
|
|||
Q_INVOKABLE QVector<QString> getActionNames() const;
|
||||
|
||||
/**jsdoc
|
||||
* Get the value of a controller button or axis output. Note: Also gets the value of a controller axis output.
|
||||
* Gets the value of a controller button or axis output. Note: Also gets the value of a controller axis output.
|
||||
* @function Controller.getValue
|
||||
* @param {number} source - The {@link Controller.Standard} or {@link Controller.Hardware} item.
|
||||
* @returns {number} The current value of the controller item output if <code>source</code> is valid, otherwise
|
||||
|
@ -186,7 +186,7 @@ namespace controller {
|
|||
Q_INVOKABLE float getValue(const int& source) const;
|
||||
|
||||
/**jsdoc
|
||||
* Get the value of a controller axis output. Note: Also gets the value of a controller button output.
|
||||
* Gets the value of a controller axis output. Note: Also gets the value of a controller button output.
|
||||
* @function Controller.getAxisValue
|
||||
* @param {number} source - The {@link Controller.Standard} or {@link Controller.Hardware} item.
|
||||
* @returns {number} The current value of the controller item output if <code>source</code> is valid, otherwise
|
||||
|
@ -196,7 +196,7 @@ namespace controller {
|
|||
Q_INVOKABLE float getAxisValue(int source) const;
|
||||
|
||||
/**jsdoc
|
||||
* Get the value of a controller pose output.
|
||||
* Gets the value of a controller pose output.
|
||||
* @function Controller.getPoseValue
|
||||
* @param {number} source - The {@link Controller.Standard} or {@link Controller.Hardware} pose output.
|
||||
* @returns {Pose} The current value of the controller pose output if <code>source</code> is a pose output, otherwise
|
||||
|
@ -210,9 +210,9 @@ namespace controller {
|
|||
/**jsdoc
|
||||
* Triggers a haptic pulse on connected and enabled devices that have the capability.
|
||||
* @function Controller.triggerHapticPulse
|
||||
* @param {number} strength - The strength of the haptic pulse, <code>0.0</code> – <code>1.0</code>.
|
||||
* @param {number} strength - The strength of the haptic pulse, range <code>0.0</code> – <code>1.0</code>.
|
||||
* @param {number} duration - The duration of the haptic pulse, in milliseconds.
|
||||
* @param {Controller.Hand} hand=2 - The hand or hands to trigger the haptic pulse on.
|
||||
* @param {Controller.Hand} [hand=2] - The hand or hands to trigger the haptic pulse on.
|
||||
* @example <caption>Trigger a haptic pulse on the right hand.</caption>
|
||||
* var HAPTIC_STRENGTH = 0.5;
|
||||
* var HAPTIC_DURATION = 10;
|
||||
|
@ -224,8 +224,8 @@ namespace controller {
|
|||
/**jsdoc
|
||||
* Triggers a 250ms haptic pulse on connected and enabled devices that have the capability.
|
||||
* @function Controller.triggerShortHapticPulse
|
||||
* @param {number} strength - The strength of the haptic pulse, <code>0.0</code> – <code>1.0</code>.
|
||||
* @param {Controller.Hand} hand=2 - The hand or hands to trigger the haptic pulse on.
|
||||
* @param {number} strength - The strength of the haptic pulse, range <code>0.0</code> – <code>1.0</code>.
|
||||
* @param {Controller.Hand} [hand=2] - The hand or hands to trigger the haptic pulse on.
|
||||
*/
|
||||
Q_INVOKABLE bool triggerShortHapticPulse(float strength, controller::Hand hand = BOTH) const;
|
||||
|
||||
|
@ -233,9 +233,9 @@ namespace controller {
|
|||
* Triggers a haptic pulse on a particular device if connected and enabled and it has the capability.
|
||||
* @function Controller.triggerHapticPulseOnDevice
|
||||
* @param {number} deviceID - The ID of the device to trigger the haptic pulse on.
|
||||
* @param {number} strength - The strength of the haptic pulse, <code>0.0</code> – <code>1.0</code>.
|
||||
* @param {number} strength - The strength of the haptic pulse, range <code>0.0</code> – <code>1.0</code>.
|
||||
* @param {number} duration - The duration of the haptic pulse, in milliseconds.
|
||||
* @param {Controller.Hand} hand=2 - The hand or hands to trigger the haptic pulse on.
|
||||
* @param {Controller.Hand} [hand=2] - The hand or hands to trigger the haptic pulse on.
|
||||
* @example <caption>Trigger a haptic pulse on an Oculus Touch controller.</caption>
|
||||
* var HAPTIC_STRENGTH = 0.5;
|
||||
* var deviceID = Controller.findDevice("OculusTouch");
|
||||
|
@ -250,19 +250,19 @@ namespace controller {
|
|||
* Triggers a 250ms haptic pulse on a particular device if connected and enabled and it has the capability.
|
||||
* @function Controller.triggerShortHapticPulseOnDevice
|
||||
* @param {number} deviceID - The ID of the device to trigger the haptic pulse on.
|
||||
* @param {number} strength - The strength of the haptic pulse, <code>0.0</code> – <code>1.0</code>.
|
||||
* @param {Controller.Hand} hand=2 - The hand or hands to trigger the haptic pulse on.
|
||||
* @param {number} strength - The strength of the haptic pulse, range <code>0.0</code> – <code>1.0</code>.
|
||||
* @param {Controller.Hand} [hand=2] - The hand or hands to trigger the haptic pulse on.
|
||||
*/
|
||||
Q_INVOKABLE bool triggerShortHapticPulseOnDevice(unsigned int device, float strength, controller::Hand hand = BOTH)
|
||||
const;
|
||||
|
||||
|
||||
/**jsdoc
|
||||
* Create a new controller mapping. Routes can then be added to the mapping using {@link MappingObject} methods and
|
||||
* Creates a new controller mapping. Routes can then be added to the mapping using {@link MappingObject} methods and
|
||||
* routed to <code>Standard</code> controls, <code>Actions</code>, or script functions using {@link RouteObject}
|
||||
* methods. The mapping can then be enabled using {@link Controller.enableMapping|enableMapping} for it to take effect.
|
||||
* @function Controller.newMapping
|
||||
* @param {string} mappingName=Uuid.generate() - A unique name for the mapping. If not specified a new UUID generated
|
||||
* @param {string} [mappingName=Uuid.generate()] - A unique name for the mapping. If not specified a new UUID generated
|
||||
* by {@link Uuid.generate} is used.
|
||||
* @returns {MappingObject} A controller mapping object.
|
||||
* @example <caption>Create a simple mapping that makes the right trigger move your avatar up.</caption>
|
||||
|
@ -279,22 +279,22 @@ namespace controller {
|
|||
Q_INVOKABLE QObject* newMapping(const QString& mappingName = QUuid::createUuid().toString());
|
||||
|
||||
/**jsdoc
|
||||
* Enable or disable a controller mapping. When enabled, the routes in the mapping have effect.
|
||||
* Enables or disables a controller mapping. When enabled, the routes in the mapping have effect.
|
||||
* @function Controller.enableMapping
|
||||
* @param {string} mappingName - The name of the mapping.
|
||||
* @param {boolean} enable=true - If <code>true</code> then the mapping is enabled, otherwise it is disabled.
|
||||
* @param {boolean} [[enable=true] - If <code>true</code> then the mapping is enabled, otherwise it is disabled.
|
||||
*/
|
||||
Q_INVOKABLE void enableMapping(const QString& mappingName, bool enable = true);
|
||||
|
||||
/**jsdoc
|
||||
* Disable a controller mapping. When disabled, the routes in the mapping have no effect.
|
||||
* Disables a controller mapping. When disabled, the routes in the mapping have no effect.
|
||||
* @function Controller.disableMapping
|
||||
* @param {string} mappingName - The name of the mapping.
|
||||
*/
|
||||
Q_INVOKABLE void disableMapping(const QString& mappingName) { enableMapping(mappingName, false); }
|
||||
|
||||
/**jsdoc
|
||||
* Create a new controller mapping from a {@link Controller.MappingJSON|MappingJSON} string. Use
|
||||
* Creates a new controller mapping from a {@link Controller.MappingJSON|MappingJSON} string. Use
|
||||
* {@link Controller.enableMapping|enableMapping} to enable the mapping for it to take effect.
|
||||
* @function Controller.parseMapping
|
||||
* @param {string} jsonString - A JSON string of the {@link Controller.MappingJSON|MappingJSON}.
|
||||
|
@ -317,19 +317,19 @@ namespace controller {
|
|||
Q_INVOKABLE QObject* parseMapping(const QString& json);
|
||||
|
||||
/**jsdoc
|
||||
* Create a new controller mapping from a {@link Controller.MappingJSON|MappingJSON} JSON file at a URL. Use
|
||||
* Creates a new controller mapping from a {@link Controller.MappingJSON|MappingJSON} JSON file at a URL. Use
|
||||
* {@link Controller.enableMapping|enableMapping} to enable the mapping for it to take effect.
|
||||
* <p><strong>Warning:</strong> This function is not yet implemented; it doesn't load a mapping and just returns
|
||||
* <code>null</code>.
|
||||
* @function Controller.loadMapping
|
||||
* @param {string} jsonURL - The URL the {@link Controller.MappingJSON|MappingJSON} JSON file.
|
||||
* @returns {MappingObject} A controller mapping object.
|
||||
* @todo <em>Implement this function. It currently does not load the mapping from the file; it just returns
|
||||
* <code>null</code>.</em>
|
||||
*/
|
||||
Q_INVOKABLE QObject* loadMapping(const QString& jsonUrl);
|
||||
|
||||
|
||||
/**jsdoc
|
||||
* Get the {@link Controller.Hardware} property tree. Calling this function is the same as using the {@link Controller}
|
||||
* Gets the {@link Controller.Hardware} property tree. Calling this function is the same as using the {@link Controller}
|
||||
* property, <code>Controller.Hardware</code>.
|
||||
* @function Controller.getHardware
|
||||
* @returns {Controller.Hardware} The {@link Controller.Hardware} property tree.
|
||||
|
@ -337,7 +337,7 @@ namespace controller {
|
|||
Q_INVOKABLE const QVariantMap getHardware() { return _hardware; }
|
||||
|
||||
/**jsdoc
|
||||
* Get the {@link Controller.Actions} property tree. Calling this function is the same as using the {@link Controller}
|
||||
* Gets the {@link Controller.Actions} property tree. Calling this function is the same as using the {@link Controller}
|
||||
* property, <code>Controller.Actions</code>.
|
||||
* @function Controller.getActions
|
||||
* @returns {Controller.Actions} The {@link Controller.Actions} property tree.
|
||||
|
@ -345,7 +345,7 @@ namespace controller {
|
|||
Q_INVOKABLE const QVariantMap getActions() { return _actions; } //undefined
|
||||
|
||||
/**jsdoc
|
||||
* Get the {@link Controller.Standard} property tree. Calling this function is the same as using the {@link Controller}
|
||||
* Gets the {@link Controller.Standard} property tree. Calling this function is the same as using the {@link Controller}
|
||||
* property, <code>Controller.Standard</code>.
|
||||
* @function Controller.getStandard
|
||||
* @returns {Controller.Standard} The {@link Controller.Standard} property tree.
|
||||
|
@ -354,7 +354,7 @@ namespace controller {
|
|||
|
||||
|
||||
/**jsdoc
|
||||
* Start making a recording of currently active controllers.
|
||||
* Starts making a recording of currently active controllers.
|
||||
* @function Controller.startInputRecording
|
||||
* @example <caption>Make a controller recording.</caption>
|
||||
* // Delay start of recording for 2s.
|
||||
|
@ -374,13 +374,13 @@ namespace controller {
|
|||
Q_INVOKABLE void startInputRecording();
|
||||
|
||||
/**jsdoc
|
||||
* Stop making a recording started by {@link Controller.startInputRecording|startInputRecording}.
|
||||
* Stops making a recording started by {@link Controller.startInputRecording|startInputRecording}.
|
||||
* @function Controller.stopInputRecording
|
||||
*/
|
||||
Q_INVOKABLE void stopInputRecording();
|
||||
|
||||
/**jsdoc
|
||||
* Play back the current recording from the beginning. The current recording may have been recorded by
|
||||
* Plays back the current recording from the beginning. The current recording may have been recorded by
|
||||
* {@link Controller.startInputRecording|startInputRecording} and
|
||||
* {@link Controller.stopInputRecording|stopInputRecording}, or loaded by
|
||||
* {@link Controller.loadInputRecording|loadInputRecording}. Playback repeats in a loop until
|
||||
|
@ -403,13 +403,13 @@ namespace controller {
|
|||
Q_INVOKABLE void startInputPlayback();
|
||||
|
||||
/**jsdoc
|
||||
* Stop play back of a recording started by {@link Controller.startInputPlayback|startInputPlayback}.
|
||||
* Stops play back of a recording started by {@link Controller.startInputPlayback|startInputPlayback}.
|
||||
* @function Controller.stopInputPlayback
|
||||
*/
|
||||
Q_INVOKABLE void stopInputPlayback();
|
||||
|
||||
/**jsdoc
|
||||
* Save the current recording to a file. The current recording may have been recorded by
|
||||
* Saves the current recording to a file. The current recording may have been recorded by
|
||||
* {@link Controller.startInputRecording|startInputRecording} and
|
||||
* {@link Controller.stopInputRecording|stopInputRecording}, or loaded by
|
||||
* {@link Controller.loadInputRecording|loadInputRecording}. It is saved in the directory returned by
|
||||
|
@ -419,24 +419,26 @@ namespace controller {
|
|||
Q_INVOKABLE void saveInputRecording();
|
||||
|
||||
/**jsdoc
|
||||
* Load an input recording, ready for play back.
|
||||
* Loads an input recording, ready for play back.
|
||||
* @function Controller.loadInputRecording
|
||||
* @param {string} file - The path to the recording file, prefixed by <code>"file:///"</code>.
|
||||
*/
|
||||
Q_INVOKABLE void loadInputRecording(const QString& file);
|
||||
|
||||
/**jsdoc
|
||||
* Get the directory in which input recordings are saved.
|
||||
* Gets the directory in which input recordings are saved.
|
||||
* @function Controller.getInputRecorderSaveDirectory
|
||||
* @returns {string} The directory in which input recordings are saved.
|
||||
*/
|
||||
Q_INVOKABLE QString getInputRecorderSaveDirectory();
|
||||
|
||||
/**jsdoc
|
||||
* Get all the active and enabled (running) input devices
|
||||
* @function Controller.getRunningInputDevices
|
||||
* @returns {string[]} An array of strings with the names
|
||||
*/
|
||||
* Gets the names of all the active and running (enabled) input devices.
|
||||
* @function Controller.getRunningInputDevices
|
||||
* @returns {string[]} The list of current active and running input devices.
|
||||
* @example <caption>List all active and running input devices.</caption>
|
||||
* print("Running devices: " + JSON.stringify(Controller.getRunningInputDeviceNames()));
|
||||
*/
|
||||
Q_INVOKABLE QStringList getRunningInputDeviceNames();
|
||||
|
||||
bool isMouseCaptured() const { return _mouseCaptured; }
|
||||
|
@ -447,7 +449,7 @@ namespace controller {
|
|||
public slots:
|
||||
|
||||
/**jsdoc
|
||||
* Disable processing of mouse "move", "press", "double-press", and "release" events into
|
||||
* Disables processing of mouse "move", "press", "double-press", and "release" events into
|
||||
* {@link Controller.Hardware|Controller.Hardware.Keyboard} outputs.
|
||||
* @function Controller.captureMouseEvents
|
||||
* @example <caption>Disable Controller.Hardware.Keyboard mouse events for a short period.</caption>
|
||||
|
@ -475,7 +477,7 @@ namespace controller {
|
|||
virtual void captureMouseEvents() { _mouseCaptured = true; }
|
||||
|
||||
/**jsdoc
|
||||
* Enable processing of mouse "move", "press", "double-press", and "release" events into
|
||||
* Enables processing of mouse "move", "press", "double-press", and "release" events into
|
||||
* {@link Controller.Hardware-Keyboard|Controller.Hardware.Keyboard} outputs that were disabled using
|
||||
* {@link Controller.captureMouseEvents|captureMouseEvents}.
|
||||
* @function Controller.releaseMouseEvents
|
||||
|
@ -484,7 +486,7 @@ namespace controller {
|
|||
|
||||
|
||||
/**jsdoc
|
||||
* Disable processing of touch "begin", "update", and "end" events into
|
||||
* Disables processing of touch "begin", "update", and "end" events into
|
||||
* {@link Controller.Hardware|Controller.Hardware.Keyboard},
|
||||
* {@link Controller.Hardware|Controller.Hardware.Touchscreen}, and
|
||||
* {@link Controller.Hardware|Controller.Hardware.TouchscreenVirtualPad} outputs.
|
||||
|
@ -493,7 +495,7 @@ namespace controller {
|
|||
virtual void captureTouchEvents() { _touchCaptured = true; }
|
||||
|
||||
/**jsdoc
|
||||
* Enable processing of touch "begin", "update", and "end" events into
|
||||
* Enables processing of touch "begin", "update", and "end" events into
|
||||
* {@link Controller.Hardware|Controller.Hardware.Keyboard},
|
||||
* {@link Controller.Hardware|Controller.Hardware.Touchscreen}, and
|
||||
* {@link Controller.Hardware|Controller.Hardware.TouchscreenVirtualPad} outputs that were disabled using
|
||||
|
@ -504,14 +506,14 @@ namespace controller {
|
|||
|
||||
|
||||
/**jsdoc
|
||||
* Disable processing of mouse wheel rotation events into {@link Controller.Hardware|Controller.Hardware.Keyboard}
|
||||
* Disables processing of mouse wheel rotation events into {@link Controller.Hardware|Controller.Hardware.Keyboard}
|
||||
* outputs.
|
||||
* @function Controller.captureWheelEvents
|
||||
*/
|
||||
virtual void captureWheelEvents() { _wheelCaptured = true; }
|
||||
|
||||
/**jsdoc
|
||||
* Enable processing of mouse wheel rotation events into {@link Controller.Hardware|Controller.Hardware.Keyboard}
|
||||
* Enables processing of mouse wheel rotation events into {@link Controller.Hardware|Controller.Hardware.Keyboard}
|
||||
* outputs that wer disabled using {@link Controller.captureWheelEvents|captureWheelEvents}.
|
||||
* @function Controller.releaseWheelEvents
|
||||
*/
|
||||
|
@ -519,7 +521,7 @@ namespace controller {
|
|||
|
||||
|
||||
/**jsdoc
|
||||
* Disable translating and rotating the user's avatar in response to keyboard and controller controls.
|
||||
* Disables translating and rotating the user's avatar in response to keyboard and controller controls.
|
||||
* @function Controller.captureActionEvents
|
||||
* @example <caption>Disable avatar translation and rotation for a short period.</caption>
|
||||
* Script.setTimeout(function () {
|
||||
|
@ -533,12 +535,19 @@ namespace controller {
|
|||
virtual void captureActionEvents() { _actionsCaptured = true; }
|
||||
|
||||
/**jsdoc
|
||||
* Enable translating and rotating the user's avatar in response to keyboard and controller controls that were disabled
|
||||
* Enables translating and rotating the user's avatar in response to keyboard and controller controls that were disabled
|
||||
* using {@link Controller.captureActionEvents|captureActionEvents}.
|
||||
* @function Controller.releaseActionEvents
|
||||
*/
|
||||
virtual void releaseActionEvents() { _actionsCaptured = false; }
|
||||
|
||||
/**jsdoc
|
||||
* @function Controller.updateRunningInputDevices
|
||||
* @param {string} deviceName - Device name.
|
||||
* @param {boolean} isRunning - Is running.
|
||||
* @param {string[]} runningDevices - Running devices.
|
||||
* @deprecated This function is deprecated and will be removed.
|
||||
*/
|
||||
void updateRunningInputDevices(const QString& deviceName, bool isRunning, const QStringList& runningDevices);
|
||||
|
||||
signals:
|
||||
|
@ -593,7 +602,7 @@ namespace controller {
|
|||
|
||||
/**jsdoc
|
||||
* Triggered when a device is registered or unregistered by a plugin. Not all plugins generate
|
||||
* <code>hardwareChanged</code> events: for example connecting or disconnecting a mouse will not generate an event but
|
||||
* <code>hardwareChanged</code> events: for example, connecting or disconnecting a mouse will not generate an event but
|
||||
* connecting or disconnecting an Xbox controller will.
|
||||
* @function Controller.hardwareChanged
|
||||
* @returns {Signal}
|
||||
|
@ -601,13 +610,13 @@ namespace controller {
|
|||
void hardwareChanged();
|
||||
|
||||
/**jsdoc
|
||||
* Triggered when a device is enabled/disabled
|
||||
* Enabling/Disabling Leapmotion on settings/controls will trigger this signal.
|
||||
* @function Controller.deviceRunningChanged
|
||||
* @param {string} deviceName - The name of the device that is getting enabled/disabled
|
||||
* @param {boolean} isEnabled - Return if the device is enabled.
|
||||
* @returns {Signal}
|
||||
*/
|
||||
* Triggered when an input device starts or stops being active and running (enabled). For example, enabling or
|
||||
* disabling the LeapMotion in Settings > Controls > Calibration will trigger this signal.
|
||||
* @function Controller.inputDeviceRunningChanged
|
||||
* @param {string} deviceName - The name of the device.
|
||||
* @param {boolean} isRunning - <code>true</code> if the device is active and running, <code>false</code> if it isn't.
|
||||
* @returns {Signal}
|
||||
*/
|
||||
void inputDeviceRunningChanged(QString deviceName, bool isRunning);
|
||||
|
||||
|
||||
|
|
|
@ -30,17 +30,16 @@ void StandardController::focusOutEvent() {
|
|||
/**jsdoc
|
||||
* <p>The <code>Controller.Standard</code> object has properties representing standard controller outputs. Those for physical
|
||||
* controllers are based on the XBox controller, with aliases for PlayStation. The property values are integer IDs, uniquely
|
||||
* identifying each output. <em>Read-only.</em> These can be mapped to actions or functions in a {@link RouteObject}
|
||||
* mapping.</p>
|
||||
*
|
||||
* <p>The data value provided by each control is either a number or a {@link Pose}. Numbers are typically normalized to
|
||||
* <code>0.0</code> or <code>1.0</code> for button states, the range <code>0.0 – 1.0</code> for unidirectional scales,
|
||||
* and the range <code>-1.0 – 1.0</code> for bidirectional scales.</p>
|
||||
*
|
||||
* <p>Each hardware device has a mapping from its outputs to <code>Controller.Standard</code> items, specified in a JSON file.
|
||||
* For example, <a href="https://github.com/highfidelity/hifi/blob/master/interface/resources/controllers/leapmotion.json">
|
||||
* leapmotion.json</a> and
|
||||
* <a href="https://github.com/highfidelity/hifi/blob/master/interface/resources/controllers/vive.json">vive.json</a>.</p>
|
||||
* identifying each output. <em>Read-only.</em></p>
|
||||
* <p>These outputs can be mapped to actions or functions in a {@link RouteObject} mapping. The data value provided by each
|
||||
* control is either a number or a {@link Pose}. Numbers are typically normalized to <code>0.0</code> or <code>1.0</code> for
|
||||
* button states, the range <code>0.0</code> – </code>1.0</code> for unidirectional scales, and the range
|
||||
* <code>-1.0</code> – <code>1.0</code> for bidirectional scales.</p>
|
||||
* <p>Each hardware device has a mapping from its outputs to a subset of <code>Controller.Standard</code> items, specified in a
|
||||
* JSON file. For example,
|
||||
* <a href="https://github.com/highfidelity/hifi/blob/master/interface/resources/controllers/vive.json">vive.json</a>
|
||||
* and <a href="https://github.com/highfidelity/hifi/blob/master/interface/resources/controllers/leapmotion.json">
|
||||
* leapmotion.json</a>.</p>
|
||||
*
|
||||
* <table>
|
||||
* <thead>
|
||||
|
@ -119,12 +118,12 @@ void StandardController::focusOutEvent() {
|
|||
* button.</td></tr>
|
||||
* <tr><td><code>RightThumbUp</code></td><td>number</td><td>number</td><td>Right thumb not touching primary or secondary
|
||||
* thumb buttons.</td></tr>
|
||||
* <tr><td><code>LeftPrimaryIndex</code></td><td>number</td><td>number</td><td>Left primary index control pressed.
|
||||
* <strong>To Do:</strong> <em>Implement this for current controllers.</em></td></tr>
|
||||
* <tr><td><code>LeftPrimaryIndex</code></td><td>number</td><td>number</td><td>Left primary index control
|
||||
* pressed.</em></td></tr>
|
||||
* <tr><td><code>LeftSecondaryIndex</code></td><td>number</td><td>number</td><td>Left secondary index control pressed.
|
||||
* </td></tr>
|
||||
* <tr><td><code>RightPrimaryIndex</code></td><td>number</td><td>number</td><td>Right primary index control pressed.
|
||||
* <strong>To Do:</strong> <em>Implement this for current controllers.</em></td></tr>
|
||||
* </td></tr>
|
||||
* <tr><td><code>RightSecondaryIndex</code></td><td>number</td><td>number</td><td>Right secondary index control pressed.
|
||||
* </td></tr>
|
||||
* <tr><td><code>LeftPrimaryIndexTouch</code></td><td>number</td><td>number</td><td>Left index finger is touching primary
|
||||
|
|
|
@ -84,12 +84,12 @@ class UserInputMapper;
|
|||
/**jsdoc
|
||||
* A route in a {@link Controller.MappingJSON}.
|
||||
* @typedef {object} Controller.MappingJSONRoute
|
||||
* @property {string|Controller.MappingJSONAxis} from - The name of a {@link Controller.Hardware} property name or an axis
|
||||
* made from them. If a property name, the leading <code>"Controller.Hardware."</code> can be omitted.
|
||||
* @property {boolean} [peek=false] - If <codd>true</code> then peeking is enabled per {@link RouteObject#peek}.
|
||||
* @property {boolean} [debug=false] - If <code>true</code> then debug is enabled per {@link RouteObject#debug}.
|
||||
* @property {string|Controller.MappingJSONAxis} from - The name of a {@link Controller.Hardware} property or an axis made from
|
||||
* them. If a property name, the leading <code>"Controller.Hardware."</code> can be omitted.
|
||||
* @property {boolean} [peek=false] - If <codd>true</code>, then peeking is enabled per {@link RouteObject#peek}.
|
||||
* @property {boolean} [debug=false] - If <code>true</code>, then debug is enabled per {@link RouteObject#debug}.
|
||||
* @property {string|string[]} [when=[]] - One or more numeric {@link Controller.Hardware} property names which are evaluated
|
||||
* as booleans and ANDed together. Prepend with a <code>!</code> to use the logical NOT of the property value. The leading
|
||||
* as booleans and ANDed together. Prepend a property name with a <code>!</code> to do a logical NOT. The leading
|
||||
* <code>"Controller.Hardware."</code> can be omitted from the property names.
|
||||
* @property {Controller.MappingJSONFilter|Controller.MappingJSONFilter[]} [filters=[]] - One or more filters in the route.
|
||||
* @property {string} to - The name of a {@link Controller.Actions} or {@link Controller.Standard} property. The leading
|
||||
|
@ -134,7 +134,7 @@ public:
|
|||
: _parent(parent), _mapping(mapping) { }
|
||||
|
||||
/**jsdoc
|
||||
* Create a new {@link RouteObject} from a controller output, ready to be mapped to a standard control, action, or
|
||||
* Creates a new {@link RouteObject} from a controller output, ready to be mapped to a standard control, action, or
|
||||
* function.<br />
|
||||
* This is a QML-specific version of {@link MappingObject#from|from}: use this version in QML files.
|
||||
* @function MappingObject#fromQml
|
||||
|
@ -145,7 +145,7 @@ public:
|
|||
Q_INVOKABLE QObject* fromQml(const QJSValue& source);
|
||||
|
||||
/**jsdoc
|
||||
* Create a new {@link RouteObject} from two numeric {@link Controller.Hardware} outputs, one applied in the negative
|
||||
* Creates a new {@link RouteObject} from two numeric {@link Controller.Hardware} outputs, one applied in the negative
|
||||
* direction and the other in the positive direction, ready to be mapped to a standard control, action, or function.<br />
|
||||
* This is a QML-specific version of {@link MappingObject#makeAxis|makeAxis}: use this version in QML files.
|
||||
* @function MappingObject#makeAxisQml
|
||||
|
@ -157,7 +157,7 @@ public:
|
|||
Q_INVOKABLE QObject* makeAxisQml(const QJSValue& source1, const QJSValue& source2);
|
||||
|
||||
/**jsdoc
|
||||
* Create a new {@link RouteObject} from a controller output, ready to be mapped to a standard control, action, or
|
||||
* Creates a new {@link RouteObject} from a controller output, ready to be mapped to a standard control, action, or
|
||||
* function.
|
||||
* @function MappingObject#from
|
||||
* @param {Controller.Standard|Controller.Hardware|function} source - The controller output or function that is the source
|
||||
|
@ -167,7 +167,7 @@ public:
|
|||
Q_INVOKABLE QObject* from(const QScriptValue& source);
|
||||
|
||||
/**jsdoc
|
||||
* Create a new {@link RouteObject} from two numeric {@link Controller.Hardware} outputs, one applied in the negative
|
||||
* Creates a new {@link RouteObject} from two numeric {@link Controller.Hardware} outputs, one applied in the negative
|
||||
* direction and the other in the positive direction, ready to be mapped to a standard control, action, or function.
|
||||
* @function MappingObject#makeAxis
|
||||
* @param {Controller.Hardware} source1 - The first, negative-direction controller output.
|
||||
|
@ -189,7 +189,7 @@ public:
|
|||
Q_INVOKABLE QObject* makeAxis(const QScriptValue& source1, const QScriptValue& source2);
|
||||
|
||||
/**jsdoc
|
||||
* Enable or disable the mapping. When enabled, the routes in the mapping take effect.<br />
|
||||
* Enables or disables the mapping. When enabled, the routes in the mapping take effect.<br />
|
||||
* Synonymous with {@link Controller.enableMapping}.
|
||||
* @function MappingObject#enable
|
||||
* @param {boolean} enable=true - If <code>true</code> then the mapping is enabled, otherwise it is disabled.
|
||||
|
@ -198,7 +198,7 @@ public:
|
|||
Q_INVOKABLE QObject* enable(bool enable = true);
|
||||
|
||||
/**jsdoc
|
||||
* Disable the mapping. When disabled, the routes in the mapping have no effect.<br />
|
||||
* Disables the mapping. When disabled, the routes in the mapping have no effect.<br />
|
||||
* Synonymous with {@link Controller.disableMapping}.
|
||||
* @function MappingObject#disable
|
||||
* @returns {MappingObject} The mapping object, so that further routes can be added.
|
||||
|
|
|
@ -66,6 +66,8 @@ QObject* RouteBuilderProxy::peek(bool enable) {
|
|||
}
|
||||
|
||||
QObject* RouteBuilderProxy::when(const QScriptValue& expression) {
|
||||
// FIXME: Support "!" conditional in simple expression and array expression.
|
||||
// Note that "!" is supported when parsing a JSON file, in UserInputMapper::parseConditional().
|
||||
auto newConditional = _parent.conditionalFor(expression);
|
||||
if (_route->conditional) {
|
||||
_route->conditional = ConditionalPointer(new AndConditional(_route->conditional, newConditional));
|
||||
|
|
|
@ -51,7 +51,7 @@ class RouteBuilderProxy : public QObject {
|
|||
: _parent(parent), _mapping(mapping), _route(route) { }
|
||||
|
||||
/**jsdoc
|
||||
* Terminate the route with a standard control, an action, or a script function. The output value from the route is
|
||||
* Terminates the route with a standard control, an action, or a script function. The output value from the route is
|
||||
* sent to the specified destination.<br />
|
||||
* This is a QML-specific version of {@link MappingObject#to|to}: use this version in QML files.
|
||||
* @function RouteObject#toQml
|
||||
|
@ -62,7 +62,7 @@ class RouteBuilderProxy : public QObject {
|
|||
Q_INVOKABLE void toQml(const QJSValue& destination);
|
||||
|
||||
/**jsdoc
|
||||
* Process the route only if a condition is satisfied. The condition is evaluated before the route input is read, and
|
||||
* Processes the route only if a condition is satisfied. The condition is evaluated before the route input is read, and
|
||||
* the input is read only if the condition is <code>true</code>. Thus, if the condition is not met then subsequent
|
||||
* routes using the same input are processed.<br />
|
||||
* This is a QML-specific version of {@link MappingObject#to|to}: use this version in QML files.
|
||||
|
@ -81,7 +81,7 @@ class RouteBuilderProxy : public QObject {
|
|||
Q_INVOKABLE QObject* whenQml(const QJSValue& expression);
|
||||
|
||||
/**jsdoc
|
||||
* Terminate the route with a standard control, an action, or a script function. The output value from the route is
|
||||
* Terminates the route with a standard control, an action, or a script function. The output value from the route is
|
||||
* sent to the specified destination.
|
||||
* @function RouteObject#to
|
||||
* @param {Controller.Standard|Controller.Actions|function} destination - The standard control, action, or JavaScript
|
||||
|
@ -117,7 +117,7 @@ class RouteBuilderProxy : public QObject {
|
|||
Q_INVOKABLE void to(const QScriptValue& destination);
|
||||
|
||||
/**jsdoc
|
||||
* Enable and disabling writing debug information for a route to the program log.
|
||||
* Enables or disables writing debug information for a route to the program log.
|
||||
* @function RouteObject#debug
|
||||
* @param {boolean} [enable=true] - If <code>true</code> then writing debug information is enabled for the route,
|
||||
* otherwise it is disabled.
|
||||
|
@ -147,7 +147,7 @@ class RouteBuilderProxy : public QObject {
|
|||
Q_INVOKABLE QObject* debug(bool enable = true);
|
||||
|
||||
/**jsdoc
|
||||
* Process the route without marking the controller output as having been read, so that other routes from the same
|
||||
* Processes the route without marking the controller output as having been read, so that other routes from the same
|
||||
* controller output can also process.
|
||||
* @function RouteObject#peek
|
||||
* @param {boolean} [enable=true] - If <code>true</code> then the route is processed without marking the route's
|
||||
|
@ -157,7 +157,7 @@ class RouteBuilderProxy : public QObject {
|
|||
Q_INVOKABLE QObject* peek(bool enable = true);
|
||||
|
||||
/**jsdoc
|
||||
* Process the route only if a condition is satisfied. The condition is evaluated before the route input is read, and
|
||||
* Processes the route only if a condition is satisfied. The condition is evaluated before the route input is read, and
|
||||
* the input is read only if the condition is <code>true</code>. Thus, if the condition is not met then subsequent
|
||||
* routes using the same input are processed.
|
||||
* @function RouteObject#when
|
||||
|
@ -170,6 +170,8 @@ class RouteBuilderProxy : public QObject {
|
|||
* definition.</li>
|
||||
* </ul>
|
||||
* <p>If an array of conditions is provided, their values are ANDed together.</p>
|
||||
* <p><strong>Warning:</strong> The use of <code>!</code> is not currently supported in JavaScript <code>.when()</code>
|
||||
* calls.</p>
|
||||
* @returns {RouteObject} The <code>RouteObject</code> with the condition added.
|
||||
* @example <caption>Process the right trigger differently in HMD and desktop modes.</caption>
|
||||
* var MAPPING_NAME = "com.highfidelity.controllers.example.newMapping";
|
||||
|
@ -193,7 +195,7 @@ class RouteBuilderProxy : public QObject {
|
|||
Q_INVOKABLE QObject* when(const QScriptValue& expression);
|
||||
|
||||
/**jsdoc
|
||||
* Filter numeric route values to lie between two values; values outside this range are not passed on through the
|
||||
* Filters numeric route values to lie between two values; values outside this range are not passed on through the
|
||||
* route.
|
||||
* @function RouteObject#clamp
|
||||
* @param {number} min - The minimum value to pass through.
|
||||
|
@ -214,7 +216,7 @@ class RouteBuilderProxy : public QObject {
|
|||
Q_INVOKABLE QObject* clamp(float min, float max);
|
||||
|
||||
/**jsdoc
|
||||
* Filter numeric route values such that they are rounded to <code>0</code> or <code>1</code> without output values
|
||||
* Filters numeric route values such that they are rounded to <code>0</code> or <code>1</code> without output values
|
||||
* flickering when the input value hovers around <code>0.5</code>. For example, this enables you to use an analog input
|
||||
* as if it were a toggle.
|
||||
* @function RouteObject#hysteresis
|
||||
|
@ -239,7 +241,7 @@ class RouteBuilderProxy : public QObject {
|
|||
Q_INVOKABLE QObject* hysteresis(float min, float max);
|
||||
|
||||
/**jsdoc
|
||||
* Filter numeric route values to send at a specified interval.
|
||||
* Filters numeric route values to send at a specified interval.
|
||||
* @function RouteObject#pulse
|
||||
* @param {number} interval - The interval between sending values, in seconds.
|
||||
* @returns {RouteObject} The <code>RouteObject</code> with the filter applied.
|
||||
|
@ -258,7 +260,7 @@ class RouteBuilderProxy : public QObject {
|
|||
Q_INVOKABLE QObject* pulse(float interval);
|
||||
|
||||
/**jsdoc
|
||||
* Filter numeric and {@link Pose} route values to be scaled by a constant amount.
|
||||
* Filters numeric and {@link Pose} route values to be scaled by a constant amount.
|
||||
* @function RouteObject#scale
|
||||
* @param {number} multiplier - The scale to multiply the value by.
|
||||
* @returns {RouteObject} The <code>RouteObject</code> with the filter applied.
|
||||
|
@ -280,7 +282,7 @@ class RouteBuilderProxy : public QObject {
|
|||
Q_INVOKABLE QObject* scale(float multiplier);
|
||||
|
||||
/**jsdoc
|
||||
* Filter numeric and {@link Pose} route values to have the opposite sign, e.g., <code>0.5</code> is changed to
|
||||
* Filters numeric and {@link Pose} route values to have the opposite sign, e.g., <code>0.5</code> is changed to
|
||||
* <code>-0.5</code>.
|
||||
* @function RouteObject#invert
|
||||
* @returns {RouteObject} The <code>RouteObject</code> with the filter applied.
|
||||
|
@ -302,7 +304,7 @@ class RouteBuilderProxy : public QObject {
|
|||
Q_INVOKABLE QObject* invert();
|
||||
|
||||
/**jsdoc
|
||||
* Filter numeric route values such that they're sent only when the input value is outside a dead-zone. When the input
|
||||
* Filters numeric route values such that they're sent only when the input value is outside a dead-zone. When the input
|
||||
* passes the dead-zone value, output is sent starting at <code>0.0</code> and catching up with the input value. As the
|
||||
* input returns toward the dead-zone value, output values reduce to <code>0.0</code> at the dead-zone value.
|
||||
* @function RouteObject#deadZone
|
||||
|
@ -324,7 +326,7 @@ class RouteBuilderProxy : public QObject {
|
|||
Q_INVOKABLE QObject* deadZone(float min);
|
||||
|
||||
/**jsdoc
|
||||
* Filter numeric route values such that they are rounded to <code>-1</code>, <code>0</code>, or <code>1</code>.
|
||||
* Filters numeric route values such that they are rounded to <code>-1</code>, <code>0</code>, or <code>1</code>.
|
||||
* For example, this enables you to use an analog input as if it were a toggle or, in the case of a bidirectional axis,
|
||||
* a tri-state switch.
|
||||
* @function RouteObject#constrainToInteger
|
||||
|
@ -345,7 +347,7 @@ class RouteBuilderProxy : public QObject {
|
|||
Q_INVOKABLE QObject* constrainToInteger();
|
||||
|
||||
/**jsdoc
|
||||
* Filter numeric route values such that they are rounded to <code>0</code> or <code>1</code>. For example, this
|
||||
* Filters numeric route values such that they are rounded to <code>0</code> or <code>1</code>. For example, this
|
||||
* enables you to use an analog input as if it were a toggle.
|
||||
* @function RouteObject#constrainToPositiveInteger
|
||||
* @returns {RouteObject} The <code>RouteObject</code> with the filter applied.
|
||||
|
@ -364,7 +366,7 @@ class RouteBuilderProxy : public QObject {
|
|||
Q_INVOKABLE QObject* constrainToPositiveInteger();
|
||||
|
||||
/**jsdoc
|
||||
* Filter {@link Pose} route values to have a pre-translation applied.
|
||||
* Filters {@link Pose} route values to have a pre-translation applied.
|
||||
* @function RouteObject#translate
|
||||
* @param {Vec3} translate - The pre-translation to add to the pose.
|
||||
* @returns {RouteObject} The <code>RouteObject</code> with the pre-translation applied.
|
||||
|
@ -373,7 +375,7 @@ class RouteBuilderProxy : public QObject {
|
|||
Q_INVOKABLE QObject* translate(glm::vec3 translate);
|
||||
|
||||
/**jsdoc
|
||||
* Filter {@link Pose} route values to have a pre-transform applied.
|
||||
* Filters {@link Pose} route values to have a pre-transform applied.
|
||||
* @function RouteObject#transform
|
||||
* @param {Mat4} transform - The pre-transform to apply.
|
||||
* @returns {RouteObject} The <code>RouteObject</code> with the pre-transform applied.
|
||||
|
@ -382,7 +384,7 @@ class RouteBuilderProxy : public QObject {
|
|||
Q_INVOKABLE QObject* transform(glm::mat4 transform);
|
||||
|
||||
/**jsdoc
|
||||
* Filter {@link Pose} route values to have a post-transform applied.
|
||||
* Filters {@link Pose} route values to have a post-transform applied.
|
||||
* @function RouteObject#postTransform
|
||||
* @param {Mat4} transform - The post-transform to apply.
|
||||
* @returns {RouteObject} The <code>RouteObject</code> with the post-transform applied.
|
||||
|
@ -391,7 +393,7 @@ class RouteBuilderProxy : public QObject {
|
|||
Q_INVOKABLE QObject* postTransform(glm::mat4 transform);
|
||||
|
||||
/**jsdoc
|
||||
* Filter {@link Pose} route values to have a pre-rotation applied.
|
||||
* Filters {@link Pose} route values to have a pre-rotation applied.
|
||||
* @function RouteObject#rotate
|
||||
* @param {Quat} rotation - The pre-rotation to add to the pose.
|
||||
* @returns {RouteObject} The <code>RouteObject</code> with the pre-rotation applied.
|
||||
|
@ -400,7 +402,7 @@ class RouteBuilderProxy : public QObject {
|
|||
Q_INVOKABLE QObject* rotate(glm::quat rotation);
|
||||
|
||||
/**jsdoc
|
||||
* Filter {@link Pose} route values to be smoothed by a low velocity filter. The filter's rotation and translation
|
||||
* Filters {@link Pose} route values to be smoothed by a low velocity filter. The filter's rotation and translation
|
||||
* values are calculated as: <code>(1 - f) * currentValue + f * previousValue</code> where
|
||||
* <code>f = currentVelocity / filterConstant</code>. At low velocities, the filter value is largely the previous
|
||||
* value; at high velocities the value is wholly the current controller value.
|
||||
|
@ -415,7 +417,7 @@ class RouteBuilderProxy : public QObject {
|
|||
Q_INVOKABLE QObject* lowVelocity(float rotationConstant, float translationConstant);
|
||||
|
||||
/**jsdoc
|
||||
* Filter {@link Pose} route values to be smoothed by an exponential decay filter. The filter's rotation and
|
||||
* Filters {@link Pose} route values to be smoothed by an exponential decay filter. The filter's rotation and
|
||||
* translation values are calculated as: <code>filterConstant * currentValue + (1 - filterConstant) *
|
||||
* previousValue</code>. Values near 1 are less smooth with lower latency; values near 0 are more smooth with higher
|
||||
* latency.
|
||||
|
@ -428,7 +430,7 @@ class RouteBuilderProxy : public QObject {
|
|||
Q_INVOKABLE QObject* exponentialSmoothing(float rotationConstant, float translationConstant);
|
||||
|
||||
/**jsdoc
|
||||
* Filter numeric route values such that a value of <code>0.0</code> is changed to <code>1.0</code>, and other values
|
||||
* Filters numeric route values such that a value of <code>0.0</code> is changed to <code>1.0</code>, and other values
|
||||
* are changed to <code>0.0</code>.
|
||||
* @function RouteObject#logicalNot
|
||||
* @returns {RouteObject} The <code>RouteObject</code> with the filter applied.
|
||||
|
|
|
@ -38,6 +38,7 @@
|
|||
#include <gpu/gl/GLBackend.h>
|
||||
#include <GeometryCache.h>
|
||||
|
||||
#include <CursorManager.h>
|
||||
#include <FramebufferCache.h>
|
||||
#include <shared/NsightHelpers.h>
|
||||
#include <ui-plugins/PluginContainer.h>
|
||||
|
@ -681,11 +682,14 @@ void OpenGLDisplayPlugin::compositeLayers() {
|
|||
compositeExtra();
|
||||
}
|
||||
|
||||
// Draw the pointer last so it's on top of everything
|
||||
auto compositorHelper = DependencyManager::get<CompositorHelper>();
|
||||
if (compositorHelper->getReticleVisible()) {
|
||||
PROFILE_RANGE_EX(render_detail, "compositePointer", 0xff0077ff, (uint64_t)presentCount())
|
||||
auto& cursorManager = Cursor::Manager::instance();
|
||||
if (isHmd() || cursorManager.getCursor()->getIcon() == Cursor::RETICLE) {
|
||||
auto compositorHelper = DependencyManager::get<CompositorHelper>();
|
||||
// Draw the pointer last so it's on top of everything
|
||||
if (compositorHelper->getReticleVisible()) {
|
||||
PROFILE_RANGE_EX(render_detail, "compositePointer", 0xff0077ff, (uint64_t)presentCount())
|
||||
compositePointer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -205,8 +205,11 @@ void EntityTreeRenderer::stopDomainAndNonOwnedEntities() {
|
|||
foreach (const EntityItemID& entityID, entitiesWithEntityScripts) {
|
||||
EntityItemPointer entityItem = getTree()->findEntityByEntityItemID(entityID);
|
||||
|
||||
if (entityItem) {
|
||||
if (entityItem && !entityItem->getScript().isEmpty()) {
|
||||
if (!(entityItem->isLocalEntity() || (entityItem->isAvatarEntity() && entityItem->getOwningAvatarID() == getTree()->getMyAvatarSessionUUID()))) {
|
||||
if (entityItem->contains(_avatarPosition)) {
|
||||
_entitiesScriptEngine->callEntityScriptMethod(entityID, "leaveEntity");
|
||||
}
|
||||
_entitiesScriptEngine->unloadEntityScript(entityID, true);
|
||||
}
|
||||
}
|
||||
|
@ -217,6 +220,7 @@ void EntityTreeRenderer::stopDomainAndNonOwnedEntities() {
|
|||
void EntityTreeRenderer::clearDomainAndNonOwnedEntities() {
|
||||
stopDomainAndNonOwnedEntities();
|
||||
|
||||
auto sessionUUID = getTree()->getMyAvatarSessionUUID();
|
||||
std::unordered_map<EntityItemID, EntityRendererPointer> savedEntities;
|
||||
// remove all entities from the scene
|
||||
auto scene = _viewState->getMain3DScene();
|
||||
|
@ -224,7 +228,7 @@ void EntityTreeRenderer::clearDomainAndNonOwnedEntities() {
|
|||
for (const auto& entry : _entitiesInScene) {
|
||||
const auto& renderer = entry.second;
|
||||
const EntityItemPointer& entityItem = renderer->getEntity();
|
||||
if (!(entityItem->isLocalEntity() || (entityItem->isAvatarEntity() && entityItem->getOwningAvatarID() == getTree()->getMyAvatarSessionUUID()))) {
|
||||
if (!(entityItem->isLocalEntity() || (entityItem->isAvatarEntity() && entityItem->getOwningAvatarID() == sessionUUID))) {
|
||||
fadeOutRenderable(renderer);
|
||||
} else {
|
||||
savedEntities[entry.first] = entry.second;
|
||||
|
@ -235,7 +239,9 @@ void EntityTreeRenderer::clearDomainAndNonOwnedEntities() {
|
|||
_renderablesToUpdate = savedEntities;
|
||||
_entitiesInScene = savedEntities;
|
||||
|
||||
_layeredZones.clearNonLocalLayeredZones();
|
||||
if (_layeredZones.clearDomainAndNonOwnedZones(sessionUUID)) {
|
||||
applyLayeredZones();
|
||||
}
|
||||
|
||||
OctreeProcessor::clearDomainAndNonOwnedEntities();
|
||||
}
|
||||
|
@ -268,6 +274,9 @@ void EntityTreeRenderer::clear() {
|
|||
|
||||
// reset the zone to the default (while we load the next scene)
|
||||
_layeredZones.clear();
|
||||
if (!_shuttingDown) {
|
||||
applyLayeredZones();
|
||||
}
|
||||
|
||||
OctreeProcessor::clear();
|
||||
}
|
||||
|
@ -360,6 +369,7 @@ void EntityTreeRenderer::addPendingEntities(const render::ScenePointer& scene, r
|
|||
for (const auto& processedId : processedIds) {
|
||||
_entitiesToAdd.erase(processedId);
|
||||
}
|
||||
forceRecheckEntities();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -534,8 +544,7 @@ void EntityTreeRenderer::handleSpaceUpdate(std::pair<int32_t, glm::vec4> proxyUp
|
|||
_spaceUpdates.emplace_back(proxyUpdate.first, proxyUpdate.second);
|
||||
}
|
||||
|
||||
bool EntityTreeRenderer::findBestZoneAndMaybeContainingEntities(QVector<EntityItemID>* entitiesContainingAvatar) {
|
||||
bool didUpdate = false;
|
||||
void EntityTreeRenderer::findBestZoneAndMaybeContainingEntities(QSet<EntityItemID>& entitiesContainingAvatar) {
|
||||
float radius = 0.01f; // for now, assume 0.01 meter radius, because we actually check the point inside later
|
||||
QVector<QUuid> entityIDs;
|
||||
|
||||
|
@ -547,7 +556,7 @@ bool EntityTreeRenderer::findBestZoneAndMaybeContainingEntities(QVector<EntityIt
|
|||
// FIXME - if EntityTree had a findEntitiesContainingPoint() this could theoretically be a little faster
|
||||
entityTree->evalEntitiesInSphere(_avatarPosition, radius, PickFilter(), entityIDs);
|
||||
|
||||
LayeredZones oldLayeredZones(std::move(_layeredZones));
|
||||
LayeredZones oldLayeredZones(_layeredZones);
|
||||
_layeredZones.clear();
|
||||
|
||||
// create a list of entities that actually contain the avatar's position
|
||||
|
@ -575,36 +584,26 @@ bool EntityTreeRenderer::findBestZoneAndMaybeContainingEntities(QVector<EntityIt
|
|||
|
||||
if (contains) {
|
||||
// if this entity is a zone and visible, add it to our layered zones
|
||||
if (isZone && entity->getVisible() && renderableForEntity(entity)) {
|
||||
_layeredZones.insert(std::dynamic_pointer_cast<ZoneEntityItem>(entity));
|
||||
if (isZone && entity->getVisible() && renderableIdForEntity(entity) != render::Item::INVALID_ITEM_ID) {
|
||||
_layeredZones.emplace(std::dynamic_pointer_cast<ZoneEntityItem>(entity));
|
||||
}
|
||||
|
||||
if ((!hasScript && isZone) || scriptHasLoaded) {
|
||||
if (entitiesContainingAvatar) {
|
||||
*entitiesContainingAvatar << entity->getEntityItemID();
|
||||
}
|
||||
entitiesContainingAvatar << entity->getEntityItemID();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check if our layered zones have changed
|
||||
if ((_layeredZones.empty() && oldLayeredZones.empty()) || (!oldLayeredZones.empty() && _layeredZones.contains(oldLayeredZones))) {
|
||||
return;
|
||||
if (!_layeredZones.equals(oldLayeredZones)) {
|
||||
applyLayeredZones();
|
||||
}
|
||||
|
||||
applyLayeredZones();
|
||||
|
||||
didUpdate = true;
|
||||
});
|
||||
|
||||
return didUpdate;
|
||||
}
|
||||
|
||||
bool EntityTreeRenderer::checkEnterLeaveEntities() {
|
||||
void EntityTreeRenderer::checkEnterLeaveEntities() {
|
||||
PROFILE_RANGE(simulation_physics, "EnterLeave");
|
||||
PerformanceTimer perfTimer("enterLeave");
|
||||
auto now = usecTimestampNow();
|
||||
bool didUpdate = false;
|
||||
|
||||
if (_tree && !_shuttingDown) {
|
||||
glm::vec3 avatarPosition = _viewState->getAvatarPosition();
|
||||
|
@ -616,44 +615,43 @@ bool EntityTreeRenderer::checkEnterLeaveEntities() {
|
|||
auto movedEnough = glm::distance(avatarPosition, _avatarPosition) > ZONE_CHECK_DISTANCE;
|
||||
auto enoughTimeElapsed = (now - _lastZoneCheck) > ZONE_CHECK_INTERVAL;
|
||||
|
||||
if (movedEnough || enoughTimeElapsed) {
|
||||
if (_forceRecheckEntities || movedEnough || enoughTimeElapsed) {
|
||||
_avatarPosition = avatarPosition;
|
||||
_lastZoneCheck = now;
|
||||
QVector<EntityItemID> entitiesContainingAvatar;
|
||||
didUpdate = findBestZoneAndMaybeContainingEntities(&entitiesContainingAvatar);
|
||||
|
||||
_forceRecheckEntities = false;
|
||||
|
||||
QSet<EntityItemID> entitiesContainingAvatar;
|
||||
findBestZoneAndMaybeContainingEntities(entitiesContainingAvatar);
|
||||
|
||||
// Note: at this point we don't need to worry about the tree being locked, because we only deal with
|
||||
// EntityItemIDs from here. The callEntityScriptMethod() method is robust against attempting to call scripts
|
||||
// for entity IDs that no longer exist.
|
||||
|
||||
// for all of our previous containing entities, if they are no longer containing then send them a leave event
|
||||
foreach(const EntityItemID& entityID, _currentEntitiesInside) {
|
||||
if (!entitiesContainingAvatar.contains(entityID)) {
|
||||
emit leaveEntity(entityID);
|
||||
if (_entitiesScriptEngine) {
|
||||
if (_entitiesScriptEngine) {
|
||||
// for all of our previous containing entities, if they are no longer containing then send them a leave event
|
||||
foreach(const EntityItemID& entityID, _currentEntitiesInside) {
|
||||
if (!entitiesContainingAvatar.contains(entityID)) {
|
||||
emit leaveEntity(entityID);
|
||||
_entitiesScriptEngine->callEntityScriptMethod(entityID, "leaveEntity");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// for all of our new containing entities, if they weren't previously containing then send them an enter event
|
||||
foreach(const EntityItemID& entityID, entitiesContainingAvatar) {
|
||||
if (!_currentEntitiesInside.contains(entityID)) {
|
||||
emit enterEntity(entityID);
|
||||
if (_entitiesScriptEngine) {
|
||||
// for all of our new containing entities, if they weren't previously containing then send them an enter event
|
||||
foreach(const EntityItemID& entityID, entitiesContainingAvatar) {
|
||||
if (!_currentEntitiesInside.contains(entityID)) {
|
||||
emit enterEntity(entityID);
|
||||
_entitiesScriptEngine->callEntityScriptMethod(entityID, "enterEntity");
|
||||
}
|
||||
}
|
||||
_currentEntitiesInside = entitiesContainingAvatar;
|
||||
}
|
||||
_currentEntitiesInside = entitiesContainingAvatar;
|
||||
}
|
||||
}
|
||||
return didUpdate;
|
||||
}
|
||||
|
||||
void EntityTreeRenderer::leaveDomainAndNonOwnedEntities() {
|
||||
if (_tree && !_shuttingDown) {
|
||||
QVector<EntityItemID> currentEntitiesInsideToSave;
|
||||
QSet<EntityItemID> currentEntitiesInsideToSave;
|
||||
foreach (const EntityItemID& entityID, _currentEntitiesInside) {
|
||||
EntityItemPointer entityItem = getTree()->findEntityByEntityItemID(entityID);
|
||||
if (!(entityItem->isLocalEntity() || (entityItem->isAvatarEntity() && entityItem->getOwningAvatarID() == getTree()->getMyAvatarSessionUUID()))) {
|
||||
|
@ -662,7 +660,7 @@ void EntityTreeRenderer::leaveDomainAndNonOwnedEntities() {
|
|||
_entitiesScriptEngine->callEntityScriptMethod(entityID, "leaveEntity");
|
||||
}
|
||||
} else {
|
||||
currentEntitiesInsideToSave.push_back(entityID);
|
||||
currentEntitiesInsideToSave.insert(entityID);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -687,9 +685,7 @@ void EntityTreeRenderer::leaveAllEntities() {
|
|||
}
|
||||
|
||||
void EntityTreeRenderer::forceRecheckEntities() {
|
||||
// make sure our "last avatar position" is something other than our current position,
|
||||
// so that on our next chance, we'll check for enter/leave entity events.
|
||||
_avatarPosition = _viewState->getAvatarPosition() + glm::vec3((float)TREE_SCALE);
|
||||
_forceRecheckEntities = true;
|
||||
}
|
||||
|
||||
bool EntityTreeRenderer::applyLayeredZones() {
|
||||
|
@ -697,18 +693,12 @@ bool EntityTreeRenderer::applyLayeredZones() {
|
|||
// in the expected layered order and update the scene with it
|
||||
auto scene = _viewState->getMain3DScene();
|
||||
if (scene) {
|
||||
render::Transaction transaction;
|
||||
render::ItemIDs list;
|
||||
for (auto& zone : _layeredZones) {
|
||||
auto id = renderableIdForEntity(zone.zone);
|
||||
// The zone may not have been rendered yet.
|
||||
if (id != render::Item::INVALID_ITEM_ID) {
|
||||
list.push_back(id);
|
||||
}
|
||||
}
|
||||
render::Selection selection("RankedZones", list);
|
||||
transaction.resetSelection(selection);
|
||||
_layeredZones.appendRenderIDs(list, this);
|
||||
|
||||
render::Selection selection("RankedZones", list);
|
||||
render::Transaction transaction;
|
||||
transaction.resetSelection(selection);
|
||||
scene->enqueueTransaction(transaction);
|
||||
} else {
|
||||
qCWarning(entitiesrenderer) << "EntityTreeRenderer::applyLayeredZones(), Unexpected null scene, possibly during application shutdown";
|
||||
|
@ -992,7 +982,10 @@ void EntityTreeRenderer::deletingEntity(const EntityItemID& entityID) {
|
|||
return;
|
||||
}
|
||||
|
||||
if (_tree && !_shuttingDown && _entitiesScriptEngine) {
|
||||
if (_tree && !_shuttingDown && _entitiesScriptEngine && !itr->second->getEntity()->getScript().isEmpty()) {
|
||||
if (itr->second->getEntity()->contains(_avatarPosition)) {
|
||||
_entitiesScriptEngine->callEntityScriptMethod(entityID, "leaveEntity");
|
||||
}
|
||||
_entitiesScriptEngine->unloadEntityScript(entityID, true);
|
||||
}
|
||||
|
||||
|
@ -1016,7 +1009,6 @@ void EntityTreeRenderer::deletingEntity(const EntityItemID& entityID) {
|
|||
}
|
||||
|
||||
void EntityTreeRenderer::addingEntity(const EntityItemID& entityID) {
|
||||
forceRecheckEntities(); // reset our state to force checking our inside/outsideness of entities
|
||||
checkAndCallPreload(entityID);
|
||||
auto entity = std::static_pointer_cast<EntityTree>(_tree)->findEntityByID(entityID);
|
||||
if (entity) {
|
||||
|
@ -1038,6 +1030,9 @@ void EntityTreeRenderer::checkAndCallPreload(const EntityItemID& entityID, bool
|
|||
QString scriptUrl = entity->getScript();
|
||||
if ((shouldLoad && unloadFirst) || scriptUrl.isEmpty()) {
|
||||
if (_entitiesScriptEngine) {
|
||||
if (entity->contains(_avatarPosition)) {
|
||||
_entitiesScriptEngine->callEntityScriptMethod(entityID, "leaveEntity");
|
||||
}
|
||||
_entitiesScriptEngine->unloadEntityScript(entityID);
|
||||
}
|
||||
entity->scriptHasUnloaded();
|
||||
|
@ -1185,107 +1180,98 @@ void EntityTreeRenderer::updateEntityRenderStatus(bool shouldRenderEntities) {
|
|||
}
|
||||
|
||||
void EntityTreeRenderer::updateZone(const EntityItemID& id) {
|
||||
// Get in the zone!
|
||||
auto zone = std::dynamic_pointer_cast<ZoneEntityItem>(getTree()->findEntityByEntityItemID(id));
|
||||
if (zone && zone->contains(_avatarPosition)) {
|
||||
_layeredZones.update(zone);
|
||||
if (auto zone = std::dynamic_pointer_cast<ZoneEntityItem>(getTree()->findEntityByEntityItemID(id))) {
|
||||
_layeredZones.update(zone, _avatarPosition, this);
|
||||
applyLayeredZones();
|
||||
}
|
||||
}
|
||||
|
||||
EntityTreeRenderer::LayeredZones::LayeredZones(LayeredZones&& other) {
|
||||
// In a swap:
|
||||
// > All iterators and references remain valid. The past-the-end iterator is invalidated.
|
||||
bool isSkyboxLayerValid = (other._skyboxLayer != other.end());
|
||||
bool EntityTreeRenderer::LayeredZones::clearDomainAndNonOwnedZones(const QUuid& sessionUUID) {
|
||||
bool zonesChanged = false;
|
||||
|
||||
swap(other);
|
||||
_map.swap(other._map);
|
||||
_skyboxLayer = other._skyboxLayer;
|
||||
auto it = c.begin();
|
||||
while (it != c.end()) {
|
||||
auto zone = it->zone.lock();
|
||||
if (!zone || !(zone->isLocalEntity() || (zone->isAvatarEntity() && zone->getOwningAvatarID() == sessionUUID))) {
|
||||
zonesChanged = true;
|
||||
it = c.erase(it);
|
||||
} else {
|
||||
it++;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isSkyboxLayerValid) {
|
||||
_skyboxLayer = end();
|
||||
if (zonesChanged) {
|
||||
std::make_heap(c.begin(), c.end(), comp);
|
||||
}
|
||||
return zonesChanged;
|
||||
}
|
||||
|
||||
std::pair<bool, bool> EntityTreeRenderer::LayeredZones::getZoneInteractionProperties() const {
|
||||
auto it = c.cbegin();
|
||||
while (it != c.cend()) {
|
||||
auto zone = it->zone.lock();
|
||||
if (zone && zone->isDomainEntity()) {
|
||||
return { zone->getFlyingAllowed(), zone->getGhostingAllowed() };
|
||||
}
|
||||
it++;
|
||||
}
|
||||
return { true, true };
|
||||
}
|
||||
|
||||
void EntityTreeRenderer::LayeredZones::remove(const std::shared_ptr<ZoneEntityItem>& zone) {
|
||||
auto it = c.begin();
|
||||
while (it != c.end()) {
|
||||
if (it->zone.lock() == zone) {
|
||||
break;
|
||||
}
|
||||
it++;
|
||||
}
|
||||
if (it != c.end()) {
|
||||
c.erase(it);
|
||||
std::make_heap(c.begin(), c.end(), comp);
|
||||
}
|
||||
}
|
||||
|
||||
void EntityTreeRenderer::LayeredZones::clearNonLocalLayeredZones() {
|
||||
std::set<LayeredZone> localLayeredZones;
|
||||
std::map<QUuid, iterator> newMap;
|
||||
void EntityTreeRenderer::LayeredZones::update(std::shared_ptr<ZoneEntityItem> zone, const glm::vec3& position, EntityTreeRenderer* entityTreeRenderer) {
|
||||
// When a zone's position or visibility changes, we call this method
|
||||
// In order to resort our zones, we first remove the changed zone, and then re-insert it if necessary
|
||||
remove(zone);
|
||||
|
||||
for (auto iter = begin(); iter != end(); iter++) {
|
||||
LayeredZone layeredZone = *iter;
|
||||
// Only call contains if the zone is rendering
|
||||
if (zone->isVisible() && entityTreeRenderer->renderableIdForEntity(zone) != render::Item::INVALID_ITEM_ID && zone->contains(position)) {
|
||||
emplace(zone);
|
||||
}
|
||||
}
|
||||
|
||||
if (layeredZone.zone->isLocalEntity()) {
|
||||
bool success;
|
||||
iterator it;
|
||||
std::tie(it, success) = localLayeredZones.insert(layeredZone);
|
||||
bool EntityTreeRenderer::LayeredZones::equals(const LayeredZones& other) const {
|
||||
if (size() != other.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (success) {
|
||||
newMap.emplace(layeredZone.id, it);
|
||||
auto it = c.cbegin();
|
||||
auto otherIt = other.c.cbegin();
|
||||
while (it != c.cend()) {
|
||||
if (*it != *otherIt) {
|
||||
return false;
|
||||
}
|
||||
it++;
|
||||
otherIt++;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void EntityTreeRenderer::LayeredZones::appendRenderIDs(render::ItemIDs& list, EntityTreeRenderer* entityTreeRenderer) const {
|
||||
auto it = c.cbegin();
|
||||
while (it != c.cend()) {
|
||||
if (it->zone.lock()) {
|
||||
auto id = entityTreeRenderer->renderableIdForEntityId(it->id);
|
||||
if (id != render::Item::INVALID_ITEM_ID) {
|
||||
list.push_back(id);
|
||||
}
|
||||
}
|
||||
it++;
|
||||
}
|
||||
|
||||
std::set<LayeredZone>::operator=(localLayeredZones);
|
||||
_map = newMap;
|
||||
_skyboxLayer = empty() ? end() : begin();
|
||||
}
|
||||
|
||||
void EntityTreeRenderer::LayeredZones::clear() {
|
||||
std::set<LayeredZone>::clear();
|
||||
_map.clear();
|
||||
_skyboxLayer = end();
|
||||
}
|
||||
|
||||
std::pair<EntityTreeRenderer::LayeredZones::iterator, bool> EntityTreeRenderer::LayeredZones::insert(const LayeredZone& layer) {
|
||||
iterator it;
|
||||
bool success;
|
||||
std::tie(it, success) = std::set<LayeredZone>::insert(layer);
|
||||
|
||||
if (success) {
|
||||
_map.emplace(it->id, it);
|
||||
}
|
||||
|
||||
return { it, success };
|
||||
}
|
||||
|
||||
void EntityTreeRenderer::LayeredZones::update(std::shared_ptr<ZoneEntityItem> zone) {
|
||||
bool isVisible = zone->isVisible();
|
||||
|
||||
if (empty() && isVisible) {
|
||||
// there are no zones: set this one
|
||||
insert(zone);
|
||||
return;
|
||||
} else {
|
||||
LayeredZone zoneLayer(zone);
|
||||
|
||||
// find this zone's layer, if it exists
|
||||
iterator layer = end();
|
||||
auto it = _map.find(zoneLayer.id);
|
||||
if (it != _map.end()) {
|
||||
layer = it->second;
|
||||
// if the volume changed, we need to resort the layer (reinsertion)
|
||||
// if the visibility changed, we need to erase the layer
|
||||
if (zoneLayer.volume != layer->volume || !isVisible) {
|
||||
erase(layer);
|
||||
_map.erase(it);
|
||||
layer = end();
|
||||
}
|
||||
}
|
||||
|
||||
// (re)insert this zone's layer if necessary
|
||||
if (layer == end() && isVisible) {
|
||||
std::tie(layer, std::ignore) = insert(zoneLayer);
|
||||
_map.emplace(layer->id, layer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool EntityTreeRenderer::LayeredZones::contains(const LayeredZones& other) {
|
||||
bool result = std::equal(other.begin(), other._skyboxLayer, begin());
|
||||
if (result) {
|
||||
// if valid, set the _skyboxLayer from the other LayeredZones
|
||||
_skyboxLayer = std::next(begin(), std::distance(other.begin(), other._skyboxLayer));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
CalculateEntityLoadingPriority EntityTreeRenderer::_calculateEntityLoadingPriorityFunc = [](const EntityItem& item) -> float {
|
||||
|
@ -1293,14 +1279,7 @@ CalculateEntityLoadingPriority EntityTreeRenderer::_calculateEntityLoadingPriori
|
|||
};
|
||||
|
||||
std::pair<bool, bool> EntityTreeRenderer::getZoneInteractionProperties() {
|
||||
for (auto& zone : _layeredZones) {
|
||||
// Only domain entities control flying allowed and ghosting allowed
|
||||
if (zone.zone && zone.zone->isDomainEntity()) {
|
||||
return { zone.zone->getFlyingAllowed(), zone.zone->getGhostingAllowed() };
|
||||
}
|
||||
}
|
||||
|
||||
return { true, true };
|
||||
return _layeredZones.getZoneInteractionProperties();
|
||||
}
|
||||
|
||||
bool EntityTreeRenderer::wantsKeyboardFocus(const EntityItemID& id) const {
|
||||
|
|
|
@ -169,7 +169,7 @@ private:
|
|||
|
||||
void resetEntitiesScriptEngine();
|
||||
|
||||
bool findBestZoneAndMaybeContainingEntities(QVector<EntityItemID>* entitiesContainingAvatar = nullptr);
|
||||
void findBestZoneAndMaybeContainingEntities(QSet<EntityItemID>& entitiesContainingAvatar);
|
||||
|
||||
bool applyLayeredZones();
|
||||
void stopDomainAndNonOwnedEntities();
|
||||
|
@ -180,13 +180,14 @@ private:
|
|||
EntityItemID _currentClickingOnEntityID;
|
||||
|
||||
QScriptValueList createEntityArgs(const EntityItemID& entityID);
|
||||
bool checkEnterLeaveEntities();
|
||||
void checkEnterLeaveEntities();
|
||||
void leaveDomainAndNonOwnedEntities();
|
||||
void leaveAllEntities();
|
||||
void forceRecheckEntities();
|
||||
|
||||
glm::vec3 _avatarPosition { 0.0f };
|
||||
QVector<EntityItemID> _currentEntitiesInside;
|
||||
bool _forceRecheckEntities { true };
|
||||
QSet<EntityItemID> _currentEntitiesInside;
|
||||
|
||||
bool _wantScripts;
|
||||
ScriptEnginePointer _entitiesScriptEngine;
|
||||
|
@ -209,48 +210,38 @@ private:
|
|||
|
||||
class LayeredZone {
|
||||
public:
|
||||
LayeredZone(std::shared_ptr<ZoneEntityItem> zone, QUuid id, float volume) : zone(zone), id(id), volume(volume) {}
|
||||
LayeredZone(std::shared_ptr<ZoneEntityItem> zone) : LayeredZone(zone, zone->getID(), zone->getVolumeEstimate()) {}
|
||||
LayeredZone(std::shared_ptr<ZoneEntityItem> zone) : zone(zone), id(zone->getID()), volume(zone->getVolumeEstimate()) {}
|
||||
|
||||
bool operator<(const LayeredZone& r) const { return std::tie(volume, id) < std::tie(r.volume, r.id); }
|
||||
bool operator==(const LayeredZone& r) const { return id == r.id; }
|
||||
bool operator<=(const LayeredZone& r) const { return (*this < r) || (*this == r); }
|
||||
bool operator>(const LayeredZone& r) const { return volume > r.volume; }
|
||||
bool operator==(const LayeredZone& r) const { return zone.lock() == r.zone.lock(); }
|
||||
bool operator!=(const LayeredZone& r) const { return !(*this == r); }
|
||||
bool operator>=(const LayeredZone& r) const { return (*this > r) || (*this == r); }
|
||||
|
||||
std::shared_ptr<ZoneEntityItem> zone;
|
||||
std::weak_ptr<ZoneEntityItem> zone;
|
||||
QUuid id;
|
||||
float volume;
|
||||
};
|
||||
|
||||
class LayeredZones : public std::set<LayeredZone> {
|
||||
class LayeredZones : public std::priority_queue<LayeredZone, std::vector<LayeredZone>, std::greater<LayeredZone>> {
|
||||
public:
|
||||
LayeredZones() {};
|
||||
LayeredZones(LayeredZones&& other);
|
||||
void clear() { *this = LayeredZones(); }
|
||||
bool clearDomainAndNonOwnedZones(const QUuid& sessionUUID);
|
||||
|
||||
// avoid accidental misconstruction
|
||||
LayeredZones(const LayeredZones&) = delete;
|
||||
LayeredZones& operator=(const LayeredZones&) = delete;
|
||||
LayeredZones& operator=(LayeredZones&&) = delete;
|
||||
bool equals(const LayeredZones& other) const;
|
||||
void remove(const std::shared_ptr<ZoneEntityItem>& zone);
|
||||
void update(std::shared_ptr<ZoneEntityItem> zone, const glm::vec3& position, EntityTreeRenderer* entityTreeRenderer);
|
||||
|
||||
void clear();
|
||||
void clearNonLocalLayeredZones();
|
||||
std::pair<iterator, bool> insert(const LayeredZone& layer);
|
||||
void update(std::shared_ptr<ZoneEntityItem> zone);
|
||||
bool contains(const LayeredZones& other);
|
||||
|
||||
std::shared_ptr<ZoneEntityItem> getZone() { return empty() ? nullptr : begin()->zone; }
|
||||
|
||||
private:
|
||||
std::map<QUuid, iterator> _map;
|
||||
iterator _skyboxLayer { end() };
|
||||
void appendRenderIDs(render::ItemIDs& list, EntityTreeRenderer* entityTreeRenderer) const;
|
||||
std::pair<bool, bool> getZoneInteractionProperties() const;
|
||||
};
|
||||
|
||||
LayeredZones _layeredZones;
|
||||
float _avgRenderableUpdateCost { 0.0f };
|
||||
|
||||
uint64_t _lastZoneCheck { 0 };
|
||||
const uint64_t ZONE_CHECK_INTERVAL = USECS_PER_MSEC * 100; // ~10hz
|
||||
const float ZONE_CHECK_DISTANCE = 0.001f;
|
||||
|
||||
float _avgRenderableUpdateCost { 0.0f };
|
||||
|
||||
ReadWriteLockable _changedEntitiesGuard;
|
||||
std::unordered_set<EntityItemID> _changedEntities;
|
||||
|
||||
|
|
|
@ -92,9 +92,7 @@ void ZoneEntityRenderer::doRender(RenderArgs* args) {
|
|||
}
|
||||
|
||||
{ // Sun
|
||||
// Need an update ?
|
||||
if (_needSunUpdate) {
|
||||
// Do we need to allocate the light in the stage ?
|
||||
if (LightStage::isIndexInvalid(_sunIndex)) {
|
||||
_sunIndex = _stage->addLight(_sunLight);
|
||||
} else {
|
||||
|
@ -107,9 +105,7 @@ void ZoneEntityRenderer::doRender(RenderArgs* args) {
|
|||
{ // Ambient
|
||||
updateAmbientMap();
|
||||
|
||||
// Need an update ?
|
||||
if (_needAmbientUpdate) {
|
||||
// Do we need to allocate the light in the stage ?
|
||||
if (LightStage::isIndexInvalid(_ambientIndex)) {
|
||||
_ambientIndex = _stage->addLight(_ambientLight);
|
||||
} else {
|
||||
|
@ -123,7 +119,7 @@ void ZoneEntityRenderer::doRender(RenderArgs* args) {
|
|||
updateSkyboxMap();
|
||||
|
||||
if (_needBackgroundUpdate) {
|
||||
if (_skyboxMode == COMPONENT_MODE_ENABLED && BackgroundStage::isIndexInvalid(_backgroundIndex)) {
|
||||
if (BackgroundStage::isIndexInvalid(_backgroundIndex)) {
|
||||
_backgroundIndex = _backgroundStage->addBackground(_background);
|
||||
}
|
||||
_needBackgroundUpdate = false;
|
||||
|
@ -186,24 +182,19 @@ void ZoneEntityRenderer::doRender(RenderArgs* args) {
|
|||
}
|
||||
}
|
||||
|
||||
void ZoneEntityRenderer::removeFromScene(const ScenePointer& scene, Transaction& transaction) {
|
||||
#if 0
|
||||
if (_model) {
|
||||
_model->removeFromScene(scene, transaction);
|
||||
}
|
||||
#endif
|
||||
Parent::removeFromScene(scene, transaction);
|
||||
}
|
||||
|
||||
void ZoneEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) {
|
||||
DependencyManager::get<EntityTreeRenderer>()->updateZone(entity->getID());
|
||||
|
||||
auto position = entity->getWorldPosition();
|
||||
auto rotation = entity->getWorldOrientation();
|
||||
auto dimensions = entity->getScaledDimensions();
|
||||
bool rotationChanged = rotation != _lastRotation;
|
||||
bool transformChanged = rotationChanged || position != _lastPosition || dimensions != _lastDimensions;
|
||||
|
||||
auto visible = entity->getVisible();
|
||||
if (transformChanged || visible != _lastVisible) {
|
||||
_lastVisible = visible;
|
||||
DependencyManager::get<EntityTreeRenderer>()->updateZone(entity->getID());
|
||||
}
|
||||
|
||||
auto proceduralUserData = entity->getUserData();
|
||||
bool proceduralUserDataChanged = _proceduralUserData != proceduralUserData;
|
||||
|
||||
|
@ -226,25 +217,6 @@ void ZoneEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scen
|
|||
_proceduralUserData = entity->getUserData();
|
||||
}
|
||||
|
||||
#if 0
|
||||
if (_lastShapeURL != _typedEntity->getCompoundShapeURL()) {
|
||||
_lastShapeURL = _typedEntity->getCompoundShapeURL();
|
||||
_model.reset();
|
||||
_model = std::make_shared<Model>();
|
||||
_model->setIsWireframe(true);
|
||||
_model->init();
|
||||
_model->setURL(_lastShapeURL);
|
||||
}
|
||||
|
||||
if (_model && _model->isActive()) {
|
||||
_model->setScaleToFit(true, _lastDimensions);
|
||||
_model->setSnapModelToRegistrationPoint(true, _entity->getRegistrationPoint());
|
||||
_model->setRotation(_lastRotation);
|
||||
_model->setTranslation(_lastPosition);
|
||||
_model->simulate(0.0f);
|
||||
}
|
||||
#endif
|
||||
|
||||
updateKeyZoneItemFromEntity(entity);
|
||||
|
||||
if (keyLightChanged) {
|
||||
|
@ -296,6 +268,10 @@ ItemKey ZoneEntityRenderer::getKey() {
|
|||
}
|
||||
|
||||
bool ZoneEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const {
|
||||
if (entity->getVisible() != _lastVisible) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (entity->keyLightPropertiesChanged() ||
|
||||
entity->ambientLightPropertiesChanged() ||
|
||||
entity->hazePropertiesChanged() ||
|
||||
|
@ -323,25 +299,7 @@ bool ZoneEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPoint
|
|||
return true;
|
||||
}
|
||||
|
||||
#if 0
|
||||
if (_typedEntity->getCompoundShapeURL() != _lastShapeURL) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_model) {
|
||||
if (!_model->needsFixupInScene() && (!ZoneEntityItem::getDrawZoneBoundaries() || _entity->getShapeType() != SHAPE_TYPE_COMPOUND)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_model->needsFixupInScene() && (ZoneEntityItem::getDrawZoneBoundaries() || _entity->getShapeType() == SHAPE_TYPE_COMPOUND)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_lastModelActive != _model->isActive()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
// FIXME: do we need to trigger an update when shapeType changes? see doRenderUpdateAsynchronousTyped
|
||||
|
||||
return false;
|
||||
}
|
||||
|
@ -450,7 +408,6 @@ void ZoneEntityRenderer::updateKeyZoneItemFromEntity(const TypedEntityPointer& e
|
|||
}
|
||||
|
||||
void ZoneEntityRenderer::setAmbientURL(const QString& ambientUrl) {
|
||||
// nothing change if nothing change
|
||||
if (_ambientTextureURL == ambientUrl) {
|
||||
return;
|
||||
}
|
||||
|
@ -466,8 +423,6 @@ void ZoneEntityRenderer::setAmbientURL(const QString& ambientUrl) {
|
|||
_pendingAmbientTexture = true;
|
||||
auto textureCache = DependencyManager::get<TextureCache>();
|
||||
_ambientTexture = textureCache->getTexture(_ambientTextureURL, image::TextureUsage::AMBIENT_TEXTURE);
|
||||
|
||||
// keep whatever is assigned on the ambient map/sphere until texture is loaded
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -492,7 +447,6 @@ void ZoneEntityRenderer::updateAmbientMap() {
|
|||
}
|
||||
|
||||
void ZoneEntityRenderer::setSkyboxURL(const QString& skyboxUrl) {
|
||||
// nothing change if nothing change
|
||||
if (_skyboxTextureURL == skyboxUrl) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -24,9 +24,6 @@
|
|||
#include "RenderableEntityItem.h"
|
||||
#include <ComponentMode.h>
|
||||
|
||||
#if 0
|
||||
#include <Model.h>
|
||||
#endif
|
||||
namespace render { namespace entities {
|
||||
|
||||
class ZoneEntityRenderer : public TypedEntityRenderer<ZoneEntityItem> {
|
||||
|
@ -40,7 +37,6 @@ protected:
|
|||
virtual void onRemoveFromSceneTyped(const TypedEntityPointer& entity) override;
|
||||
virtual ItemKey getKey() override;
|
||||
virtual void doRender(RenderArgs* args) override;
|
||||
virtual void removeFromScene(const ScenePointer& scene, Transaction& transaction) override;
|
||||
virtual bool needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const override;
|
||||
virtual void doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) override;
|
||||
virtual void doRenderUpdateAsynchronousTyped(const TypedEntityPointer& entity) override;
|
||||
|
@ -76,14 +72,7 @@ private:
|
|||
glm::vec3 _lastPosition;
|
||||
glm::vec3 _lastDimensions;
|
||||
glm::quat _lastRotation;
|
||||
|
||||
// FIXME compount shapes are currently broken
|
||||
// FIXME draw zone boundaries are currently broken (also broken in master)
|
||||
#if 0
|
||||
ModelPointer _model;
|
||||
bool _lastModelActive { false };
|
||||
QString _lastShapeURL;
|
||||
#endif
|
||||
bool _lastVisible;
|
||||
|
||||
LightStagePointer _stage;
|
||||
const graphics::LightPointer _sunLight { std::make_shared<graphics::Light>() };
|
||||
|
@ -137,25 +126,4 @@ private:
|
|||
|
||||
} } // namespace
|
||||
|
||||
#if 0
|
||||
|
||||
class NetworkGeometry;
|
||||
class KeyLightPayload;
|
||||
|
||||
class RenderableZoneEntityItemMeta;
|
||||
|
||||
class RenderableZoneEntityItem : public ZoneEntityItem, public RenderableEntityInterface {
|
||||
public:
|
||||
virtual bool contains(const glm::vec3& point) const override;
|
||||
virtual bool addToScene(const EntityItemPointer& self, const render::ScenePointer& scene, render::Transaction& transaction) override;
|
||||
virtual void removeFromScene(const EntityItemPointer& self, const render::ScenePointer& scene, render::Transaction& transaction) override;
|
||||
private:
|
||||
virtual void locationChanged(bool tellPhysics = true, bool tellChildren = true) override { EntityItem::locationChanged(tellPhysics, tellChildren); notifyBoundChanged(); }
|
||||
virtual void dimensionsChanged() override { EntityItem::dimensionsChanged(); notifyBoundChanged(); }
|
||||
void notifyBoundChanged();
|
||||
void notifyChangedRenderItem();
|
||||
void sceneUpdateRenderItemFromEntity(render::Transaction& transaction);
|
||||
};
|
||||
#endif
|
||||
|
||||
#endif // hifi_RenderableZoneEntityItem_h
|
||||
|
|
|
@ -2023,9 +2023,10 @@ void EntityItem::setCollisionMask(uint16_t value) {
|
|||
|
||||
void EntityItem::setDynamic(bool value) {
|
||||
if (getDynamic() != value) {
|
||||
auto shapeType = getShapeType();
|
||||
withWriteLock([&] {
|
||||
// dynamic and STATIC_MESH are incompatible so we check for that case
|
||||
if (value && getShapeType() == SHAPE_TYPE_STATIC_MESH) {
|
||||
if (value && shapeType == SHAPE_TYPE_STATIC_MESH) {
|
||||
if (_dynamic) {
|
||||
_dynamic = false;
|
||||
_flags |= Simulation::DIRTY_MOTION_TYPE;
|
||||
|
|
|
@ -41,8 +41,14 @@ QVariant readBinaryArray(QDataStream& in, int& position) {
|
|||
quint32 compressedLength;
|
||||
|
||||
in >> arrayLength;
|
||||
if (arrayLength > std::numeric_limits<int>::max() / sizeof(T)) { // Upcoming byte containers are limited to max signed int
|
||||
throw QString("FBX file most likely corrupt: binary data exceeds data limits");
|
||||
}
|
||||
in >> encoding;
|
||||
in >> compressedLength;
|
||||
if (compressedLength > std::numeric_limits<int>::max() / sizeof(T)) { // Upcoming byte containers are limited to max signed int
|
||||
throw QString("FBX file most likely corrupt: compressed binary data exceeds data limits");
|
||||
}
|
||||
position += sizeof(quint32) * 3;
|
||||
|
||||
QVector<T> values;
|
||||
|
|
|
@ -874,6 +874,7 @@ bool GLTFSerializer::buildGeometry(HFMModel& hfmModel, const hifi::VariantHash&
|
|||
joint.isSkeletonJoint = false;
|
||||
hfmModel.joints.push_back(joint);
|
||||
}
|
||||
hfmModel.shapeVertices.resize(hfmModel.joints.size());
|
||||
|
||||
|
||||
// Build skeleton
|
||||
|
@ -1243,6 +1244,13 @@ bool GLTFSerializer::buildGeometry(HFMModel& hfmModel, const hifi::VariantHash&
|
|||
}
|
||||
}
|
||||
|
||||
for (int clusterIndex = 0; clusterIndex < mesh.clusters.size() - 1; clusterIndex++) {
|
||||
ShapeVertices& points = hfmModel.shapeVertices.at(clusterIndex);
|
||||
for (glm::vec3 vertex : mesh.vertices) {
|
||||
points.push_back(vertex);
|
||||
}
|
||||
}
|
||||
|
||||
mesh.meshExtents.reset();
|
||||
foreach(const glm::vec3& vertex, mesh.vertices) {
|
||||
mesh.meshExtents.addPoint(vertex);
|
||||
|
|
|
@ -219,9 +219,10 @@ controller::Input KeyboardMouseDevice::InputDevice::makeInput(KeyboardMouseDevic
|
|||
|
||||
/**jsdoc
|
||||
* <p>The <code>Controller.Hardware.Keyboard</code> object has properties representing keyboard, mouse, and display touch
|
||||
* events. The property values are integer IDs, uniquely identifying each output. <em>Read-only.</em> These can be mapped to
|
||||
* actions or functions or <code>Controller.Standard</code> items in a {@link RouteObject} mapping. For presses, each data
|
||||
* value is either <code>1.0</code> for "true" or <code>0.0</code> for "false".</p>
|
||||
* events. The property values are integer IDs, uniquely identifying each output. <em>Read-only.</em></p>
|
||||
* <p>These events can be mapped to actions or functions or <code>Controller.Standard</code> items in a {@link RouteObject}
|
||||
* mapping. For presses, each data value is either <code>1.0</code> for "true" or <code>0.0</code> for "false".</p>
|
||||
*
|
||||
* <table>
|
||||
* <thead>
|
||||
* <tr><th>Property</th><th>Type</th><td>Data</th><th>Description</th></tr>
|
||||
|
@ -269,14 +270,18 @@ controller::Input KeyboardMouseDevice::InputDevice::makeInput(KeyboardMouseDevic
|
|||
* new x-coordinate value.</td></tr>
|
||||
* <tr><td><code>MouseY</code></td><td>number</td><td>number</td><td>The mouse y-coordinate changed. The data value is its
|
||||
* new y-coordinate value.</td></tr>
|
||||
* <tr><td><code>MouseWheelRight</code></td><td>number</td><td>number</td><td>The mouse wheel rotated left. The data value
|
||||
* <tr><td><code>MouseWheelRight</code></td><td>number</td><td>number</td><td>The mouse wheel rotated right. The data value
|
||||
* is the number of units rotated (typically <code>1.0</code>).</td></tr>
|
||||
* <tr><td><code>MouseWheelLeft</code></td><td>number</td><td>number</td><td>The mouse wheel rotated left. The data value
|
||||
* is the number of units rotated (typically <code>1.0</code>).</td></tr>
|
||||
* <tr><td><code>MouseWheelUp</code></td><td>number</td><td>number</td><td>The mouse wheel rotated up. The data value
|
||||
* is the number of units rotated (typically <code>1.0</code>).</td></tr>
|
||||
* is the number of units rotated (typically <code>1.0</code>).<br />
|
||||
* <strong>Warning:</strong> The mouse wheel in an ordinary mouse generates left/right wheel events instead of
|
||||
* up/down.</td></tr>
|
||||
* <tr><td><code>MouseWheelDown</code></td><td>number</td><td>number</td><td>The mouse wheel rotated down. The data value
|
||||
* is the number of units rotated (typically <code>1.0</code>).</td></tr>
|
||||
* is the number of units rotated (typically <code>1.0</code>).<br />
|
||||
* <strong>Warning:</strong> The mouse wheel in an ordinary mouse generates left/right wheel events instead of
|
||||
* up/down.</td></tr>
|
||||
* <tr><td><code>TouchpadRight</code></td><td>number</td><td>number</td><td>The average touch on a touch-enabled device
|
||||
* moved right. The data value is how far the average position of all touch points moved.</td></tr>
|
||||
* <tr><td><code>TouchpadLeft</code></td><td>number</td><td>number</td><td>The average touch on a touch-enabled device
|
||||
|
@ -288,7 +293,6 @@ controller::Input KeyboardMouseDevice::InputDevice::makeInput(KeyboardMouseDevic
|
|||
* </tbody>
|
||||
* </table>
|
||||
* @typedef {object} Controller.Hardware-Keyboard
|
||||
* @todo <em>Currently, the mouse wheel in an ordinary mouse generates left/right wheel events instead of up/down.</em>
|
||||
*/
|
||||
controller::Input::NamedVector KeyboardMouseDevice::InputDevice::getAvailableInputs() const {
|
||||
using namespace controller;
|
||||
|
|
|
@ -187,38 +187,43 @@ void Midi::MidiSetup() {
|
|||
|
||||
MIDIINCAPS incaps;
|
||||
for (unsigned int i = 0; i < midiInGetNumDevs(); i++) {
|
||||
midiInGetDevCaps(i, &incaps, sizeof(MIDIINCAPS));
|
||||
if (MMSYSERR_NOERROR == midiInGetDevCaps(i, &incaps, sizeof(MIDIINCAPS))) {
|
||||
|
||||
bool found = false;
|
||||
for (int j = 0; j < midiInExclude.size(); j++) {
|
||||
if (midiInExclude[j].toStdString().compare(incaps.szPname) == 0) {
|
||||
found = true;
|
||||
break;
|
||||
bool found = false;
|
||||
for (int j = 0; j < midiInExclude.size(); j++) {
|
||||
if (midiInExclude[j].toStdString().compare(incaps.szPname) == 0) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) { // EXCLUDE AN INPUT BY NAME
|
||||
HMIDIIN tmphin;
|
||||
if (MMSYSERR_NOERROR == midiInOpen(&tmphin, i, (DWORD_PTR)MidiInProc, NULL, CALLBACK_FUNCTION)) {
|
||||
if (MMSYSERR_NOERROR == midiInStart(tmphin)) {
|
||||
midihin.push_back(tmphin);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!found) { // EXCLUDE AN INPUT BY NAME
|
||||
HMIDIIN tmphin;
|
||||
midiInOpen(&tmphin, i, (DWORD_PTR)MidiInProc, NULL, CALLBACK_FUNCTION);
|
||||
midiInStart(tmphin);
|
||||
midihin.push_back(tmphin);
|
||||
}
|
||||
}
|
||||
|
||||
MIDIOUTCAPS outcaps;
|
||||
for (unsigned int i = 0; i < midiOutGetNumDevs(); i++) {
|
||||
midiOutGetDevCaps(i, &outcaps, sizeof(MIDIINCAPS));
|
||||
if (MMSYSERR_NOERROR == midiOutGetDevCaps(i, &outcaps, sizeof(MIDIOUTCAPS))) {
|
||||
|
||||
bool found = false;
|
||||
for (int j = 0; j < midiOutExclude.size(); j++) {
|
||||
if (midiOutExclude[j].toStdString().compare(outcaps.szPname) == 0) {
|
||||
found = true;
|
||||
break;
|
||||
bool found = false;
|
||||
for (int j = 0; j < midiOutExclude.size(); j++) {
|
||||
if (midiOutExclude[j].toStdString().compare(outcaps.szPname) == 0) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) { // EXCLUDE AN OUTPUT BY NAME
|
||||
HMIDIOUT tmphout;
|
||||
if (MMSYSERR_NOERROR == midiOutOpen(&tmphout, i, (DWORD_PTR)MidiOutProc, NULL, CALLBACK_FUNCTION)) {
|
||||
midihout.push_back(tmphout);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!found) { // EXCLUDE AN OUTPUT BY NAME
|
||||
HMIDIOUT tmphout;
|
||||
midiOutOpen(&tmphout, i, (DWORD_PTR)MidiOutProc, NULL, CALLBACK_FUNCTION);
|
||||
midihout.push_back(tmphout);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -261,6 +266,61 @@ void Midi::MidiCleanup() {
|
|||
}
|
||||
#endif
|
||||
|
||||
/**jsdoc
|
||||
* A MIDI message.
|
||||
* <p><strong>Warning:</strong> The <code>status</code> property is NOT a MIDI status value.</p>
|
||||
* @typedef {object} Midi.MidiMessage
|
||||
* @property {number} device - Device number.
|
||||
* @property {Midi.RawMidiMessage} raw - Raw MIDI message.
|
||||
* @property {number} status - Channel + status. <em>Legacy value.</em>
|
||||
* @property {number} channel - Channel: <code>1</code> – <code>16</code>.
|
||||
* @property {number} type - Status: {@link Midi.MidiStatus}; <code>8</code> – <code>15</code>.
|
||||
* @property {number} note - Note: <code>0</code> – <code>127</code>.
|
||||
* @property {number} velocity - Note velocity: <code>0</code> – <code>127</code>. (<code>0</code> means "note off".)
|
||||
* @property {number} bend - Pitch bend: <code>-8192</code> – <code>8191</code>.
|
||||
* @property {number} program - Program change: <code>0</code> – <code>127</code>.
|
||||
*/
|
||||
/**jsdoc
|
||||
* An integer DWORD (unsigned 32 bit) message with bits having values as follows:
|
||||
* <table>
|
||||
* <tbody>
|
||||
* <tr>
|
||||
* <td width=25%><code>00000000</code></td>
|
||||
* <td width=25%><code>0vvvvvvv</code></td>
|
||||
* <td width=25%><code>0nnnnnnn</code></td>
|
||||
* <td width=12%><code>1sss</code></td>
|
||||
* <td width=12%><code>cccc</code></td>
|
||||
* </tbody>
|
||||
* </table>
|
||||
* <p>Where:</p>
|
||||
* <ul>
|
||||
* <li><code>v</code> = Velocity.
|
||||
* <li><code>n</code> = Note.
|
||||
* <li><code>s</code> = Status - {@link Midi.MidiStatus}
|
||||
* <li><code>c</code> = Channel.
|
||||
* </ul>
|
||||
* <p>The number in the first bit of each byte denotes whether it is a command (1) or data (0).
|
||||
* @typedef {number} Midi.RawMidiMessage
|
||||
*/
|
||||
/**jsdoc
|
||||
* <p>A MIDI status value. The following MIDI status values are supported:</p>
|
||||
* <table>
|
||||
* <thead>
|
||||
* <tr><th>Value</th><th>Description</th>
|
||||
* </thead>
|
||||
* <tbody>
|
||||
* <tr><td><code>8</code></td><td>Note off.</td></tr>
|
||||
* <tr><td><code>9</code></td><td>Note on.</td></tr>
|
||||
* <tr><td><code>10</code></td><td>Polyphonic key pressure.</td></tr>
|
||||
* <tr><td><code>11</code></td><td>Control change.</td></tr>
|
||||
* <tr><td><code>12</code></td><td>Program change.</td></tr>
|
||||
* <tr><td><code>13</code></td><td>Channel pressure.</td></tr>
|
||||
* <tr><td><code>14</code></td><td>Pitch bend.</td></tr>
|
||||
* <tr><td><code>15</code></td><td>System message.</td></tr>
|
||||
* </tbody>
|
||||
* </table>
|
||||
* @typedef {number} Midi.MidiStatus
|
||||
*/
|
||||
void Midi::midiReceived(int device, int raw, int channel, int status, int type, int note, int velocity, int bend, int program) {
|
||||
QVariantMap eventData;
|
||||
eventData["device"] = device;
|
||||
|
|
|
@ -21,6 +21,12 @@
|
|||
#include <string>
|
||||
|
||||
/**jsdoc
|
||||
* The <code>Midi</code> API provides the ability to connect Interface with musical instruments and other external or virtual
|
||||
* devices via the MIDI protocol. For further information and examples, see the tutorial:
|
||||
* <a href="https://docs.highfidelity.com/en/rc81/script/midi-tutorial.html">Use MIDI to Control Your Environment</a>.</p>
|
||||
*
|
||||
* <p><strong>Note:</strong> Only works on Windows.</p>
|
||||
*
|
||||
* @namespace Midi
|
||||
*
|
||||
* @hifi-interface
|
||||
|
@ -49,88 +55,112 @@ private:
|
|||
void MidiCleanup();
|
||||
|
||||
signals:
|
||||
void midiNote(QVariantMap eventData);
|
||||
void midiMessage(QVariantMap eventData);
|
||||
void midiReset();
|
||||
|
||||
public slots:
|
||||
|
||||
/**jsdoc
|
||||
* Send Raw MIDI packet to a particular device.
|
||||
* Triggered when a connected device sends an output.
|
||||
* @function Midi.midiNote
|
||||
* @param {Midi.MidiMessage} message - The MIDI message.
|
||||
* @returns {Signal}
|
||||
* @deprecated This signal is deprecated and will be removed. Use {@link Midi.midiMessage|midiMessage} instead.
|
||||
*/
|
||||
void midiNote(QVariantMap eventData);
|
||||
|
||||
/**jsdoc
|
||||
* Triggered when a connected device sends an output.
|
||||
* @function Midi.midiMessage
|
||||
* @param {Midi.MidiMessage} message - The MIDI message.
|
||||
* @returns {Signal}
|
||||
*/
|
||||
void midiMessage(QVariantMap eventData);
|
||||
|
||||
/**jsdoc
|
||||
* Triggered when the system detects there was a reset such as when a device is plugged in or unplugged.
|
||||
* @function Midi.midiReset
|
||||
* @returns {Signal}
|
||||
*/
|
||||
void midiReset();
|
||||
|
||||
public slots:
|
||||
|
||||
/**jsdoc
|
||||
* Sends a raw MIDI packet to a particular device.
|
||||
* @function Midi.sendRawDword
|
||||
* @param {number} device - Integer device number.
|
||||
* @param {number} raw - Integer (DWORD) raw MIDI message.
|
||||
* @param {Midi.RawMidiMessage} raw - Raw MIDI message.
|
||||
*/
|
||||
Q_INVOKABLE void sendRawDword(int device, int raw);
|
||||
|
||||
/**jsdoc
|
||||
* Send MIDI message to a particular device.
|
||||
* Sends a MIDI message to a particular device.
|
||||
* @function Midi.sendMidiMessage
|
||||
* @param {number} device - Integer device number.
|
||||
* @param {number} channel - Integer channel number.
|
||||
* @param {number} type - 0x8 is note off, 0x9 is note on (if velocity=0, note off), etc.
|
||||
* @param {number} note - MIDI note number.
|
||||
* @param {number} velocity - Note velocity (0 means note off).
|
||||
* @param {Midi.MidiStatus} type - Integer status value.
|
||||
* @param {number} note - Note number.
|
||||
* @param {number} velocity - Note velocity. (<code>0</code> means "note off".)
|
||||
* @comment The "type" parameter has that name to match up with {@link Midi.MidiMessage}.
|
||||
*/
|
||||
Q_INVOKABLE void sendMidiMessage(int device, int channel, int type, int note, int velocity);
|
||||
|
||||
/**jsdoc
|
||||
* Play a note on all connected devices.
|
||||
* Plays a note on all connected devices.
|
||||
* @function Midi.playMidiNote
|
||||
* @param {number} status - 0x80 is note off, 0x90 is note on (if velocity=0, note off), etc.
|
||||
* @param {number} note - MIDI note number.
|
||||
* @param {number} velocity - Note velocity (0 means note off).
|
||||
* @param {MidiStatus} status - Note status.
|
||||
* @param {number} note - Note number.
|
||||
* @param {number} velocity - Note velocity. (<code>0</code> means "note off".)
|
||||
*/
|
||||
Q_INVOKABLE void playMidiNote(int status, int note, int velocity);
|
||||
|
||||
/**jsdoc
|
||||
* Turn off all notes on all connected devices.
|
||||
* Turns off all notes on all connected MIDI devices.
|
||||
* @function Midi.allNotesOff
|
||||
*/
|
||||
Q_INVOKABLE void allNotesOff();
|
||||
|
||||
/**jsdoc
|
||||
* Clean up and re-discover attached devices.
|
||||
* Cleans up and rediscovers attached MIDI devices.
|
||||
* @function Midi.resetDevices
|
||||
*/
|
||||
Q_INVOKABLE void resetDevices();
|
||||
|
||||
/**jsdoc
|
||||
* Get a list of inputs/outputs.
|
||||
* Gets a list of MIDI input or output devices.
|
||||
* @function Midi.listMidiDevices
|
||||
* @param {boolean} output
|
||||
* @param {boolean} output - <code>true</code> to list output devices, <code>false</code> to list input devices.
|
||||
* @returns {string[]}
|
||||
*/
|
||||
Q_INVOKABLE QStringList listMidiDevices(bool output);
|
||||
|
||||
/**jsdoc
|
||||
* Block an input/output by name.
|
||||
* Blocks a MIDI device's input or output.
|
||||
* @function Midi.blockMidiDevice
|
||||
* @param {string} name
|
||||
* @param {boolean} output
|
||||
* @param {string} name - The name of the MIDI device to block.
|
||||
* @param {boolean} output - <code>true</code> to block the device's output, <code>false</code> to block its input.
|
||||
*/
|
||||
Q_INVOKABLE void blockMidiDevice(QString name, bool output);
|
||||
|
||||
/**jsdoc
|
||||
* Unblock an input/output by name.
|
||||
* Unblocks a MIDI device's input or output.
|
||||
* @function Midi.unblockMidiDevice
|
||||
* @param {string} name
|
||||
* @param {boolean} output
|
||||
* @param {string} name- The name of the MIDI device to unblock.
|
||||
* @param {boolean} output - <code>true</code> to unblock the device's output, <code>false</code> to unblock its input.
|
||||
*/
|
||||
Q_INVOKABLE void unblockMidiDevice(QString name, bool output);
|
||||
|
||||
/**jsdoc
|
||||
* Repeat all incoming notes to all outputs (default disabled).
|
||||
* Enables or disables repeating all incoming notes to all outputs. (Default is disabled.)
|
||||
* @function Midi.thruModeEnable
|
||||
* @param {boolean} enable
|
||||
* @param {boolean} enable - <code>true</code> to enable repeating all incoming notes to all output, <code>false</code> to
|
||||
* disable.
|
||||
*/
|
||||
Q_INVOKABLE void thruModeEnable(bool enable);
|
||||
|
||||
|
||||
/**jsdoc
|
||||
* Broadcast on all unblocked devices.
|
||||
* Enables or disables broadcasts to all unblocked devices.
|
||||
* @function Midi.broadcastEnable
|
||||
* @param {boolean} enable
|
||||
* @param {boolean} enable - <code>true</code> to have "send" functions broadcast to all devices, <code>false</code> to
|
||||
* have them send to specific output devices.
|
||||
*/
|
||||
Q_INVOKABLE void broadcastEnable(bool enable);
|
||||
|
||||
|
@ -138,50 +168,58 @@ signals:
|
|||
/// filter by event types
|
||||
|
||||
/**jsdoc
|
||||
* Enables or disables note off events.
|
||||
* @function Midi.typeNoteOffEnable
|
||||
* @param {boolean} enable
|
||||
* @param {boolean} enable - <code>true</code> to enable, <code>false</code> to disable.
|
||||
*/
|
||||
Q_INVOKABLE void typeNoteOffEnable(bool enable);
|
||||
|
||||
/**jsdoc
|
||||
* Enables or disables note on events.
|
||||
* @function Midi.typeNoteOnEnable
|
||||
* @param {boolean} enable
|
||||
* @param {boolean} enable - <code>true</code> to enable, <code>false</code> to disable.
|
||||
*/
|
||||
Q_INVOKABLE void typeNoteOnEnable(bool enable);
|
||||
|
||||
/**jsdoc
|
||||
* Enables or disables poly key pressure events.
|
||||
* @function Midi.typePolyKeyPressureEnable
|
||||
* @param {boolean} enable
|
||||
* @param {boolean} enable - <code>true</code> to enable, <code>false</code> to disable.
|
||||
*/
|
||||
Q_INVOKABLE void typePolyKeyPressureEnable(bool enable);
|
||||
|
||||
/**jsdoc
|
||||
* Enables or disables control change events.
|
||||
* @function Midi.typeControlChangeEnable
|
||||
* @param {boolean} enable
|
||||
* @param {boolean} enable - <code>true</code> to enable, <code>false</code> to disable.
|
||||
*/
|
||||
Q_INVOKABLE void typeControlChangeEnable(bool enable);
|
||||
|
||||
/**jsdoc
|
||||
* Enables or disables program change events.
|
||||
* @function Midi.typeProgramChangeEnable
|
||||
* @param {boolean} enable
|
||||
* @param {boolean} enable - <code>true</code> to enable, <code>false</code> to disable.
|
||||
*/
|
||||
Q_INVOKABLE void typeProgramChangeEnable(bool enable);
|
||||
|
||||
/**jsdoc
|
||||
* Enables or disables channel pressure events.
|
||||
* @function Midi.typeChanPressureEnable
|
||||
* @param {boolean} enable
|
||||
* @param {boolean} enable - <code>true</code> to enable, <code>false</code> to disable.
|
||||
*/
|
||||
Q_INVOKABLE void typeChanPressureEnable(bool enable);
|
||||
|
||||
/**jsdoc
|
||||
* Enables or disables pitch bend events.
|
||||
* @function Midi.typePitchBendEnable
|
||||
* @param {boolean} enable
|
||||
* @param {boolean} enable - <code>true</code> to enable, <code>false</code> to disable.
|
||||
*/
|
||||
Q_INVOKABLE void typePitchBendEnable(bool enable);
|
||||
|
||||
/**jsdoc
|
||||
* Enables or disables system message events.
|
||||
* @function Midi.typeSystemMessageEnable
|
||||
* @param {boolean} enable
|
||||
* @param {boolean} enable - <code>true</code> to enable, <code>false</code> to disable.
|
||||
*/
|
||||
Q_INVOKABLE void typeSystemMessageEnable(bool enable);
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@ void CongestionControl::setMaxBandwidth(int maxBandwidth) {
|
|||
void CongestionControl::setPacketSendPeriod(double newSendPeriod) {
|
||||
Q_ASSERT_X(newSendPeriod >= 0, "CongestionControl::setPacketPeriod", "Can not set a negative packet send period");
|
||||
|
||||
auto packetsPerSecond = (double)_maxBandwidth / (BITS_PER_BYTE * _mss);
|
||||
auto packetsPerSecond = _mss > 0 ? (double)_maxBandwidth / (BITS_PER_BYTE * _mss) : -1.0;
|
||||
if (packetsPerSecond > 0.0) {
|
||||
// anytime the packet send period is about to be increased, make sure it stays below the minimum period,
|
||||
// calculated based on the maximum desired bandwidth
|
||||
|
|
|
@ -38,10 +38,10 @@ PacketVersion versionForPacketType(PacketType packetType) {
|
|||
return static_cast<PacketVersion>(EntityQueryPacketVersion::ConicalFrustums);
|
||||
case PacketType::AvatarIdentity:
|
||||
case PacketType::AvatarData:
|
||||
return static_cast<PacketVersion>(AvatarMixerPacketVersion::HandControllerSection);
|
||||
return static_cast<PacketVersion>(AvatarMixerPacketVersion::SendVerificationFailed);
|
||||
case PacketType::BulkAvatarData:
|
||||
case PacketType::KillAvatar:
|
||||
return static_cast<PacketVersion>(AvatarMixerPacketVersion::HandControllerSection);
|
||||
return static_cast<PacketVersion>(AvatarMixerPacketVersion::SendVerificationFailed);
|
||||
case PacketType::MessagesData:
|
||||
return static_cast<PacketVersion>(MessageDataVersion::TextOrBinaryData);
|
||||
// ICE packets
|
||||
|
|
|
@ -269,6 +269,7 @@ enum class EntityVersion : PacketVersion {
|
|||
CertificateTypeProperty,
|
||||
DisableWebMedia,
|
||||
ParticleShapeType,
|
||||
ParticleShapeTypeDeadlockFix,
|
||||
|
||||
// Add new versions above here
|
||||
NUM_PACKET_TYPE,
|
||||
|
@ -333,6 +334,7 @@ enum class AvatarMixerPacketVersion : PacketVersion {
|
|||
SendMaxTranslationDimension,
|
||||
FBXJointOrderChange,
|
||||
HandControllerSection,
|
||||
SendVerificationFailed
|
||||
};
|
||||
|
||||
enum class DomainConnectRequestVersion : PacketVersion {
|
||||
|
|
|
@ -31,6 +31,10 @@ bool RenderEventHandler::event(QEvent* e) {
|
|||
onRender();
|
||||
return true;
|
||||
|
||||
case OffscreenEvent::RenderSync:
|
||||
onRenderSync();
|
||||
return true;
|
||||
|
||||
case OffscreenEvent::Initialize:
|
||||
onInitalize();
|
||||
return true;
|
||||
|
@ -106,6 +110,14 @@ void RenderEventHandler::resize() {
|
|||
}
|
||||
|
||||
void RenderEventHandler::onRender() {
|
||||
qmlRender(false);
|
||||
}
|
||||
|
||||
void RenderEventHandler::onRenderSync() {
|
||||
qmlRender(true);
|
||||
}
|
||||
|
||||
void RenderEventHandler::qmlRender(bool sceneGraphSync) {
|
||||
if (_shared->isQuit()) {
|
||||
return;
|
||||
}
|
||||
|
@ -117,7 +129,8 @@ void RenderEventHandler::onRender() {
|
|||
PROFILE_RANGE(render_qml_gl, __FUNCTION__);
|
||||
|
||||
gl::globalLock();
|
||||
if (!_shared->preRender()) {
|
||||
if (!_shared->preRender(sceneGraphSync)) {
|
||||
gl::globalRelease();
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@ public:
|
|||
enum Type {
|
||||
Initialize = QEvent::User + 1,
|
||||
Render,
|
||||
RenderSync,
|
||||
Quit
|
||||
};
|
||||
|
||||
|
@ -45,6 +46,8 @@ private:
|
|||
void onInitalize();
|
||||
void resize();
|
||||
void onRender();
|
||||
void onRenderSync();
|
||||
void qmlRender(bool sceneGraphSync);
|
||||
void onQuit();
|
||||
|
||||
SharedObject* const _shared;
|
||||
|
@ -59,4 +62,4 @@ private:
|
|||
|
||||
}}} // namespace hifi::qml::impl
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
|
|
@ -344,17 +344,17 @@ void SharedObject::setSize(const QSize& size) {
|
|||
#endif
|
||||
}
|
||||
|
||||
bool SharedObject::preRender() {
|
||||
bool SharedObject::preRender(bool sceneGraphSync) {
|
||||
#ifndef DISABLE_QML
|
||||
QMutexLocker lock(&_mutex);
|
||||
if (_paused) {
|
||||
if (_syncRequested) {
|
||||
if (sceneGraphSync) {
|
||||
wake();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_syncRequested) {
|
||||
if (sceneGraphSync) {
|
||||
bool syncResult = true;
|
||||
if (!nsightActive()) {
|
||||
PROFILE_RANGE(render_qml_gl, "sync")
|
||||
|
@ -364,7 +364,6 @@ bool SharedObject::preRender() {
|
|||
if (!syncResult) {
|
||||
return false;
|
||||
}
|
||||
_syncRequested = false;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@ -475,9 +474,10 @@ void SharedObject::onRender() {
|
|||
lock.unlock();
|
||||
_renderControl->polishItems();
|
||||
lock.relock();
|
||||
QCoreApplication::postEvent(_renderObject, new OffscreenEvent(OffscreenEvent::Render));
|
||||
QCoreApplication::postEvent(_renderObject, new OffscreenEvent(OffscreenEvent::RenderSync));
|
||||
// sync and render request, main and render threads must be synchronized
|
||||
wait();
|
||||
_syncRequested = false;
|
||||
} else {
|
||||
QCoreApplication::postEvent(_renderObject, new OffscreenEvent(OffscreenEvent::Render));
|
||||
}
|
||||
|
|
|
@ -71,7 +71,7 @@ public:
|
|||
private:
|
||||
bool event(QEvent* e) override;
|
||||
|
||||
bool preRender();
|
||||
bool preRender(bool sceneGraphSync);
|
||||
void shutdownRendering(OffscreenGLCanvas& canvas, const QSize& size);
|
||||
// Called by the render event handler, from the render thread
|
||||
void initializeRenderControl(QOpenGLContext* context);
|
||||
|
|
|
@ -237,9 +237,11 @@ void CauterizedModel::updateRenderItems() {
|
|||
if (useDualQuaternionSkinning) {
|
||||
data.updateClusterBuffer(meshState.clusterDualQuaternions,
|
||||
cauterizedMeshState.clusterDualQuaternions);
|
||||
data.computeAdjustedLocalBound(meshState.clusterDualQuaternions);
|
||||
} else {
|
||||
data.updateClusterBuffer(meshState.clusterMatrices,
|
||||
cauterizedMeshState.clusterMatrices);
|
||||
data.computeAdjustedLocalBound(meshState.clusterMatrices);
|
||||
}
|
||||
|
||||
Transform renderTransform = modelTransform;
|
||||
|
|
|
@ -450,9 +450,9 @@ void ModelMeshPartPayload::render(RenderArgs* args) {
|
|||
void ModelMeshPartPayload::computeAdjustedLocalBound(const std::vector<glm::mat4>& clusterMatrices) {
|
||||
_adjustedLocalBound = _localBound;
|
||||
if (clusterMatrices.size() > 0) {
|
||||
_adjustedLocalBound.transform(clusterMatrices[0]);
|
||||
_adjustedLocalBound.transform(clusterMatrices.back());
|
||||
|
||||
for (int i = 1; i < (int)clusterMatrices.size(); ++i) {
|
||||
for (int i = 0; i < (int)clusterMatrices.size() - 1; ++i) {
|
||||
AABox clusterBound = _localBound;
|
||||
clusterBound.transform(clusterMatrices[i]);
|
||||
_adjustedLocalBound += clusterBound;
|
||||
|
@ -463,12 +463,12 @@ void ModelMeshPartPayload::computeAdjustedLocalBound(const std::vector<glm::mat4
|
|||
void ModelMeshPartPayload::computeAdjustedLocalBound(const std::vector<Model::TransformDualQuaternion>& clusterDualQuaternions) {
|
||||
_adjustedLocalBound = _localBound;
|
||||
if (clusterDualQuaternions.size() > 0) {
|
||||
Transform rootTransform(clusterDualQuaternions[0].getRotation(),
|
||||
clusterDualQuaternions[0].getScale(),
|
||||
clusterDualQuaternions[0].getTranslation());
|
||||
Transform rootTransform(clusterDualQuaternions.back().getRotation(),
|
||||
clusterDualQuaternions.back().getScale(),
|
||||
clusterDualQuaternions.back().getTranslation());
|
||||
_adjustedLocalBound.transform(rootTransform);
|
||||
|
||||
for (int i = 1; i < (int)clusterDualQuaternions.size(); ++i) {
|
||||
for (int i = 0; i < (int)clusterDualQuaternions.size() - 1; ++i) {
|
||||
AABox clusterBound = _localBound;
|
||||
Transform transform(clusterDualQuaternions[i].getRotation(),
|
||||
clusterDualQuaternions[i].getScale(),
|
||||
|
|
|
@ -241,8 +241,10 @@ void Model::updateRenderItems() {
|
|||
invalidatePayloadShapeKey, primitiveMode, renderItemKeyGlobalFlags, cauterized](ModelMeshPartPayload& data) {
|
||||
if (useDualQuaternionSkinning) {
|
||||
data.updateClusterBuffer(meshState.clusterDualQuaternions);
|
||||
data.computeAdjustedLocalBound(meshState.clusterDualQuaternions);
|
||||
} else {
|
||||
data.updateClusterBuffer(meshState.clusterMatrices);
|
||||
data.computeAdjustedLocalBound(meshState.clusterMatrices);
|
||||
}
|
||||
|
||||
Transform renderTransform = modelTransform;
|
||||
|
@ -774,11 +776,14 @@ scriptable::ScriptableModelBase Model::getScriptableModel() {
|
|||
auto& materialName = _modelMeshMaterialNames[shapeID];
|
||||
result.appendMaterial(graphics::MaterialLayer(getGeometry()->getShapeMaterial(shapeID), 0), shapeID, materialName);
|
||||
|
||||
auto mappedMaterialIter = _materialMapping.find(shapeID);
|
||||
if (mappedMaterialIter != _materialMapping.end()) {
|
||||
auto mappedMaterials = mappedMaterialIter->second;
|
||||
for (auto& mappedMaterial : mappedMaterials) {
|
||||
result.appendMaterial(mappedMaterial, shapeID, materialName);
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(_materialMappingMutex);
|
||||
auto mappedMaterialIter = _materialMapping.find(shapeID);
|
||||
if (mappedMaterialIter != _materialMapping.end()) {
|
||||
auto mappedMaterials = mappedMaterialIter->second;
|
||||
for (auto& mappedMaterial : mappedMaterials) {
|
||||
result.appendMaterial(mappedMaterial, shapeID, materialName);
|
||||
}
|
||||
}
|
||||
}
|
||||
shapeID++;
|
||||
|
@ -1367,8 +1372,6 @@ void Model::simulate(float deltaTime, bool fullUpdate) {
|
|||
// update the world space transforms for all joints
|
||||
glm::mat4 parentTransform = glm::scale(_scale) * glm::translate(_offset);
|
||||
updateRig(deltaTime, parentTransform);
|
||||
|
||||
computeMeshPartLocalBounds();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1379,17 +1382,6 @@ void Model::updateRig(float deltaTime, glm::mat4 parentTransform) {
|
|||
_rig.updateAnimations(deltaTime, parentTransform, rigToWorldTransform);
|
||||
}
|
||||
|
||||
void Model::computeMeshPartLocalBounds() {
|
||||
for (auto& part : _modelMeshRenderItems) {
|
||||
const Model::MeshState& state = _meshStates.at(part->_meshIndex);
|
||||
if (_useDualQuaternionSkinning) {
|
||||
part->computeAdjustedLocalBound(state.clusterDualQuaternions);
|
||||
} else {
|
||||
part->computeAdjustedLocalBound(state.clusterMatrices);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// virtual
|
||||
void Model::updateClusterMatrices() {
|
||||
DETAILED_PERFORMANCE_TIMER("Model::updateClusterMatrices");
|
||||
|
@ -1569,6 +1561,13 @@ void Model::applyMaterialMapping() {
|
|||
auto renderItemsKey = _renderItemKeyGlobalFlags;
|
||||
PrimitiveMode primitiveMode = getPrimitiveMode();
|
||||
bool useDualQuaternionSkinning = _useDualQuaternionSkinning;
|
||||
auto modelMeshRenderItemIDs = _modelMeshRenderItemIDs;
|
||||
auto modelMeshRenderItemShapes = _modelMeshRenderItemShapes;
|
||||
std::unordered_map<int, bool> shouldInvalidatePayloadShapeKeyMap;
|
||||
|
||||
for (auto& shape : _modelMeshRenderItemShapes) {
|
||||
shouldInvalidatePayloadShapeKeyMap[shape.meshIndex] = shouldInvalidatePayloadShapeKey(shape.meshIndex);
|
||||
}
|
||||
|
||||
auto& materialMapping = getMaterialMapping();
|
||||
for (auto& mapping : materialMapping) {
|
||||
|
@ -1588,7 +1587,8 @@ void Model::applyMaterialMapping() {
|
|||
priorityMapPerResource[shapeID] = ++_priorityMap[shapeID];
|
||||
}
|
||||
|
||||
auto materialLoaded = [this, networkMaterialResource, shapeIDs, priorityMapPerResource, renderItemsKey, primitiveMode, useDualQuaternionSkinning]() {
|
||||
auto materialLoaded = [this, networkMaterialResource, shapeIDs, priorityMapPerResource, renderItemsKey, primitiveMode, useDualQuaternionSkinning,
|
||||
modelMeshRenderItemIDs, modelMeshRenderItemShapes, shouldInvalidatePayloadShapeKeyMap]() {
|
||||
if (networkMaterialResource->isFailed() || networkMaterialResource->parsedMaterials.names.size() == 0) {
|
||||
return;
|
||||
}
|
||||
|
@ -1611,12 +1611,15 @@ void Model::applyMaterialMapping() {
|
|||
}
|
||||
}
|
||||
for (auto shapeID : shapeIDs) {
|
||||
if (shapeID < _modelMeshRenderItemIDs.size()) {
|
||||
auto itemID = _modelMeshRenderItemIDs[shapeID];
|
||||
auto meshIndex = _modelMeshRenderItemShapes[shapeID].meshIndex;
|
||||
bool invalidatePayloadShapeKey = shouldInvalidatePayloadShapeKey(meshIndex);
|
||||
if (shapeID < modelMeshRenderItemIDs.size()) {
|
||||
auto itemID = modelMeshRenderItemIDs[shapeID];
|
||||
auto meshIndex = modelMeshRenderItemShapes[shapeID].meshIndex;
|
||||
bool invalidatePayloadShapeKey = shouldInvalidatePayloadShapeKeyMap.at(meshIndex);
|
||||
graphics::MaterialLayer material = graphics::MaterialLayer(networkMaterial, priorityMapPerResource.at(shapeID));
|
||||
_materialMapping[shapeID].push_back(material);
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(_materialMappingMutex);
|
||||
_materialMapping[shapeID].push_back(material);
|
||||
}
|
||||
transaction.updateItem<ModelMeshPartPayload>(itemID, [material, renderItemsKey,
|
||||
invalidatePayloadShapeKey, primitiveMode, useDualQuaternionSkinning](ModelMeshPartPayload& data) {
|
||||
data.addMaterial(material);
|
||||
|
@ -1744,9 +1747,9 @@ void Blender::run() {
|
|||
if (_model && _model->isLoaded()) {
|
||||
DETAILED_PROFILE_RANGE_EX(simulation_animation, __FUNCTION__, 0xFFFF0000, 0, { { "url", _model->getURL().toString() } });
|
||||
int offset = 0;
|
||||
auto meshes = _model->getHFMModel().meshes;
|
||||
const auto& meshes = _model->getHFMModel().meshes;
|
||||
int meshIndex = 0;
|
||||
foreach(const HFMMesh& mesh, meshes) {
|
||||
for(const HFMMesh& mesh : meshes) {
|
||||
auto modelMeshBlendshapeOffsets = _model->_blendshapeOffsets.find(meshIndex++);
|
||||
if (mesh.blendshapes.isEmpty() || modelMeshBlendshapeOffsets == _model->_blendshapeOffsets.end()) {
|
||||
// Not blendshaped or not initialized
|
||||
|
@ -1777,33 +1780,30 @@ void Blender::run() {
|
|||
|
||||
float normalCoefficient = vertexCoefficient * NORMAL_COEFFICIENT_SCALE;
|
||||
const HFMBlendshape& blendshape = mesh.blendshapes.at(i);
|
||||
for (int j = 0; j < blendshape.indices.size(); ++j) {
|
||||
int index = blendshape.indices.at(j);
|
||||
|
||||
tbb::parallel_for(tbb::blocked_range<int>(0, blendshape.indices.size()), [&](const tbb::blocked_range<int>& range) {
|
||||
for (auto j = range.begin(); j < range.end(); j++) {
|
||||
int index = blendshape.indices.at(j);
|
||||
auto& currentBlendshapeOffset = unpackedBlendshapeOffsets[index];
|
||||
currentBlendshapeOffset.positionOffset += blendshape.vertices.at(j) * vertexCoefficient;
|
||||
|
||||
auto& currentBlendshapeOffset = unpackedBlendshapeOffsets[index];
|
||||
currentBlendshapeOffset.positionOffset += blendshape.vertices.at(j) * vertexCoefficient;
|
||||
|
||||
currentBlendshapeOffset.normalOffset += blendshape.normals.at(j) * normalCoefficient;
|
||||
if (j < blendshape.tangents.size()) {
|
||||
currentBlendshapeOffset.tangentOffset += blendshape.tangents.at(j) * normalCoefficient;
|
||||
}
|
||||
currentBlendshapeOffset.normalOffset += blendshape.normals.at(j) * normalCoefficient;
|
||||
if (j < blendshape.tangents.size()) {
|
||||
currentBlendshapeOffset.tangentOffset += blendshape.tangents.at(j) * normalCoefficient;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Blendshape offsets are generrated, now let's pack it on its way to gpu
|
||||
tbb::parallel_for(tbb::blocked_range<int>(0, (int) unpackedBlendshapeOffsets.size()), [&](const tbb::blocked_range<int>& range) {
|
||||
auto unpacked = unpackedBlendshapeOffsets.data() + range.begin();
|
||||
auto packed = meshBlendshapeOffsets + range.begin();
|
||||
for (auto j = range.begin(); j < range.end(); j++) {
|
||||
// FIXME it feels like we could be more effectively using SIMD here
|
||||
{
|
||||
auto unpacked = unpackedBlendshapeOffsets.data();
|
||||
auto packed = meshBlendshapeOffsets;
|
||||
for (int j = 0; j < (int)unpackedBlendshapeOffsets.size(); ++j) {
|
||||
packBlendshapeOffsetTo_Pos_F32_3xSN10_Nor_3xSN10_Tan_3xSN10((*packed).packedPosNorTan, (*unpacked));
|
||||
|
||||
unpacked++;
|
||||
packed++;
|
||||
++unpacked;
|
||||
++packed;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
// post the result to the ModelBlender, which will dispatch to the model if still alive
|
||||
|
|
|
@ -379,6 +379,7 @@ protected:
|
|||
|
||||
std::unordered_map<unsigned int, quint16> _priorityMap; // only used for materialMapping
|
||||
std::unordered_map<unsigned int, std::vector<graphics::MaterialLayer>> _materialMapping; // generated during applyMaterialMapping
|
||||
std::mutex _materialMappingMutex;
|
||||
void applyMaterialMapping();
|
||||
|
||||
void setBlendshapeCoefficients(const QVector<float>& coefficients) { _blendshapeCoefficients = coefficients; }
|
||||
|
@ -427,7 +428,6 @@ protected:
|
|||
void setScaleInternal(const glm::vec3& scale);
|
||||
void snapToRegistrationPoint();
|
||||
|
||||
void computeMeshPartLocalBounds();
|
||||
virtual void updateRig(float deltaTime, glm::mat4 parentTransform);
|
||||
|
||||
/// Allow sub classes to force invalidating the bboxes
|
||||
|
|
|
@ -43,7 +43,9 @@ const Selection::Name ZoneRendererTask::ZONES_SELECTION { "RankedZones" };
|
|||
|
||||
void ZoneRendererTask::build(JobModel& task, const Varying& input, Varying& output) {
|
||||
// Filter out the sorted list of zones
|
||||
const auto zoneItems = task.addJob<render::SelectSortItems>("FilterZones", input, ZONES_SELECTION.c_str());
|
||||
// FIXME: the zones in the selection are already sorted, but we're doing another sort here to pick the selected items
|
||||
// out of `input`, which means we're also looping over the inItems an extra time.
|
||||
const auto zoneItems = task.addJob<render::SelectSortItems>("FilterZones", input, ZONES_SELECTION);
|
||||
|
||||
// just setup the current zone env
|
||||
task.addJob<SetupZones>("SetupZones", zoneItems);
|
||||
|
|
|
@ -287,12 +287,7 @@ void Scene::processTransactionFrame(const Transaction& transaction) {
|
|||
_numAllocatedItems.exchange(maxID);
|
||||
}
|
||||
|
||||
if (transaction.touchTransactions()) {
|
||||
std::unique_lock<std::mutex> lock(_selectionsMutex);
|
||||
|
||||
// resets and potential NEW items
|
||||
resetSelections(transaction._resetSelections);
|
||||
}
|
||||
resetSelections(transaction._resetSelections);
|
||||
|
||||
resetHighlights(transaction._highlightResets);
|
||||
removeHighlights(transaction._highlightRemoves);
|
||||
|
@ -392,6 +387,10 @@ void Scene::updateItems(const Transaction::Updates& transactions) {
|
|||
void Scene::transitionItems(const Transaction::TransitionAdds& transactions) {
|
||||
auto transitionStage = getStage<TransitionStage>(TransitionStage::getName());
|
||||
|
||||
if (!transitionStage) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto& add : transactions) {
|
||||
auto itemId = std::get<0>(add);
|
||||
// Access the true item
|
||||
|
@ -433,6 +432,10 @@ void Scene::reApplyTransitions(const Transaction::TransitionReApplies& transacti
|
|||
void Scene::queryTransitionItems(const Transaction::TransitionQueries& transactions) {
|
||||
auto transitionStage = getStage<TransitionStage>(TransitionStage::getName());
|
||||
|
||||
if (!transitionStage) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto& query : transactions) {
|
||||
auto itemId = std::get<0>(query);
|
||||
// Access the true item
|
||||
|
@ -553,11 +556,14 @@ void Scene::setItemTransition(ItemID itemId, Index transitionId) {
|
|||
}
|
||||
|
||||
void Scene::resetItemTransition(ItemID itemId) {
|
||||
auto transitionStage = getStage<TransitionStage>(TransitionStage::getName());
|
||||
if (!transitionStage) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& item = _items[itemId];
|
||||
TransitionStage::Index transitionId = item.getTransitionId();
|
||||
if (!render::TransitionStage::isIndexInvalid(transitionId)) {
|
||||
auto transitionStage = getStage<TransitionStage>(TransitionStage::getName());
|
||||
|
||||
auto finishedOperators = _transitionFinishedOperatorMap[transitionId];
|
||||
for (auto finishedOperator : finishedOperators) {
|
||||
if (finishedOperator) {
|
||||
|
@ -570,35 +576,34 @@ void Scene::resetItemTransition(ItemID itemId) {
|
|||
}
|
||||
}
|
||||
|
||||
// This function is thread safe
|
||||
Selection Scene::getSelection(const Selection::Name& name) const {
|
||||
std::unique_lock<std::mutex> lock(_selectionsMutex);
|
||||
auto found = _selections.find(name);
|
||||
if (found == _selections.end()) {
|
||||
return Selection();
|
||||
} else {
|
||||
return (*found).second;
|
||||
return found->second;
|
||||
}
|
||||
}
|
||||
|
||||
// This function is thread safe
|
||||
bool Scene::isSelectionEmpty(const Selection::Name& name) const {
|
||||
std::unique_lock<std::mutex> lock(_selectionsMutex);
|
||||
auto found = _selections.find(name);
|
||||
if (found == _selections.end()) {
|
||||
return true;
|
||||
} else {
|
||||
return (*found).second.isEmpty();
|
||||
return found->second.isEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
void Scene::resetSelections(const Transaction::SelectionResets& transactions) {
|
||||
std::unique_lock<std::mutex> lock(_selectionsMutex);
|
||||
for (auto selection : transactions) {
|
||||
auto found = _selections.find(selection.getName());
|
||||
if (found == _selections.end()) {
|
||||
_selections.insert(SelectionMap::value_type(selection.getName(), selection));
|
||||
_selections[selection.getName()] = selection;
|
||||
} else {
|
||||
(*found).second = selection;
|
||||
found->second = selection;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -78,9 +78,6 @@ public:
|
|||
void merge(Transaction&& transaction);
|
||||
void clear();
|
||||
|
||||
// Checkers if there is work to do when processing the transaction
|
||||
bool touchTransactions() const { return !_resetSelections.empty(); }
|
||||
|
||||
protected:
|
||||
|
||||
using Reset = std::tuple<ItemID, PayloadPointer>;
|
||||
|
|
|
@ -45,9 +45,8 @@ namespace render {
|
|||
Name _name;
|
||||
ItemIDs _items;
|
||||
};
|
||||
using Selections = std::vector<Selection>;
|
||||
|
||||
using SelectionMap = std::map<const Selection::Name, Selection>;
|
||||
using SelectionMap = std::unordered_map<Selection::Name, Selection>;
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#include <unordered_set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <stdexcept>
|
||||
|
||||
#include <QtCore/QtGlobal>
|
||||
|
||||
|
|
|
@ -16,6 +16,13 @@
|
|||
#include <QString>
|
||||
#include <QUrl>
|
||||
|
||||
/**jsdoc
|
||||
* Information about a resource request.
|
||||
* @typedef {object} ResourceRequestObserver.ResourceRequest
|
||||
* @property {string} url - The URL of the resource request.
|
||||
* @property {number} callerId - An ID identifying the request.
|
||||
* @property {string} extra - Extra information about the request.
|
||||
*/
|
||||
void ResourceRequestObserver::update(const QUrl& requestUrl,
|
||||
const qint64 callerId,
|
||||
const QString& extra) {
|
||||
|
|
|
@ -16,7 +16,15 @@
|
|||
|
||||
#include "DependencyManager.h"
|
||||
|
||||
|
||||
/**jsdoc
|
||||
* The <code>ResourceRequestObserver</code> API provides notifications when an observable resource request is made.
|
||||
*
|
||||
* @namespace ResourceRequestObserver
|
||||
*
|
||||
* @hifi-interface
|
||||
* @hifi-client-entity
|
||||
* @hifi-avatar
|
||||
*/
|
||||
class ResourceRequestObserver : public QObject, public Dependency {
|
||||
Q_OBJECT
|
||||
SINGLETON_DEPENDENCY
|
||||
|
@ -25,5 +33,29 @@ public:
|
|||
void update(const QUrl& requestUrl, const qint64 callerId = -1, const QString& extra = "");
|
||||
|
||||
signals:
|
||||
/**jsdoc
|
||||
* Triggered when an observable resource request is made.
|
||||
* @function ResourceRequestObserver.resourceRequestEvent
|
||||
* @param {ResourceRequestObserver.ResourceRequest} request - Information about the resource request.
|
||||
* @returns {Signal}
|
||||
* @example <caption>Report when a particular Clipboard.importEntities() resource request is made.</caption>
|
||||
* ResourceRequestObserver.resourceRequestEvent.connect(function (request) {
|
||||
* if (request.callerId === 100) {
|
||||
* print("Resource request: " + JSON.stringify(request));
|
||||
* }
|
||||
* });
|
||||
*
|
||||
* function importEntities() {
|
||||
* var filename = Window.browse("Import entities to clipboard", "", "*.json");
|
||||
* if (filename) {
|
||||
* Clipboard.importEntities(filename, true, 100);
|
||||
* pastedEntities = Clipboard.pasteEntities(Vec3.sum(MyAvatar.position,
|
||||
* Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -3 })));
|
||||
* print("Entities pasted: " + JSON.stringify(pastedEntities));
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* Script.setTimeout(importEntities, 2000);
|
||||
*/
|
||||
void resourceRequestEvent(QVariantMap result);
|
||||
};
|
||||
|
|
47
libraries/ui/src/DockWidget.cpp
Normal file
47
libraries/ui/src/DockWidget.cpp
Normal file
|
@ -0,0 +1,47 @@
|
|||
//
|
||||
// DockWidget.cpp
|
||||
// libraries/ui/src
|
||||
//
|
||||
// Created by Dante Ruiz 05-07-2019
|
||||
// Copyright 2019 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 "DockWidget.h"
|
||||
|
||||
#include "OffscreenUi.h"
|
||||
|
||||
#include <QtQml/QQmlEngine>
|
||||
#include <QtQml/QQmlContext>
|
||||
#include <QQuickView>
|
||||
|
||||
#include <PathUtils.h>
|
||||
|
||||
static void quickViewDeleter(QQuickView* quickView) {
|
||||
quickView->deleteLater();
|
||||
}
|
||||
|
||||
DockWidget::DockWidget(const QString& title, QWidget* parent) : QDockWidget(title, parent) {
|
||||
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
||||
auto qmlEngine = offscreenUi->getSurfaceContext()->engine();
|
||||
_quickView = std::shared_ptr<QQuickView>(new QQuickView(qmlEngine, nullptr), quickViewDeleter);
|
||||
QWidget* widget = QWidget::createWindowContainer(_quickView.get());
|
||||
setWidget(widget);
|
||||
QWidget* headerWidget = new QWidget();
|
||||
setTitleBarWidget(headerWidget);
|
||||
}
|
||||
|
||||
void DockWidget::setSource(const QUrl& url) {
|
||||
_quickView->setSource(url);
|
||||
}
|
||||
|
||||
QQuickItem* DockWidget::getRootItem() const {
|
||||
return _quickView->rootObject();
|
||||
}
|
||||
|
||||
std::shared_ptr<QQuickView> DockWidget::getQuickView() const {
|
||||
return _quickView;
|
||||
}
|
33
libraries/ui/src/DockWidget.h
Normal file
33
libraries/ui/src/DockWidget.h
Normal file
|
@ -0,0 +1,33 @@
|
|||
//
|
||||
// DockWidget.h
|
||||
// libraries/ui/src
|
||||
//
|
||||
// Created by Dante Ruiz 05-07-2019
|
||||
// Copyright 2019 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_DockWidget_h
|
||||
#define hifi_DockWidget_h
|
||||
|
||||
#include <QDockWidget>
|
||||
#include <memory>
|
||||
|
||||
class QQuickView;
|
||||
class QQuickItem;
|
||||
class DockWidget : public QDockWidget {
|
||||
public:
|
||||
DockWidget(const QString& title, QWidget* parent = nullptr);
|
||||
~DockWidget() = default;
|
||||
|
||||
void setSource(const QUrl& url);
|
||||
QQuickItem* getRootItem() const;
|
||||
std::shared_ptr<QQuickView> getQuickView() const;
|
||||
private:
|
||||
std::shared_ptr<QQuickView> _quickView;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -26,6 +26,10 @@
|
|||
#include <QDebug>
|
||||
|
||||
#include "ui/Logging.h"
|
||||
#include "DockWidget.h"
|
||||
|
||||
#include <QSizePolicy>
|
||||
#include <QLayout>
|
||||
|
||||
MainWindow::MainWindow(QWidget* parent) :
|
||||
QMainWindow(parent),
|
||||
|
@ -34,6 +38,7 @@ MainWindow::MainWindow(QWidget* parent) :
|
|||
{
|
||||
setAttribute(Qt::WA_NoSystemBackground);
|
||||
setAcceptDrops(true);
|
||||
setStyleSheet("QMainWindow::separator {width: 1px; border: none;}");
|
||||
}
|
||||
|
||||
MainWindow::~MainWindow() {
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
#include <SettingHandle.h>
|
||||
|
||||
class DockWidget;
|
||||
class MainWindow : public QMainWindow {
|
||||
Q_OBJECT
|
||||
public:
|
||||
|
@ -23,11 +24,10 @@ public:
|
|||
~MainWindow();
|
||||
|
||||
static QWindow* findMainWindow();
|
||||
|
||||
public slots:
|
||||
void restoreGeometry();
|
||||
void saveGeometry();
|
||||
|
||||
|
||||
signals:
|
||||
void windowGeometryChanged(QRect geometry);
|
||||
void windowShown(bool shown);
|
||||
|
@ -42,7 +42,7 @@ protected:
|
|||
virtual void changeEvent(QEvent* event) override;
|
||||
virtual void dragEnterEvent(QDragEnterEvent *e) override;
|
||||
virtual void dropEvent(QDropEvent *e) override;
|
||||
|
||||
|
||||
private:
|
||||
Setting::Handle<QRect> _windowGeometry;
|
||||
Setting::Handle<int> _windowState;
|
||||
|
|
|
@ -252,7 +252,9 @@ private slots:
|
|||
_finished = true;
|
||||
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
||||
emit response(_result);
|
||||
offscreenUi->removeModalDialog(qobject_cast<QObject*>(this));
|
||||
if (!offscreenUi.isNull()) {
|
||||
offscreenUi->removeModalDialog(qobject_cast<QObject*>(this));
|
||||
}
|
||||
disconnect(_dialog);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -409,9 +409,10 @@ void OculusControllerManager::TouchDevice::stopHapticPulse(bool leftHand) {
|
|||
}
|
||||
|
||||
/**jsdoc
|
||||
* <p>The <code>Controller.Hardware.OculusTouch</code> object has properties representing Oculus Rift. The property values are
|
||||
* integer IDs, uniquely identifying each output. <em>Read-only.</em> These can be mapped to actions or functions or
|
||||
* <code>Controller.Standard</code> items in a {@link RouteObject} mapping.</p>
|
||||
* <p>The <code>Controller.Hardware.OculusTouch</code> object has properties representing the Oculus Rift. The property values
|
||||
* are integer IDs, uniquely identifying each output. <em>Read-only.</em></p>
|
||||
* <p>These outputs can be mapped to actions or functions or <code>Controller.Standard</code> items in a {@link RouteObject}
|
||||
* mapping.</p>
|
||||
* <table>
|
||||
* <thead>
|
||||
* <tr><th>Property</th><th>Type</th><th>Data</th><th>Description</th></tr>
|
||||
|
|
|
@ -1299,14 +1299,20 @@ void ViveControllerManager::InputDevice::setConfigFromString(const QString& valu
|
|||
}
|
||||
|
||||
/**jsdoc
|
||||
* <p>The <code>Controller.Hardware.Vive</code> object has properties representing Vive. The property values are integer IDs,
|
||||
* uniquely identifying each output. <em>Read-only.</em> These can be mapped to actions or functions or
|
||||
* <code>Controller.Standard</code> items in a {@link RouteObject} mapping.</p>
|
||||
* <p>The <code>Controller.Hardware.Vive</code> object has properties representing the Vive. The property values are integer
|
||||
* IDs, uniquely identifying each output. <em>Read-only.</em></p>
|
||||
* <p>These outputs can be mapped to actions or functions or <code>Controller.Standard</code> items in a {@link RouteObject}
|
||||
* mapping.</p>
|
||||
* <table>
|
||||
* <thead>
|
||||
* <tr><th>Property</th><th>Type</th><th>Data</th><th>Description</th></tr>
|
||||
* </thead>
|
||||
* <tbody>
|
||||
* <tr><td colspan="4"><strong>Buttons</strong></td></tr>
|
||||
* <tr><td><code>LeftApplicationMenu</code></td><td>number</td><td>number</td><td>Left application menu button pressed.
|
||||
* </td></tr>
|
||||
* <tr><td><code>RightApplicationMenu</code></td><td>number</td><td>number</td><td>Right application menu button pressed.
|
||||
* </td></tr>
|
||||
* <tr><td colspan="4"><strong>Touch Pad (Sticks)</strong></td></tr>
|
||||
* <tr><td><code>LX</code></td><td>number</td><td>number</td><td>Left touch pad x-axis scale.</td></tr>
|
||||
* <tr><td><code>LY</code></td><td>number</td><td>number</td><td>Left touch pad y-axis scale.</td></tr>
|
||||
|
|
|
@ -94,7 +94,7 @@ def parse_args():
|
|||
parser.add_argument('--vcpkg-root', type=str, help='The location of the vcpkg distribution')
|
||||
parser.add_argument('--build-root', required=True, type=str, help='The location of the cmake build')
|
||||
parser.add_argument('--ports-path', type=str, default=defaultPortsPath)
|
||||
parser.add_argument('--ci-build', action='store_true')
|
||||
parser.add_argument('--ci-build', action='store_true', default=os.getenv('CI_BUILD') is not None)
|
||||
if True:
|
||||
args = parser.parse_args()
|
||||
else:
|
||||
|
|
|
@ -203,7 +203,7 @@ function setAwayProperties() {
|
|||
if (!wasMuted) {
|
||||
Audio.muted = !Audio.muted;
|
||||
}
|
||||
MyAvatar.setEnableMeshVisible(false); // just for our own display, without changing point of view
|
||||
MyAvatar.setEnableMeshVisible(false); // just for our own display, without changing point of view
|
||||
playAwayAnimation(); // animation is still seen by others
|
||||
showOverlay();
|
||||
|
||||
|
@ -223,8 +223,8 @@ function setAwayProperties() {
|
|||
|
||||
function setActiveProperties() {
|
||||
isAway = false;
|
||||
if (!wasMuted) {
|
||||
Audio.muted = !Audio.muted;
|
||||
if (Audio.muted && !wasMuted) {
|
||||
Audio.muted = false;
|
||||
}
|
||||
MyAvatar.setEnableMeshVisible(true); // IWBNI we respected Developer->Avatar->Draw Mesh setting.
|
||||
stopAwayAnimation();
|
||||
|
@ -254,7 +254,7 @@ function setActiveProperties() {
|
|||
}
|
||||
|
||||
function maybeGoActive(event) {
|
||||
if (event.isAutoRepeat) { // isAutoRepeat is true when held down (or when Windows feels like it)
|
||||
if (event.isAutoRepeat) { // isAutoRepeat is true when held down (or when Windows feels like it)
|
||||
return;
|
||||
}
|
||||
if (!isAway && (event.text === 'ESC')) {
|
||||
|
@ -314,6 +314,13 @@ function setEnabled(value) {
|
|||
isEnabled = value;
|
||||
}
|
||||
|
||||
function checkAudioToggled() {
|
||||
if (isAway && !Audio.muted) {
|
||||
goActive();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var CHANNEL_AWAY_ENABLE = "Hifi-Away-Enable";
|
||||
var handleMessage = function(channel, message, sender) {
|
||||
if (channel === CHANNEL_AWAY_ENABLE && sender === MyAvatar.sessionUUID) {
|
||||
|
@ -324,9 +331,10 @@ var handleMessage = function(channel, message, sender) {
|
|||
Messages.subscribe(CHANNEL_AWAY_ENABLE);
|
||||
Messages.messageReceived.connect(handleMessage);
|
||||
|
||||
var maybeIntervalTimer = Script.setInterval(function(){
|
||||
var maybeIntervalTimer = Script.setInterval(function() {
|
||||
maybeMoveOverlay();
|
||||
maybeGoAway();
|
||||
checkAudioToggled();
|
||||
}, BASIC_TIMER_INTERVAL);
|
||||
|
||||
|
||||
|
|
7
scripts/system/clickToAvatarApp.js
Normal file
7
scripts/system/clickToAvatarApp.js
Normal file
|
@ -0,0 +1,7 @@
|
|||
(function () {
|
||||
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
|
||||
this.clickDownOnEntity = function (entityID, mouseEvent) {
|
||||
tablet.loadQMLSource("hifi/AvatarApp.qml");
|
||||
};
|
||||
}
|
||||
);
|
36
tools/ci-scripts/hifi_backtrace_post.py
Normal file
36
tools/ci-scripts/hifi_backtrace_post.py
Normal file
|
@ -0,0 +1,36 @@
|
|||
# Parameters:
|
||||
# 1 - $BACKTRACE_UPLOAD_TOKEN
|
||||
# 2 - $SYMBOLS_ARCHIVE
|
||||
# 3 - $RELEASE_NUMBER
|
||||
#
|
||||
import sys
|
||||
import urllib.request
|
||||
import urllib.parse
|
||||
|
||||
print("Running python script to upload to BackTrace")
|
||||
|
||||
post_headers = {}
|
||||
post_headers['Content-Type'] = 'application/json'
|
||||
post_headers['Expect'] = ''
|
||||
|
||||
post_url = 'https://highfidelity.sp.backtrace.io:6098/post?format=symbols&token=' + sys.argv[1] + '&upload_file=' + sys.argv[2] + '&tag=' + sys.argv[3]
|
||||
|
||||
try:
|
||||
post_data = open(sys.argv[2], 'rb')
|
||||
except:
|
||||
print('file ' + sys.argv[2] + ' not found')
|
||||
exit(1)
|
||||
|
||||
try:
|
||||
post_request = urllib.request.Request(post_url, post_data, post_headers)
|
||||
except:
|
||||
print('urllib.request.Request failed')
|
||||
exit(1)
|
||||
|
||||
try:
|
||||
post_response = urllib.request.urlopen(post_request)
|
||||
except:
|
||||
print('urllib.request.urlopen failed')
|
||||
exit(1)
|
||||
|
||||
print("Upload to BackTrace completed without errors")
|
Loading…
Reference in a new issue