mirror of
https://thingvellir.net/git/overte
synced 2025-03-27 23:52:03 +01:00
Merged with master
This commit is contained in:
commit
5e6300ce46
147 changed files with 3611 additions and 977 deletions
|
@ -39,7 +39,6 @@
|
|||
#include "SendAssetTask.h"
|
||||
#include "UploadAssetTask.h"
|
||||
|
||||
|
||||
static const uint8_t MIN_CORES_FOR_MULTICORE = 4;
|
||||
static const uint8_t CPU_AFFINITY_COUNT_HIGH = 2;
|
||||
static const uint8_t CPU_AFFINITY_COUNT_LOW = 1;
|
||||
|
@ -56,7 +55,7 @@ static const QString BAKED_MODEL_SIMPLE_NAME = "asset.fbx";
|
|||
static const QString BAKED_TEXTURE_SIMPLE_NAME = "texture.ktx";
|
||||
static const QString BAKED_SCRIPT_SIMPLE_NAME = "asset.js";
|
||||
|
||||
void AssetServer::bakeAsset(const AssetHash& assetHash, const AssetPath& assetPath, const QString& filePath) {
|
||||
void AssetServer::bakeAsset(const AssetUtils::AssetHash& assetHash, const AssetUtils::AssetPath& assetPath, const QString& filePath) {
|
||||
qDebug() << "Starting bake for: " << assetPath << assetHash;
|
||||
auto it = _pendingBakes.find(assetHash);
|
||||
if (it == _pendingBakes.end()) {
|
||||
|
@ -74,23 +73,23 @@ void AssetServer::bakeAsset(const AssetHash& assetHash, const AssetPath& assetPa
|
|||
}
|
||||
}
|
||||
|
||||
QString AssetServer::getPathToAssetHash(const AssetHash& assetHash) {
|
||||
QString AssetServer::getPathToAssetHash(const AssetUtils::AssetHash& assetHash) {
|
||||
return _filesDirectory.absoluteFilePath(assetHash);
|
||||
}
|
||||
|
||||
std::pair<BakingStatus, QString> AssetServer::getAssetStatus(const AssetPath& path, const AssetHash& hash) {
|
||||
std::pair<AssetUtils::BakingStatus, QString> AssetServer::getAssetStatus(const AssetUtils::AssetPath& path, const AssetUtils::AssetHash& hash) {
|
||||
auto it = _pendingBakes.find(hash);
|
||||
if (it != _pendingBakes.end()) {
|
||||
return { (*it)->isBaking() ? Baking : Pending, "" };
|
||||
return { (*it)->isBaking() ? AssetUtils::Baking : AssetUtils::Pending, "" };
|
||||
}
|
||||
|
||||
if (path.startsWith(HIDDEN_BAKED_CONTENT_FOLDER)) {
|
||||
return { Baked, "" };
|
||||
if (path.startsWith(AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER)) {
|
||||
return { AssetUtils::Baked, "" };
|
||||
}
|
||||
|
||||
auto dotIndex = path.lastIndexOf(".");
|
||||
if (dotIndex == -1) {
|
||||
return { Irrelevant, "" };
|
||||
return { AssetUtils::Irrelevant, "" };
|
||||
}
|
||||
|
||||
auto extension = path.mid(dotIndex + 1);
|
||||
|
@ -104,16 +103,16 @@ std::pair<BakingStatus, QString> AssetServer::getAssetStatus(const AssetPath& pa
|
|||
} else if (BAKEABLE_SCRIPT_EXTENSIONS.contains(extension)) {
|
||||
bakedFilename = BAKED_SCRIPT_SIMPLE_NAME;
|
||||
} else {
|
||||
return { Irrelevant, "" };
|
||||
return { AssetUtils::Irrelevant, "" };
|
||||
}
|
||||
|
||||
auto bakedPath = HIDDEN_BAKED_CONTENT_FOLDER + hash + "/" + bakedFilename;
|
||||
auto bakedPath = AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER + hash + "/" + bakedFilename;
|
||||
auto jt = _fileMappings.find(bakedPath);
|
||||
if (jt != _fileMappings.end()) {
|
||||
if (jt->second == hash) {
|
||||
return { NotBaked, "" };
|
||||
return { AssetUtils::NotBaked, "" };
|
||||
} else {
|
||||
return { Baked, "" };
|
||||
return { AssetUtils::Baked, "" };
|
||||
}
|
||||
} else {
|
||||
bool loaded;
|
||||
|
@ -121,11 +120,11 @@ std::pair<BakingStatus, QString> AssetServer::getAssetStatus(const AssetPath& pa
|
|||
|
||||
std::tie(loaded, meta) = readMetaFile(hash);
|
||||
if (loaded && meta.failedLastBake) {
|
||||
return { Error, meta.lastBakeErrors };
|
||||
return { AssetUtils::Error, meta.lastBakeErrors };
|
||||
}
|
||||
}
|
||||
|
||||
return { Pending, "" };
|
||||
return { AssetUtils::Pending, "" };
|
||||
}
|
||||
|
||||
void AssetServer::bakeAssets() {
|
||||
|
@ -137,14 +136,14 @@ void AssetServer::bakeAssets() {
|
|||
}
|
||||
}
|
||||
|
||||
void AssetServer::maybeBake(const AssetPath& path, const AssetHash& hash) {
|
||||
void AssetServer::maybeBake(const AssetUtils::AssetPath& path, const AssetUtils::AssetHash& hash) {
|
||||
if (needsToBeBaked(path, hash)) {
|
||||
qDebug() << "Queuing bake of: " << path;
|
||||
bakeAsset(hash, path, getPathToAssetHash(hash));
|
||||
}
|
||||
}
|
||||
|
||||
void AssetServer::createEmptyMetaFile(const AssetHash& hash) {
|
||||
void AssetServer::createEmptyMetaFile(const AssetUtils::AssetHash& hash) {
|
||||
QString metaFilePath = "atp:/" + hash + "/meta.json";
|
||||
QFile metaFile { metaFilePath };
|
||||
|
||||
|
@ -157,14 +156,14 @@ void AssetServer::createEmptyMetaFile(const AssetHash& hash) {
|
|||
}
|
||||
}
|
||||
|
||||
bool AssetServer::hasMetaFile(const AssetHash& hash) {
|
||||
QString metaFilePath = HIDDEN_BAKED_CONTENT_FOLDER + hash + "/meta.json";
|
||||
bool AssetServer::hasMetaFile(const AssetUtils::AssetHash& hash) {
|
||||
QString metaFilePath = AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER + hash + "/meta.json";
|
||||
|
||||
return _fileMappings.find(metaFilePath) != _fileMappings.end();
|
||||
}
|
||||
|
||||
bool AssetServer::needsToBeBaked(const AssetPath& path, const AssetHash& assetHash) {
|
||||
if (path.startsWith(HIDDEN_BAKED_CONTENT_FOLDER)) {
|
||||
bool AssetServer::needsToBeBaked(const AssetUtils::AssetPath& path, const AssetUtils::AssetHash& assetHash) {
|
||||
if (path.startsWith(AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -196,7 +195,7 @@ bool AssetServer::needsToBeBaked(const AssetPath& path, const AssetHash& assetHa
|
|||
return false;
|
||||
}
|
||||
|
||||
auto bakedPath = HIDDEN_BAKED_CONTENT_FOLDER + assetHash + "/" + bakedFilename;
|
||||
auto bakedPath = AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER + assetHash + "/" + bakedFilename;
|
||||
return _fileMappings.find(bakedPath) == _fileMappings.end();
|
||||
}
|
||||
|
||||
|
@ -235,7 +234,7 @@ AssetServer::AssetServer(ReceivedMessage& message) :
|
|||
ThreadedAssignment(message),
|
||||
_transferTaskPool(this),
|
||||
_bakingTaskPool(this),
|
||||
_filesizeLimit(MAX_UPLOAD_SIZE)
|
||||
_filesizeLimit(AssetUtils::MAX_UPLOAD_SIZE)
|
||||
{
|
||||
// store the current state of image compression so we can reset it when this assignment is complete
|
||||
_wasColorTextureCompressionEnabled = image::isColorTexturesCompressionEnabled();
|
||||
|
@ -390,7 +389,7 @@ void AssetServer::completeSetup() {
|
|||
// Check the asset directory to output some information about what we have
|
||||
auto files = _filesDirectory.entryList(QDir::Files);
|
||||
|
||||
QRegExp hashFileRegex { ASSET_HASH_REGEX_STRING };
|
||||
QRegExp hashFileRegex { AssetUtils::ASSET_HASH_REGEX_STRING };
|
||||
auto hashedFiles = files.filter(hashFileRegex);
|
||||
|
||||
qCInfo(asset_server) << "There are" << hashedFiles.size() << "asset files in the asset directory.";
|
||||
|
@ -410,9 +409,9 @@ void AssetServer::completeSetup() {
|
|||
// get file size limit for an asset
|
||||
static const QString ASSETS_FILESIZE_LIMIT_OPTION = "assets_filesize_limit";
|
||||
auto assetsFilesizeLimitJSONValue = assetServerObject[ASSETS_FILESIZE_LIMIT_OPTION];
|
||||
auto assetsFilesizeLimit = (uint64_t)assetsFilesizeLimitJSONValue.toInt(MAX_UPLOAD_SIZE);
|
||||
auto assetsFilesizeLimit = (uint64_t)assetsFilesizeLimitJSONValue.toInt(AssetUtils::MAX_UPLOAD_SIZE);
|
||||
|
||||
if (assetsFilesizeLimit != 0 && assetsFilesizeLimit < MAX_UPLOAD_SIZE) {
|
||||
if (assetsFilesizeLimit != 0 && assetsFilesizeLimit < AssetUtils::MAX_UPLOAD_SIZE) {
|
||||
_filesizeLimit = assetsFilesizeLimit * BITS_PER_MEGABITS;
|
||||
}
|
||||
|
||||
|
@ -421,7 +420,7 @@ void AssetServer::completeSetup() {
|
|||
}
|
||||
|
||||
void AssetServer::cleanupUnmappedFiles() {
|
||||
QRegExp hashFileRegex { "^[a-f0-9]{" + QString::number(SHA256_HASH_HEX_LENGTH) + "}" };
|
||||
QRegExp hashFileRegex { "^[a-f0-9]{" + QString::number(AssetUtils::SHA256_HASH_HEX_LENGTH) + "}" };
|
||||
|
||||
auto files = _filesDirectory.entryInfoList(QDir::Files);
|
||||
|
||||
|
@ -454,6 +453,8 @@ void AssetServer::cleanupUnmappedFiles() {
|
|||
}
|
||||
|
||||
void AssetServer::handleAssetMappingOperation(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
|
||||
using AssetMappingOperationType = AssetUtils::AssetMappingOperationType;
|
||||
|
||||
MessageID messageID;
|
||||
message->readPrimitive(&messageID);
|
||||
|
||||
|
@ -519,7 +520,7 @@ void AssetServer::handleGetMappingOperation(ReceivedMessage& message, SharedNode
|
|||
|
||||
if (!bakedRootFile.isEmpty()) {
|
||||
// we ran into an asset for which we could have a baked version, let's check if it's ready
|
||||
bakedAssetPath = HIDDEN_BAKED_CONTENT_FOLDER + originalAssetHash + "/" + bakedRootFile;
|
||||
bakedAssetPath = AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER + originalAssetHash + "/" + bakedRootFile;
|
||||
auto bakedIt = _fileMappings.find(bakedAssetPath);
|
||||
|
||||
if (bakedIt != _fileMappings.end()) {
|
||||
|
@ -537,7 +538,7 @@ void AssetServer::handleGetMappingOperation(ReceivedMessage& message, SharedNode
|
|||
}
|
||||
}
|
||||
|
||||
replyPacket.writePrimitive(AssetServerError::NoError);
|
||||
replyPacket.writePrimitive(AssetUtils::AssetServerError::NoError);
|
||||
|
||||
if (wasRedirected) {
|
||||
qDebug() << "Writing re-directed hash for" << originalAssetHash << "to" << redirectedAssetHash;
|
||||
|
@ -563,12 +564,12 @@ void AssetServer::handleGetMappingOperation(ReceivedMessage& message, SharedNode
|
|||
}
|
||||
}
|
||||
} else {
|
||||
replyPacket.writePrimitive(AssetServerError::AssetNotFound);
|
||||
replyPacket.writePrimitive(AssetUtils::AssetServerError::AssetNotFound);
|
||||
}
|
||||
}
|
||||
|
||||
void AssetServer::handleGetAllMappingOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket) {
|
||||
replyPacket.writePrimitive(AssetServerError::NoError);
|
||||
replyPacket.writePrimitive(AssetUtils::AssetServerError::NoError);
|
||||
|
||||
uint32_t count = (uint32_t)_fileMappings.size();
|
||||
|
||||
|
@ -580,11 +581,11 @@ void AssetServer::handleGetAllMappingOperation(ReceivedMessage& message, SharedN
|
|||
replyPacket.writeString(mapping);
|
||||
replyPacket.write(QByteArray::fromHex(hash.toUtf8()));
|
||||
|
||||
BakingStatus status;
|
||||
AssetUtils::BakingStatus status;
|
||||
QString lastBakeErrors;
|
||||
std::tie(status, lastBakeErrors) = getAssetStatus(mapping, hash);
|
||||
replyPacket.writePrimitive(status);
|
||||
if (status == Error) {
|
||||
if (status == AssetUtils::Error) {
|
||||
replyPacket.writeString(lastBakeErrors);
|
||||
}
|
||||
}
|
||||
|
@ -594,22 +595,22 @@ void AssetServer::handleSetMappingOperation(ReceivedMessage& message, SharedNode
|
|||
if (senderNode->getCanWriteToAssetServer()) {
|
||||
QString assetPath = message.readString();
|
||||
|
||||
auto assetHash = message.read(SHA256_HASH_LENGTH).toHex();
|
||||
auto assetHash = message.read(AssetUtils::SHA256_HASH_LENGTH).toHex();
|
||||
|
||||
// don't process a set mapping operation that is inside the hidden baked folder
|
||||
if (assetPath.startsWith(HIDDEN_BAKED_CONTENT_FOLDER)) {
|
||||
qCDebug(asset_server) << "Refusing to process a set mapping operation inside" << HIDDEN_BAKED_CONTENT_FOLDER;
|
||||
replyPacket.writePrimitive(AssetServerError::PermissionDenied);
|
||||
if (assetPath.startsWith(AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER)) {
|
||||
qCDebug(asset_server) << "Refusing to process a set mapping operation inside" << AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER;
|
||||
replyPacket.writePrimitive(AssetUtils::AssetServerError::PermissionDenied);
|
||||
} else {
|
||||
if (setMapping(assetPath, assetHash)) {
|
||||
replyPacket.writePrimitive(AssetServerError::NoError);
|
||||
replyPacket.writePrimitive(AssetUtils::AssetServerError::NoError);
|
||||
} else {
|
||||
replyPacket.writePrimitive(AssetServerError::MappingOperationFailed);
|
||||
replyPacket.writePrimitive(AssetUtils::AssetServerError::MappingOperationFailed);
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
replyPacket.writePrimitive(AssetServerError::PermissionDenied);
|
||||
replyPacket.writePrimitive(AssetUtils::AssetServerError::PermissionDenied);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -623,21 +624,21 @@ void AssetServer::handleDeleteMappingsOperation(ReceivedMessage& message, Shared
|
|||
for (int i = 0; i < numberOfDeletedMappings; ++i) {
|
||||
auto mapping = message.readString();
|
||||
|
||||
if (!mapping.startsWith(HIDDEN_BAKED_CONTENT_FOLDER)) {
|
||||
if (!mapping.startsWith(AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER)) {
|
||||
mappingsToDelete << mapping;
|
||||
} else {
|
||||
qCDebug(asset_server) << "Refusing to delete mapping" << mapping
|
||||
<< "that is inside" << HIDDEN_BAKED_CONTENT_FOLDER;
|
||||
<< "that is inside" << AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER;
|
||||
}
|
||||
}
|
||||
|
||||
if (deleteMappings(mappingsToDelete)) {
|
||||
replyPacket.writePrimitive(AssetServerError::NoError);
|
||||
replyPacket.writePrimitive(AssetUtils::AssetServerError::NoError);
|
||||
} else {
|
||||
replyPacket.writePrimitive(AssetServerError::MappingOperationFailed);
|
||||
replyPacket.writePrimitive(AssetUtils::AssetServerError::MappingOperationFailed);
|
||||
}
|
||||
} else {
|
||||
replyPacket.writePrimitive(AssetServerError::PermissionDenied);
|
||||
replyPacket.writePrimitive(AssetUtils::AssetServerError::PermissionDenied);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -646,20 +647,20 @@ void AssetServer::handleRenameMappingOperation(ReceivedMessage& message, SharedN
|
|||
QString oldPath = message.readString();
|
||||
QString newPath = message.readString();
|
||||
|
||||
if (oldPath.startsWith(HIDDEN_BAKED_CONTENT_FOLDER) || newPath.startsWith(HIDDEN_BAKED_CONTENT_FOLDER)) {
|
||||
if (oldPath.startsWith(AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER) || newPath.startsWith(AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER)) {
|
||||
qCDebug(asset_server) << "Cannot rename" << oldPath << "to" << newPath
|
||||
<< "since one of the paths is inside" << HIDDEN_BAKED_CONTENT_FOLDER;
|
||||
replyPacket.writePrimitive(AssetServerError::PermissionDenied);
|
||||
<< "since one of the paths is inside" << AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER;
|
||||
replyPacket.writePrimitive(AssetUtils::AssetServerError::PermissionDenied);
|
||||
} else {
|
||||
if (renameMapping(oldPath, newPath)) {
|
||||
replyPacket.writePrimitive(AssetServerError::NoError);
|
||||
replyPacket.writePrimitive(AssetUtils::AssetServerError::NoError);
|
||||
} else {
|
||||
replyPacket.writePrimitive(AssetServerError::MappingOperationFailed);
|
||||
replyPacket.writePrimitive(AssetUtils::AssetServerError::MappingOperationFailed);
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
replyPacket.writePrimitive(AssetServerError::PermissionDenied);
|
||||
replyPacket.writePrimitive(AssetUtils::AssetServerError::PermissionDenied);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -678,12 +679,12 @@ void AssetServer::handleSetBakingEnabledOperation(ReceivedMessage& message, Shar
|
|||
}
|
||||
|
||||
if (setBakingEnabled(mappings, enabled)) {
|
||||
replyPacket.writePrimitive(AssetServerError::NoError);
|
||||
replyPacket.writePrimitive(AssetUtils::AssetServerError::NoError);
|
||||
} else {
|
||||
replyPacket.writePrimitive(AssetServerError::MappingOperationFailed);
|
||||
replyPacket.writePrimitive(AssetUtils::AssetServerError::MappingOperationFailed);
|
||||
}
|
||||
} else {
|
||||
replyPacket.writePrimitive(AssetServerError::PermissionDenied);
|
||||
replyPacket.writePrimitive(AssetUtils::AssetServerError::PermissionDenied);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -691,15 +692,15 @@ void AssetServer::handleAssetGetInfo(QSharedPointer<ReceivedMessage> message, Sh
|
|||
QByteArray assetHash;
|
||||
MessageID messageID;
|
||||
|
||||
if (message->getSize() < qint64(SHA256_HASH_LENGTH + sizeof(messageID))) {
|
||||
if (message->getSize() < qint64(AssetUtils::SHA256_HASH_LENGTH + sizeof(messageID))) {
|
||||
qCDebug(asset_server) << "ERROR bad file request";
|
||||
return;
|
||||
}
|
||||
|
||||
message->readPrimitive(&messageID);
|
||||
assetHash = message->readWithoutCopy(SHA256_HASH_LENGTH);
|
||||
assetHash = message->readWithoutCopy(AssetUtils::SHA256_HASH_LENGTH);
|
||||
|
||||
auto size = qint64(sizeof(MessageID) + SHA256_HASH_LENGTH + sizeof(AssetServerError) + sizeof(qint64));
|
||||
auto size = qint64(sizeof(MessageID) + AssetUtils::SHA256_HASH_LENGTH + sizeof(AssetUtils::AssetServerError) + sizeof(qint64));
|
||||
auto replyPacket = NLPacket::create(PacketType::AssetGetInfoReply, size, true);
|
||||
|
||||
QByteArray hexHash = assetHash.toHex();
|
||||
|
@ -712,11 +713,11 @@ void AssetServer::handleAssetGetInfo(QSharedPointer<ReceivedMessage> message, Sh
|
|||
|
||||
if (fileInfo.exists() && fileInfo.isReadable()) {
|
||||
qCDebug(asset_server) << "Opening file: " << fileInfo.filePath();
|
||||
replyPacket->writePrimitive(AssetServerError::NoError);
|
||||
replyPacket->writePrimitive(AssetUtils::AssetServerError::NoError);
|
||||
replyPacket->writePrimitive(fileInfo.size());
|
||||
} else {
|
||||
qCDebug(asset_server) << "Asset not found: " << QString(hexHash);
|
||||
replyPacket->writePrimitive(AssetServerError::AssetNotFound);
|
||||
replyPacket->writePrimitive(AssetUtils::AssetServerError::AssetNotFound);
|
||||
}
|
||||
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
@ -725,7 +726,7 @@ void AssetServer::handleAssetGetInfo(QSharedPointer<ReceivedMessage> message, Sh
|
|||
|
||||
void AssetServer::handleAssetGet(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
|
||||
|
||||
auto minSize = qint64(sizeof(MessageID) + SHA256_HASH_LENGTH + sizeof(DataOffset) + sizeof(DataOffset));
|
||||
auto minSize = qint64(sizeof(MessageID) + AssetUtils::SHA256_HASH_LENGTH + sizeof(AssetUtils::DataOffset) + sizeof(AssetUtils::DataOffset));
|
||||
|
||||
if (message->getSize() < minSize) {
|
||||
qCDebug(asset_server) << "ERROR bad file request";
|
||||
|
@ -749,14 +750,14 @@ void AssetServer::handleAssetUpload(QSharedPointer<ReceivedMessage> message, Sha
|
|||
// for now this also means it isn't allowed to add assets
|
||||
// so return a packet with error that indicates that
|
||||
|
||||
auto permissionErrorPacket = NLPacket::create(PacketType::AssetUploadReply, sizeof(MessageID) + sizeof(AssetServerError), true);
|
||||
auto permissionErrorPacket = NLPacket::create(PacketType::AssetUploadReply, sizeof(MessageID) + sizeof(AssetUtils::AssetServerError), true);
|
||||
|
||||
MessageID messageID;
|
||||
message->readPrimitive(&messageID);
|
||||
|
||||
// write the message ID and a permission denied error
|
||||
permissionErrorPacket->writePrimitive(messageID);
|
||||
permissionErrorPacket->writePrimitive(AssetServerError::PermissionDenied);
|
||||
permissionErrorPacket->writePrimitive(AssetUtils::AssetServerError::PermissionDenied);
|
||||
|
||||
// send off the packet
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
@ -863,12 +864,12 @@ bool AssetServer::loadMappingsFromFile() {
|
|||
continue;
|
||||
}
|
||||
|
||||
if (!isValidFilePath(key)) {
|
||||
if (!AssetUtils::isValidFilePath(key)) {
|
||||
qCWarning(asset_server) << "Will not keep mapping for" << key << "since it is not a valid path.";
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isValidHash(value.toString())) {
|
||||
if (!AssetUtils::isValidHash(value.toString())) {
|
||||
qCWarning(asset_server) << "Will not keep mapping for" << key << "since it does not have a valid hash.";
|
||||
continue;
|
||||
}
|
||||
|
@ -918,15 +919,15 @@ bool AssetServer::writeMappingsToFile() {
|
|||
return false;
|
||||
}
|
||||
|
||||
bool AssetServer::setMapping(AssetPath path, AssetHash hash) {
|
||||
bool AssetServer::setMapping(AssetUtils::AssetPath path, AssetUtils::AssetHash hash) {
|
||||
path = path.trimmed();
|
||||
|
||||
if (!isValidFilePath(path)) {
|
||||
if (!AssetUtils::isValidFilePath(path)) {
|
||||
qCWarning(asset_server) << "Cannot set a mapping for invalid path:" << path << "=>" << hash;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isValidHash(hash)) {
|
||||
if (!AssetUtils::isValidHash(hash)) {
|
||||
qCWarning(asset_server) << "Cannot set a mapping for invalid hash" << path << "=>" << hash;
|
||||
return false;
|
||||
}
|
||||
|
@ -958,23 +959,23 @@ bool AssetServer::setMapping(AssetPath path, AssetHash hash) {
|
|||
}
|
||||
}
|
||||
|
||||
bool pathIsFolder(const AssetPath& path) {
|
||||
bool pathIsFolder(const AssetUtils::AssetPath& path) {
|
||||
return path.endsWith('/');
|
||||
}
|
||||
|
||||
void AssetServer::removeBakedPathsForDeletedAsset(AssetHash hash) {
|
||||
void AssetServer::removeBakedPathsForDeletedAsset(AssetUtils::AssetHash hash) {
|
||||
// we deleted the file with this hash
|
||||
|
||||
// check if we had baked content for that file that should also now be removed
|
||||
// by calling deleteMappings for the hidden baked content folder for this hash
|
||||
AssetPathList hiddenBakedFolder { HIDDEN_BAKED_CONTENT_FOLDER + hash + "/" };
|
||||
AssetUtils::AssetPathList hiddenBakedFolder { AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER + hash + "/" };
|
||||
|
||||
qCDebug(asset_server) << "Deleting baked content below" << hiddenBakedFolder << "since" << hash << "was deleted";
|
||||
|
||||
deleteMappings(hiddenBakedFolder);
|
||||
}
|
||||
|
||||
bool AssetServer::deleteMappings(const AssetPathList& paths) {
|
||||
bool AssetServer::deleteMappings(const AssetUtils::AssetPathList& paths) {
|
||||
// take a copy of the current mappings in case persistence of these deletes fails
|
||||
auto oldMappings = _fileMappings;
|
||||
|
||||
|
@ -1060,11 +1061,11 @@ bool AssetServer::deleteMappings(const AssetPathList& paths) {
|
|||
}
|
||||
}
|
||||
|
||||
bool AssetServer::renameMapping(AssetPath oldPath, AssetPath newPath) {
|
||||
bool AssetServer::renameMapping(AssetUtils::AssetPath oldPath, AssetUtils::AssetPath newPath) {
|
||||
oldPath = oldPath.trimmed();
|
||||
newPath = newPath.trimmed();
|
||||
|
||||
if (!isValidFilePath(oldPath) || !isValidFilePath(newPath)) {
|
||||
if (!AssetUtils::isValidFilePath(oldPath) || !AssetUtils::isValidFilePath(newPath)) {
|
||||
qCWarning(asset_server) << "Cannot perform rename with invalid paths - both should have leading forward and no ending slashes:"
|
||||
<< oldPath << "=>" << newPath;
|
||||
|
||||
|
@ -1164,8 +1165,8 @@ static const QString BAKED_ASSET_SIMPLE_FBX_NAME = "asset.fbx";
|
|||
static const QString BAKED_ASSET_SIMPLE_TEXTURE_NAME = "texture.ktx";
|
||||
static const QString BAKED_ASSET_SIMPLE_JS_NAME = "asset.js";
|
||||
|
||||
QString getBakeMapping(const AssetHash& hash, const QString& relativeFilePath) {
|
||||
return HIDDEN_BAKED_CONTENT_FOLDER + hash + "/" + relativeFilePath;
|
||||
QString getBakeMapping(const AssetUtils::AssetHash& hash, const QString& relativeFilePath) {
|
||||
return AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER + hash + "/" + relativeFilePath;
|
||||
}
|
||||
|
||||
void AssetServer::handleFailedBake(QString originalAssetHash, QString assetPath, QString errors) {
|
||||
|
@ -1197,7 +1198,7 @@ void AssetServer::handleCompletedBake(QString originalAssetHash, QString origina
|
|||
|
||||
qDebug() << "File path: " << filePath;
|
||||
|
||||
AssetHash bakedFileHash;
|
||||
AssetUtils::AssetHash bakedFileHash;
|
||||
|
||||
if (file.open(QIODevice::ReadOnly)) {
|
||||
QCryptographicHash hasher(QCryptographicHash::Sha256);
|
||||
|
@ -1290,8 +1291,8 @@ static const QString BAKE_VERSION_KEY = "bake_version";
|
|||
static const QString FAILED_LAST_BAKE_KEY = "failed_last_bake";
|
||||
static const QString LAST_BAKE_ERRORS_KEY = "last_bake_errors";
|
||||
|
||||
std::pair<bool, AssetMeta> AssetServer::readMetaFile(AssetHash hash) {
|
||||
auto metaFilePath = HIDDEN_BAKED_CONTENT_FOLDER + hash + "/" + "meta.json";
|
||||
std::pair<bool, AssetMeta> AssetServer::readMetaFile(AssetUtils::AssetHash hash) {
|
||||
auto metaFilePath = AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER + hash + "/" + "meta.json";
|
||||
|
||||
auto it = _fileMappings.find(metaFilePath);
|
||||
if (it == _fileMappings.end()) {
|
||||
|
@ -1335,7 +1336,7 @@ std::pair<bool, AssetMeta> AssetServer::readMetaFile(AssetHash hash) {
|
|||
return { false, {} };
|
||||
}
|
||||
|
||||
bool AssetServer::writeMetaFile(AssetHash originalAssetHash, const AssetMeta& meta) {
|
||||
bool AssetServer::writeMetaFile(AssetUtils::AssetHash originalAssetHash, const AssetMeta& meta) {
|
||||
// construct the JSON that will be in the meta file
|
||||
QJsonObject metaFileObject;
|
||||
|
||||
|
@ -1349,7 +1350,7 @@ bool AssetServer::writeMetaFile(AssetHash originalAssetHash, const AssetMeta& me
|
|||
auto metaFileJSON = metaFileDoc.toJson();
|
||||
|
||||
// get a hash for the contents of the meta-file
|
||||
AssetHash metaFileHash = QCryptographicHash::hash(metaFileJSON, QCryptographicHash::Sha256).toHex();
|
||||
AssetUtils::AssetHash metaFileHash = QCryptographicHash::hash(metaFileJSON, QCryptographicHash::Sha256).toHex();
|
||||
|
||||
// create the meta file in our files folder, named by the hash of its contents
|
||||
QFile metaFile(_filesDirectory.absoluteFilePath(metaFileHash));
|
||||
|
@ -1359,7 +1360,7 @@ bool AssetServer::writeMetaFile(AssetHash originalAssetHash, const AssetMeta& me
|
|||
metaFile.close();
|
||||
|
||||
// add a mapping to the meta file so it doesn't get deleted because it is unmapped
|
||||
auto metaFileMapping = HIDDEN_BAKED_CONTENT_FOLDER + originalAssetHash + "/" + "meta.json";
|
||||
auto metaFileMapping = AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER + originalAssetHash + "/" + "meta.json";
|
||||
|
||||
return setMapping(metaFileMapping, metaFileHash);
|
||||
} else {
|
||||
|
@ -1367,7 +1368,7 @@ bool AssetServer::writeMetaFile(AssetHash originalAssetHash, const AssetMeta& me
|
|||
}
|
||||
}
|
||||
|
||||
bool AssetServer::setBakingEnabled(const AssetPathList& paths, bool enabled) {
|
||||
bool AssetServer::setBakingEnabled(const AssetUtils::AssetPathList& paths, bool enabled) {
|
||||
for (const auto& path : paths) {
|
||||
auto it = _fileMappings.find(path);
|
||||
if (it != _fileMappings.end()) {
|
||||
|
|
|
@ -21,7 +21,6 @@
|
|||
#include "AssetUtils.h"
|
||||
#include "ReceivedMessage.h"
|
||||
|
||||
|
||||
namespace std {
|
||||
template <>
|
||||
struct hash<QString> {
|
||||
|
@ -75,29 +74,29 @@ private:
|
|||
bool writeMappingsToFile();
|
||||
|
||||
/// Set the mapping for path to hash
|
||||
bool setMapping(AssetPath path, AssetHash hash);
|
||||
bool setMapping(AssetUtils::AssetPath path, AssetUtils::AssetHash hash);
|
||||
|
||||
/// Delete mapping `path`. Returns `true` if deletion of mappings succeeds, else `false`.
|
||||
bool deleteMappings(const AssetPathList& paths);
|
||||
bool deleteMappings(const AssetUtils::AssetPathList& paths);
|
||||
|
||||
/// Rename mapping from `oldPath` to `newPath`. Returns true if successful
|
||||
bool renameMapping(AssetPath oldPath, AssetPath newPath);
|
||||
bool renameMapping(AssetUtils::AssetPath oldPath, AssetUtils::AssetPath newPath);
|
||||
|
||||
bool setBakingEnabled(const AssetPathList& paths, bool enabled);
|
||||
bool setBakingEnabled(const AssetUtils::AssetPathList& paths, bool enabled);
|
||||
|
||||
/// Delete any unmapped files from the local asset directory
|
||||
void cleanupUnmappedFiles();
|
||||
|
||||
QString getPathToAssetHash(const AssetHash& assetHash);
|
||||
QString getPathToAssetHash(const AssetUtils::AssetHash& assetHash);
|
||||
|
||||
std::pair<BakingStatus, QString> getAssetStatus(const AssetPath& path, const AssetHash& hash);
|
||||
std::pair<AssetUtils::BakingStatus, QString> getAssetStatus(const AssetUtils::AssetPath& path, const AssetUtils::AssetHash& hash);
|
||||
|
||||
void bakeAssets();
|
||||
void maybeBake(const AssetPath& path, const AssetHash& hash);
|
||||
void createEmptyMetaFile(const AssetHash& hash);
|
||||
bool hasMetaFile(const AssetHash& hash);
|
||||
bool needsToBeBaked(const AssetPath& path, const AssetHash& assetHash);
|
||||
void bakeAsset(const AssetHash& assetHash, const AssetPath& assetPath, const QString& filePath);
|
||||
void maybeBake(const AssetUtils::AssetPath& path, const AssetUtils::AssetHash& hash);
|
||||
void createEmptyMetaFile(const AssetUtils::AssetHash& hash);
|
||||
bool hasMetaFile(const AssetUtils::AssetHash& hash);
|
||||
bool needsToBeBaked(const AssetUtils::AssetPath& path, const AssetUtils::AssetHash& assetHash);
|
||||
void bakeAsset(const AssetUtils::AssetHash& assetHash, const AssetUtils::AssetPath& assetPath, const QString& filePath);
|
||||
|
||||
/// Move baked content for asset to baked directory and update baked status
|
||||
void handleCompletedBake(QString originalAssetHash, QString assetPath, QString bakedTempOutputDir,
|
||||
|
@ -106,11 +105,11 @@ private:
|
|||
void handleAbortedBake(QString originalAssetHash, QString assetPath);
|
||||
|
||||
/// Create meta file to describe baked content for original asset
|
||||
std::pair<bool, AssetMeta> readMetaFile(AssetHash hash);
|
||||
bool writeMetaFile(AssetHash originalAssetHash, const AssetMeta& meta = AssetMeta());
|
||||
std::pair<bool, AssetMeta> readMetaFile(AssetUtils::AssetHash hash);
|
||||
bool writeMetaFile(AssetUtils::AssetHash originalAssetHash, const AssetMeta& meta = AssetMeta());
|
||||
|
||||
/// Remove baked paths when the original asset is deleteds
|
||||
void removeBakedPathsForDeletedAsset(AssetHash originalAssetHash);
|
||||
void removeBakedPathsForDeletedAsset(AssetUtils::AssetHash originalAssetHash);
|
||||
|
||||
Mappings _fileMappings;
|
||||
|
||||
|
@ -120,7 +119,7 @@ private:
|
|||
/// Task pool for handling uploads and downloads of assets
|
||||
QThreadPool _transferTaskPool;
|
||||
|
||||
QHash<AssetHash, std::shared_ptr<BakeAssetTask>> _pendingBakes;
|
||||
QHash<AssetUtils::AssetHash, std::shared_ptr<BakeAssetTask>> _pendingBakes;
|
||||
QThreadPool _bakingTaskPool;
|
||||
|
||||
bool _wasColorTextureCompressionEnabled { false };
|
||||
|
|
|
@ -24,7 +24,7 @@ static const int OVEN_STATUS_CODE_ABORT { 2 };
|
|||
|
||||
std::once_flag registerMetaTypesFlag;
|
||||
|
||||
BakeAssetTask::BakeAssetTask(const AssetHash& assetHash, const AssetPath& assetPath, const QString& filePath) :
|
||||
BakeAssetTask::BakeAssetTask(const AssetUtils::AssetHash& assetHash, const AssetUtils::AssetPath& assetPath, const QString& filePath) :
|
||||
_assetHash(assetHash),
|
||||
_assetPath(assetPath),
|
||||
_filePath(filePath)
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
class BakeAssetTask : public QObject, public QRunnable {
|
||||
Q_OBJECT
|
||||
public:
|
||||
BakeAssetTask(const AssetHash& assetHash, const AssetPath& assetPath, const QString& filePath);
|
||||
BakeAssetTask(const AssetUtils::AssetHash& assetHash, const AssetUtils::AssetPath& assetPath, const QString& filePath);
|
||||
|
||||
bool isBaking() { return _isBaking.load(); }
|
||||
|
||||
|
@ -41,8 +41,8 @@ signals:
|
|||
|
||||
private:
|
||||
std::atomic<bool> _isBaking { false };
|
||||
AssetHash _assetHash;
|
||||
AssetPath _assetPath;
|
||||
AssetUtils::AssetHash _assetHash;
|
||||
AssetUtils::AssetPath _assetPath;
|
||||
QString _filePath;
|
||||
std::unique_ptr<QProcess> _ovenProcess { nullptr };
|
||||
std::atomic<bool> _wasAborted { false };
|
||||
|
|
|
@ -40,7 +40,7 @@ void SendAssetTask::run() {
|
|||
ByteRange byteRange;
|
||||
|
||||
_message->readPrimitive(&messageID);
|
||||
QByteArray assetHash = _message->read(SHA256_HASH_LENGTH);
|
||||
QByteArray assetHash = _message->read(AssetUtils::SHA256_HASH_LENGTH);
|
||||
|
||||
// `start` and `end` indicate the range of data to retrieve for the asset identified by `assetHash`.
|
||||
// `start` is inclusive, `end` is exclusive. Requesting `start` = 1, `end` = 10 will retrieve 9 bytes of data,
|
||||
|
@ -61,7 +61,7 @@ void SendAssetTask::run() {
|
|||
replyPacketList->writePrimitive(messageID);
|
||||
|
||||
if (!byteRange.isValid()) {
|
||||
replyPacketList->writePrimitive(AssetServerError::InvalidByteRange);
|
||||
replyPacketList->writePrimitive(AssetUtils::AssetServerError::InvalidByteRange);
|
||||
} else {
|
||||
QString filePath = _resourcesDir.filePath(QString(hexHash));
|
||||
|
||||
|
@ -75,7 +75,7 @@ void SendAssetTask::run() {
|
|||
// check if we're being asked to read data that we just don't have
|
||||
// because of the file size
|
||||
if (file.size() < byteRange.fromInclusive || file.size() < byteRange.toExclusive) {
|
||||
replyPacketList->writePrimitive(AssetServerError::InvalidByteRange);
|
||||
replyPacketList->writePrimitive(AssetUtils::AssetServerError::InvalidByteRange);
|
||||
qCDebug(networking) << "Bad byte range: " << hexHash << " "
|
||||
<< byteRange.fromInclusive << ":" << byteRange.toExclusive;
|
||||
} else {
|
||||
|
@ -86,7 +86,7 @@ void SendAssetTask::run() {
|
|||
|
||||
// this range is positive, meaning we just need to seek into the file and then read from there
|
||||
file.seek(byteRange.fromInclusive);
|
||||
replyPacketList->writePrimitive(AssetServerError::NoError);
|
||||
replyPacketList->writePrimitive(AssetUtils::AssetServerError::NoError);
|
||||
replyPacketList->writePrimitive(size);
|
||||
replyPacketList->write(file.read(size));
|
||||
} else {
|
||||
|
@ -95,7 +95,7 @@ void SendAssetTask::run() {
|
|||
// seek to the part of the file where the negative range begins
|
||||
file.seek(file.size() + byteRange.fromInclusive);
|
||||
|
||||
replyPacketList->writePrimitive(AssetServerError::NoError);
|
||||
replyPacketList->writePrimitive(AssetUtils::AssetServerError::NoError);
|
||||
replyPacketList->writePrimitive(size);
|
||||
|
||||
// first write everything from the negative range to the end of the file
|
||||
|
@ -107,7 +107,7 @@ void SendAssetTask::run() {
|
|||
file.close();
|
||||
} else {
|
||||
qCDebug(networking) << "Asset not found: " << filePath << "(" << hexHash << ")";
|
||||
replyPacketList->writePrimitive(AssetServerError::AssetNotFound);
|
||||
replyPacketList->writePrimitive(AssetUtils::AssetServerError::AssetNotFound);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -20,7 +20,6 @@
|
|||
|
||||
#include "ClientServerUtils.h"
|
||||
|
||||
|
||||
UploadAssetTask::UploadAssetTask(QSharedPointer<ReceivedMessage> receivedMessage, SharedNodePointer senderNode,
|
||||
const QDir& resourcesDir, uint64_t filesizeLimit) :
|
||||
_receivedMessage(receivedMessage),
|
||||
|
@ -50,11 +49,11 @@ void UploadAssetTask::run() {
|
|||
replyPacket->writePrimitive(messageID);
|
||||
|
||||
if (fileSize > _filesizeLimit) {
|
||||
replyPacket->writePrimitive(AssetServerError::AssetTooLarge);
|
||||
replyPacket->writePrimitive(AssetUtils::AssetServerError::AssetTooLarge);
|
||||
} else {
|
||||
QByteArray fileData = buffer.read(fileSize);
|
||||
|
||||
auto hash = hashData(fileData);
|
||||
auto hash = AssetUtils::hashData(fileData);
|
||||
auto hexHash = hash.toHex();
|
||||
|
||||
qDebug() << "Hash for uploaded file from" << uuidStringWithoutCurlyBraces(_senderNode->getUUID())
|
||||
|
@ -66,12 +65,12 @@ void UploadAssetTask::run() {
|
|||
|
||||
if (file.exists()) {
|
||||
// check if the local file has the correct contents, otherwise we overwrite
|
||||
if (file.open(QIODevice::ReadOnly) && hashData(file.readAll()) == hash) {
|
||||
if (file.open(QIODevice::ReadOnly) && AssetUtils::hashData(file.readAll()) == hash) {
|
||||
qDebug() << "Not overwriting existing verified file: " << hexHash;
|
||||
|
||||
existingCorrectFile = true;
|
||||
|
||||
replyPacket->writePrimitive(AssetServerError::NoError);
|
||||
replyPacket->writePrimitive(AssetUtils::AssetServerError::NoError);
|
||||
replyPacket->write(hash);
|
||||
} else {
|
||||
qDebug() << "Overwriting an existing file whose contents did not match the expected hash: " << hexHash;
|
||||
|
@ -84,7 +83,7 @@ void UploadAssetTask::run() {
|
|||
qDebug() << "Wrote file" << hexHash << "to disk. Upload complete";
|
||||
file.close();
|
||||
|
||||
replyPacket->writePrimitive(AssetServerError::NoError);
|
||||
replyPacket->writePrimitive(AssetUtils::AssetServerError::NoError);
|
||||
replyPacket->write(hash);
|
||||
} else {
|
||||
qWarning() << "Failed to upload or write to file" << hexHash << " - upload failed.";
|
||||
|
@ -96,7 +95,7 @@ void UploadAssetTask::run() {
|
|||
qWarning() << "Removal of failed upload file" << hexHash << "failed.";
|
||||
}
|
||||
|
||||
replyPacket->writePrimitive(AssetServerError::FileOperationFailed);
|
||||
replyPacket->writePrimitive(AssetUtils::AssetServerError::FileOperationFailed);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -17,11 +17,12 @@ ListModel {
|
|||
id: root;
|
||||
property string sortColumnName: "";
|
||||
property bool isSortingDescending: true;
|
||||
property bool valuesAreNumerical: false;
|
||||
|
||||
function swap(a, b) {
|
||||
if (a < b) {
|
||||
move(a, b, 1);
|
||||
move (b - 1, a, 1);
|
||||
move(b - 1, a, 1);
|
||||
} else if (a > b) {
|
||||
move(b, a, 1);
|
||||
move(a - 1, b, 1);
|
||||
|
@ -29,26 +30,53 @@ ListModel {
|
|||
}
|
||||
|
||||
function partition(begin, end, pivot) {
|
||||
var piv = get(pivot)[sortColumnName];
|
||||
swap(pivot, end - 1);
|
||||
var store = begin;
|
||||
if (valuesAreNumerical) {
|
||||
var piv = get(pivot)[sortColumnName];
|
||||
swap(pivot, end - 1);
|
||||
var store = begin;
|
||||
var i;
|
||||
|
||||
for (var i = begin; i < end - 1; ++i) {
|
||||
if (isSortingDescending) {
|
||||
if (get(i)[sortColumnName] < piv) {
|
||||
swap(store, i);
|
||||
++store;
|
||||
}
|
||||
} else {
|
||||
if (get(i)[sortColumnName] > piv) {
|
||||
swap(store, i);
|
||||
++store;
|
||||
for (i = begin; i < end - 1; ++i) {
|
||||
var currentElement = get(i)[sortColumnName];
|
||||
if (isSortingDescending) {
|
||||
if (currentElement > piv) {
|
||||
swap(store, i);
|
||||
++store;
|
||||
}
|
||||
} else {
|
||||
if (currentElement < piv) {
|
||||
swap(store, i);
|
||||
++store;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
swap(end - 1, store);
|
||||
swap(end - 1, store);
|
||||
|
||||
return store;
|
||||
return store;
|
||||
} else {
|
||||
var piv = get(pivot)[sortColumnName].toLowerCase();
|
||||
swap(pivot, end - 1);
|
||||
var store = begin;
|
||||
var i;
|
||||
|
||||
for (i = begin; i < end - 1; ++i) {
|
||||
var currentElement = get(i)[sortColumnName].toLowerCase();
|
||||
if (isSortingDescending) {
|
||||
if (currentElement > piv) {
|
||||
swap(store, i);
|
||||
++store;
|
||||
}
|
||||
} else {
|
||||
if (currentElement < piv) {
|
||||
swap(store, i);
|
||||
++store;
|
||||
}
|
||||
}
|
||||
}
|
||||
swap(end - 1, store);
|
||||
|
||||
return store;
|
||||
}
|
||||
}
|
||||
|
||||
function qsort(begin, end) {
|
||||
|
|
|
@ -317,6 +317,7 @@ Rectangle {
|
|||
|
||||
HifiControlsUit.TextField {
|
||||
id: filterBar;
|
||||
property string previousText: "";
|
||||
colorScheme: hifi.colorSchemes.faintGray;
|
||||
hasClearButton: true;
|
||||
hasRoundedBorder: true;
|
||||
|
@ -329,6 +330,8 @@ Rectangle {
|
|||
|
||||
onTextChanged: {
|
||||
buildFilteredPurchasesModel();
|
||||
purchasesContentsList.positionViewAtIndex(0, ListView.Beginning)
|
||||
filterBar.previousText = filterBar.text;
|
||||
}
|
||||
|
||||
onAccepted: {
|
||||
|
@ -647,7 +650,8 @@ Rectangle {
|
|||
|
||||
function sortByDate() {
|
||||
filteredPurchasesModel.sortColumnName = "purchase_date";
|
||||
filteredPurchasesModel.isSortingDescending = false;
|
||||
filteredPurchasesModel.isSortingDescending = true;
|
||||
filteredPurchasesModel.valuesAreNumerical = true;
|
||||
filteredPurchasesModel.quickSort();
|
||||
}
|
||||
|
||||
|
@ -677,7 +681,7 @@ Rectangle {
|
|||
}
|
||||
}
|
||||
|
||||
if (sameItemCount !== tempPurchasesModel.count) {
|
||||
if (sameItemCount !== tempPurchasesModel.count || filterBar.text !== filterBar.previousText) {
|
||||
filteredPurchasesModel.clear();
|
||||
for (var i = 0; i < tempPurchasesModel.count; i++) {
|
||||
filteredPurchasesModel.append(tempPurchasesModel.get(i));
|
||||
|
|
|
@ -193,7 +193,7 @@ Item {
|
|||
color: hifi.colors.white;
|
||||
}
|
||||
|
||||
// "Change Passphrase" button
|
||||
// "Change Security Pic" button
|
||||
HifiControlsUit.Button {
|
||||
id: changeSecurityImageButton;
|
||||
color: hifi.buttons.blue;
|
||||
|
|
|
@ -34,13 +34,11 @@ Item {
|
|||
securityImageChangePageSecurityImage.source = "image://security/securityImage";
|
||||
if (exists) { // Success submitting new security image
|
||||
if (root.justSubmitted) {
|
||||
root.resetSubmitButton();
|
||||
sendSignalToWallet({method: "walletSecurity_changeSecurityImageSuccess"});
|
||||
root.justSubmitted = false;
|
||||
}
|
||||
} else if (root.justSubmitted) {
|
||||
// Error submitting new security image.
|
||||
root.resetSubmitButton();
|
||||
root.justSubmitted = false;
|
||||
}
|
||||
}
|
||||
|
@ -180,7 +178,8 @@ Item {
|
|||
// "Submit" button
|
||||
HifiControlsUit.Button {
|
||||
id: securityImageSubmitButton;
|
||||
enabled: securityImageSelection.currentIndex !== -1;
|
||||
text: root.justSubmitted ? "Submitting..." : "Submit";
|
||||
enabled: securityImageSelection.currentIndex !== -1 && !root.justSubmitted;
|
||||
color: hifi.buttons.blue;
|
||||
colorScheme: hifi.colorSchemes.dark;
|
||||
anchors.top: parent.top;
|
||||
|
@ -188,11 +187,8 @@ Item {
|
|||
anchors.right: parent.right;
|
||||
anchors.rightMargin: 20;
|
||||
width: 150;
|
||||
text: "Submit";
|
||||
onClicked: {
|
||||
root.justSubmitted = true;
|
||||
securityImageSubmitButton.text = "Submitting...";
|
||||
securityImageSubmitButton.enabled = false;
|
||||
var securityImagePath = securityImageSelection.getImagePathFromImageID(securityImageSelection.getSelectedImageIndex())
|
||||
Commerce.chooseSecurityImage(securityImagePath);
|
||||
}
|
||||
|
@ -205,11 +201,6 @@ Item {
|
|||
|
||||
signal sendSignalToWallet(var msg);
|
||||
|
||||
function resetSubmitButton() {
|
||||
securityImageSubmitButton.enabled = true;
|
||||
securityImageSubmitButton.text = "Submit";
|
||||
}
|
||||
|
||||
function initModel() {
|
||||
securityImageSelection.initModel();
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ Item {
|
|||
HifiConstants { id: hifi; }
|
||||
|
||||
id: root;
|
||||
property int currentIndex: securityImageGrid.currentIndex;
|
||||
property alias currentIndex: securityImageGrid.currentIndex;
|
||||
|
||||
// This will cause a bug -- if you bring up security image selection in HUD mode while
|
||||
// in HMD while having HMD preview enabled, then move, then finish passphrase selection,
|
||||
|
@ -98,6 +98,11 @@ Item {
|
|||
|
||||
function initModel() {
|
||||
gridModel.initModel();
|
||||
securityImageGrid.currentIndex = -1;
|
||||
}
|
||||
|
||||
function resetSelection() {
|
||||
securityImageGrid.currentIndex = -1;
|
||||
}
|
||||
//
|
||||
// FUNCTION DEFINITIONS END
|
||||
|
|
|
@ -348,6 +348,7 @@ Item {
|
|||
width: 200;
|
||||
text: "Back"
|
||||
onClicked: {
|
||||
securityImageSelection.resetSelection();
|
||||
root.activeView = "step_1";
|
||||
}
|
||||
}
|
||||
|
@ -516,6 +517,7 @@ Item {
|
|||
width: 200;
|
||||
text: "Back"
|
||||
onClicked: {
|
||||
securityImageSelection.resetSelection();
|
||||
root.lastPage = "step_3";
|
||||
root.activeView = "step_2";
|
||||
}
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
//
|
||||
// AdvancedPreferencesDialog.qml
|
||||
//
|
||||
// Created by Brad Hefta-Gaub on 20 Jan 2018
|
||||
// Copyright 2018 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
import QtQuick 2.5
|
||||
import Qt.labs.settings 1.0
|
||||
|
||||
import "../../dialogs"
|
||||
|
||||
PreferencesDialog {
|
||||
id: root
|
||||
objectName: "AdvancedPreferencesDialog"
|
||||
title: "Advanced Settings"
|
||||
showCategories: ["Advanced UI" ]
|
||||
property var settings: Settings {
|
||||
category: root.objectName
|
||||
property alias x: root.x
|
||||
property alias y: root.y
|
||||
property alias width: root.width
|
||||
property alias height: root.height
|
||||
}
|
||||
}
|
||||
|
|
@ -17,7 +17,7 @@ PreferencesDialog {
|
|||
id: root
|
||||
objectName: "GeneralPreferencesDialog"
|
||||
title: "General Settings"
|
||||
showCategories: ["UI", "Snapshots", "Scripts", "Privacy", "Octree", "HMD", "Game Controller", "Sixense Controllers", "Perception Neuron", "Kinect", "Leap Motion"]
|
||||
showCategories: ["UI", "Snapshots", "Privacy", "HMD", "Game Controller", "Sixense Controllers", "Perception Neuron", "Kinect", "Leap Motion"]
|
||||
property var settings: Settings {
|
||||
category: root.objectName
|
||||
property alias x: root.x
|
||||
|
|
|
@ -41,14 +41,14 @@ QSpinBox, QDoubleSpinBox {
|
|||
|
||||
QDoubleSpinBox::up-arrow,
|
||||
QSpinBox::up-arrow {
|
||||
background-image: url(styles/up.svg);
|
||||
background-image: url(:/styles/up.svg);
|
||||
background-repeat: no-repeat;
|
||||
background-position: center center;
|
||||
}
|
||||
|
||||
QDoubleSpinBox::down-arrow,
|
||||
QSpinBox::down-arrow {
|
||||
background-image: url(styles/down.svg);
|
||||
background-image: url(:/styles/down.svg);
|
||||
background-repeat: no-repeat;
|
||||
background-position: center center;
|
||||
}
|
||||
|
@ -88,7 +88,7 @@ QSlider {
|
|||
|
||||
QSlider::groove:horizontal {
|
||||
border: none;
|
||||
background-image: url(styles/slider-bg.svg);
|
||||
background-image: url(:/styles/slider-bg.svg);
|
||||
background-repeat: no-repeat;
|
||||
background-position: center center;
|
||||
}
|
||||
|
@ -96,7 +96,7 @@ QSlider::groove:horizontal {
|
|||
QSlider::handle:horizontal {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
background-image: url(styles/slider-handle.svg);
|
||||
background-image: url(:/styles/slider-handle.svg);
|
||||
background-repeat: no-repeat;
|
||||
background-position: center center;
|
||||
}
|
||||
|
@ -107,7 +107,7 @@ QPushButton#closeButton {
|
|||
border-width: 1px;
|
||||
border-radius: 0;
|
||||
background-color: #fff;
|
||||
background-image: url(styles/close.svg);
|
||||
background-image: url(:/styles/close.svg);
|
||||
background-repeat: no-repeat;
|
||||
background-position: center center;
|
||||
}
|
||||
|
|
|
@ -63,17 +63,17 @@ QPushButton#cancelButton {
|
|||
}
|
||||
|
||||
#backButton {
|
||||
background-image: url(icons/backButton.svg);
|
||||
background-image: url(:/icons/backButton.svg);
|
||||
border-radius: 0px;
|
||||
}
|
||||
|
||||
#forwardButton {
|
||||
background-image: url(icons/forwardButton.svg);
|
||||
background-image: url(:/icons/forwardButton.svg);
|
||||
border-radius: 0px;
|
||||
}
|
||||
|
||||
#toParentButton {
|
||||
background-image: url(icons/toParentButton.svg);
|
||||
background-image: url(:/icons/toParentButton.svg);
|
||||
border-radius: 0px;
|
||||
}
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ QLineEdit {
|
|||
}
|
||||
|
||||
QPushButton#searchButton {
|
||||
background: url(styles/search.svg);
|
||||
background: url(:/styles/search.svg);
|
||||
background-repeat: none;
|
||||
background-position: left center;
|
||||
background-origin: content;
|
||||
|
@ -55,7 +55,7 @@ QPushButton#searchPrevButton {
|
|||
|
||||
QPushButton#revealLogButton {
|
||||
font-family: Helvetica, Arial, sans-serif;
|
||||
background: url(styles/txt-file.svg);
|
||||
background: url(:/styles/txt-file.svg);
|
||||
background-repeat: none;
|
||||
background-position: left center;
|
||||
background-origin: content;
|
||||
|
@ -86,11 +86,11 @@ QCheckBox {
|
|||
}
|
||||
|
||||
QCheckBox::indicator:unchecked {
|
||||
image: url(styles/unchecked.svg);
|
||||
image: url(:/styles/unchecked.svg);
|
||||
}
|
||||
|
||||
QCheckBox::indicator:checked {
|
||||
image: url(styles/checked.svg);
|
||||
image: url(:/styles/checked.svg);
|
||||
}
|
||||
|
||||
QComboBox {
|
||||
|
@ -110,6 +110,6 @@ QComboBox::drop-down {
|
|||
}
|
||||
|
||||
QComboBox::down-arrow {
|
||||
image: url(styles/filter.png);
|
||||
image: url(:/styles/filter.png);
|
||||
border-width: 0px;
|
||||
}
|
|
@ -68,6 +68,7 @@
|
|||
#include <Midi.h>
|
||||
#include <AudioInjectorManager.h>
|
||||
#include <AvatarBookmarks.h>
|
||||
#include <AvatarEntitiesBookmarks.h>
|
||||
#include <CursorManager.h>
|
||||
#include <DebugDraw.h>
|
||||
#include <DeferredLightingEffect.h>
|
||||
|
@ -351,7 +352,7 @@ static const float MIRROR_FULLSCREEN_DISTANCE = 0.389f;
|
|||
static const quint64 TOO_LONG_SINCE_LAST_SEND_DOWNSTREAM_AUDIO_STATS = 1 * USECS_PER_SECOND;
|
||||
|
||||
static const QString INFO_EDIT_ENTITIES_PATH = "html/edit-commands.html";
|
||||
static const QString INFO_HELP_PATH = "../../../html/tabletHelp.html";
|
||||
static const QString INFO_HELP_PATH = "html/tabletHelp.html";
|
||||
|
||||
static const unsigned int THROTTLED_SIM_FRAMERATE = 15;
|
||||
static const int THROTTLED_SIM_FRAME_PERIOD_MS = MSECS_PER_SECOND / THROTTLED_SIM_FRAMERATE;
|
||||
|
@ -763,6 +764,7 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) {
|
|||
DependencyManager::set<GooglePolyScriptingInterface>();
|
||||
DependencyManager::set<OctreeStatsProvider>(nullptr, qApp->getOcteeSceneStats());
|
||||
DependencyManager::set<AvatarBookmarks>();
|
||||
DependencyManager::set<AvatarEntitiesBookmarks>();
|
||||
DependencyManager::set<LocationBookmarks>();
|
||||
DependencyManager::set<Snapshot>();
|
||||
DependencyManager::set<CloseEventSender>();
|
||||
|
@ -1436,8 +1438,9 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
userInputMapper->registerDevice(_touchscreenDevice->getInputDevice());
|
||||
}
|
||||
|
||||
// force the model the look at the correct directory (weird order of operations issue)
|
||||
scriptEngines->setScriptsLocation(scriptEngines->getScriptsLocation());
|
||||
// this will force the model the look at the correct directory (weird order of operations issue)
|
||||
scriptEngines->reloadLocalFiles();
|
||||
|
||||
// do this as late as possible so that all required subsystems are initialized
|
||||
// If we've overridden the default scripts location, just load default scripts
|
||||
// otherwise, load 'em all
|
||||
|
@ -1905,7 +1908,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
entityResult.distance = pickResult->distance;
|
||||
entityResult.surfaceNormal = pickResult->surfaceNormal;
|
||||
entityResult.entityID = pickResult->objectID;
|
||||
entityResult.entity = DependencyManager::get<EntityTreeRenderer>()->getTree()->findEntityByID(entityResult.entityID);
|
||||
entityResult.extraInfo = pickResult->extraInfo;
|
||||
}
|
||||
}
|
||||
return entityResult;
|
||||
|
@ -2442,6 +2445,7 @@ void Application::initializeUi() {
|
|||
surfaceContext->setContextProperty("Settings", SettingsScriptingInterface::getInstance());
|
||||
surfaceContext->setContextProperty("ScriptDiscoveryService", DependencyManager::get<ScriptEngines>().data());
|
||||
surfaceContext->setContextProperty("AvatarBookmarks", DependencyManager::get<AvatarBookmarks>().data());
|
||||
surfaceContext->setContextProperty("AvatarEntitiesBookmarks", DependencyManager::get<AvatarEntitiesBookmarks>().data());
|
||||
surfaceContext->setContextProperty("LocationBookmarks", DependencyManager::get<LocationBookmarks>().data());
|
||||
|
||||
// Caches
|
||||
|
@ -2723,7 +2727,8 @@ void Application::showHelp() {
|
|||
queryString.addQueryItem("defaultTab", defaultTab);
|
||||
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
|
||||
TabletProxy* tablet = dynamic_cast<TabletProxy*>(tabletScriptingInterface->getTablet(SYSTEM_TABLET));
|
||||
tablet->gotoWebScreen(INFO_HELP_PATH + "?" + queryString.toString());
|
||||
tablet->gotoWebScreen(PathUtils::resourcesUrl() + INFO_HELP_PATH + "?" + queryString.toString());
|
||||
DependencyManager::get<HMDScriptingInterface>()->openTablet();
|
||||
//InfoView::show(INFO_HELP_PATH, false, queryString.toString());
|
||||
}
|
||||
|
||||
|
@ -5849,6 +5854,7 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointe
|
|||
scriptEngine->registerGlobalObject("AudioStats", DependencyManager::get<AudioClient>()->getStats().data());
|
||||
scriptEngine->registerGlobalObject("AudioScope", DependencyManager::get<AudioScope>().data());
|
||||
scriptEngine->registerGlobalObject("AvatarBookmarks", DependencyManager::get<AvatarBookmarks>().data());
|
||||
scriptEngine->registerGlobalObject("AvatarEntitiesBookmarks", DependencyManager::get<AvatarEntitiesBookmarks>().data());
|
||||
scriptEngine->registerGlobalObject("LocationBookmarks", DependencyManager::get<LocationBookmarks>().data());
|
||||
|
||||
scriptEngine->registerGlobalObject("RayPick", DependencyManager::get<RayPickScriptingInterface>().data());
|
||||
|
|
168
interface/src/AvatarEntitiesBookmarks.cpp
Normal file
168
interface/src/AvatarEntitiesBookmarks.cpp
Normal file
|
@ -0,0 +1,168 @@
|
|||
//
|
||||
// AvatarEntitiesBookmarks.cpp
|
||||
// interface/src
|
||||
//
|
||||
// Created by Dante Ruiz on 15/01/18.
|
||||
// Copyright 2018 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <QAction>
|
||||
#include <QInputDialog>
|
||||
#include <QMessageBox>
|
||||
#include <QStandardPaths>
|
||||
#include <QQmlContext>
|
||||
#include <QList>
|
||||
|
||||
#include <Application.h>
|
||||
#include <OffscreenUi.h>
|
||||
#include <EntityItemProperties.h>
|
||||
#include <GLMHelpers.h>
|
||||
#include <avatar/AvatarManager.h>
|
||||
#include <EntityItemID.h>
|
||||
#include <EntityTree.h>
|
||||
#include <PhysicalEntitySimulation.h>
|
||||
#include <EntityEditPacketSender.h>
|
||||
#include <VariantMapToScriptValue.h>
|
||||
|
||||
#include "MainWindow.h"
|
||||
#include "Menu.h"
|
||||
#include "AvatarEntitiesBookmarks.h"
|
||||
#include "InterfaceLogging.h"
|
||||
|
||||
#include "QVariantGLM.h"
|
||||
|
||||
#include <QtQuick/QQuickWindow>
|
||||
|
||||
void addAvatarEntities(const QVariantList& avatarEntities) {
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
const QUuid myNodeID = nodeList->getSessionUUID();
|
||||
EntityTreePointer entityTree = DependencyManager::get<EntityTreeRenderer>()->getTree();
|
||||
if (!entityTree) {
|
||||
return;
|
||||
}
|
||||
EntitySimulationPointer entitySimulation = entityTree->getSimulation();
|
||||
PhysicalEntitySimulationPointer physicalEntitySimulation = std::static_pointer_cast<PhysicalEntitySimulation>(entitySimulation);
|
||||
EntityEditPacketSender* entityPacketSender = physicalEntitySimulation->getPacketSender();
|
||||
QScriptEngine scriptEngine;
|
||||
for (int index = 0; index < avatarEntities.count(); index++) {
|
||||
const QVariantMap& avatarEntityProperties = avatarEntities.at(index).toMap();
|
||||
QVariant variantProperties = avatarEntityProperties["properties"];
|
||||
QVariantMap asMap = variantProperties.toMap();
|
||||
QScriptValue scriptProperties = variantMapToScriptValue(asMap, scriptEngine);
|
||||
EntityItemProperties entityProperties;
|
||||
EntityItemPropertiesFromScriptValueHonorReadOnly(scriptProperties, entityProperties);
|
||||
|
||||
entityProperties.setParentID(myNodeID);
|
||||
entityProperties.setClientOnly(true);
|
||||
entityProperties.setOwningAvatarID(myNodeID);
|
||||
entityProperties.setSimulationOwner(myNodeID, AVATAR_ENTITY_SIMULATION_PRIORITY);
|
||||
entityProperties.markAllChanged();
|
||||
|
||||
EntityItemID id = EntityItemID(QUuid::createUuid());
|
||||
bool success = true;
|
||||
entityTree->withWriteLock([&] {
|
||||
EntityItemPointer entity = entityTree->addEntity(id, entityProperties);
|
||||
if (entity) {
|
||||
if (entityProperties.queryAACubeRelatedPropertyChanged()) {
|
||||
// due to parenting, the server may not know where something is in world-space, so include the bounding cube.
|
||||
bool success;
|
||||
AACube queryAACube = entity->getQueryAACube(success);
|
||||
if (success) {
|
||||
entityProperties.setQueryAACube(queryAACube);
|
||||
}
|
||||
}
|
||||
|
||||
entity->setLastBroadcast(usecTimestampNow());
|
||||
// since we're creating this object we will immediately volunteer to own its simulation
|
||||
entity->flagForOwnershipBid(VOLUNTEER_SIMULATION_PRIORITY);
|
||||
entityProperties.setLastEdited(entity->getLastEdited());
|
||||
} else {
|
||||
qCDebug(entities) << "AvatarEntitiesBookmark failed to add new Entity to local Octree";
|
||||
success = false;
|
||||
}
|
||||
});
|
||||
|
||||
if (success) {
|
||||
entityPacketSender->queueEditEntityMessage(PacketType::EntityAdd, entityTree, id, entityProperties);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AvatarEntitiesBookmarks::AvatarEntitiesBookmarks() {
|
||||
_bookmarksFilename = PathUtils::getAppDataPath() + "/" + AVATAR_ENTITIES_BOOKMARKS_FILENAME;
|
||||
Bookmarks::readFromFile();
|
||||
}
|
||||
|
||||
void AvatarEntitiesBookmarks::setupMenus(Menu* menubar, MenuWrapper* menu) {
|
||||
auto bookmarkAction = menubar->addActionToQMenuAndActionHash(menu, MenuOption::BookmarkAvatarEntities);
|
||||
QObject::connect(bookmarkAction, SIGNAL(triggered()), this, SLOT(addBookmark()), Qt::QueuedConnection);
|
||||
_bookmarksMenu = menu->addMenu(MenuOption::AvatarEntitiesBookmarks);
|
||||
_deleteBookmarksAction = menubar->addActionToQMenuAndActionHash(menu, MenuOption::DeleteAvatarEntitiesBookmark);
|
||||
QObject::connect(_deleteBookmarksAction, SIGNAL(triggered()), this, SLOT(deleteBookmark()), Qt::QueuedConnection);
|
||||
|
||||
for (auto it = _bookmarks.begin(); it != _bookmarks.end(); ++it) {
|
||||
addBookmarkToMenu(menubar, it.key(), it.value());
|
||||
}
|
||||
|
||||
Bookmarks::sortActions(menubar, _bookmarksMenu);
|
||||
}
|
||||
|
||||
void AvatarEntitiesBookmarks::applyBookmarkedAvatarEntities() {
|
||||
QAction* action = qobject_cast<QAction*>(sender());
|
||||
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
|
||||
|
||||
const QMap<QString, QVariant> bookmark = action->data().toMap();
|
||||
|
||||
if (bookmark.value(ENTRY_VERSION) == AVATAR_ENTITIES_BOOKMARK_VERSION) {
|
||||
myAvatar->removeAvatarEntities();
|
||||
const QString& avatarUrl = bookmark.value(ENTRY_AVATAR_URL, "").toString();
|
||||
myAvatar->useFullAvatarURL(avatarUrl);
|
||||
const QVariantList& avatarEntities = bookmark.value(ENTRY_AVATAR_ENTITIES, QVariantList()).toList();
|
||||
addAvatarEntities(avatarEntities);
|
||||
const float& avatarScale = bookmark.value(ENTRY_AVATAR_SCALE, 1.0f).toFloat();
|
||||
myAvatar->setAvatarScale(avatarScale);
|
||||
} else {
|
||||
qCDebug(interfaceapp) << " Bookmark entry does not match client version, make sure client has a handler for the new AvatarEntitiesBookmark";
|
||||
}
|
||||
}
|
||||
|
||||
void AvatarEntitiesBookmarks::addBookmark() {
|
||||
ModalDialogListener* dlg = OffscreenUi::getTextAsync(OffscreenUi::ICON_PLACEMARK, "Bookmark Avatar Entities", "Name", QString());
|
||||
connect(dlg, &ModalDialogListener::response, this, [=] (QVariant response) {
|
||||
disconnect(dlg, &ModalDialogListener::response, this, nullptr);
|
||||
auto bookmarkName = response.toString();
|
||||
bookmarkName = bookmarkName.trimmed().replace(QRegExp("(\r\n|[\r\n\t\v ])+"), " ");
|
||||
if (bookmarkName.length() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
|
||||
|
||||
const QString& avatarUrl = myAvatar->getSkeletonModelURL().toString();
|
||||
const QVariant& avatarScale = myAvatar->getAvatarScale();
|
||||
|
||||
QVariantMap *bookmark = new QVariantMap;
|
||||
bookmark->insert(ENTRY_VERSION, AVATAR_ENTITIES_BOOKMARK_VERSION);
|
||||
bookmark->insert(ENTRY_AVATAR_URL, avatarUrl);
|
||||
bookmark->insert(ENTRY_AVATAR_SCALE, avatarScale);
|
||||
bookmark->insert(ENTRY_AVATAR_ENTITIES, myAvatar->getAvatarEntitiesVariant());
|
||||
|
||||
Bookmarks::addBookmarkToFile(bookmarkName, *bookmark);
|
||||
});
|
||||
}
|
||||
|
||||
void AvatarEntitiesBookmarks::addBookmarkToMenu(Menu* menubar, const QString& name, const QVariant& bookmark) {
|
||||
QAction* changeAction = _bookmarksMenu->newAction();
|
||||
changeAction->setData(bookmark);
|
||||
connect(changeAction, SIGNAL(triggered()), this, SLOT(applyBookmarkedAvatarEntities()));
|
||||
if (!_isMenuSorted) {
|
||||
menubar->addActionToQMenuAndActionHash(_bookmarksMenu, changeAction, name, 0, QAction::NoRole);
|
||||
} else {
|
||||
// TODO: this is aggressive but other alternatives have proved less fruitful so far.
|
||||
menubar->addActionToQMenuAndActionHash(_bookmarksMenu, changeAction, name, 0, QAction::NoRole);
|
||||
Bookmarks::sortActions(menubar, _bookmarksMenu);
|
||||
}
|
||||
}
|
45
interface/src/AvatarEntitiesBookmarks.h
Normal file
45
interface/src/AvatarEntitiesBookmarks.h
Normal file
|
@ -0,0 +1,45 @@
|
|||
//
|
||||
// AvatarEntitiesBookmarks.h
|
||||
// interface/src
|
||||
//
|
||||
// Created by Dante Ruiz on 15/01/18.
|
||||
// Copyright 2018 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_AvatarEntitiesBookmarks_h
|
||||
#define hifi_AvatarEntitiesBookmarks_h
|
||||
|
||||
#include <DependencyManager.h>
|
||||
#include "Bookmarks.h"
|
||||
|
||||
class AvatarEntitiesBookmarks: public Bookmarks, public Dependency {
|
||||
Q_OBJECT
|
||||
SINGLETON_DEPENDENCY
|
||||
|
||||
public:
|
||||
AvatarEntitiesBookmarks();
|
||||
void setupMenus(Menu* menubar, MenuWrapper* menu) override;
|
||||
|
||||
public slots:
|
||||
void addBookmark();
|
||||
|
||||
protected:
|
||||
void addBookmarkToMenu(Menu* menubar, const QString& name, const QVariant& bookmark) override;
|
||||
|
||||
private:
|
||||
const QString AVATAR_ENTITIES_BOOKMARKS_FILENAME = "AvatarEntitiesBookmarks.json";
|
||||
const QString ENTRY_AVATAR_URL = "AvatarUrl";
|
||||
const QString ENTRY_AVATAR_SCALE = "AvatarScale";
|
||||
const QString ENTRY_AVATAR_ENTITIES = "AvatarEntities";
|
||||
const QString ENTRY_VERSION = "version";
|
||||
|
||||
const int AVATAR_ENTITIES_BOOKMARK_VERSION = 1;
|
||||
|
||||
private slots:
|
||||
void applyBookmarkedAvatarEntities();
|
||||
};
|
||||
|
||||
#endif
|
|
@ -34,6 +34,7 @@
|
|||
#include "audio/AudioScope.h"
|
||||
#include "avatar/AvatarManager.h"
|
||||
#include "AvatarBookmarks.h"
|
||||
#include "AvatarEntitiesBookmarks.h"
|
||||
#include "devices/DdeFaceTracker.h"
|
||||
#include "MainWindow.h"
|
||||
#include "render/DrawStatus.h"
|
||||
|
@ -206,6 +207,9 @@ Menu::Menu() {
|
|||
auto avatarBookmarks = DependencyManager::get<AvatarBookmarks>();
|
||||
avatarBookmarks->setupMenus(this, avatarMenu);
|
||||
|
||||
auto avatarEntitiesBookmarks = DependencyManager::get<AvatarEntitiesBookmarks>();
|
||||
avatarEntitiesBookmarks->setupMenus(this, avatarMenu);
|
||||
|
||||
// Display menu ----------------------------------
|
||||
// FIXME - this is not yet matching Alan's spec because it doesn't have
|
||||
// menus for "2D"/"3D" - we need to add support for detecting the appropriate
|
||||
|
@ -756,6 +760,13 @@ Menu::Menu() {
|
|||
// Developer > Stats
|
||||
addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::Stats);
|
||||
|
||||
// Developer > Advanced Settings...
|
||||
action = addActionToQMenuAndActionHash(developerMenu, "Advanced Preferences...");
|
||||
connect(action, &QAction::triggered, [] {
|
||||
qApp->showDialog(QString("hifi/dialogs/AdvancedPreferencesDialog.qml"),
|
||||
QString("hifi/tablet/AdvancedPreferencesDialog.qml"), "AdvancedPreferencesDialog");
|
||||
});
|
||||
|
||||
// Developer > API Debugger
|
||||
action = addActionToQMenuAndActionHash(developerMenu, "API Debugger");
|
||||
connect(action, &QAction::triggered, [] {
|
||||
|
|
|
@ -46,9 +46,11 @@ namespace MenuOption {
|
|||
const QString AutoMuteAudio = "Auto Mute Microphone";
|
||||
const QString AvatarReceiveStats = "Show Receive Stats";
|
||||
const QString AvatarBookmarks = "Avatar Bookmarks";
|
||||
const QString AvatarEntitiesBookmarks = "Avatar Entities Bookmarks";
|
||||
const QString Back = "Back";
|
||||
const QString BinaryEyelidControl = "Binary Eyelid Control";
|
||||
const QString BookmarkAvatar = "Bookmark Avatar";
|
||||
const QString BookmarkAvatarEntities = "Bookmark Avatar Entities";
|
||||
const QString BookmarkLocation = "Bookmark Location";
|
||||
const QString CalibrateCamera = "Calibrate Camera";
|
||||
const QString CameraEntityMode = "Entity Mode";
|
||||
|
@ -78,6 +80,7 @@ namespace MenuOption {
|
|||
const QString DecreaseAvatarSize = "Decrease Avatar Size";
|
||||
const QString DefaultSkybox = "Default Skybox";
|
||||
const QString DeleteAvatarBookmark = "Delete Avatar Bookmark...";
|
||||
const QString DeleteAvatarEntitiesBookmark = "Delete Avatar Entities Bookmark";
|
||||
const QString DeleteBookmark = "Delete Bookmark...";
|
||||
const QString DisableActivityLogger = "Disable Activity Logger";
|
||||
const QString DisableEyelidAdjustment = "Disable Eyelid Adjustment";
|
||||
|
|
|
@ -546,7 +546,7 @@ RayToAvatarIntersectionResult AvatarManager::findRayIntersectionVector(const Pic
|
|||
continue;
|
||||
}
|
||||
|
||||
QString extraInfo;
|
||||
QVariantMap extraInfo;
|
||||
intersects = avatarModel->findRayIntersectionAgainstSubMeshes(ray.origin, normDirection,
|
||||
distance, face, surfaceNormal, extraInfo, true);
|
||||
|
||||
|
@ -554,6 +554,7 @@ RayToAvatarIntersectionResult AvatarManager::findRayIntersectionVector(const Pic
|
|||
result.intersects = true;
|
||||
result.avatarID = avatar->getID();
|
||||
result.distance = distance;
|
||||
result.extraInfo = extraInfo;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -35,6 +35,8 @@
|
|||
#include <PerfStat.h>
|
||||
#include <SharedUtil.h>
|
||||
#include <SoundCache.h>
|
||||
#include <ModelEntityItem.h>
|
||||
#include <GLMHelpers.h>
|
||||
#include <TextRenderer3D.h>
|
||||
#include <UserActivityLogger.h>
|
||||
#include <AnimDebugDraw.h>
|
||||
|
@ -561,6 +563,12 @@ void MyAvatar::simulate(float deltaTime) {
|
|||
if (!_skeletonModel->getHeadPosition(headPosition)) {
|
||||
headPosition = getWorldPosition();
|
||||
}
|
||||
|
||||
if (isNaN(headPosition)) {
|
||||
qCDebug(interfaceapp) << "MyAvatar::simulate headPosition is NaN";
|
||||
headPosition = glm::vec3(0.0f);
|
||||
}
|
||||
|
||||
head->setPosition(headPosition);
|
||||
head->setScale(getModelScale());
|
||||
head->simulate(deltaTime);
|
||||
|
@ -1415,6 +1423,37 @@ void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) {
|
|||
|
||||
}
|
||||
|
||||
void MyAvatar::removeAvatarEntities() {
|
||||
auto treeRenderer = DependencyManager::get<EntityTreeRenderer>();
|
||||
EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr;
|
||||
if (entityTree) {
|
||||
entityTree->withWriteLock([&] {
|
||||
AvatarEntityMap avatarEntities = getAvatarEntityData();
|
||||
for (auto entityID : avatarEntities.keys()) {
|
||||
entityTree->deleteEntity(entityID, true, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
QVariantList MyAvatar::getAvatarEntitiesVariant() {
|
||||
QVariantList avatarEntitiesData;
|
||||
QScriptEngine scriptEngine;
|
||||
forEachChild([&](SpatiallyNestablePointer child) {
|
||||
if (child->getNestableType() == NestableType::Entity) {
|
||||
auto modelEntity = std::dynamic_pointer_cast<ModelEntityItem>(child);
|
||||
if (modelEntity) {
|
||||
QVariantMap avatarEntityData;
|
||||
EntityItemProperties entityProperties = modelEntity->getProperties();
|
||||
QScriptValue scriptProperties = EntityItemPropertiesToScriptValue(&scriptEngine, entityProperties);
|
||||
avatarEntityData["properties"] = scriptProperties.toVariant();
|
||||
avatarEntitiesData.append(QVariant(avatarEntityData));
|
||||
}
|
||||
}
|
||||
});
|
||||
return avatarEntitiesData;
|
||||
}
|
||||
|
||||
|
||||
void MyAvatar::resetFullAvatarURL() {
|
||||
auto lastAvatarURL = getFullAvatarURLFromPreferences();
|
||||
|
@ -2414,7 +2453,6 @@ bool MyAvatar::requiresSafeLanding(const glm::vec3& positionIn, glm::vec3& bette
|
|||
};
|
||||
auto findIntersection = [&](const glm::vec3& startPointIn, const glm::vec3& directionIn, glm::vec3& intersectionOut, EntityItemID& entityIdOut, glm::vec3& normalOut) {
|
||||
OctreeElementPointer element;
|
||||
EntityItemPointer intersectedEntity = NULL;
|
||||
float distance;
|
||||
BoxFace face;
|
||||
const bool visibleOnly = false;
|
||||
|
@ -2426,13 +2464,14 @@ bool MyAvatar::requiresSafeLanding(const glm::vec3& positionIn, glm::vec3& bette
|
|||
const auto lockType = Octree::Lock; // Should we refactor to take a lock just once?
|
||||
bool* accurateResult = NULL;
|
||||
|
||||
bool intersects = entityTree->findRayIntersection(startPointIn, directionIn, include, ignore, visibleOnly, collidableOnly, precisionPicking,
|
||||
element, distance, face, normalOut, (void**)&intersectedEntity, lockType, accurateResult);
|
||||
if (!intersects || !intersectedEntity) {
|
||||
QVariantMap extraInfo;
|
||||
EntityItemID entityID = entityTree->findRayIntersection(startPointIn, directionIn, include, ignore, visibleOnly, collidableOnly, precisionPicking,
|
||||
element, distance, face, normalOut, extraInfo, lockType, accurateResult);
|
||||
if (entityID.isNull()) {
|
||||
return false;
|
||||
}
|
||||
intersectionOut = startPointIn + (directionIn * distance);
|
||||
entityIdOut = intersectedEntity->getEntityItemID();
|
||||
entityIdOut = entityID;
|
||||
return true;
|
||||
};
|
||||
|
||||
|
@ -2700,27 +2739,48 @@ void MyAvatar::setWalkSpeed(float value) {
|
|||
}
|
||||
|
||||
glm::vec3 MyAvatar::getPositionForAudio() {
|
||||
glm::vec3 result;
|
||||
switch (_audioListenerMode) {
|
||||
case AudioListenerMode::FROM_HEAD:
|
||||
return getHead()->getPosition();
|
||||
result = getHead()->getPosition();
|
||||
break;
|
||||
case AudioListenerMode::FROM_CAMERA:
|
||||
return qApp->getCamera().getPosition();
|
||||
result = qApp->getCamera().getPosition();
|
||||
break;
|
||||
case AudioListenerMode::CUSTOM:
|
||||
return _customListenPosition;
|
||||
result = _customListenPosition;
|
||||
break;
|
||||
}
|
||||
return vec3();
|
||||
|
||||
if (isNaN(result)) {
|
||||
qCDebug(interfaceapp) << "MyAvatar::getPositionForAudio produced NaN" << _audioListenerMode;
|
||||
result = glm::vec3(0.0f);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
glm::quat MyAvatar::getOrientationForAudio() {
|
||||
glm::quat result;
|
||||
|
||||
switch (_audioListenerMode) {
|
||||
case AudioListenerMode::FROM_HEAD:
|
||||
return getHead()->getFinalOrientationInWorldFrame();
|
||||
result = getHead()->getFinalOrientationInWorldFrame();
|
||||
break;
|
||||
case AudioListenerMode::FROM_CAMERA:
|
||||
return qApp->getCamera().getOrientation();
|
||||
result = qApp->getCamera().getOrientation();
|
||||
break;
|
||||
case AudioListenerMode::CUSTOM:
|
||||
return _customListenOrientation;
|
||||
result = _customListenOrientation;
|
||||
break;
|
||||
}
|
||||
return quat();
|
||||
|
||||
if (isNaN(result)) {
|
||||
qCDebug(interfaceapp) << "MyAvatar::getOrientationForAudio produced NaN" << _audioListenerMode;
|
||||
result = glm::quat();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void MyAvatar::setAudioListenerMode(AudioListenerMode audioListenerMode) {
|
||||
|
|
|
@ -512,6 +512,9 @@ public:
|
|||
|
||||
bool hasDriveInput() const;
|
||||
|
||||
QVariantList getAvatarEntitiesVariant();
|
||||
void removeAvatarEntities();
|
||||
|
||||
Q_INVOKABLE bool isFlying();
|
||||
Q_INVOKABLE bool isInAir();
|
||||
Q_INVOKABLE void setFlyingEnabled(bool enabled);
|
||||
|
|
|
@ -591,8 +591,8 @@ void Wallet::chooseSecurityImage(const QString& filename) {
|
|||
if (_securityImage) {
|
||||
delete _securityImage;
|
||||
}
|
||||
QString path = qApp->applicationDirPath();
|
||||
path.append("/resources/qml/hifi/commerce/wallet/");
|
||||
QString path = PathUtils::resourcesPath();
|
||||
path.append("/qml/hifi/commerce/wallet/");
|
||||
path.append(filename);
|
||||
|
||||
// now create a new security image pixmap
|
||||
|
|
|
@ -114,6 +114,7 @@ public:
|
|||
* @property {float} distance The distance to the intersection point from the origin of the ray.
|
||||
* @property {Vec3} intersection The intersection point in world-space.
|
||||
* @property {Vec3} surfaceNormal The surface normal at the intersected point. All NANs if type == INTERSECTED_HUD.
|
||||
* @property {Variant} extraInfo Additional intersection details when available for Model objects.
|
||||
* @property {PickRay} searchRay The PickRay that was used. Valid even if there was no intersection.
|
||||
*/
|
||||
|
||||
|
@ -127,6 +128,7 @@ public:
|
|||
* @property {float} distance The distance to the intersection point from the origin of the ray.
|
||||
* @property {Vec3} intersection The intersection point in world-space.
|
||||
* @property {Vec3} surfaceNormal The surface normal at the intersected point. All NANs if type == INTERSECTED_HUD.
|
||||
* @property {Variant} extraInfo Additional intersection details when available for Model objects.
|
||||
* @property {StylusTip} stylusTip The StylusTip that was used. Valid even if there was no intersection.
|
||||
*/
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ PickResultPointer RayPick::getEntityIntersection(const PickRay& pick) {
|
|||
DependencyManager::get<EntityScriptingInterface>()->findRayIntersectionVector(pick, !getFilter().doesPickCoarse(),
|
||||
getIncludeItemsAs<EntityItemID>(), getIgnoreItemsAs<EntityItemID>(), !getFilter().doesPickInvisible(), !getFilter().doesPickNonCollidable());
|
||||
if (entityRes.intersects) {
|
||||
return std::make_shared<RayPickResult>(IntersectionType::ENTITY, entityRes.entityID, entityRes.distance, entityRes.intersection, pick, entityRes.surfaceNormal);
|
||||
return std::make_shared<RayPickResult>(IntersectionType::ENTITY, entityRes.entityID, entityRes.distance, entityRes.intersection, pick, entityRes.surfaceNormal, entityRes.extraInfo);
|
||||
} else {
|
||||
return std::make_shared<RayPickResult>(pick.toVariantMap());
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ PickResultPointer RayPick::getOverlayIntersection(const PickRay& pick) {
|
|||
qApp->getOverlays().findRayIntersectionVector(pick, !getFilter().doesPickCoarse(),
|
||||
getIncludeItemsAs<OverlayID>(), getIgnoreItemsAs<OverlayID>(), !getFilter().doesPickInvisible(), !getFilter().doesPickNonCollidable());
|
||||
if (overlayRes.intersects) {
|
||||
return std::make_shared<RayPickResult>(IntersectionType::OVERLAY, overlayRes.overlayID, overlayRes.distance, overlayRes.intersection, pick, overlayRes.surfaceNormal);
|
||||
return std::make_shared<RayPickResult>(IntersectionType::OVERLAY, overlayRes.overlayID, overlayRes.distance, overlayRes.intersection, pick, overlayRes.surfaceNormal, overlayRes.extraInfo);
|
||||
} else {
|
||||
return std::make_shared<RayPickResult>(pick.toVariantMap());
|
||||
}
|
||||
|
@ -39,7 +39,7 @@ PickResultPointer RayPick::getOverlayIntersection(const PickRay& pick) {
|
|||
PickResultPointer RayPick::getAvatarIntersection(const PickRay& pick) {
|
||||
RayToAvatarIntersectionResult avatarRes = DependencyManager::get<AvatarManager>()->findRayIntersectionVector(pick, getIncludeItemsAs<EntityItemID>(), getIgnoreItemsAs<EntityItemID>());
|
||||
if (avatarRes.intersects) {
|
||||
return std::make_shared<RayPickResult>(IntersectionType::AVATAR, avatarRes.avatarID, avatarRes.distance, avatarRes.intersection, pick);
|
||||
return std::make_shared<RayPickResult>(IntersectionType::AVATAR, avatarRes.avatarID, avatarRes.distance, avatarRes.intersection, pick, glm::vec3(NAN), avatarRes.extraInfo);
|
||||
} else {
|
||||
return std::make_shared<RayPickResult>(pick.toVariantMap());
|
||||
}
|
||||
|
|
|
@ -18,8 +18,8 @@ class RayPickResult : public PickResult {
|
|||
public:
|
||||
RayPickResult() {}
|
||||
RayPickResult(const QVariantMap& pickVariant) : PickResult(pickVariant) {}
|
||||
RayPickResult(const IntersectionType type, const QUuid& objectID, float distance, const glm::vec3& intersection, const PickRay& searchRay, const glm::vec3& surfaceNormal = glm::vec3(NAN)) :
|
||||
PickResult(searchRay.toVariantMap()), type(type), intersects(type != NONE), objectID(objectID), distance(distance), intersection(intersection), surfaceNormal(surfaceNormal) {
|
||||
RayPickResult(const IntersectionType type, const QUuid& objectID, float distance, const glm::vec3& intersection, const PickRay& searchRay, const glm::vec3& surfaceNormal = glm::vec3(NAN), const QVariantMap& extraInfo = QVariantMap()) :
|
||||
PickResult(searchRay.toVariantMap()), type(type), intersects(type != NONE), objectID(objectID), distance(distance), intersection(intersection), surfaceNormal(surfaceNormal), extraInfo(extraInfo) {
|
||||
}
|
||||
|
||||
RayPickResult(const RayPickResult& rayPickResult) : PickResult(rayPickResult.pickVariant) {
|
||||
|
@ -29,6 +29,7 @@ public:
|
|||
distance = rayPickResult.distance;
|
||||
intersection = rayPickResult.intersection;
|
||||
surfaceNormal = rayPickResult.surfaceNormal;
|
||||
extraInfo = rayPickResult.extraInfo;
|
||||
}
|
||||
|
||||
IntersectionType type { NONE };
|
||||
|
@ -37,6 +38,7 @@ public:
|
|||
float distance { FLT_MAX };
|
||||
glm::vec3 intersection { NAN };
|
||||
glm::vec3 surfaceNormal { NAN };
|
||||
QVariantMap extraInfo;
|
||||
|
||||
virtual QVariantMap toVariantMap() const override {
|
||||
QVariantMap toReturn;
|
||||
|
@ -47,6 +49,7 @@ public:
|
|||
toReturn["intersection"] = vec3toVariant(intersection);
|
||||
toReturn["surfaceNormal"] = vec3toVariant(surfaceNormal);
|
||||
toReturn["searchRay"] = PickResult::toVariantMap();
|
||||
toReturn["extraInfo"] = extraInfo;
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
|
|
|
@ -260,7 +260,7 @@ void AssetMappingModel::refresh() {
|
|||
for (auto& mapping : mappings) {
|
||||
auto& path = mapping.first;
|
||||
|
||||
if (path.startsWith(HIDDEN_BAKED_CONTENT_FOLDER)) {
|
||||
if (path.startsWith(AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER)) {
|
||||
// Hide baked mappings
|
||||
continue;
|
||||
}
|
||||
|
@ -303,7 +303,7 @@ void AssetMappingModel::refresh() {
|
|||
auto statusString = isFolder ? "--" : bakingStatusToString(mapping.second.status);
|
||||
lastItem->setData(statusString, Qt::UserRole + 5);
|
||||
lastItem->setData(mapping.second.bakingErrors, Qt::UserRole + 6);
|
||||
if (mapping.second.status == Pending) {
|
||||
if (mapping.second.status == AssetUtils::Pending) {
|
||||
++numPendingBakes;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,7 +39,6 @@ BaseLogDialog::BaseLogDialog(QWidget* parent) : QDialog(parent, Qt::Window) {
|
|||
|
||||
QFile styleSheet(PathUtils::resourcesPath() + "styles/log_dialog.qss");
|
||||
if (styleSheet.open(QIODevice::ReadOnly)) {
|
||||
QDir::setCurrent(PathUtils::resourcesPath());
|
||||
setStyleSheet(styleSheet.readAll());
|
||||
}
|
||||
|
||||
|
|
|
@ -82,19 +82,42 @@ void setupPreferences() {
|
|||
preference->setMax(500);
|
||||
preferences->addPreference(preference);
|
||||
}
|
||||
{
|
||||
auto getter = []()->float { return qApp->getDesktopTabletScale(); };
|
||||
auto setter = [](float value) { qApp->setDesktopTabletScale(value); };
|
||||
auto preference = new SpinnerPreference(UI_CATEGORY, "Desktop Tablet Scale %", getter, setter);
|
||||
preference->setMin(20);
|
||||
preference->setMax(500);
|
||||
preferences->addPreference(preference);
|
||||
}
|
||||
|
||||
|
||||
{
|
||||
auto getter = []()->bool { return qApp->getPreferStylusOverLaser(); };
|
||||
auto setter = [](bool value) { qApp->setPreferStylusOverLaser(value); };
|
||||
preferences->addPreference(new CheckPreference(UI_CATEGORY, "Prefer Stylus Over Laser", getter, setter));
|
||||
}
|
||||
|
||||
static const QString ADVANCED_UI_CATEGORY { "Advanced UI" };
|
||||
{
|
||||
auto getter = []()->float { return qApp->getDesktopTabletScale(); };
|
||||
auto setter = [](float value) { qApp->setDesktopTabletScale(value); };
|
||||
auto preference = new SpinnerPreference(ADVANCED_UI_CATEGORY, "Desktop Tablet Scale %", getter, setter);
|
||||
preference->setMin(20);
|
||||
preference->setMax(500);
|
||||
preferences->addPreference(preference);
|
||||
}
|
||||
{
|
||||
auto getter = [=]()->float { return myAvatar->getRealWorldFieldOfView(); };
|
||||
auto setter = [=](float value) { myAvatar->setRealWorldFieldOfView(value); };
|
||||
auto preference = new SpinnerPreference(ADVANCED_UI_CATEGORY, "Real world vertical field of view (angular size of monitor)", getter, setter);
|
||||
preference->setMin(1);
|
||||
preference->setMax(180);
|
||||
preferences->addPreference(preference);
|
||||
}
|
||||
{
|
||||
auto getter = []()->float { return qApp->getFieldOfView(); };
|
||||
auto setter = [](float value) { qApp->setFieldOfView(value); };
|
||||
auto preference = new SpinnerPreference(ADVANCED_UI_CATEGORY, "Vertical field of view", getter, setter);
|
||||
preference->setMin(1);
|
||||
preference->setMax(180);
|
||||
preference->setStep(1);
|
||||
preferences->addPreference(preference);
|
||||
}
|
||||
|
||||
|
||||
// FIXME: Remove setting completely or make available through JavaScript API?
|
||||
/*
|
||||
{
|
||||
|
@ -128,21 +151,13 @@ void setupPreferences() {
|
|||
preferences->addPreference(preference);
|
||||
}
|
||||
|
||||
// Scripts
|
||||
{
|
||||
auto getter = []()->QString { return DependencyManager::get<ScriptEngines>()->getScriptsLocation(); };
|
||||
auto setter = [](const QString& value) { DependencyManager::get<ScriptEngines>()->setScriptsLocation(value); };
|
||||
preferences->addPreference(new BrowsePreference("Scripts", "Load scripts from this directory", getter, setter));
|
||||
}
|
||||
|
||||
preferences->addPreference(new ButtonPreference("Scripts", "Load Default Scripts", [] {
|
||||
DependencyManager::get<ScriptEngines>()->loadDefaultScripts();
|
||||
}));
|
||||
|
||||
{
|
||||
auto getter = []()->bool { return !Menu::getInstance()->isOptionChecked(MenuOption::DisableActivityLogger); };
|
||||
auto setter = [](bool value) { Menu::getInstance()->setIsOptionChecked(MenuOption::DisableActivityLogger, !value); };
|
||||
preferences->addPreference(new CheckPreference("Privacy", "Send data", getter, setter));
|
||||
preferences->addPreference(new CheckPreference("Privacy", "Send data - High Fidelity uses information provided by your "
|
||||
"client to improve the product through the logging of errors, tracking of usage patterns, "
|
||||
"installation and system details, and crash events. By allowing High Fidelity to collect "
|
||||
"this information you are helping to improve the product. ", getter, setter));
|
||||
}
|
||||
|
||||
static const QString LOD_TUNING("Level of Detail Tuning");
|
||||
|
@ -167,23 +182,6 @@ void setupPreferences() {
|
|||
}
|
||||
|
||||
static const QString AVATAR_TUNING { "Avatar Tuning" };
|
||||
{
|
||||
auto getter = [=]()->float { return myAvatar->getRealWorldFieldOfView(); };
|
||||
auto setter = [=](float value) { myAvatar->setRealWorldFieldOfView(value); };
|
||||
auto preference = new SpinnerPreference(AVATAR_TUNING, "Real world vertical field of view (angular size of monitor)", getter, setter);
|
||||
preference->setMin(1);
|
||||
preference->setMax(180);
|
||||
preferences->addPreference(preference);
|
||||
}
|
||||
{
|
||||
auto getter = []()->float { return qApp->getFieldOfView(); };
|
||||
auto setter = [](float value) { qApp->setFieldOfView(value); };
|
||||
auto preference = new SpinnerPreference(AVATAR_TUNING, "Vertical field of view", getter, setter);
|
||||
preference->setMin(1);
|
||||
preference->setMax(180);
|
||||
preference->setStep(1);
|
||||
preferences->addPreference(preference);
|
||||
}
|
||||
{
|
||||
auto getter = [=]()->QString { return myAvatar->getDominantHand(); };
|
||||
auto setter = [=](const QString& value) { myAvatar->setDominantHand(value); };
|
||||
|
@ -297,26 +295,6 @@ void setupPreferences() {
|
|||
}
|
||||
#endif
|
||||
|
||||
{
|
||||
auto getter = []()->float { return qApp->getMaxOctreePacketsPerSecond(); };
|
||||
auto setter = [](float value) { qApp->setMaxOctreePacketsPerSecond(value); };
|
||||
auto preference = new SpinnerPreference("Octree", "Max packets sent each second", getter, setter);
|
||||
preference->setMin(60);
|
||||
preference->setMax(6000);
|
||||
preference->setStep(10);
|
||||
preferences->addPreference(preference);
|
||||
}
|
||||
|
||||
|
||||
{
|
||||
auto getter = []()->float { return qApp->getApplicationCompositor().getHmdUIAngularSize(); };
|
||||
auto setter = [](float value) { qApp->getApplicationCompositor().setHmdUIAngularSize(value); };
|
||||
auto preference = new SpinnerPreference("HMD", "UI horizontal angular size (degrees)", getter, setter);
|
||||
preference->setMin(30);
|
||||
preference->setMax(160);
|
||||
preference->setStep(1);
|
||||
preferences->addPreference(preference);
|
||||
}
|
||||
|
||||
{
|
||||
static const QString RENDER("Graphics");
|
||||
|
@ -342,7 +320,7 @@ void setupPreferences() {
|
|||
}
|
||||
}
|
||||
{
|
||||
static const QString RENDER("Networking");
|
||||
static const QString NETWORKING("Networking");
|
||||
|
||||
auto nodelist = DependencyManager::get<NodeList>();
|
||||
{
|
||||
|
@ -350,10 +328,21 @@ void setupPreferences() {
|
|||
static const int MAX_PORT_NUMBER { 65535 };
|
||||
auto getter = [nodelist] { return static_cast<int>(nodelist->getSocketLocalPort()); };
|
||||
auto setter = [nodelist](int preset) { nodelist->setSocketLocalPort(static_cast<quint16>(preset)); };
|
||||
auto preference = new IntSpinnerPreference(RENDER, "Listening Port", getter, setter);
|
||||
auto preference = new IntSpinnerPreference(NETWORKING, "Listening Port", getter, setter);
|
||||
preference->setMin(MIN_PORT_NUMBER);
|
||||
preference->setMax(MAX_PORT_NUMBER);
|
||||
preferences->addPreference(preference);
|
||||
}
|
||||
|
||||
{
|
||||
auto getter = []()->float { return qApp->getMaxOctreePacketsPerSecond(); };
|
||||
auto setter = [](float value) { qApp->setMaxOctreePacketsPerSecond(value); };
|
||||
auto preference = new SpinnerPreference(NETWORKING, "Max entities packets sent each second", getter, setter);
|
||||
preference->setMin(60);
|
||||
preference->setMax(6000);
|
||||
preference->setStep(10);
|
||||
preferences->addPreference(preference);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -68,7 +68,7 @@ public:
|
|||
BoxFace& face, glm::vec3& surfaceNormal);
|
||||
|
||||
virtual bool findRayIntersectionExtraInfo(const glm::vec3& origin, const glm::vec3& direction,
|
||||
float& distance, BoxFace& face, glm::vec3& surfaceNormal, QString& extraInfo) {
|
||||
float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo) {
|
||||
return findRayIntersection(origin, direction, distance, face, surfaceNormal);
|
||||
}
|
||||
|
||||
|
|
|
@ -168,7 +168,7 @@ bool ContextOverlayInterface::createOrDestroyContextOverlay(const EntityItemID&
|
|||
_contextOverlay->setColorPulse(CONTEXT_OVERLAY_UNHOVERED_COLORPULSE);
|
||||
_contextOverlay->setIgnoreRayIntersection(false);
|
||||
_contextOverlay->setDrawInFront(true);
|
||||
_contextOverlay->setURL(PathUtils::resourcesPath() + "images/inspect-icon.png");
|
||||
_contextOverlay->setURL(PathUtils::resourcesUrl() + "images/inspect-icon.png");
|
||||
_contextOverlay->setIsFacingAvatar(true);
|
||||
_contextOverlayID = qApp->getOverlays().addOverlay(_contextOverlay);
|
||||
}
|
||||
|
|
|
@ -446,12 +446,12 @@ QVariant ModelOverlay::getProperty(const QString& property) {
|
|||
bool ModelOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
float& distance, BoxFace& face, glm::vec3& surfaceNormal) {
|
||||
|
||||
QString subMeshNameTemp;
|
||||
return _model->findRayIntersectionAgainstSubMeshes(origin, direction, distance, face, surfaceNormal, subMeshNameTemp);
|
||||
QVariantMap extraInfo;
|
||||
return _model->findRayIntersectionAgainstSubMeshes(origin, direction, distance, face, surfaceNormal, extraInfo);
|
||||
}
|
||||
|
||||
bool ModelOverlay::findRayIntersectionExtraInfo(const glm::vec3& origin, const glm::vec3& direction,
|
||||
float& distance, BoxFace& face, glm::vec3& surfaceNormal, QString& extraInfo) {
|
||||
float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo) {
|
||||
|
||||
return _model->findRayIntersectionAgainstSubMeshes(origin, direction, distance, face, surfaceNormal, extraInfo);
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@ public:
|
|||
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal) override;
|
||||
virtual bool findRayIntersectionExtraInfo(const glm::vec3& origin, const glm::vec3& direction,
|
||||
float& distance, BoxFace& face, glm::vec3& surfaceNormal, QString& extraInfo) override;
|
||||
float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo) override;
|
||||
|
||||
virtual ModelOverlay* createClone() const override;
|
||||
|
||||
|
|
|
@ -520,7 +520,7 @@ RayToOverlayIntersectionResult Overlays::findRayIntersectionVector(const PickRay
|
|||
float thisDistance;
|
||||
BoxFace thisFace;
|
||||
glm::vec3 thisSurfaceNormal;
|
||||
QString thisExtraInfo;
|
||||
QVariantMap thisExtraInfo;
|
||||
if (thisOverlay->findRayIntersectionExtraInfo(ray.origin, ray.direction, thisDistance,
|
||||
thisFace, thisSurfaceNormal, thisExtraInfo)) {
|
||||
bool isDrawInFront = thisOverlay->getDrawInFront();
|
||||
|
@ -578,7 +578,7 @@ QScriptValue RayToOverlayIntersectionResultToScriptValue(QScriptEngine* engine,
|
|||
obj.setProperty("face", faceName);
|
||||
auto intersection = vec3toScriptValue(engine, value.intersection);
|
||||
obj.setProperty("intersection", intersection);
|
||||
obj.setProperty("extraInfo", value.extraInfo);
|
||||
obj.setProperty("extraInfo", engine->toScriptValue(value.extraInfo));
|
||||
return obj;
|
||||
}
|
||||
|
||||
|
@ -612,7 +612,7 @@ void RayToOverlayIntersectionResultFromScriptValue(const QScriptValue& objectVar
|
|||
value.intersection = newIntersection;
|
||||
}
|
||||
}
|
||||
value.extraInfo = object["extraInfo"].toString();
|
||||
value.extraInfo = object["extraInfo"].toMap();
|
||||
}
|
||||
|
||||
bool Overlays::isLoaded(OverlayID id) {
|
||||
|
|
|
@ -51,6 +51,7 @@ const OverlayID UNKNOWN_OVERLAY_ID = OverlayID();
|
|||
* @property {number} distance - The distance from the {@link PickRay} origin to the intersection point.
|
||||
* @property {Vec3} surfaceNormal - The normal of the overlay surface at the intersection point.
|
||||
* @property {Vec3} intersection - The position of the intersection point.
|
||||
* @property {Object} extraInfo Additional intersection details, if available.
|
||||
*/
|
||||
class RayToOverlayIntersectionResult {
|
||||
public:
|
||||
|
@ -60,7 +61,7 @@ public:
|
|||
BoxFace face;
|
||||
glm::vec3 surfaceNormal;
|
||||
glm::vec3 intersection;
|
||||
QString extraInfo;
|
||||
QVariantMap extraInfo;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -445,22 +445,31 @@ void Rig::setJointRotation(int index, bool valid, const glm::quat& rotation, flo
|
|||
}
|
||||
|
||||
bool Rig::getJointPositionInWorldFrame(int jointIndex, glm::vec3& position, glm::vec3 translation, glm::quat rotation) const {
|
||||
bool success { false };
|
||||
if (QThread::currentThread() == thread()) {
|
||||
if (isIndexValid(jointIndex)) {
|
||||
position = (rotation * _internalPoseSet._absolutePoses[jointIndex].trans()) + translation;
|
||||
return true;
|
||||
success = true;
|
||||
} else {
|
||||
return false;
|
||||
success = false;
|
||||
}
|
||||
} else {
|
||||
QReadLocker readLock(&_externalPoseSetLock);
|
||||
if (jointIndex >= 0 && jointIndex < (int)_externalPoseSet._absolutePoses.size()) {
|
||||
position = (rotation * _externalPoseSet._absolutePoses[jointIndex].trans()) + translation;
|
||||
success = true;
|
||||
} else {
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
|
||||
QReadLocker readLock(&_externalPoseSetLock);
|
||||
if (jointIndex >= 0 && jointIndex < (int)_externalPoseSet._absolutePoses.size()) {
|
||||
position = (rotation * _externalPoseSet._absolutePoses[jointIndex].trans()) + translation;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
if (isNaN(position)) {
|
||||
qCWarning(animation) << "Rig::getJointPositionInWorldFrame produces NaN";
|
||||
success = false;
|
||||
position = glm::vec3(0.0f);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool Rig::getJointPosition(int jointIndex, glm::vec3& position) const {
|
||||
|
|
|
@ -427,28 +427,11 @@ AudioInjectorPointer AudioInjector::playSound(SharedSoundPointer sound, const fl
|
|||
options.stereo = sound->isStereo();
|
||||
options.position = position;
|
||||
options.volume = volume;
|
||||
options.pitch = 1.0f / stretchFactor;
|
||||
|
||||
QByteArray samples = sound->getByteArray();
|
||||
if (stretchFactor == 1.0f) {
|
||||
return playSoundAndDelete(samples, options);
|
||||
}
|
||||
|
||||
const int standardRate = AudioConstants::SAMPLE_RATE;
|
||||
const int resampledRate = standardRate * stretchFactor;
|
||||
const int channelCount = sound->isStereo() ? 2 : 1;
|
||||
|
||||
AudioSRC resampler(standardRate, resampledRate, channelCount);
|
||||
|
||||
const int nInputFrames = samples.size() / (channelCount * sizeof(int16_t));
|
||||
const int maxOutputFrames = resampler.getMaxOutput(nInputFrames);
|
||||
QByteArray resampled(maxOutputFrames * channelCount * sizeof(int16_t), '\0');
|
||||
|
||||
int nOutputFrames = resampler.render(reinterpret_cast<const int16_t*>(samples.data()),
|
||||
reinterpret_cast<int16_t*>(resampled.data()),
|
||||
nInputFrames);
|
||||
|
||||
Q_UNUSED(nOutputFrames);
|
||||
return playSoundAndDelete(resampled, options);
|
||||
return playSoundAndDelete(samples, options);
|
||||
}
|
||||
|
||||
AudioInjectorPointer AudioInjector::playSoundAndDelete(const QByteArray& buffer, const AudioInjectorOptions options) {
|
||||
|
@ -461,12 +444,40 @@ AudioInjectorPointer AudioInjector::playSoundAndDelete(const QByteArray& buffer,
|
|||
return sound;
|
||||
}
|
||||
|
||||
|
||||
AudioInjectorPointer AudioInjector::playSound(const QByteArray& buffer, const AudioInjectorOptions options) {
|
||||
AudioInjectorPointer injector = AudioInjectorPointer::create(buffer, options);
|
||||
|
||||
if (!injector->inject(&AudioInjectorManager::threadInjector)) {
|
||||
qWarning() << "AudioInjector::playSound failed to thread injector";
|
||||
if (options.pitch == 1.0f) {
|
||||
|
||||
AudioInjectorPointer injector = AudioInjectorPointer::create(buffer, options);
|
||||
|
||||
if (!injector->inject(&AudioInjectorManager::threadInjector)) {
|
||||
qWarning() << "AudioInjector::playSound failed to thread injector";
|
||||
}
|
||||
return injector;
|
||||
|
||||
} else {
|
||||
|
||||
const int standardRate = AudioConstants::SAMPLE_RATE;
|
||||
const int resampledRate = AudioConstants::SAMPLE_RATE / glm::clamp(options.pitch, 1/16.0f, 16.0f); // limit to 4 octaves
|
||||
const int numChannels = options.ambisonic ? AudioConstants::AMBISONIC :
|
||||
(options.stereo ? AudioConstants::STEREO : AudioConstants::MONO);
|
||||
|
||||
AudioSRC resampler(standardRate, resampledRate, numChannels);
|
||||
|
||||
// create a resampled buffer that is guaranteed to be large enough
|
||||
const int nInputFrames = buffer.size() / (numChannels * sizeof(int16_t));
|
||||
const int maxOutputFrames = resampler.getMaxOutput(nInputFrames);
|
||||
QByteArray resampledBuffer(maxOutputFrames * numChannels * sizeof(int16_t), '\0');
|
||||
|
||||
resampler.render(reinterpret_cast<const int16_t*>(buffer.data()),
|
||||
reinterpret_cast<int16_t*>(resampledBuffer.data()),
|
||||
nInputFrames);
|
||||
|
||||
AudioInjectorPointer injector = AudioInjectorPointer::create(resampledBuffer, options);
|
||||
|
||||
if (!injector->inject(&AudioInjectorManager::threadInjector)) {
|
||||
qWarning() << "AudioInjector::playSound failed to thread pitch-shifted injector";
|
||||
}
|
||||
return injector;
|
||||
}
|
||||
return injector;
|
||||
}
|
||||
|
|
|
@ -26,7 +26,8 @@ AudioInjectorOptions::AudioInjectorOptions() :
|
|||
ambisonic(false),
|
||||
ignorePenumbra(false),
|
||||
localOnly(false),
|
||||
secondOffset(0.0f)
|
||||
secondOffset(0.0f),
|
||||
pitch(1.0f)
|
||||
{
|
||||
|
||||
}
|
||||
|
@ -40,6 +41,7 @@ QScriptValue injectorOptionsToScriptValue(QScriptEngine* engine, const AudioInje
|
|||
obj.setProperty("ignorePenumbra", injectorOptions.ignorePenumbra);
|
||||
obj.setProperty("localOnly", injectorOptions.localOnly);
|
||||
obj.setProperty("secondOffset", injectorOptions.secondOffset);
|
||||
obj.setProperty("pitch", injectorOptions.pitch);
|
||||
return obj;
|
||||
}
|
||||
|
||||
|
@ -87,6 +89,12 @@ void injectorOptionsFromScriptValue(const QScriptValue& object, AudioInjectorOpt
|
|||
} else {
|
||||
qCWarning(audio) << "Audio injector options: secondOffset is not a number";
|
||||
}
|
||||
} else if (it.name() == "pitch") {
|
||||
if (it.value().isNumber()) {
|
||||
injectorOptions.pitch = it.value().toNumber();
|
||||
} else {
|
||||
qCWarning(audio) << "Audio injector options: pitch is not a number";
|
||||
}
|
||||
} else {
|
||||
qCWarning(audio) << "Unknown audio injector option:" << it.name();
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@ public:
|
|||
bool ignorePenumbra;
|
||||
bool localOnly;
|
||||
float secondOffset;
|
||||
float pitch; // multiplier, where 2.0f shifts up one octave
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(AudioInjectorOptions);
|
||||
|
|
|
@ -2504,6 +2504,7 @@ QScriptValue RayToAvatarIntersectionResultToScriptValue(QScriptEngine* engine, c
|
|||
obj.setProperty("distance", value.distance);
|
||||
QScriptValue intersection = vec3toScriptValue(engine, value.intersection);
|
||||
obj.setProperty("intersection", intersection);
|
||||
obj.setProperty("extraInfo", engine->toScriptValue(value.extraInfo));
|
||||
return obj;
|
||||
}
|
||||
|
||||
|
@ -2516,6 +2517,7 @@ void RayToAvatarIntersectionResultFromScriptValue(const QScriptValue& object, Ra
|
|||
if (intersection.isValid()) {
|
||||
vec3FromScriptValue(intersection, value.intersection);
|
||||
}
|
||||
value.extraInfo = object.property("extraInfo").toVariant().toMap();
|
||||
}
|
||||
|
||||
const float AvatarData::OUT_OF_VIEW_PENALTY = -10.0f;
|
||||
|
|
|
@ -982,6 +982,7 @@ RayToAvatarIntersectionResult() : intersects(false), avatarID(), distance(0) {}
|
|||
QUuid avatarID;
|
||||
float distance;
|
||||
glm::vec3 intersection;
|
||||
QVariantMap extraInfo;
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(RayToAvatarIntersectionResult)
|
||||
|
|
|
@ -53,8 +53,6 @@ public:
|
|||
|
||||
bool calculateRayUICollisionPoint(const glm::vec3& position, const glm::vec3& direction, glm::vec3& result) const;
|
||||
|
||||
float getHmdUIAngularSize() const { return _hmdUIAngularSize; }
|
||||
void setHmdUIAngularSize(float hmdUIAngularSize) { _hmdUIAngularSize = hmdUIAngularSize; }
|
||||
bool isHMD() const;
|
||||
bool fakeEventActive() const { return _fakeMouseEvent; }
|
||||
|
||||
|
@ -139,7 +137,6 @@ private:
|
|||
//quint64 _hoverItemEnterUsecs { 0 };
|
||||
|
||||
bool _isOverDesktop { true };
|
||||
float _hmdUIAngularSize { glm::degrees(VIRTUAL_UI_TARGET_FOV.y) };
|
||||
float _textureFov { VIRTUAL_UI_TARGET_FOV.y };
|
||||
float _textureAspectRatio { VIRTUAL_UI_ASPECT_RATIO };
|
||||
|
||||
|
|
|
@ -669,15 +669,16 @@ void EntityTreeRenderer::mousePressEvent(QMouseEvent* event) {
|
|||
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
|
||||
PickRay ray = _viewState->computePickRay(event->x(), event->y());
|
||||
RayToEntityIntersectionResult rayPickResult = _getPrevRayPickResultOperator(_mouseRayPickID);
|
||||
if (rayPickResult.intersects && rayPickResult.entity) {
|
||||
auto properties = rayPickResult.entity->getProperties();
|
||||
EntityItemPointer entity;
|
||||
if (rayPickResult.intersects && (entity = getTree()->findEntityByID(rayPickResult.entityID))) {
|
||||
auto properties = entity->getProperties();
|
||||
QString urlString = properties.getHref();
|
||||
QUrl url = QUrl(urlString, QUrl::StrictMode);
|
||||
if (url.isValid() && !url.isEmpty()){
|
||||
DependencyManager::get<AddressManager>()->handleLookupString(urlString);
|
||||
}
|
||||
|
||||
glm::vec2 pos2D = projectOntoEntityXYPlane(rayPickResult.entity, ray, rayPickResult);
|
||||
glm::vec2 pos2D = projectOntoEntityXYPlane(entity, ray, rayPickResult);
|
||||
PointerEvent pointerEvent(PointerEvent::Press, PointerManager::MOUSE_POINTER_ID,
|
||||
pos2D, rayPickResult.intersection,
|
||||
rayPickResult.surfaceNormal, ray.direction,
|
||||
|
@ -708,8 +709,9 @@ void EntityTreeRenderer::mouseDoublePressEvent(QMouseEvent* event) {
|
|||
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
|
||||
PickRay ray = _viewState->computePickRay(event->x(), event->y());
|
||||
RayToEntityIntersectionResult rayPickResult = _getPrevRayPickResultOperator(_mouseRayPickID);
|
||||
if (rayPickResult.intersects && rayPickResult.entity) {
|
||||
glm::vec2 pos2D = projectOntoEntityXYPlane(rayPickResult.entity, ray, rayPickResult);
|
||||
EntityItemPointer entity;
|
||||
if (rayPickResult.intersects && (entity = getTree()->findEntityByID(rayPickResult.entityID))) {
|
||||
glm::vec2 pos2D = projectOntoEntityXYPlane(entity, ray, rayPickResult);
|
||||
PointerEvent pointerEvent(PointerEvent::Press, PointerManager::MOUSE_POINTER_ID,
|
||||
pos2D, rayPickResult.intersection,
|
||||
rayPickResult.surfaceNormal, ray.direction,
|
||||
|
@ -738,10 +740,11 @@ void EntityTreeRenderer::mouseReleaseEvent(QMouseEvent* event) {
|
|||
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
|
||||
PickRay ray = _viewState->computePickRay(event->x(), event->y());
|
||||
RayToEntityIntersectionResult rayPickResult = _getPrevRayPickResultOperator(_mouseRayPickID);
|
||||
if (rayPickResult.intersects && rayPickResult.entity) {
|
||||
EntityItemPointer entity;
|
||||
if (rayPickResult.intersects && (entity = getTree()->findEntityByID(rayPickResult.entityID))) {
|
||||
// qCDebug(entitiesrenderer) << "mouseReleaseEvent over entity:" << rayPickResult.entityID;
|
||||
|
||||
glm::vec2 pos2D = projectOntoEntityXYPlane(rayPickResult.entity, ray, rayPickResult);
|
||||
glm::vec2 pos2D = projectOntoEntityXYPlane(entity, ray, rayPickResult);
|
||||
PointerEvent pointerEvent(PointerEvent::Release, PointerManager::MOUSE_POINTER_ID,
|
||||
pos2D, rayPickResult.intersection,
|
||||
rayPickResult.surfaceNormal, ray.direction,
|
||||
|
@ -757,7 +760,7 @@ void EntityTreeRenderer::mouseReleaseEvent(QMouseEvent* event) {
|
|||
// Even if we're no longer intersecting with an entity, if we started clicking on it, and now
|
||||
// we're releasing the button, then this is considered a clickReleaseOn event
|
||||
if (!_currentClickingOnEntityID.isInvalidID()) {
|
||||
glm::vec2 pos2D = projectOntoEntityXYPlane(rayPickResult.entity, ray, rayPickResult);
|
||||
glm::vec2 pos2D = projectOntoEntityXYPlane(entity, ray, rayPickResult);
|
||||
PointerEvent pointerEvent(PointerEvent::Release, PointerManager::MOUSE_POINTER_ID,
|
||||
pos2D, rayPickResult.intersection,
|
||||
rayPickResult.surfaceNormal, ray.direction,
|
||||
|
@ -782,8 +785,9 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event) {
|
|||
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
|
||||
PickRay ray = _viewState->computePickRay(event->x(), event->y());
|
||||
RayToEntityIntersectionResult rayPickResult = _getPrevRayPickResultOperator(_mouseRayPickID);
|
||||
if (rayPickResult.intersects && rayPickResult.entity) {
|
||||
glm::vec2 pos2D = projectOntoEntityXYPlane(rayPickResult.entity, ray, rayPickResult);
|
||||
EntityItemPointer entity;
|
||||
if (rayPickResult.intersects && (entity = getTree()->findEntityByID(rayPickResult.entityID))) {
|
||||
glm::vec2 pos2D = projectOntoEntityXYPlane(entity, ray, rayPickResult);
|
||||
PointerEvent pointerEvent(PointerEvent::Move, PointerManager::MOUSE_POINTER_ID,
|
||||
pos2D, rayPickResult.intersection,
|
||||
rayPickResult.surfaceNormal, ray.direction,
|
||||
|
@ -797,7 +801,7 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event) {
|
|||
// if we were previously hovering over an entity, and this new entity is not the same as our previous entity
|
||||
// then we need to send the hover leave.
|
||||
if (!_currentHoverOverEntityID.isInvalidID() && rayPickResult.entityID != _currentHoverOverEntityID) {
|
||||
glm::vec2 pos2D = projectOntoEntityXYPlane(rayPickResult.entity, ray, rayPickResult);
|
||||
glm::vec2 pos2D = projectOntoEntityXYPlane(entity, ray, rayPickResult);
|
||||
PointerEvent pointerEvent(PointerEvent::Move, PointerManager::MOUSE_POINTER_ID,
|
||||
pos2D, rayPickResult.intersection,
|
||||
rayPickResult.surfaceNormal, ray.direction,
|
||||
|
@ -828,7 +832,7 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event) {
|
|||
// if we were previously hovering over an entity, and we're no longer hovering over any entity then we need to
|
||||
// send the hover leave for our previous entity
|
||||
if (!_currentHoverOverEntityID.isInvalidID()) {
|
||||
glm::vec2 pos2D = projectOntoEntityXYPlane(rayPickResult.entity, ray, rayPickResult);
|
||||
glm::vec2 pos2D = projectOntoEntityXYPlane(entity, ray, rayPickResult);
|
||||
PointerEvent pointerEvent(PointerEvent::Move, PointerManager::MOUSE_POINTER_ID,
|
||||
pos2D, rayPickResult.intersection,
|
||||
rayPickResult.surfaceNormal, ray.direction,
|
||||
|
|
|
@ -282,7 +282,7 @@ bool RenderableModelEntityItem::supportsDetailedRayIntersection() const {
|
|||
|
||||
bool RenderableModelEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
bool& keepSearching, OctreeElementPointer& element, float& distance, BoxFace& face,
|
||||
glm::vec3& surfaceNormal, void** intersectedObject, bool precisionPicking) const {
|
||||
glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const {
|
||||
auto model = getModel();
|
||||
if (!model) {
|
||||
return true;
|
||||
|
@ -290,9 +290,8 @@ bool RenderableModelEntityItem::findDetailedRayIntersection(const glm::vec3& ori
|
|||
// qCDebug(entitiesrenderer) << "RenderableModelEntityItem::findDetailedRayIntersection() precisionPicking:"
|
||||
// << precisionPicking;
|
||||
|
||||
QString extraInfo;
|
||||
return model->findRayIntersectionAgainstSubMeshes(origin, direction, distance,
|
||||
face, surfaceNormal, extraInfo, precisionPicking, false);
|
||||
face, surfaceNormal, extraInfo, precisionPicking, false);
|
||||
}
|
||||
|
||||
void RenderableModelEntityItem::getCollisionGeometryResource() {
|
||||
|
@ -1329,7 +1328,9 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce
|
|||
_currentTextures = newTextures;
|
||||
}
|
||||
}
|
||||
|
||||
if (entity->_needsJointSimulation) {
|
||||
entity->copyAnimationJointDataToModel();
|
||||
}
|
||||
entity->updateModelBounds();
|
||||
entity->stopModelOverrideIfNoParent();
|
||||
|
||||
|
|
|
@ -70,7 +70,7 @@ public:
|
|||
virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
bool& keepSearching, OctreeElementPointer& element, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal,
|
||||
void** intersectedObject, bool precisionPicking) const override;
|
||||
QVariantMap& extraInfo, bool precisionPicking) const override;
|
||||
|
||||
virtual void setShapeType(ShapeType type) override;
|
||||
virtual void setCompoundShapeURL(const QString& url) override;
|
||||
|
|
|
@ -567,7 +567,7 @@ public:
|
|||
bool RenderablePolyVoxEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
bool& keepSearching, OctreeElementPointer& element,
|
||||
float& distance, BoxFace& face, glm::vec3& surfaceNormal,
|
||||
void** intersectedObject, bool precisionPicking) const
|
||||
QVariantMap& extraInfo, bool precisionPicking) const
|
||||
{
|
||||
// TODO -- correctly pick against marching-cube generated meshes
|
||||
if (!precisionPicking) {
|
||||
|
|
|
@ -55,7 +55,7 @@ public:
|
|||
virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
bool& keepSearching, OctreeElementPointer& element, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal,
|
||||
void** intersectedObject, bool precisionPicking) const override;
|
||||
QVariantMap& extraInfo, bool precisionPicking) const override;
|
||||
|
||||
virtual void setVoxelData(const QByteArray& voxelData) override;
|
||||
virtual void setVoxelVolumeSize(const glm::vec3& voxelVolumeSize) override;
|
||||
|
|
|
@ -160,7 +160,7 @@ public:
|
|||
virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
bool& keepSearching, OctreeElementPointer& element, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal,
|
||||
void** intersectedObject, bool precisionPicking) const { return true; }
|
||||
QVariantMap& extraInfo, bool precisionPicking) const { return true; }
|
||||
|
||||
// attributes applicable to all entity types
|
||||
EntityTypes::EntityType getType() const { return _type; }
|
||||
|
|
|
@ -816,13 +816,12 @@ RayToEntityIntersectionResult EntityScriptingInterface::findRayIntersectionWorke
|
|||
RayToEntityIntersectionResult result;
|
||||
if (_entityTree) {
|
||||
OctreeElementPointer element;
|
||||
EntityItemPointer intersectedEntity = NULL;
|
||||
result.intersects = _entityTree->findRayIntersection(ray.origin, ray.direction,
|
||||
result.entityID = _entityTree->findRayIntersection(ray.origin, ray.direction,
|
||||
entityIdsToInclude, entityIdsToDiscard, visibleOnly, collidableOnly, precisionPicking,
|
||||
element, result.distance, result.face, result.surfaceNormal,
|
||||
(void**)&intersectedEntity, lockType, &result.accurate);
|
||||
if (result.intersects && intersectedEntity) {
|
||||
result.entityID = intersectedEntity->getEntityItemID();
|
||||
result.extraInfo, lockType, &result.accurate);
|
||||
result.intersects = !result.entityID.isNull();
|
||||
if (result.intersects) {
|
||||
result.intersection = ray.origin + (ray.direction * result.distance);
|
||||
}
|
||||
}
|
||||
|
@ -988,8 +987,7 @@ RayToEntityIntersectionResult::RayToEntityIntersectionResult() :
|
|||
accurate(true), // assume it's accurate
|
||||
entityID(),
|
||||
distance(0),
|
||||
face(),
|
||||
entity(NULL)
|
||||
face()
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -1036,6 +1034,7 @@ QScriptValue RayToEntityIntersectionResultToScriptValue(QScriptEngine* engine, c
|
|||
|
||||
QScriptValue surfaceNormal = vec3toScriptValue(engine, value.surfaceNormal);
|
||||
obj.setProperty("surfaceNormal", surfaceNormal);
|
||||
obj.setProperty("extraInfo", engine->toScriptValue(value.extraInfo));
|
||||
return obj;
|
||||
}
|
||||
|
||||
|
@ -1071,6 +1070,7 @@ void RayToEntityIntersectionResultFromScriptValue(const QScriptValue& object, Ra
|
|||
if (surfaceNormal.isValid()) {
|
||||
vec3FromScriptValue(surfaceNormal, value.surfaceNormal);
|
||||
}
|
||||
value.extraInfo = object.property("extraInfo").toVariant().toMap();
|
||||
}
|
||||
|
||||
bool EntityScriptingInterface::polyVoxWorker(QUuid entityID, std::function<bool(PolyVoxEntityItem&)> actor) {
|
||||
|
|
|
@ -62,7 +62,7 @@ public:
|
|||
BoxFace face;
|
||||
glm::vec3 intersection;
|
||||
glm::vec3 surfaceNormal;
|
||||
EntityItemPointer entity;
|
||||
QVariantMap extraInfo;
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(RayToEntityIntersectionResult)
|
||||
|
|
|
@ -59,8 +59,8 @@ public:
|
|||
float& distance;
|
||||
BoxFace& face;
|
||||
glm::vec3& surfaceNormal;
|
||||
void** intersectedObject;
|
||||
bool found;
|
||||
QVariantMap& extraInfo;
|
||||
EntityItemID entityID;
|
||||
};
|
||||
|
||||
|
||||
|
@ -748,23 +748,24 @@ bool findRayIntersectionOp(const OctreeElementPointer& element, void* extraData)
|
|||
RayArgs* args = static_cast<RayArgs*>(extraData);
|
||||
bool keepSearching = true;
|
||||
EntityTreeElementPointer entityTreeElementPointer = std::static_pointer_cast<EntityTreeElement>(element);
|
||||
if (entityTreeElementPointer->findRayIntersection(args->origin, args->direction, keepSearching,
|
||||
EntityItemID entityID = entityTreeElementPointer->findRayIntersection(args->origin, args->direction, keepSearching,
|
||||
args->element, args->distance, args->face, args->surfaceNormal, args->entityIdsToInclude,
|
||||
args->entityIdsToDiscard, args->visibleOnly, args->collidableOnly, args->intersectedObject, args->precisionPicking)) {
|
||||
args->found = true;
|
||||
args->entityIdsToDiscard, args->visibleOnly, args->collidableOnly, args->extraInfo, args->precisionPicking);
|
||||
if (!entityID.isNull()) {
|
||||
args->entityID = entityID;
|
||||
}
|
||||
return keepSearching;
|
||||
}
|
||||
|
||||
bool EntityTree::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
EntityItemID EntityTree::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
QVector<EntityItemID> entityIdsToInclude, QVector<EntityItemID> entityIdsToDiscard,
|
||||
bool visibleOnly, bool collidableOnly, bool precisionPicking,
|
||||
OctreeElementPointer& element, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal, void** intersectedObject,
|
||||
BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo,
|
||||
Octree::lockType lockType, bool* accurateResult) {
|
||||
RayArgs args = { origin, direction, entityIdsToInclude, entityIdsToDiscard,
|
||||
visibleOnly, collidableOnly, precisionPicking,
|
||||
element, distance, face, surfaceNormal, intersectedObject, false };
|
||||
element, distance, face, surfaceNormal, extraInfo, EntityItemID() };
|
||||
distance = FLT_MAX;
|
||||
|
||||
bool requireLock = lockType == Octree::Lock;
|
||||
|
@ -776,7 +777,7 @@ bool EntityTree::findRayIntersection(const glm::vec3& origin, const glm::vec3& d
|
|||
*accurateResult = lockResult; // if user asked to accuracy or result, let them know this is accurate
|
||||
}
|
||||
|
||||
return args.found;
|
||||
return args.entityID;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -97,11 +97,11 @@ public:
|
|||
virtual void processChallengeOwnershipReplyPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) override;
|
||||
virtual void processChallengeOwnershipPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) override;
|
||||
|
||||
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
virtual EntityItemID findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
QVector<EntityItemID> entityIdsToInclude, QVector<EntityItemID> entityIdsToDiscard,
|
||||
bool visibleOnly, bool collidableOnly, bool precisionPicking,
|
||||
OctreeElementPointer& node, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal, void** intersectedObject = NULL,
|
||||
BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo,
|
||||
Octree::lockType lockType = Octree::TryLock, bool* accurateResult = NULL);
|
||||
|
||||
virtual bool rootElementHasData() const override { return true; }
|
||||
|
|
|
@ -588,57 +588,60 @@ bool EntityTreeElement::bestFitBounds(const glm::vec3& minPoint, const glm::vec3
|
|||
return false;
|
||||
}
|
||||
|
||||
bool EntityTreeElement::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
EntityItemID EntityTreeElement::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
bool& keepSearching, OctreeElementPointer& element, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal, const QVector<EntityItemID>& entityIdsToInclude,
|
||||
const QVector<EntityItemID>& entityIdsToDiscard, bool visibleOnly, bool collidableOnly,
|
||||
void** intersectedObject, bool precisionPicking) {
|
||||
QVariantMap& extraInfo, bool precisionPicking) {
|
||||
|
||||
keepSearching = true; // assume that we will continue searching after this.
|
||||
|
||||
EntityItemID result;
|
||||
float distanceToElementCube = std::numeric_limits<float>::max();
|
||||
float distanceToElementDetails = distance;
|
||||
BoxFace localFace;
|
||||
glm::vec3 localSurfaceNormal;
|
||||
QVariantMap localExtraInfo;
|
||||
|
||||
// if the ray doesn't intersect with our cube, we can stop searching!
|
||||
if (!_cube.findRayIntersection(origin, direction, distanceToElementCube, localFace, localSurfaceNormal)) {
|
||||
keepSearching = false; // no point in continuing to search
|
||||
return false; // we did not intersect
|
||||
return result; // we did not intersect
|
||||
}
|
||||
|
||||
// by default, we only allow intersections with leaves with content
|
||||
if (!canRayIntersect()) {
|
||||
return false; // we don't intersect with non-leaves, and we keep searching
|
||||
return result; // we don't intersect with non-leaves, and we keep searching
|
||||
}
|
||||
|
||||
// if the distance to the element cube is not less than the current best distance, then it's not possible
|
||||
// for any details inside the cube to be closer so we don't need to consider them.
|
||||
if (_cube.contains(origin) || distanceToElementCube < distance) {
|
||||
|
||||
if (findDetailedRayIntersection(origin, direction, keepSearching, element, distanceToElementDetails,
|
||||
EntityItemID entityID = findDetailedRayIntersection(origin, direction, keepSearching, element, distanceToElementDetails,
|
||||
face, localSurfaceNormal, entityIdsToInclude, entityIdsToDiscard, visibleOnly, collidableOnly,
|
||||
intersectedObject, precisionPicking, distanceToElementCube)) {
|
||||
|
||||
localExtraInfo, precisionPicking, distanceToElementCube);
|
||||
if (!entityID.isNull()) {
|
||||
if (distanceToElementDetails < distance) {
|
||||
distance = distanceToElementDetails;
|
||||
face = localFace;
|
||||
surfaceNormal = localSurfaceNormal;
|
||||
return true;
|
||||
extraInfo = localExtraInfo;
|
||||
result = entityID;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return result;
|
||||
}
|
||||
|
||||
bool EntityTreeElement::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, bool& keepSearching,
|
||||
EntityItemID EntityTreeElement::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, bool& keepSearching,
|
||||
OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal,
|
||||
const QVector<EntityItemID>& entityIdsToInclude, const QVector<EntityItemID>& entityIDsToDiscard,
|
||||
bool visibleOnly, bool collidableOnly, void** intersectedObject, bool precisionPicking, float distanceToElementCube) {
|
||||
bool visibleOnly, bool collidableOnly, QVariantMap& extraInfo, bool precisionPicking, float distanceToElementCube) {
|
||||
|
||||
// only called if we do intersect our bounding cube, but find if we actually intersect with entities...
|
||||
int entityNumber = 0;
|
||||
bool somethingIntersected = false;
|
||||
EntityItemID entityID;
|
||||
forEachEntity([&](EntityItemPointer entity) {
|
||||
if ( (visibleOnly && !entity->isVisible()) || (collidableOnly && (entity->getCollisionless() || entity->getShapeType() == SHAPE_TYPE_NONE))
|
||||
|| (entityIdsToInclude.size() > 0 && !entityIdsToInclude.contains(entity->getID()))
|
||||
|
@ -655,6 +658,7 @@ bool EntityTreeElement::findDetailedRayIntersection(const glm::vec3& origin, con
|
|||
float localDistance;
|
||||
BoxFace localFace;
|
||||
glm::vec3 localSurfaceNormal;
|
||||
QVariantMap localExtraInfo;
|
||||
|
||||
// if the ray doesn't intersect with our cube, we can stop searching!
|
||||
if (!entityBox.findRayIntersection(origin, direction, localDistance, localFace, localSurfaceNormal)) {
|
||||
|
@ -684,14 +688,14 @@ bool EntityTreeElement::findDetailedRayIntersection(const glm::vec3& origin, con
|
|||
// now ask the entity if we actually intersect
|
||||
if (entity->supportsDetailedRayIntersection()) {
|
||||
if (entity->findDetailedRayIntersection(origin, direction, keepSearching, element, localDistance,
|
||||
localFace, localSurfaceNormal, intersectedObject, precisionPicking)) {
|
||||
localFace, localSurfaceNormal, localExtraInfo, precisionPicking)) {
|
||||
|
||||
if (localDistance < distance) {
|
||||
distance = localDistance;
|
||||
face = localFace;
|
||||
surfaceNormal = localSurfaceNormal;
|
||||
*intersectedObject = (void*)entity.get();
|
||||
somethingIntersected = true;
|
||||
extraInfo = localExtraInfo;
|
||||
entityID = entity->getEntityItemID();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -701,15 +705,14 @@ bool EntityTreeElement::findDetailedRayIntersection(const glm::vec3& origin, con
|
|||
distance = localDistance;
|
||||
face = localFace;
|
||||
surfaceNormal = glm::vec3(rotation * glm::vec4(localSurfaceNormal, 1.0f));
|
||||
*intersectedObject = (void*)entity.get();
|
||||
somethingIntersected = true;
|
||||
entityID = entity->getEntityItemID();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
entityNumber++;
|
||||
});
|
||||
return somethingIntersected;
|
||||
return entityID;
|
||||
}
|
||||
|
||||
// TODO: change this to use better bounding shape for entity than sphere
|
||||
|
|
|
@ -146,16 +146,16 @@ public:
|
|||
virtual bool deleteApproved() const override { return !hasEntities(); }
|
||||
|
||||
virtual bool canRayIntersect() const override { return hasEntities(); }
|
||||
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
virtual EntityItemID findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
bool& keepSearching, OctreeElementPointer& node, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal, const QVector<EntityItemID>& entityIdsToInclude,
|
||||
const QVector<EntityItemID>& entityIdsToDiscard, bool visibleOnly = false, bool collidableOnly = false,
|
||||
void** intersectedObject = NULL, bool precisionPicking = false);
|
||||
virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
const QVector<EntityItemID>& entityIdsToDiscard, bool visibleOnly, bool collidableOnly,
|
||||
QVariantMap& extraInfo, bool precisionPicking = false);
|
||||
virtual EntityItemID findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
bool& keepSearching, OctreeElementPointer& element, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal, const QVector<EntityItemID>& entityIdsToInclude,
|
||||
const QVector<EntityItemID>& entityIdsToDiscard, bool visibleOnly, bool collidableOnly,
|
||||
void** intersectedObject, bool precisionPicking, float distanceToElementCube);
|
||||
QVariantMap& extraInfo, bool precisionPicking, float distanceToElementCube);
|
||||
virtual bool findSpherePenetration(const glm::vec3& center, float radius,
|
||||
glm::vec3& penetration, void** penetratedObject) const override;
|
||||
|
||||
|
|
|
@ -300,7 +300,7 @@ void LightEntityItem::resetLightPropertiesChanged() {
|
|||
bool LightEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
bool& keepSearching, OctreeElementPointer& element, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal,
|
||||
void** intersectedObject, bool precisionPicking) const {
|
||||
QVariantMap& extraInfo, bool precisionPicking) const {
|
||||
|
||||
// TODO: consider if this is really what we want to do. We've made it so that "lights are pickable" is a global state
|
||||
// this is probably reasonable since there's typically only one tree you'd be picking on at a time. Technically we could
|
||||
|
|
|
@ -88,7 +88,7 @@ public:
|
|||
virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
bool& keepSearching, OctreeElementPointer& element, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal,
|
||||
void** intersectedObject, bool precisionPicking) const override;
|
||||
QVariantMap& extraInfo, bool precisionPicking) const override;
|
||||
|
||||
private:
|
||||
// properties of a light
|
||||
|
|
|
@ -63,7 +63,7 @@ class LineEntityItem : public EntityItem {
|
|||
virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
bool& keepSearching, OctreeElementPointer& element, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal,
|
||||
void** intersectedObject,
|
||||
QVariantMap& extraInfo,
|
||||
bool precisionPicking) const override { return false; }
|
||||
bool pointsChanged() const { return _pointsChanged; }
|
||||
void resetPointsChanged();
|
||||
|
|
|
@ -96,7 +96,7 @@ class PolyLineEntityItem : public EntityItem {
|
|||
virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
bool& keepSearching, OctreeElementPointer& element, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal,
|
||||
void** intersectedObject, bool precisionPicking) const override { return false; }
|
||||
QVariantMap& extraInfo, bool precisionPicking) const override { return false; }
|
||||
|
||||
// disable these external interfaces as PolyLineEntities caculate their own dimensions based on the points they contain
|
||||
virtual void setRegistrationPoint(const glm::vec3& value) override {}; // FIXME: this is suspicious!
|
||||
|
|
|
@ -47,7 +47,7 @@ class PolyVoxEntityItem : public EntityItem {
|
|||
virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
bool& keepSearching, OctreeElementPointer& element, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal,
|
||||
void** intersectedObject, bool precisionPicking) const override { return false; }
|
||||
QVariantMap& extraInfo, bool precisionPicking) const override { return false; }
|
||||
|
||||
virtual void debugDump() const override;
|
||||
|
||||
|
|
|
@ -223,7 +223,7 @@ bool ShapeEntityItem::supportsDetailedRayIntersection() const {
|
|||
bool ShapeEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
bool& keepSearching, OctreeElementPointer& element,
|
||||
float& distance, BoxFace& face, glm::vec3& surfaceNormal,
|
||||
void** intersectedObject, bool precisionPicking) const {
|
||||
QVariantMap& extraInfo, bool precisionPicking) const {
|
||||
// determine the ray in the frame of the entity transformed from a unit sphere
|
||||
glm::mat4 entityToWorldMatrix = getEntityToWorldMatrix();
|
||||
glm::mat4 worldToEntityMatrix = glm::inverse(entityToWorldMatrix);
|
||||
|
|
|
@ -94,7 +94,7 @@ public:
|
|||
bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
bool& keepSearching, OctreeElementPointer& element, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal,
|
||||
void** intersectedObject, bool precisionPicking) const override;
|
||||
QVariantMap& extraInfo, bool precisionPicking) const override;
|
||||
|
||||
void debugDump() const override;
|
||||
|
||||
|
|
|
@ -131,7 +131,7 @@ void TextEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBits
|
|||
bool TextEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
bool& keepSearching, OctreeElementPointer& element, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal,
|
||||
void** intersectedObject, bool precisionPicking) const {
|
||||
QVariantMap& extraInfo, bool precisionPicking) const {
|
||||
glm::vec3 dimensions = getScaledDimensions();
|
||||
glm::vec2 xyDimensions(dimensions.x, dimensions.y);
|
||||
glm::quat rotation = getWorldOrientation();
|
||||
|
|
|
@ -50,7 +50,7 @@ public:
|
|||
virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
bool& keepSearching, OctreeElementPointer& element, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal,
|
||||
void** intersectedObject, bool precisionPicking) const override;
|
||||
QVariantMap& extraInfo, bool precisionPicking) const override;
|
||||
|
||||
static const QString DEFAULT_TEXT;
|
||||
void setText(const QString& value);
|
||||
|
|
|
@ -108,7 +108,7 @@ void WebEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBitst
|
|||
bool WebEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
bool& keepSearching, OctreeElementPointer& element, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal,
|
||||
void** intersectedObject, bool precisionPicking) const {
|
||||
QVariantMap& extraInfo, bool precisionPicking) const {
|
||||
glm::vec3 dimensions = getScaledDimensions();
|
||||
glm::vec2 xyDimensions(dimensions.x, dimensions.y);
|
||||
glm::quat rotation = getWorldOrientation();
|
||||
|
|
|
@ -49,7 +49,7 @@ public:
|
|||
virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
bool& keepSearching, OctreeElementPointer& element, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal,
|
||||
void** intersectedObject, bool precisionPicking) const override;
|
||||
QVariantMap& extraInfo, bool precisionPicking) const override;
|
||||
|
||||
virtual void setSourceUrl(const QString& value);
|
||||
QString getSourceUrl() const;
|
||||
|
|
|
@ -298,7 +298,7 @@ void ZoneEntityItem::setCompoundShapeURL(const QString& url) {
|
|||
bool ZoneEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
bool& keepSearching, OctreeElementPointer& element, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal,
|
||||
void** intersectedObject, bool precisionPicking) const {
|
||||
QVariantMap& extraInfo, bool precisionPicking) const {
|
||||
|
||||
return _zonesArePickable;
|
||||
}
|
||||
|
|
|
@ -107,7 +107,7 @@ public:
|
|||
virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
bool& keepSearching, OctreeElementPointer& element, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal,
|
||||
void** intersectedObject, bool precisionPicking) const override;
|
||||
QVariantMap& extraInfo, bool precisionPicking) const override;
|
||||
|
||||
virtual void debugDump() const override;
|
||||
|
||||
|
|
|
@ -1733,8 +1733,18 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS
|
|||
qCDebug(modelformat) << "Joint not in model list: " << jointID;
|
||||
fbxCluster.jointIndex = 0;
|
||||
}
|
||||
|
||||
fbxCluster.inverseBindMatrix = glm::inverse(cluster.transformLink) * modelTransform;
|
||||
|
||||
// slam bottom row to (0, 0, 0, 1), we KNOW this is not a perspective matrix and
|
||||
// sometimes floating point fuzz can be introduced after the inverse.
|
||||
fbxCluster.inverseBindMatrix[0][3] = 0.0f;
|
||||
fbxCluster.inverseBindMatrix[1][3] = 0.0f;
|
||||
fbxCluster.inverseBindMatrix[2][3] = 0.0f;
|
||||
fbxCluster.inverseBindMatrix[3][3] = 1.0f;
|
||||
|
||||
fbxCluster.inverseBindTransform = Transform(fbxCluster.inverseBindMatrix);
|
||||
|
||||
extracted.mesh.clusters.append(fbxCluster);
|
||||
|
||||
// override the bind rotation with the transform link
|
||||
|
@ -1836,13 +1846,13 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS
|
|||
}
|
||||
|
||||
// now that we've accumulated the most relevant weights for each vertex
|
||||
// normalize and compress to 8-bits
|
||||
// normalize and compress to 16-bits
|
||||
extracted.mesh.clusterWeights.fill(0, numClusterIndices);
|
||||
int numVertices = extracted.mesh.vertices.size();
|
||||
for (int i = 0; i < numVertices; ++i) {
|
||||
int j = i * WEIGHTS_PER_VERTEX;
|
||||
|
||||
// normalize weights into uint8_t
|
||||
// normalize weights into uint16_t
|
||||
float totalWeight = weightAccumulators[j];
|
||||
for (int k = j + 1; k < j + WEIGHTS_PER_VERTEX; ++k) {
|
||||
totalWeight += weightAccumulators[k];
|
||||
|
@ -1881,6 +1891,9 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS
|
|||
|
||||
geometry.meshes.append(extracted.mesh);
|
||||
int meshIndex = geometry.meshes.size() - 1;
|
||||
if (extracted.mesh._mesh) {
|
||||
extracted.mesh._mesh->displayName = QString("%1#/mesh/%2").arg(url).arg(meshIndex);
|
||||
}
|
||||
meshIDsToMeshIndices.insert(it.key(), meshIndex);
|
||||
}
|
||||
|
||||
|
@ -1949,7 +1962,19 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
int i = 0;
|
||||
for (const auto& mesh : geometry.meshes) {
|
||||
auto name = geometry.getModelNameOfMesh(i++);
|
||||
if (!name.isEmpty()) {
|
||||
if (mesh._mesh) {
|
||||
mesh._mesh->displayName += "#" + name;
|
||||
} else {
|
||||
qDebug() << "modelName but no mesh._mesh" << name;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return geometryPtr;
|
||||
}
|
||||
|
||||
|
@ -1965,7 +1990,7 @@ FBXGeometry* readFBX(QIODevice* device, const QVariantHash& mapping, const QStri
|
|||
reader._loadLightmaps = loadLightmaps;
|
||||
reader._lightmapLevel = lightmapLevel;
|
||||
|
||||
qDebug() << "Reading FBX: " << url;
|
||||
qCDebug(modelformat) << "Reading FBX: " << url;
|
||||
|
||||
return reader.extractFBXGeometry(mapping, url);
|
||||
}
|
||||
|
|
|
@ -135,6 +135,8 @@ public:
|
|||
|
||||
static MeshPointer createIndexedTriangles_P3F(uint32_t numVertices, uint32_t numTriangles, const glm::vec3* vertices = nullptr, const uint32_t* indices = nullptr);
|
||||
|
||||
QString displayName;
|
||||
|
||||
protected:
|
||||
|
||||
gpu::Stream::FormatPointer _vertexFormat;
|
||||
|
|
|
@ -117,13 +117,14 @@ namespace graphics {
|
|||
|
||||
// Amount of background (skybox) to display, overriding the haze effect for the background
|
||||
float hazeBackgroundBlend{ INITIAL_HAZE_BACKGROUND_BLEND };
|
||||
|
||||
// The haze attenuation exponents used by both fragment and directional light attenuation
|
||||
float hazeRangeFactor{ convertHazeRangeToHazeRangeFactor(INITIAL_HAZE_RANGE) };
|
||||
float hazeHeightFactor{ convertHazeAltitudeToHazeAltitudeFactor(INITIAL_HAZE_HEIGHT) };
|
||||
|
||||
float hazeKeyLightRangeFactor{ convertHazeRangeToHazeRangeFactor(INITIAL_KEY_LIGHT_RANGE) };
|
||||
|
||||
float hazeKeyLightAltitudeFactor{ convertHazeAltitudeToHazeAltitudeFactor(INITIAL_KEY_LIGHT_ALTITUDE) };
|
||||
// Padding required to align the structure to sizeof(vec4)
|
||||
vec3 __padding;
|
||||
|
||||
Parameters() {}
|
||||
};
|
||||
|
|
|
@ -56,10 +56,10 @@ Light getLight(int index) {
|
|||
}
|
||||
|
||||
<@else@>
|
||||
uniform lightBuffer {
|
||||
uniform keyLightBuffer {
|
||||
Light light;
|
||||
};
|
||||
Light getLight() {
|
||||
Light getKeyLight() {
|
||||
return light;
|
||||
}
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
#include <QtNetwork/QNetworkDiskCache>
|
||||
|
||||
#include <shared/GlobalAppProperties.h>
|
||||
#include <shared/MiniPromises.h>
|
||||
|
||||
#include "AssetRequest.h"
|
||||
#include "AssetUpload.h"
|
||||
|
@ -72,7 +73,177 @@ void AssetClient::init() {
|
|||
networkAccessManager.setCache(cache);
|
||||
qInfo() << "ResourceManager disk cache setup at" << _cacheDir
|
||||
<< "(size:" << MAXIMUM_CACHE_SIZE / BYTES_PER_GIGABYTES << "GB)";
|
||||
} else {
|
||||
auto cache = qobject_cast<QNetworkDiskCache*>(networkAccessManager.cache());
|
||||
qInfo() << "ResourceManager disk cache already setup at" << cache->cacheDirectory()
|
||||
<< "(size:" << cache->maximumCacheSize() / BYTES_PER_GIGABYTES << "GB)";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace {
|
||||
const QString& CACHE_ERROR_MESSAGE{ "AssetClient::Error: %1 %2" };
|
||||
}
|
||||
|
||||
MiniPromise::Promise AssetClient::cacheInfoRequestAsync(MiniPromise::Promise deferred) {
|
||||
if (!deferred) {
|
||||
deferred = makePromise(__FUNCTION__); // create on caller's thread
|
||||
}
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "cacheInfoRequestAsync", Q_ARG(MiniPromise::Promise, deferred));
|
||||
} else {
|
||||
auto cache = qobject_cast<QNetworkDiskCache*>(NetworkAccessManager::getInstance().cache());
|
||||
if (cache) {
|
||||
deferred->resolve({
|
||||
{ "cacheDirectory", cache->cacheDirectory() },
|
||||
{ "cacheSize", cache->cacheSize() },
|
||||
{ "maximumCacheSize", cache->maximumCacheSize() },
|
||||
});
|
||||
} else {
|
||||
deferred->reject(CACHE_ERROR_MESSAGE.arg(__FUNCTION__).arg("cache unavailable"));
|
||||
}
|
||||
}
|
||||
return deferred;
|
||||
}
|
||||
|
||||
MiniPromise::Promise AssetClient::queryCacheMetaAsync(const QUrl& url, MiniPromise::Promise deferred) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "queryCacheMetaAsync", Q_ARG(const QUrl&, url), Q_ARG(MiniPromise::Promise, deferred));
|
||||
} else {
|
||||
auto cache = NetworkAccessManager::getInstance().cache();
|
||||
if (cache) {
|
||||
QNetworkCacheMetaData metaData = cache->metaData(url);
|
||||
QVariantMap attributes, rawHeaders;
|
||||
if (!metaData.isValid()) {
|
||||
deferred->reject("invalid cache entry", {
|
||||
{ "_url", url },
|
||||
{ "isValid", metaData.isValid() },
|
||||
{ "metaDataURL", metaData.url() },
|
||||
});
|
||||
} else {
|
||||
auto metaAttributes = metaData.attributes();
|
||||
foreach(QNetworkRequest::Attribute k, metaAttributes.keys()) {
|
||||
attributes[QString::number(k)] = metaAttributes[k];
|
||||
}
|
||||
for (const auto& i : metaData.rawHeaders()) {
|
||||
rawHeaders[i.first] = i.second;
|
||||
}
|
||||
deferred->resolve({
|
||||
{ "_url", url },
|
||||
{ "isValid", metaData.isValid() },
|
||||
{ "url", metaData.url() },
|
||||
{ "expirationDate", metaData.expirationDate() },
|
||||
{ "lastModified", metaData.lastModified().toString().isEmpty() ? QDateTime() : metaData.lastModified() },
|
||||
{ "saveToDisk", metaData.saveToDisk() },
|
||||
{ "attributes", attributes },
|
||||
{ "rawHeaders", rawHeaders },
|
||||
});
|
||||
}
|
||||
} else {
|
||||
deferred->reject(CACHE_ERROR_MESSAGE.arg(__FUNCTION__).arg("cache unavailable"));
|
||||
}
|
||||
}
|
||||
return deferred;
|
||||
}
|
||||
|
||||
MiniPromise::Promise AssetClient::loadFromCacheAsync(const QUrl& url, MiniPromise::Promise deferred) {
|
||||
auto errorMessage = CACHE_ERROR_MESSAGE.arg(__FUNCTION__);
|
||||
if (!deferred) {
|
||||
deferred = makePromise(__FUNCTION__); // create on caller's thread
|
||||
}
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "loadFromCacheAsync", Q_ARG(const QUrl&, url), Q_ARG(MiniPromise::Promise, deferred));
|
||||
} else {
|
||||
auto cache = NetworkAccessManager::getInstance().cache();
|
||||
if (cache) {
|
||||
MiniPromise::Promise metaRequest = makePromise(__FUNCTION__);
|
||||
queryCacheMetaAsync(url, metaRequest);
|
||||
metaRequest->finally([&](QString error, QVariantMap metadata) {
|
||||
QVariantMap result = {
|
||||
{ "url", url },
|
||||
{ "metadata", metadata },
|
||||
{ "data", QByteArray() },
|
||||
};
|
||||
if (!error.isEmpty()) {
|
||||
deferred->reject(error, result);
|
||||
return;
|
||||
}
|
||||
// caller is responsible for the deletion of the ioDevice, hence the unique_ptr
|
||||
auto ioDevice = std::unique_ptr<QIODevice>(cache->data(url));
|
||||
if (ioDevice) {
|
||||
result["data"] = ioDevice->readAll();
|
||||
} else {
|
||||
error = errorMessage.arg("error reading data");
|
||||
}
|
||||
deferred->handle(error, result);
|
||||
});
|
||||
} else {
|
||||
deferred->reject(errorMessage.arg("cache unavailable"));
|
||||
}
|
||||
}
|
||||
return deferred;
|
||||
}
|
||||
|
||||
namespace {
|
||||
// parse RFC 1123 HTTP date format
|
||||
QDateTime parseHttpDate(const QString& dateString) {
|
||||
QDateTime dt = QDateTime::fromString(dateString.left(25), "ddd, dd MMM yyyy HH:mm:ss");
|
||||
if (!dt.isValid()) {
|
||||
dt = QDateTime::fromString(dateString, Qt::ISODateWithMs);
|
||||
}
|
||||
if (!dt.isValid()) {
|
||||
qDebug() << __FUNCTION__ << "unrecognized date format:" << dateString;
|
||||
}
|
||||
dt.setTimeSpec(Qt::UTC);
|
||||
return dt;
|
||||
}
|
||||
QDateTime getHttpDateValue(const QVariantMap& headers, const QString& keyName, const QDateTime& defaultValue) {
|
||||
return headers.contains(keyName) ? parseHttpDate(headers[keyName].toString()) : defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
MiniPromise::Promise AssetClient::saveToCacheAsync(const QUrl& url, const QByteArray& data, const QVariantMap& headers, MiniPromise::Promise deferred) {
|
||||
if (!deferred) {
|
||||
deferred = makePromise(__FUNCTION__); // create on caller's thread
|
||||
}
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(
|
||||
this, "saveToCacheAsync", Qt::QueuedConnection,
|
||||
Q_ARG(const QUrl&, url),
|
||||
Q_ARG(const QByteArray&, data),
|
||||
Q_ARG(const QVariantMap&, headers),
|
||||
Q_ARG(MiniPromise::Promise, deferred));
|
||||
} else {
|
||||
auto cache = NetworkAccessManager::getInstance().cache();
|
||||
if (cache) {
|
||||
QNetworkCacheMetaData metaData;
|
||||
metaData.setUrl(url);
|
||||
metaData.setSaveToDisk(true);
|
||||
metaData.setLastModified(getHttpDateValue(headers, "last-modified", QDateTime::currentDateTimeUtc()));
|
||||
metaData.setExpirationDate(getHttpDateValue(headers, "expires", QDateTime())); // nil defaultValue == never expires
|
||||
auto ioDevice = cache->prepare(metaData);
|
||||
if (ioDevice) {
|
||||
ioDevice->write(data);
|
||||
cache->insert(ioDevice);
|
||||
qCDebug(asset_client) << url.toDisplayString() << "saved to disk cache ("<< data.size()<<" bytes)";
|
||||
deferred->resolve({
|
||||
{ "url", url },
|
||||
{ "success", true },
|
||||
{ "metaDataURL", metaData.url() },
|
||||
{ "byteLength", data.size() },
|
||||
{ "expirationDate", metaData.expirationDate() },
|
||||
{ "lastModified", metaData.lastModified().toString().isEmpty() ? QDateTime() : metaData.lastModified() },
|
||||
});
|
||||
} else {
|
||||
auto error = QString("Could not save %1 to disk cache").arg(url.toDisplayString());
|
||||
qCWarning(asset_client) << error;
|
||||
deferred->reject(CACHE_ERROR_MESSAGE.arg(__FUNCTION__).arg(error));
|
||||
}
|
||||
} else {
|
||||
deferred->reject(CACHE_ERROR_MESSAGE.arg(__FUNCTION__).arg("unavailable"));
|
||||
}
|
||||
}
|
||||
return deferred;
|
||||
}
|
||||
|
||||
void AssetClient::cacheInfoRequest(QObject* reciever, QString slot) {
|
||||
|
@ -113,7 +284,7 @@ void AssetClient::handleAssetMappingOperationReply(QSharedPointer<ReceivedMessag
|
|||
MessageID messageID;
|
||||
message->readPrimitive(&messageID);
|
||||
|
||||
AssetServerError error;
|
||||
AssetUtils::AssetServerError error;
|
||||
message->readPrimitive(&error);
|
||||
|
||||
// Check if we have any pending requests for this node
|
||||
|
@ -149,7 +320,7 @@ bool haveAssetServer() {
|
|||
return true;
|
||||
}
|
||||
|
||||
GetMappingRequest* AssetClient::createGetMappingRequest(const AssetPath& path) {
|
||||
GetMappingRequest* AssetClient::createGetMappingRequest(const AssetUtils::AssetPath& path) {
|
||||
auto request = new GetMappingRequest(path);
|
||||
|
||||
request->moveToThread(thread());
|
||||
|
@ -165,7 +336,7 @@ GetAllMappingsRequest* AssetClient::createGetAllMappingsRequest() {
|
|||
return request;
|
||||
}
|
||||
|
||||
DeleteMappingsRequest* AssetClient::createDeleteMappingsRequest(const AssetPathList& paths) {
|
||||
DeleteMappingsRequest* AssetClient::createDeleteMappingsRequest(const AssetUtils::AssetPathList& paths) {
|
||||
auto request = new DeleteMappingsRequest(paths);
|
||||
|
||||
request->moveToThread(thread());
|
||||
|
@ -173,7 +344,7 @@ DeleteMappingsRequest* AssetClient::createDeleteMappingsRequest(const AssetPathL
|
|||
return request;
|
||||
}
|
||||
|
||||
SetMappingRequest* AssetClient::createSetMappingRequest(const AssetPath& path, const AssetHash& hash) {
|
||||
SetMappingRequest* AssetClient::createSetMappingRequest(const AssetUtils::AssetPath& path, const AssetUtils::AssetHash& hash) {
|
||||
auto request = new SetMappingRequest(path, hash);
|
||||
|
||||
request->moveToThread(thread());
|
||||
|
@ -181,7 +352,7 @@ SetMappingRequest* AssetClient::createSetMappingRequest(const AssetPath& path, c
|
|||
return request;
|
||||
}
|
||||
|
||||
RenameMappingRequest* AssetClient::createRenameMappingRequest(const AssetPath& oldPath, const AssetPath& newPath) {
|
||||
RenameMappingRequest* AssetClient::createRenameMappingRequest(const AssetUtils::AssetPath& oldPath, const AssetUtils::AssetPath& newPath) {
|
||||
auto request = new RenameMappingRequest(oldPath, newPath);
|
||||
|
||||
request->moveToThread(thread());
|
||||
|
@ -189,7 +360,7 @@ RenameMappingRequest* AssetClient::createRenameMappingRequest(const AssetPath& o
|
|||
return request;
|
||||
}
|
||||
|
||||
SetBakingEnabledRequest* AssetClient::createSetBakingEnabledRequest(const AssetPathList& path, bool enabled) {
|
||||
SetBakingEnabledRequest* AssetClient::createSetBakingEnabledRequest(const AssetUtils::AssetPathList& path, bool enabled) {
|
||||
auto bakingEnabledRequest = new SetBakingEnabledRequest(path, enabled);
|
||||
|
||||
bakingEnabledRequest->moveToThread(thread());
|
||||
|
@ -197,7 +368,7 @@ SetBakingEnabledRequest* AssetClient::createSetBakingEnabledRequest(const AssetP
|
|||
return bakingEnabledRequest;
|
||||
}
|
||||
|
||||
AssetRequest* AssetClient::createRequest(const AssetHash& hash, const ByteRange& byteRange) {
|
||||
AssetRequest* AssetClient::createRequest(const AssetUtils::AssetHash& hash, const ByteRange& byteRange) {
|
||||
auto request = new AssetRequest(hash, byteRange);
|
||||
|
||||
// Move to the AssetClient thread in case we are not currently on that thread (which will usually be the case)
|
||||
|
@ -222,11 +393,11 @@ AssetUpload* AssetClient::createUpload(const QByteArray& data) {
|
|||
return upload;
|
||||
}
|
||||
|
||||
MessageID AssetClient::getAsset(const QString& hash, DataOffset start, DataOffset end,
|
||||
MessageID AssetClient::getAsset(const QString& hash, AssetUtils::DataOffset start, AssetUtils::DataOffset end,
|
||||
ReceivedAssetCallback callback, ProgressCallback progressCallback) {
|
||||
Q_ASSERT(QThread::currentThread() == thread());
|
||||
|
||||
if (hash.length() != SHA256_HASH_HEX_LENGTH) {
|
||||
if (hash.length() != AssetUtils::SHA256_HASH_HEX_LENGTH) {
|
||||
qCWarning(asset_client) << "Invalid hash size";
|
||||
return false;
|
||||
}
|
||||
|
@ -238,7 +409,7 @@ MessageID AssetClient::getAsset(const QString& hash, DataOffset start, DataOffse
|
|||
|
||||
auto messageID = ++_currentID;
|
||||
|
||||
auto payloadSize = sizeof(messageID) + SHA256_HASH_LENGTH + sizeof(start) + sizeof(end);
|
||||
auto payloadSize = sizeof(messageID) + AssetUtils::SHA256_HASH_LENGTH + sizeof(start) + sizeof(end);
|
||||
auto packet = NLPacket::create(PacketType::AssetGet, payloadSize, true);
|
||||
|
||||
qCDebug(asset_client) << "Requesting data from" << start << "to" << end << "of" << hash << "from asset-server.";
|
||||
|
@ -257,7 +428,7 @@ MessageID AssetClient::getAsset(const QString& hash, DataOffset start, DataOffse
|
|||
}
|
||||
}
|
||||
|
||||
callback(false, AssetServerError::NoError, QByteArray());
|
||||
callback(false, AssetUtils::AssetServerError::NoError, QByteArray());
|
||||
return INVALID_MESSAGE_ID;
|
||||
}
|
||||
|
||||
|
@ -270,7 +441,7 @@ MessageID AssetClient::getAssetInfo(const QString& hash, GetInfoCallback callbac
|
|||
if (assetServer) {
|
||||
auto messageID = ++_currentID;
|
||||
|
||||
auto payloadSize = sizeof(messageID) + SHA256_HASH_LENGTH;
|
||||
auto payloadSize = sizeof(messageID) + AssetUtils::SHA256_HASH_LENGTH;
|
||||
auto packet = NLPacket::create(PacketType::AssetGetInfo, payloadSize, true);
|
||||
|
||||
packet->writePrimitive(messageID);
|
||||
|
@ -283,7 +454,7 @@ MessageID AssetClient::getAssetInfo(const QString& hash, GetInfoCallback callbac
|
|||
}
|
||||
}
|
||||
|
||||
callback(false, AssetServerError::NoError, { "", 0 });
|
||||
callback(false, AssetUtils::AssetServerError::NoError, { "", 0 });
|
||||
return INVALID_MESSAGE_ID;
|
||||
}
|
||||
|
||||
|
@ -292,14 +463,14 @@ void AssetClient::handleAssetGetInfoReply(QSharedPointer<ReceivedMessage> messag
|
|||
|
||||
MessageID messageID;
|
||||
message->readPrimitive(&messageID);
|
||||
auto assetHash = message->read(SHA256_HASH_LENGTH);
|
||||
auto assetHash = message->read(AssetUtils::SHA256_HASH_LENGTH);
|
||||
|
||||
AssetServerError error;
|
||||
AssetUtils::AssetServerError error;
|
||||
message->readPrimitive(&error);
|
||||
|
||||
AssetInfo info { assetHash.toHex(), 0 };
|
||||
|
||||
if (error == AssetServerError::NoError) {
|
||||
if (error == AssetUtils::AssetServerError::NoError) {
|
||||
message->readPrimitive(&info.size);
|
||||
}
|
||||
|
||||
|
@ -326,16 +497,16 @@ void AssetClient::handleAssetGetInfoReply(QSharedPointer<ReceivedMessage> messag
|
|||
void AssetClient::handleAssetGetReply(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
|
||||
Q_ASSERT(QThread::currentThread() == thread());
|
||||
|
||||
auto assetHash = message->readHead(SHA256_HASH_LENGTH);
|
||||
auto assetHash = message->readHead(AssetUtils::SHA256_HASH_LENGTH);
|
||||
qCDebug(asset_client) << "Got reply for asset: " << assetHash.toHex();
|
||||
|
||||
MessageID messageID;
|
||||
message->readHeadPrimitive(&messageID);
|
||||
|
||||
AssetServerError error;
|
||||
AssetUtils::AssetServerError error;
|
||||
message->readHeadPrimitive(&error);
|
||||
|
||||
DataOffset length = 0;
|
||||
AssetUtils::DataOffset length = 0;
|
||||
if (!error) {
|
||||
message->readHeadPrimitive(&length);
|
||||
} else {
|
||||
|
@ -388,7 +559,7 @@ void AssetClient::handleAssetGetReply(QSharedPointer<ReceivedMessage> message, S
|
|||
}
|
||||
|
||||
void AssetClient::handleProgressCallback(const QWeakPointer<Node>& node, MessageID messageID,
|
||||
qint64 size, DataOffset length) {
|
||||
qint64 size, AssetUtils::DataOffset length) {
|
||||
auto senderNode = node.toStrongRef();
|
||||
|
||||
if (!senderNode) {
|
||||
|
@ -414,7 +585,7 @@ void AssetClient::handleProgressCallback(const QWeakPointer<Node>& node, Message
|
|||
callbacks.progressCallback(size, length);
|
||||
}
|
||||
|
||||
void AssetClient::handleCompleteCallback(const QWeakPointer<Node>& node, MessageID messageID, DataOffset length) {
|
||||
void AssetClient::handleCompleteCallback(const QWeakPointer<Node>& node, MessageID messageID, AssetUtils::DataOffset length) {
|
||||
auto senderNode = node.toStrongRef();
|
||||
|
||||
if (!senderNode) {
|
||||
|
@ -448,9 +619,9 @@ void AssetClient::handleCompleteCallback(const QWeakPointer<Node>& node, Message
|
|||
}
|
||||
|
||||
if (message->failed() || length != message->getBytesLeftToRead()) {
|
||||
callbacks.completeCallback(false, AssetServerError::NoError, QByteArray());
|
||||
callbacks.completeCallback(false, AssetUtils::AssetServerError::NoError, QByteArray());
|
||||
} else {
|
||||
callbacks.completeCallback(true, AssetServerError::NoError, message->readAll());
|
||||
callbacks.completeCallback(true, AssetUtils::AssetServerError::NoError, message->readAll());
|
||||
}
|
||||
|
||||
// We should never get to this point without the associated senderNode and messageID
|
||||
|
@ -461,7 +632,7 @@ void AssetClient::handleCompleteCallback(const QWeakPointer<Node>& node, Message
|
|||
}
|
||||
|
||||
|
||||
MessageID AssetClient::getAssetMapping(const AssetPath& path, MappingOperationCallback callback) {
|
||||
MessageID AssetClient::getAssetMapping(const AssetUtils::AssetPath& path, MappingOperationCallback callback) {
|
||||
Q_ASSERT(QThread::currentThread() == thread());
|
||||
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
@ -473,7 +644,7 @@ MessageID AssetClient::getAssetMapping(const AssetPath& path, MappingOperationCa
|
|||
auto messageID = ++_currentID;
|
||||
packetList->writePrimitive(messageID);
|
||||
|
||||
packetList->writePrimitive(AssetMappingOperationType::Get);
|
||||
packetList->writePrimitive(AssetUtils::AssetMappingOperationType::Get);
|
||||
|
||||
packetList->writeString(path);
|
||||
|
||||
|
@ -484,7 +655,7 @@ MessageID AssetClient::getAssetMapping(const AssetPath& path, MappingOperationCa
|
|||
}
|
||||
}
|
||||
|
||||
callback(false, AssetServerError::NoError, QSharedPointer<ReceivedMessage>());
|
||||
callback(false, AssetUtils::AssetServerError::NoError, QSharedPointer<ReceivedMessage>());
|
||||
return INVALID_MESSAGE_ID;
|
||||
}
|
||||
|
||||
|
@ -500,7 +671,7 @@ MessageID AssetClient::getAllAssetMappings(MappingOperationCallback callback) {
|
|||
auto messageID = ++_currentID;
|
||||
packetList->writePrimitive(messageID);
|
||||
|
||||
packetList->writePrimitive(AssetMappingOperationType::GetAll);
|
||||
packetList->writePrimitive(AssetUtils::AssetMappingOperationType::GetAll);
|
||||
|
||||
if (nodeList->sendPacketList(std::move(packetList), *assetServer) != -1) {
|
||||
_pendingMappingRequests[assetServer][messageID] = callback;
|
||||
|
@ -509,11 +680,11 @@ MessageID AssetClient::getAllAssetMappings(MappingOperationCallback callback) {
|
|||
}
|
||||
}
|
||||
|
||||
callback(false, AssetServerError::NoError, QSharedPointer<ReceivedMessage>());
|
||||
callback(false, AssetUtils::AssetServerError::NoError, QSharedPointer<ReceivedMessage>());
|
||||
return INVALID_MESSAGE_ID;
|
||||
}
|
||||
|
||||
MessageID AssetClient::deleteAssetMappings(const AssetPathList& paths, MappingOperationCallback callback) {
|
||||
MessageID AssetClient::deleteAssetMappings(const AssetUtils::AssetPathList& paths, MappingOperationCallback callback) {
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
SharedNodePointer assetServer = nodeList->soloNodeOfType(NodeType::AssetServer);
|
||||
|
||||
|
@ -523,7 +694,7 @@ MessageID AssetClient::deleteAssetMappings(const AssetPathList& paths, MappingOp
|
|||
auto messageID = ++_currentID;
|
||||
packetList->writePrimitive(messageID);
|
||||
|
||||
packetList->writePrimitive(AssetMappingOperationType::Delete);
|
||||
packetList->writePrimitive(AssetUtils::AssetMappingOperationType::Delete);
|
||||
|
||||
packetList->writePrimitive(int(paths.size()));
|
||||
|
||||
|
@ -538,11 +709,11 @@ MessageID AssetClient::deleteAssetMappings(const AssetPathList& paths, MappingOp
|
|||
}
|
||||
}
|
||||
|
||||
callback(false, AssetServerError::NoError, QSharedPointer<ReceivedMessage>());
|
||||
callback(false, AssetUtils::AssetServerError::NoError, QSharedPointer<ReceivedMessage>());
|
||||
return INVALID_MESSAGE_ID;
|
||||
}
|
||||
|
||||
MessageID AssetClient::setAssetMapping(const QString& path, const AssetHash& hash, MappingOperationCallback callback) {
|
||||
MessageID AssetClient::setAssetMapping(const QString& path, const AssetUtils::AssetHash& hash, MappingOperationCallback callback) {
|
||||
Q_ASSERT(QThread::currentThread() == thread());
|
||||
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
@ -554,7 +725,7 @@ MessageID AssetClient::setAssetMapping(const QString& path, const AssetHash& has
|
|||
auto messageID = ++_currentID;
|
||||
packetList->writePrimitive(messageID);
|
||||
|
||||
packetList->writePrimitive(AssetMappingOperationType::Set);
|
||||
packetList->writePrimitive(AssetUtils::AssetMappingOperationType::Set);
|
||||
|
||||
packetList->writeString(path);
|
||||
packetList->write(QByteArray::fromHex(hash.toUtf8()));
|
||||
|
@ -566,11 +737,11 @@ MessageID AssetClient::setAssetMapping(const QString& path, const AssetHash& has
|
|||
}
|
||||
}
|
||||
|
||||
callback(false, AssetServerError::NoError, QSharedPointer<ReceivedMessage>());
|
||||
callback(false, AssetUtils::AssetServerError::NoError, QSharedPointer<ReceivedMessage>());
|
||||
return INVALID_MESSAGE_ID;
|
||||
}
|
||||
|
||||
MessageID AssetClient::renameAssetMapping(const AssetPath& oldPath, const AssetPath& newPath, MappingOperationCallback callback) {
|
||||
MessageID AssetClient::renameAssetMapping(const AssetUtils::AssetPath& oldPath, const AssetUtils::AssetPath& newPath, MappingOperationCallback callback) {
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
SharedNodePointer assetServer = nodeList->soloNodeOfType(NodeType::AssetServer);
|
||||
|
||||
|
@ -580,7 +751,7 @@ MessageID AssetClient::renameAssetMapping(const AssetPath& oldPath, const AssetP
|
|||
auto messageID = ++_currentID;
|
||||
packetList->writePrimitive(messageID);
|
||||
|
||||
packetList->writePrimitive(AssetMappingOperationType::Rename);
|
||||
packetList->writePrimitive(AssetUtils::AssetMappingOperationType::Rename);
|
||||
|
||||
packetList->writeString(oldPath);
|
||||
packetList->writeString(newPath);
|
||||
|
@ -593,11 +764,11 @@ MessageID AssetClient::renameAssetMapping(const AssetPath& oldPath, const AssetP
|
|||
}
|
||||
}
|
||||
|
||||
callback(false, AssetServerError::NoError, QSharedPointer<ReceivedMessage>());
|
||||
callback(false, AssetUtils::AssetServerError::NoError, QSharedPointer<ReceivedMessage>());
|
||||
return INVALID_MESSAGE_ID;
|
||||
}
|
||||
|
||||
MessageID AssetClient::setBakingEnabled(const AssetPathList& paths, bool enabled, MappingOperationCallback callback) {
|
||||
MessageID AssetClient::setBakingEnabled(const AssetUtils::AssetPathList& paths, bool enabled, MappingOperationCallback callback) {
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
SharedNodePointer assetServer = nodeList->soloNodeOfType(NodeType::AssetServer);
|
||||
|
||||
|
@ -607,7 +778,7 @@ MessageID AssetClient::setBakingEnabled(const AssetPathList& paths, bool enabled
|
|||
auto messageID = ++_currentID;
|
||||
packetList->writePrimitive(messageID);
|
||||
|
||||
packetList->writePrimitive(AssetMappingOperationType::SetBakingEnabled);
|
||||
packetList->writePrimitive(AssetUtils::AssetMappingOperationType::SetBakingEnabled);
|
||||
|
||||
packetList->writePrimitive(enabled);
|
||||
|
||||
|
@ -624,7 +795,7 @@ MessageID AssetClient::setBakingEnabled(const AssetPathList& paths, bool enabled
|
|||
}
|
||||
}
|
||||
|
||||
callback(false, AssetServerError::NoError, QSharedPointer<ReceivedMessage>());
|
||||
callback(false, AssetUtils::AssetServerError::NoError, QSharedPointer<ReceivedMessage>());
|
||||
return INVALID_MESSAGE_ID;
|
||||
}
|
||||
|
||||
|
@ -708,7 +879,7 @@ MessageID AssetClient::uploadAsset(const QByteArray& data, UploadResultCallback
|
|||
}
|
||||
}
|
||||
|
||||
callback(false, AssetServerError::NoError, QString());
|
||||
callback(false, AssetUtils::AssetServerError::NoError, QString());
|
||||
return INVALID_MESSAGE_ID;
|
||||
}
|
||||
|
||||
|
@ -718,7 +889,7 @@ void AssetClient::handleAssetUploadReply(QSharedPointer<ReceivedMessage> message
|
|||
MessageID messageID;
|
||||
message->readPrimitive(&messageID);
|
||||
|
||||
AssetServerError error;
|
||||
AssetUtils::AssetServerError error;
|
||||
message->readPrimitive(&error);
|
||||
|
||||
QString hashString;
|
||||
|
@ -726,7 +897,7 @@ void AssetClient::handleAssetUploadReply(QSharedPointer<ReceivedMessage> message
|
|||
if (error) {
|
||||
qCWarning(asset_client) << "Error uploading file to asset server";
|
||||
} else {
|
||||
auto hash = message->read(SHA256_HASH_LENGTH);
|
||||
auto hash = message->read(AssetUtils::SHA256_HASH_LENGTH);
|
||||
hashString = hash.toHex();
|
||||
|
||||
qCDebug(asset_client) << "Successfully uploaded asset to asset-server - SHA256 hash is " << hashString;
|
||||
|
@ -765,7 +936,7 @@ void AssetClient::handleNodeKilled(SharedNodePointer node) {
|
|||
auto messageMapIt = _pendingUploads.find(node);
|
||||
if (messageMapIt != _pendingUploads.end()) {
|
||||
for (const auto& value : messageMapIt->second) {
|
||||
value.second(false, AssetServerError::NoError, "");
|
||||
value.second(false, AssetUtils::AssetServerError::NoError, "");
|
||||
}
|
||||
messageMapIt->second.clear();
|
||||
}
|
||||
|
@ -797,7 +968,7 @@ void AssetClient::forceFailureOfPendingRequests(SharedNodePointer node) {
|
|||
disconnect(message.data(), nullptr, this, nullptr);
|
||||
}
|
||||
|
||||
value.second.completeCallback(false, AssetServerError::NoError, QByteArray());
|
||||
value.second.completeCallback(false, AssetUtils::AssetServerError::NoError, QByteArray());
|
||||
}
|
||||
messageMapIt->second.clear();
|
||||
}
|
||||
|
@ -808,7 +979,7 @@ void AssetClient::forceFailureOfPendingRequests(SharedNodePointer node) {
|
|||
if (messageMapIt != _pendingInfoRequests.end()) {
|
||||
AssetInfo info { "", 0 };
|
||||
for (const auto& value : messageMapIt->second) {
|
||||
value.second(false, AssetServerError::NoError, info);
|
||||
value.second(false, AssetUtils::AssetServerError::NoError, info);
|
||||
}
|
||||
messageMapIt->second.clear();
|
||||
}
|
||||
|
@ -818,7 +989,7 @@ void AssetClient::forceFailureOfPendingRequests(SharedNodePointer node) {
|
|||
auto messageMapIt = _pendingMappingRequests.find(node);
|
||||
if (messageMapIt != _pendingMappingRequests.end()) {
|
||||
for (const auto& value : messageMapIt->second) {
|
||||
value.second(false, AssetServerError::NoError, QSharedPointer<ReceivedMessage>());
|
||||
value.second(false, AssetUtils::AssetServerError::NoError, QSharedPointer<ReceivedMessage>());
|
||||
}
|
||||
messageMapIt->second.clear();
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
#include <map>
|
||||
|
||||
#include <DependencyManager.h>
|
||||
#include <shared/MiniPromises.h>
|
||||
|
||||
#include "AssetUtils.h"
|
||||
#include "ByteRange.h"
|
||||
|
@ -41,10 +42,10 @@ struct AssetInfo {
|
|||
int64_t size;
|
||||
};
|
||||
|
||||
using MappingOperationCallback = std::function<void(bool responseReceived, AssetServerError serverError, QSharedPointer<ReceivedMessage> message)>;
|
||||
using ReceivedAssetCallback = std::function<void(bool responseReceived, AssetServerError serverError, const QByteArray& data)>;
|
||||
using GetInfoCallback = std::function<void(bool responseReceived, AssetServerError serverError, AssetInfo info)>;
|
||||
using UploadResultCallback = std::function<void(bool responseReceived, AssetServerError serverError, const QString& hash)>;
|
||||
using MappingOperationCallback = std::function<void(bool responseReceived, AssetUtils::AssetServerError serverError, QSharedPointer<ReceivedMessage> message)>;
|
||||
using ReceivedAssetCallback = std::function<void(bool responseReceived, AssetUtils::AssetServerError serverError, const QByteArray& data)>;
|
||||
using GetInfoCallback = std::function<void(bool responseReceived, AssetUtils::AssetServerError serverError, AssetInfo info)>;
|
||||
using UploadResultCallback = std::function<void(bool responseReceived, AssetUtils::AssetServerError serverError, const QString& hash)>;
|
||||
using ProgressCallback = std::function<void(qint64 totalReceived, qint64 total)>;
|
||||
|
||||
class AssetClient : public QObject, public Dependency {
|
||||
|
@ -52,13 +53,13 @@ class AssetClient : public QObject, public Dependency {
|
|||
public:
|
||||
AssetClient();
|
||||
|
||||
Q_INVOKABLE GetMappingRequest* createGetMappingRequest(const AssetPath& path);
|
||||
Q_INVOKABLE GetMappingRequest* createGetMappingRequest(const AssetUtils::AssetPath& path);
|
||||
Q_INVOKABLE GetAllMappingsRequest* createGetAllMappingsRequest();
|
||||
Q_INVOKABLE DeleteMappingsRequest* createDeleteMappingsRequest(const AssetPathList& paths);
|
||||
Q_INVOKABLE SetMappingRequest* createSetMappingRequest(const AssetPath& path, const AssetHash& hash);
|
||||
Q_INVOKABLE RenameMappingRequest* createRenameMappingRequest(const AssetPath& oldPath, const AssetPath& newPath);
|
||||
Q_INVOKABLE SetBakingEnabledRequest* createSetBakingEnabledRequest(const AssetPathList& path, bool enabled);
|
||||
Q_INVOKABLE AssetRequest* createRequest(const AssetHash& hash, const ByteRange& byteRange = ByteRange());
|
||||
Q_INVOKABLE DeleteMappingsRequest* createDeleteMappingsRequest(const AssetUtils::AssetPathList& paths);
|
||||
Q_INVOKABLE SetMappingRequest* createSetMappingRequest(const AssetUtils::AssetPath& path, const AssetUtils::AssetHash& hash);
|
||||
Q_INVOKABLE RenameMappingRequest* createRenameMappingRequest(const AssetUtils::AssetPath& oldPath, const AssetUtils::AssetPath& newPath);
|
||||
Q_INVOKABLE SetBakingEnabledRequest* createSetBakingEnabledRequest(const AssetUtils::AssetPathList& path, bool enabled);
|
||||
Q_INVOKABLE AssetRequest* createRequest(const AssetUtils::AssetHash& hash, const ByteRange& byteRange = ByteRange());
|
||||
Q_INVOKABLE AssetUpload* createUpload(const QString& filename);
|
||||
Q_INVOKABLE AssetUpload* createUpload(const QByteArray& data);
|
||||
|
||||
|
@ -66,6 +67,10 @@ public slots:
|
|||
void init();
|
||||
|
||||
void cacheInfoRequest(QObject* reciever, QString slot);
|
||||
MiniPromise::Promise cacheInfoRequestAsync(MiniPromise::Promise deferred = nullptr);
|
||||
MiniPromise::Promise queryCacheMetaAsync(const QUrl& url, MiniPromise::Promise deferred = nullptr);
|
||||
MiniPromise::Promise loadFromCacheAsync(const QUrl& url, MiniPromise::Promise deferred = nullptr);
|
||||
MiniPromise::Promise saveToCacheAsync(const QUrl& url, const QByteArray& data, const QVariantMap& metadata = QVariantMap(), MiniPromise::Promise deferred = nullptr);
|
||||
void clearCache();
|
||||
|
||||
private slots:
|
||||
|
@ -78,15 +83,15 @@ private slots:
|
|||
void handleNodeClientConnectionReset(SharedNodePointer node);
|
||||
|
||||
private:
|
||||
MessageID getAssetMapping(const AssetHash& hash, MappingOperationCallback callback);
|
||||
MessageID getAssetMapping(const AssetUtils::AssetHash& hash, MappingOperationCallback callback);
|
||||
MessageID getAllAssetMappings(MappingOperationCallback callback);
|
||||
MessageID setAssetMapping(const QString& path, const AssetHash& hash, MappingOperationCallback callback);
|
||||
MessageID deleteAssetMappings(const AssetPathList& paths, MappingOperationCallback callback);
|
||||
MessageID renameAssetMapping(const AssetPath& oldPath, const AssetPath& newPath, MappingOperationCallback callback);
|
||||
MessageID setBakingEnabled(const AssetPathList& paths, bool enabled, MappingOperationCallback callback);
|
||||
MessageID setAssetMapping(const QString& path, const AssetUtils::AssetHash& hash, MappingOperationCallback callback);
|
||||
MessageID deleteAssetMappings(const AssetUtils::AssetPathList& paths, MappingOperationCallback callback);
|
||||
MessageID renameAssetMapping(const AssetUtils::AssetPath& oldPath, const AssetUtils::AssetPath& newPath, MappingOperationCallback callback);
|
||||
MessageID setBakingEnabled(const AssetUtils::AssetPathList& paths, bool enabled, MappingOperationCallback callback);
|
||||
|
||||
MessageID getAssetInfo(const QString& hash, GetInfoCallback callback);
|
||||
MessageID getAsset(const QString& hash, DataOffset start, DataOffset end,
|
||||
MessageID getAsset(const QString& hash, AssetUtils::DataOffset start, AssetUtils::DataOffset end,
|
||||
ReceivedAssetCallback callback, ProgressCallback progressCallback);
|
||||
MessageID uploadAsset(const QByteArray& data, UploadResultCallback callback);
|
||||
|
||||
|
@ -95,8 +100,8 @@ private:
|
|||
bool cancelGetAssetRequest(MessageID id);
|
||||
bool cancelUploadAssetRequest(MessageID id);
|
||||
|
||||
void handleProgressCallback(const QWeakPointer<Node>& node, MessageID messageID, qint64 size, DataOffset length);
|
||||
void handleCompleteCallback(const QWeakPointer<Node>& node, MessageID messageID, DataOffset length);
|
||||
void handleProgressCallback(const QWeakPointer<Node>& node, MessageID messageID, qint64 size, AssetUtils::DataOffset length);
|
||||
void handleCompleteCallback(const QWeakPointer<Node>& node, MessageID messageID, AssetUtils::DataOffset length);
|
||||
|
||||
void forceFailureOfPendingRequests(SharedNodePointer node);
|
||||
|
||||
|
|
|
@ -52,7 +52,7 @@ void AssetRequest::start() {
|
|||
}
|
||||
|
||||
// in case we haven't parsed a valid hash, return an error now
|
||||
if (!isValidHash(_hash)) {
|
||||
if (!AssetUtils::isValidHash(_hash)) {
|
||||
_error = InvalidHash;
|
||||
_state = Finished;
|
||||
|
||||
|
@ -61,7 +61,7 @@ void AssetRequest::start() {
|
|||
}
|
||||
|
||||
// Try to load from cache
|
||||
_data = loadFromCache(getUrl());
|
||||
_data = AssetUtils::loadFromCache(getUrl());
|
||||
if (!_data.isNull()) {
|
||||
_error = NoError;
|
||||
|
||||
|
@ -80,7 +80,7 @@ void AssetRequest::start() {
|
|||
auto hash = _hash;
|
||||
|
||||
_assetRequestID = assetClient->getAsset(_hash, _byteRange.fromInclusive, _byteRange.toExclusive,
|
||||
[this, that, hash](bool responseReceived, AssetServerError serverError, const QByteArray& data) {
|
||||
[this, that, hash](bool responseReceived, AssetUtils::AssetServerError serverError, const QByteArray& data) {
|
||||
|
||||
if (!that) {
|
||||
qCWarning(asset_client) << "Got reply for dead asset request " << hash << "- error code" << _error;
|
||||
|
@ -91,12 +91,12 @@ void AssetRequest::start() {
|
|||
|
||||
if (!responseReceived) {
|
||||
_error = NetworkError;
|
||||
} else if (serverError != AssetServerError::NoError) {
|
||||
} else if (serverError != AssetUtils::AssetServerError::NoError) {
|
||||
switch (serverError) {
|
||||
case AssetServerError::AssetNotFound:
|
||||
case AssetUtils::AssetServerError::AssetNotFound:
|
||||
_error = NotFound;
|
||||
break;
|
||||
case AssetServerError::InvalidByteRange:
|
||||
case AssetUtils::AssetServerError::InvalidByteRange:
|
||||
_error = InvalidByteRange;
|
||||
break;
|
||||
default:
|
||||
|
@ -104,7 +104,7 @@ void AssetRequest::start() {
|
|||
break;
|
||||
}
|
||||
} else {
|
||||
if (!_byteRange.isSet() && hashData(data).toHex() != _hash) {
|
||||
if (!_byteRange.isSet() && AssetUtils::hashData(data).toHex() != _hash) {
|
||||
// the hash of the received data does not match what we expect, so we return an error
|
||||
_error = HashVerificationFailed;
|
||||
}
|
||||
|
@ -115,7 +115,7 @@ void AssetRequest::start() {
|
|||
emit progress(_totalReceived, data.size());
|
||||
|
||||
if (!_byteRange.isSet()) {
|
||||
saveToCache(getUrl(), data);
|
||||
AssetUtils::saveToCache(getUrl(), data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -134,3 +134,14 @@ void AssetRequest::start() {
|
|||
emit progress(totalReceived, total);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
const QString AssetRequest::getErrorString() const {
|
||||
QString result;
|
||||
if (_error != Error::NoError) {
|
||||
QVariant v;
|
||||
v.setValue(_error);
|
||||
result = v.toString(); // courtesy of Q_ENUM
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -42,7 +42,7 @@ public:
|
|||
NetworkError,
|
||||
UnknownError
|
||||
};
|
||||
|
||||
Q_ENUM(Error)
|
||||
AssetRequest(const QString& hash, const ByteRange& byteRange = ByteRange());
|
||||
virtual ~AssetRequest() override;
|
||||
|
||||
|
@ -51,7 +51,8 @@ public:
|
|||
const QByteArray& getData() const { return _data; }
|
||||
const State& getState() const { return _state; }
|
||||
const Error& getError() const { return _error; }
|
||||
QUrl getUrl() const { return ::getATPUrl(_hash); }
|
||||
const QString getErrorString() const;
|
||||
QUrl getUrl() const { return AssetUtils::getATPUrl(_hash); }
|
||||
QString getHash() const { return _hash; }
|
||||
|
||||
bool loadedFromCache() const { return _loadedFromCache; }
|
||||
|
|
|
@ -68,7 +68,7 @@ void AssetResourceRequest::doSend() {
|
|||
}
|
||||
}
|
||||
|
||||
void AssetResourceRequest::requestMappingForPath(const AssetPath& path) {
|
||||
void AssetResourceRequest::requestMappingForPath(const AssetUtils::AssetPath& path) {
|
||||
auto statTracker = DependencyManager::get<StatTracker>();
|
||||
statTracker->incrementStat(STAT_ATP_MAPPING_REQUEST_STARTED);
|
||||
|
||||
|
@ -140,7 +140,7 @@ void AssetResourceRequest::requestMappingForPath(const AssetPath& path) {
|
|||
_assetMappingRequest->start();
|
||||
}
|
||||
|
||||
void AssetResourceRequest::requestHash(const AssetHash& hash) {
|
||||
void AssetResourceRequest::requestHash(const AssetUtils::AssetHash& hash) {
|
||||
// Make request to atp
|
||||
auto assetClient = DependencyManager::get<AssetClient>();
|
||||
_assetRequest = assetClient->createRequest(hash, _byteRange);
|
||||
|
|
|
@ -34,8 +34,8 @@ private slots:
|
|||
private:
|
||||
static bool urlIsAssetHash(const QUrl& url);
|
||||
|
||||
void requestMappingForPath(const AssetPath& path);
|
||||
void requestHash(const AssetHash& hash);
|
||||
void requestMappingForPath(const AssetUtils::AssetPath& path);
|
||||
void requestHash(const AssetUtils::AssetHash& hash);
|
||||
|
||||
GetMappingRequest* _assetMappingRequest { nullptr };
|
||||
AssetRequest* _assetRequest { nullptr };
|
||||
|
|
|
@ -81,21 +81,21 @@ void AssetUpload::start() {
|
|||
qCDebug(asset_client) << "Attempting to upload" << _filename << "to asset-server.";
|
||||
}
|
||||
|
||||
assetClient->uploadAsset(_data, [this](bool responseReceived, AssetServerError error, const QString& hash){
|
||||
assetClient->uploadAsset(_data, [this](bool responseReceived, AssetUtils::AssetServerError error, const QString& hash){
|
||||
if (!responseReceived) {
|
||||
_error = NetworkError;
|
||||
} else {
|
||||
switch (error) {
|
||||
case AssetServerError::NoError:
|
||||
case AssetUtils::AssetServerError::NoError:
|
||||
_error = NoError;
|
||||
break;
|
||||
case AssetServerError::AssetTooLarge:
|
||||
case AssetUtils::AssetServerError::AssetTooLarge:
|
||||
_error = TooLarge;
|
||||
break;
|
||||
case AssetServerError::PermissionDenied:
|
||||
case AssetUtils::AssetServerError::PermissionDenied:
|
||||
_error = PermissionDenied;
|
||||
break;
|
||||
case AssetServerError::FileOperationFailed:
|
||||
case AssetUtils::AssetServerError::FileOperationFailed:
|
||||
_error = ServerFileError;
|
||||
break;
|
||||
default:
|
||||
|
@ -104,8 +104,8 @@ void AssetUpload::start() {
|
|||
}
|
||||
}
|
||||
|
||||
if (_error == NoError && hash == hashData(_data).toHex()) {
|
||||
saveToCache(getATPUrl(hash), _data);
|
||||
if (_error == NoError && hash == AssetUtils::hashData(_data).toHex()) {
|
||||
AssetUtils::saveToCache(AssetUtils::getATPUrl(hash), _data);
|
||||
}
|
||||
|
||||
emit finished(this, hash);
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
|
||||
#include <QtCore/QCryptographicHash>
|
||||
#include <QtCore/QDateTime>
|
||||
#include <QtCore/QFileInfo> // for baseName
|
||||
#include <QtNetwork/QAbstractNetworkCache>
|
||||
|
||||
#include "NetworkAccessManager.h"
|
||||
|
@ -22,8 +23,38 @@
|
|||
|
||||
#include "ResourceManager.h"
|
||||
|
||||
QUrl getATPUrl(const QString& hash) {
|
||||
return QUrl(QString("%1:%2").arg(URL_SCHEME_ATP, hash));
|
||||
namespace AssetUtils {
|
||||
|
||||
// Extract the valid AssetHash portion from atp: URLs like "[atp:]HASH[.fbx][?query]"
|
||||
// (or an invalid AssetHash if not found)
|
||||
AssetHash extractAssetHash(const QString& input) {
|
||||
if (isValidHash(input)) {
|
||||
return input;
|
||||
}
|
||||
QString path = getATPUrl(input).path();
|
||||
QString baseName = QFileInfo(path).baseName();
|
||||
if (isValidHash(baseName)) {
|
||||
return baseName;
|
||||
}
|
||||
return AssetHash();
|
||||
}
|
||||
|
||||
// Get the normalized ATP URL for a raw hash, /path or "atp:" input string.
|
||||
QUrl getATPUrl(const QString& input) {
|
||||
QUrl url = input;
|
||||
if (!url.scheme().isEmpty() && url.scheme() != URL_SCHEME_ATP) {
|
||||
return QUrl();
|
||||
}
|
||||
// this strips extraneous info from the URL (while preserving fragment/querystring)
|
||||
QString path = url.toEncoded(
|
||||
QUrl::RemoveAuthority | QUrl::RemoveScheme |
|
||||
QUrl::StripTrailingSlash | QUrl::NormalizePathSegments
|
||||
);
|
||||
QString baseName = QFileInfo(url.path()).baseName();
|
||||
if (isValidPath(path) || isValidHash(baseName)) {
|
||||
return QUrl(QString("%1:%2").arg(URL_SCHEME_ATP).arg(path));
|
||||
}
|
||||
return QUrl();
|
||||
}
|
||||
|
||||
QByteArray hashData(const QByteArray& data) {
|
||||
|
@ -102,3 +133,5 @@ QString bakingStatusToString(BakingStatus status) {
|
|||
return "--";
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace AssetUtils
|
||||
|
|
|
@ -19,6 +19,8 @@
|
|||
#include <QtCore/QByteArray>
|
||||
#include <QtCore/QUrl>
|
||||
|
||||
namespace AssetUtils {
|
||||
|
||||
using DataOffset = int64_t;
|
||||
|
||||
using AssetPath = QString;
|
||||
|
@ -71,7 +73,8 @@ struct MappingInfo {
|
|||
|
||||
using AssetMapping = std::map<AssetPath, MappingInfo>;
|
||||
|
||||
QUrl getATPUrl(const QString& hash);
|
||||
QUrl getATPUrl(const QString& input);
|
||||
AssetHash extractAssetHash(const QString& input);
|
||||
|
||||
QByteArray hashData(const QByteArray& data);
|
||||
|
||||
|
@ -84,4 +87,6 @@ bool isValidHash(const QString& hashString);
|
|||
|
||||
QString bakingStatusToString(BakingStatus status);
|
||||
|
||||
} // namespace AssetUtils
|
||||
|
||||
#endif // hifi_AssetUtils_h
|
||||
|
|
365
libraries/networking/src/BaseAssetScriptingInterface.cpp
Normal file
365
libraries/networking/src/BaseAssetScriptingInterface.cpp
Normal file
|
@ -0,0 +1,365 @@
|
|||
//
|
||||
// BaseAssetScriptingInterface.cpp
|
||||
// libraries/networking/src
|
||||
//
|
||||
// Copyright 2017 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
|
||||
#include "BaseAssetScriptingInterface.h"
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonArray>
|
||||
#include <QMimeDatabase>
|
||||
#include <QThread>
|
||||
|
||||
#include "AssetRequest.h"
|
||||
#include "AssetUpload.h"
|
||||
#include "AssetUtils.h"
|
||||
#include "MappingRequest.h"
|
||||
#include "NetworkLogging.h"
|
||||
|
||||
#include <RegisteredMetaTypes.h>
|
||||
|
||||
#include <shared/QtHelpers.h>
|
||||
#include "Gzip.h"
|
||||
|
||||
using Promise = MiniPromise::Promise;
|
||||
|
||||
QSharedPointer<AssetClient> BaseAssetScriptingInterface::assetClient() {
|
||||
auto client = DependencyManager::get<AssetClient>();
|
||||
Q_ASSERT(client);
|
||||
if (!client) {
|
||||
qDebug() << "BaseAssetScriptingInterface::assetClient unavailable";
|
||||
}
|
||||
return client;
|
||||
}
|
||||
|
||||
BaseAssetScriptingInterface::BaseAssetScriptingInterface(QObject* parent) : QObject(parent) {}
|
||||
|
||||
bool BaseAssetScriptingInterface::initializeCache() {
|
||||
if (!assetClient()) {
|
||||
return false; // not yet possible to initialize the cache
|
||||
}
|
||||
if (_cacheReady) {
|
||||
return true; // cache is ready
|
||||
}
|
||||
|
||||
// attempt to initialize the cache
|
||||
QMetaObject::invokeMethod(assetClient().data(), "init");
|
||||
|
||||
Promise deferred = makePromise("BaseAssetScriptingInterface--queryCacheStatus");
|
||||
deferred->then([this](QVariantMap result) {
|
||||
_cacheReady = !result.value("cacheDirectory").toString().isEmpty();
|
||||
});
|
||||
deferred->fail([](QString error) {
|
||||
qDebug() << "BaseAssetScriptingInterface::queryCacheStatus ERROR" << QThread::currentThread() << error;
|
||||
});
|
||||
assetClient()->cacheInfoRequestAsync(deferred);
|
||||
return false; // cache is not ready yet
|
||||
}
|
||||
|
||||
Promise BaseAssetScriptingInterface::getCacheStatus() {
|
||||
return assetClient()->cacheInfoRequestAsync(makePromise(__FUNCTION__));
|
||||
}
|
||||
|
||||
Promise BaseAssetScriptingInterface::queryCacheMeta(const QUrl& url) {
|
||||
return assetClient()->queryCacheMetaAsync(url, makePromise(__FUNCTION__));
|
||||
}
|
||||
|
||||
Promise BaseAssetScriptingInterface::loadFromCache(const QUrl& url, bool decompress, const QString& responseType) {
|
||||
QVariantMap metaData = {
|
||||
{ "_type", "cache" },
|
||||
{ "url", url },
|
||||
{ "responseType", responseType },
|
||||
};
|
||||
|
||||
Promise completed = makePromise("loadFromCache::completed");
|
||||
Promise fetched = makePromise("loadFromCache::fetched");
|
||||
|
||||
Promise downloaded = assetClient()->loadFromCacheAsync(url, makePromise("loadFromCache-retrieval"));
|
||||
downloaded->mixin(metaData);
|
||||
downloaded->fail(fetched);
|
||||
|
||||
if (decompress) {
|
||||
downloaded->then([=](QVariantMap result) {
|
||||
fetched->mixin(result);
|
||||
Promise decompressed = decompressBytes(result.value("data").toByteArray());
|
||||
decompressed->mixin(result);
|
||||
decompressed->ready(fetched);
|
||||
});
|
||||
} else {
|
||||
downloaded->then(fetched);
|
||||
}
|
||||
|
||||
fetched->fail(completed);
|
||||
fetched->then([=](QVariantMap result) {
|
||||
Promise converted = convertBytes(result.value("data").toByteArray(), responseType);
|
||||
converted->mixin(result);
|
||||
converted->ready(completed);
|
||||
});
|
||||
|
||||
return completed;
|
||||
}
|
||||
|
||||
Promise BaseAssetScriptingInterface::saveToCache(const QUrl& url, const QByteArray& data, const QVariantMap& headers) {
|
||||
return assetClient()->saveToCacheAsync(url, data, headers, makePromise(__FUNCTION__));
|
||||
}
|
||||
|
||||
Promise BaseAssetScriptingInterface::loadAsset(QString asset, bool decompress, QString responseType) {
|
||||
auto hash = AssetUtils::extractAssetHash(asset);
|
||||
auto url = AssetUtils::getATPUrl(hash).toString();
|
||||
|
||||
QVariantMap metaData = {
|
||||
{ "_asset", asset },
|
||||
{ "_type", "download" },
|
||||
{ "hash", hash },
|
||||
{ "url", url },
|
||||
{ "responseType", responseType },
|
||||
};
|
||||
|
||||
Promise completed = makePromise("loadAsset::completed");
|
||||
Promise fetched = makePromise("loadAsset::fetched");
|
||||
|
||||
Promise downloaded = downloadBytes(hash);
|
||||
downloaded->mixin(metaData);
|
||||
downloaded->fail(fetched);
|
||||
|
||||
if (decompress) {
|
||||
downloaded->then([=](QVariantMap result) {
|
||||
Q_ASSERT(thread() == QThread::currentThread());
|
||||
fetched->mixin(result);
|
||||
Promise decompressed = decompressBytes(result.value("data").toByteArray());
|
||||
decompressed->mixin(result);
|
||||
decompressed->ready(fetched);
|
||||
});
|
||||
} else {
|
||||
downloaded->then(fetched);
|
||||
}
|
||||
|
||||
fetched->fail(completed);
|
||||
fetched->then([=](QVariantMap result) {
|
||||
Promise converted = convertBytes(result.value("data").toByteArray(), responseType);
|
||||
converted->mixin(result);
|
||||
converted->ready(completed);
|
||||
});
|
||||
|
||||
return completed;
|
||||
}
|
||||
|
||||
Promise BaseAssetScriptingInterface::convertBytes(const QByteArray& dataByteArray, const QString& responseType) {
|
||||
QVariantMap result = {
|
||||
{ "_contentType", QMimeDatabase().mimeTypeForData(dataByteArray).name() },
|
||||
{ "_byteLength", dataByteArray.size() },
|
||||
{ "_responseType", responseType },
|
||||
};
|
||||
QString error;
|
||||
Promise conversion = makePromise(__FUNCTION__);
|
||||
if (!RESPONSE_TYPES.contains(responseType)) {
|
||||
error = QString("convertBytes: invalid responseType: '%1' (expected: %2)").arg(responseType).arg(RESPONSE_TYPES.join(" | "));
|
||||
} else if (responseType == "arraybuffer") {
|
||||
// interpret as bytes
|
||||
result["response"] = dataByteArray;
|
||||
} else if (responseType == "text") {
|
||||
// interpret as utf-8 text
|
||||
result["response"] = QString::fromUtf8(dataByteArray);
|
||||
} else if (responseType == "json") {
|
||||
// interpret as JSON
|
||||
QJsonParseError status;
|
||||
auto parsed = QJsonDocument::fromJson(dataByteArray, &status);
|
||||
if (status.error == QJsonParseError::NoError) {
|
||||
result["response"] = parsed.isArray() ? QVariant(parsed.array().toVariantList()) : QVariant(parsed.object().toVariantMap());
|
||||
} else {
|
||||
result = {
|
||||
{ "error", status.error },
|
||||
{ "offset", status.offset },
|
||||
};
|
||||
error = "JSON Parse Error: " + status.errorString();
|
||||
}
|
||||
}
|
||||
if (result.value("response").canConvert<QByteArray>()) {
|
||||
auto data = result.value("response").toByteArray();
|
||||
result["contentType"] = QMimeDatabase().mimeTypeForData(data).name();
|
||||
result["byteLength"] = data.size();
|
||||
result["responseType"] = responseType;
|
||||
}
|
||||
return conversion->handle(error, result);
|
||||
}
|
||||
|
||||
Promise BaseAssetScriptingInterface::decompressBytes(const QByteArray& dataByteArray) {
|
||||
QByteArray inflated;
|
||||
Promise decompressed = makePromise(__FUNCTION__);
|
||||
auto start = usecTimestampNow();
|
||||
if (gunzip(dataByteArray, inflated)) {
|
||||
auto end = usecTimestampNow();
|
||||
decompressed->resolve({
|
||||
{ "_compressedByteLength", dataByteArray.size() },
|
||||
{ "_compressedContentType", QMimeDatabase().mimeTypeForData(dataByteArray).name() },
|
||||
{ "_compressMS", (double)(end - start) / 1000.0 },
|
||||
{ "decompressed", true },
|
||||
{ "byteLength", inflated.size() },
|
||||
{ "contentType", QMimeDatabase().mimeTypeForData(inflated).name() },
|
||||
{ "data", inflated },
|
||||
});
|
||||
} else {
|
||||
decompressed->reject("gunzip error");
|
||||
}
|
||||
return decompressed;
|
||||
}
|
||||
|
||||
Promise BaseAssetScriptingInterface::compressBytes(const QByteArray& dataByteArray, int level) {
|
||||
QByteArray deflated;
|
||||
auto start = usecTimestampNow();
|
||||
Promise compressed = makePromise(__FUNCTION__);
|
||||
if (gzip(dataByteArray, deflated, level)) {
|
||||
auto end = usecTimestampNow();
|
||||
compressed->resolve({
|
||||
{ "_uncompressedByteLength", dataByteArray.size() },
|
||||
{ "_uncompressedContentType", QMimeDatabase().mimeTypeForData(dataByteArray).name() },
|
||||
{ "_compressMS", (double)(end - start) / 1000.0 },
|
||||
{ "compressed", true },
|
||||
{ "byteLength", deflated.size() },
|
||||
{ "contentType", QMimeDatabase().mimeTypeForData(deflated).name() },
|
||||
{ "data", deflated },
|
||||
});
|
||||
} else {
|
||||
compressed->reject("gzip error", {});
|
||||
}
|
||||
return compressed;
|
||||
}
|
||||
|
||||
Promise BaseAssetScriptingInterface::downloadBytes(QString hash) {
|
||||
QPointer<AssetRequest> assetRequest = assetClient()->createRequest(hash);
|
||||
Promise deferred = makePromise(__FUNCTION__);
|
||||
|
||||
QObject::connect(assetRequest, &AssetRequest::finished, assetRequest, [this, deferred](AssetRequest* request) {
|
||||
// note: we are now on the "Resource Manager" thread
|
||||
Q_ASSERT(QThread::currentThread() == request->thread());
|
||||
Q_ASSERT(request->getState() == AssetRequest::Finished);
|
||||
QString error;
|
||||
QVariantMap result;
|
||||
if (request->getError() == AssetRequest::Error::NoError) {
|
||||
QByteArray data = request->getData();
|
||||
result = {
|
||||
{ "url", request->getUrl() },
|
||||
{ "hash", request->getHash() },
|
||||
{ "cached", request->loadedFromCache() },
|
||||
{ "contentType", QMimeDatabase().mimeTypeForData(data).name() },
|
||||
{ "data", data },
|
||||
};
|
||||
} else {
|
||||
error = request->getError();
|
||||
result = { { "error", request->getError() } };
|
||||
}
|
||||
// forward thread-safe copies back to our thread
|
||||
deferred->handle(error, result);
|
||||
request->deleteLater();
|
||||
});
|
||||
assetRequest->start();
|
||||
return deferred;
|
||||
}
|
||||
|
||||
Promise BaseAssetScriptingInterface::uploadBytes(const QByteArray& bytes) {
|
||||
Promise deferred = makePromise(__FUNCTION__);
|
||||
QPointer<AssetUpload> upload = assetClient()->createUpload(bytes);
|
||||
|
||||
const auto byteLength = bytes.size();
|
||||
QObject::connect(upload, &AssetUpload::finished, upload, [=](AssetUpload* upload, const QString& hash) {
|
||||
Q_ASSERT(QThread::currentThread() == upload->thread());
|
||||
// note: we are now on the "Resource Manager" thread
|
||||
QString error;
|
||||
QVariantMap result;
|
||||
if (upload->getError() == AssetUpload::NoError) {
|
||||
result = {
|
||||
{ "hash", hash },
|
||||
{ "url", AssetUtils::getATPUrl(hash).toString() },
|
||||
{ "filename", upload->getFilename() },
|
||||
{ "byteLength", byteLength },
|
||||
};
|
||||
} else {
|
||||
error = upload->getErrorString();
|
||||
result = { { "error", upload->getError() } };
|
||||
}
|
||||
// forward thread-safe copies back to our thread
|
||||
deferred->handle(error, result);
|
||||
upload->deleteLater();
|
||||
});
|
||||
upload->start();
|
||||
return deferred;
|
||||
}
|
||||
|
||||
Promise BaseAssetScriptingInterface::getAssetInfo(QString asset) {
|
||||
Promise deferred = makePromise(__FUNCTION__);
|
||||
auto url = AssetUtils::getATPUrl(asset);
|
||||
auto path = url.path();
|
||||
auto hash = AssetUtils::extractAssetHash(asset);
|
||||
if (AssetUtils::isValidHash(hash)) {
|
||||
// already a valid ATP hash -- nothing to do
|
||||
deferred->resolve({
|
||||
{ "hash", hash },
|
||||
{ "path", path },
|
||||
{ "url", url },
|
||||
});
|
||||
} else if (AssetUtils::isValidFilePath(path)) {
|
||||
QPointer<GetMappingRequest> request = assetClient()->createGetMappingRequest(path);
|
||||
|
||||
QObject::connect(request, &GetMappingRequest::finished, request, [=]() {
|
||||
Q_ASSERT(QThread::currentThread() == request->thread());
|
||||
// note: we are now on the "Resource Manager" thread
|
||||
QString error;
|
||||
QVariantMap result;
|
||||
if (request->getError() == GetMappingRequest::NoError) {
|
||||
result = {
|
||||
{ "_hash", hash },
|
||||
{ "_path", path },
|
||||
{ "_url", url },
|
||||
{ "url", url },
|
||||
{ "hash", request->getHash() },
|
||||
{ "hashURL", AssetUtils::getATPUrl(request->getHash()).toString() },
|
||||
{ "wasRedirected", request->wasRedirected() },
|
||||
{ "path", request->wasRedirected() ? request->getRedirectedPath() : path },
|
||||
};
|
||||
} else {
|
||||
error = request->getErrorString();
|
||||
result = { { "error", request->getError() } };
|
||||
}
|
||||
// forward thread-safe copies back to our thread
|
||||
deferred->handle(error, result);
|
||||
request->deleteLater();
|
||||
});
|
||||
request->start();
|
||||
} else {
|
||||
deferred->reject("invalid ATP file path: " + asset + "("+path+")", {});
|
||||
}
|
||||
return deferred;
|
||||
}
|
||||
|
||||
Promise BaseAssetScriptingInterface::symlinkAsset(QString hash, QString path) {
|
||||
auto deferred = makePromise(__FUNCTION__);
|
||||
QPointer<SetMappingRequest> setMappingRequest = assetClient()->createSetMappingRequest(path, hash);
|
||||
|
||||
connect(setMappingRequest, &SetMappingRequest::finished, setMappingRequest, [=](SetMappingRequest* request) {
|
||||
Q_ASSERT(QThread::currentThread() == request->thread());
|
||||
// we are now on the "Resource Manager" thread
|
||||
QString error;
|
||||
QVariantMap result;
|
||||
if (request->getError() == SetMappingRequest::NoError) {
|
||||
result = {
|
||||
{ "_hash", hash },
|
||||
{ "_path", path },
|
||||
{ "hash", request->getHash() },
|
||||
{ "path", request->getPath() },
|
||||
{ "url", AssetUtils::getATPUrl(request->getPath()).toString() },
|
||||
};
|
||||
} else {
|
||||
error = request->getErrorString();
|
||||
result = { { "error", request->getError() } };
|
||||
}
|
||||
// forward results back to our thread
|
||||
deferred->handle(error, result);
|
||||
request->deleteLater();
|
||||
});
|
||||
setMappingRequest->start();
|
||||
return deferred;
|
||||
}
|
59
libraries/networking/src/BaseAssetScriptingInterface.h
Normal file
59
libraries/networking/src/BaseAssetScriptingInterface.h
Normal file
|
@ -0,0 +1,59 @@
|
|||
//
|
||||
// BaseAssetScriptingInterface.h
|
||||
// libraries/networking/src
|
||||
//
|
||||
// Copyright 2017 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
// BaseAssetScriptingInterface contains the engine-agnostic support code that can be used from
|
||||
// both QML JS and QScriptEngine JS engine implementations
|
||||
|
||||
#ifndef hifi_BaseAssetScriptingInterface_h
|
||||
#define hifi_BaseAssetScriptingInterface_h
|
||||
|
||||
#include <QtCore/QObject>
|
||||
#include <QtCore/QThread>
|
||||
#include "AssetClient.h"
|
||||
#include <shared/MiniPromises.h>
|
||||
#include "NetworkAccessManager.h"
|
||||
#include <QtNetwork/QNetworkDiskCache>
|
||||
|
||||
class BaseAssetScriptingInterface : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
const QStringList RESPONSE_TYPES{ "text", "arraybuffer", "json" };
|
||||
using Promise = MiniPromise::Promise;
|
||||
QSharedPointer<AssetClient> assetClient();
|
||||
|
||||
BaseAssetScriptingInterface(QObject* parent = nullptr);
|
||||
|
||||
public slots:
|
||||
bool isValidPath(QString input) { return AssetUtils::isValidPath(input); }
|
||||
bool isValidFilePath(QString input) { return AssetUtils::isValidFilePath(input); }
|
||||
QUrl getATPUrl(QString input) { return AssetUtils::getATPUrl(input); }
|
||||
QString extractAssetHash(QString input) { return AssetUtils::extractAssetHash(input); }
|
||||
bool isValidHash(QString input) { return AssetUtils::isValidHash(input); }
|
||||
QByteArray hashData(const QByteArray& data) { return AssetUtils::hashData(data); }
|
||||
QString hashDataHex(const QByteArray& data) { return hashData(data).toHex(); }
|
||||
|
||||
protected:
|
||||
bool _cacheReady{ false };
|
||||
bool initializeCache();
|
||||
Promise getCacheStatus();
|
||||
Promise queryCacheMeta(const QUrl& url);
|
||||
Promise loadFromCache(const QUrl& url, bool decompress = false, const QString& responseType = "arraybuffer");
|
||||
Promise saveToCache(const QUrl& url, const QByteArray& data, const QVariantMap& metadata = QVariantMap());
|
||||
|
||||
Promise loadAsset(QString asset, bool decompress, QString responseType);
|
||||
Promise getAssetInfo(QString asset);
|
||||
Promise downloadBytes(QString hash);
|
||||
Promise uploadBytes(const QByteArray& bytes);
|
||||
Promise compressBytes(const QByteArray& bytes, int level = -1);
|
||||
Promise convertBytes(const QByteArray& dataByteArray, const QString& responseType);
|
||||
Promise decompressBytes(const QByteArray& bytes);
|
||||
Promise symlinkAsset(QString hash, QString path);
|
||||
};
|
||||
#endif // hifi_BaseAssetScriptingInterface_h
|
|
@ -51,13 +51,13 @@ QString MappingRequest::getErrorString() const {
|
|||
}
|
||||
}
|
||||
|
||||
GetMappingRequest::GetMappingRequest(const AssetPath& path) : _path(path.trimmed()) {
|
||||
GetMappingRequest::GetMappingRequest(const AssetUtils::AssetPath& path) : _path(path.trimmed()) {
|
||||
};
|
||||
|
||||
void GetMappingRequest::doStart() {
|
||||
|
||||
// short circuit the request if the path is invalid
|
||||
if (!isValidFilePath(_path)) {
|
||||
if (!AssetUtils::isValidFilePath(_path)) {
|
||||
_error = MappingRequest::InvalidPath;
|
||||
emit finished(this);
|
||||
return;
|
||||
|
@ -66,17 +66,17 @@ void GetMappingRequest::doStart() {
|
|||
auto assetClient = DependencyManager::get<AssetClient>();
|
||||
|
||||
_mappingRequestID = assetClient->getAssetMapping(_path,
|
||||
[this, assetClient](bool responseReceived, AssetServerError error, QSharedPointer<ReceivedMessage> message) {
|
||||
[this, assetClient](bool responseReceived, AssetUtils::AssetServerError error, QSharedPointer<ReceivedMessage> message) {
|
||||
|
||||
_mappingRequestID = INVALID_MESSAGE_ID;
|
||||
if (!responseReceived) {
|
||||
_error = NetworkError;
|
||||
} else {
|
||||
switch (error) {
|
||||
case AssetServerError::NoError:
|
||||
case AssetUtils::AssetServerError::NoError:
|
||||
_error = NoError;
|
||||
break;
|
||||
case AssetServerError::AssetNotFound:
|
||||
case AssetUtils::AssetServerError::AssetNotFound:
|
||||
_error = NotFound;
|
||||
break;
|
||||
default:
|
||||
|
@ -86,7 +86,7 @@ void GetMappingRequest::doStart() {
|
|||
}
|
||||
|
||||
if (!_error) {
|
||||
_hash = message->read(SHA256_HASH_LENGTH).toHex();
|
||||
_hash = message->read(AssetUtils::SHA256_HASH_LENGTH).toHex();
|
||||
|
||||
// check the boolean to see if this request got re-directed
|
||||
quint8 wasRedirected;
|
||||
|
@ -112,7 +112,7 @@ GetAllMappingsRequest::GetAllMappingsRequest() {
|
|||
void GetAllMappingsRequest::doStart() {
|
||||
auto assetClient = DependencyManager::get<AssetClient>();
|
||||
_mappingRequestID = assetClient->getAllAssetMappings(
|
||||
[this, assetClient](bool responseReceived, AssetServerError error, QSharedPointer<ReceivedMessage> message) {
|
||||
[this, assetClient](bool responseReceived, AssetUtils::AssetServerError error, QSharedPointer<ReceivedMessage> message) {
|
||||
|
||||
_mappingRequestID = INVALID_MESSAGE_ID;
|
||||
|
||||
|
@ -120,7 +120,7 @@ void GetAllMappingsRequest::doStart() {
|
|||
_error = NetworkError;
|
||||
} else {
|
||||
switch (error) {
|
||||
case AssetServerError::NoError:
|
||||
case AssetUtils::AssetServerError::NoError:
|
||||
_error = NoError;
|
||||
break;
|
||||
default:
|
||||
|
@ -135,11 +135,11 @@ void GetAllMappingsRequest::doStart() {
|
|||
message->readPrimitive(&numberOfMappings);
|
||||
for (uint32_t i = 0; i < numberOfMappings; ++i) {
|
||||
auto path = message->readString();
|
||||
auto hash = message->read(SHA256_HASH_LENGTH).toHex();
|
||||
BakingStatus status;
|
||||
auto hash = message->read(AssetUtils::SHA256_HASH_LENGTH).toHex();
|
||||
AssetUtils::BakingStatus status;
|
||||
QString lastBakeErrors;
|
||||
message->readPrimitive(&status);
|
||||
if (status == BakingStatus::Error) {
|
||||
if (status == AssetUtils::BakingStatus::Error) {
|
||||
lastBakeErrors = message->readString();
|
||||
}
|
||||
_mappings[path] = { hash, status, lastBakeErrors };
|
||||
|
@ -149,7 +149,7 @@ void GetAllMappingsRequest::doStart() {
|
|||
});
|
||||
};
|
||||
|
||||
SetMappingRequest::SetMappingRequest(const AssetPath& path, const AssetHash& hash) :
|
||||
SetMappingRequest::SetMappingRequest(const AssetUtils::AssetPath& path, const AssetUtils::AssetHash& hash) :
|
||||
_path(path.trimmed()),
|
||||
_hash(hash)
|
||||
{
|
||||
|
@ -159,8 +159,8 @@ SetMappingRequest::SetMappingRequest(const AssetPath& path, const AssetHash& has
|
|||
void SetMappingRequest::doStart() {
|
||||
|
||||
// short circuit the request if the hash or path are invalid
|
||||
auto validPath = isValidFilePath(_path);
|
||||
auto validHash = isValidHash(_hash);
|
||||
auto validPath = AssetUtils::isValidFilePath(_path);
|
||||
auto validHash = AssetUtils::isValidHash(_hash);
|
||||
if (!validPath || !validHash) {
|
||||
_error = !validPath ? MappingRequest::InvalidPath : MappingRequest::InvalidHash;
|
||||
emit finished(this);
|
||||
|
@ -170,17 +170,17 @@ void SetMappingRequest::doStart() {
|
|||
auto assetClient = DependencyManager::get<AssetClient>();
|
||||
|
||||
_mappingRequestID = assetClient->setAssetMapping(_path, _hash,
|
||||
[this, assetClient](bool responseReceived, AssetServerError error, QSharedPointer<ReceivedMessage> message) {
|
||||
[this, assetClient](bool responseReceived, AssetUtils::AssetServerError error, QSharedPointer<ReceivedMessage> message) {
|
||||
|
||||
_mappingRequestID = INVALID_MESSAGE_ID;
|
||||
if (!responseReceived) {
|
||||
_error = NetworkError;
|
||||
} else {
|
||||
switch (error) {
|
||||
case AssetServerError::NoError:
|
||||
case AssetUtils::AssetServerError::NoError:
|
||||
_error = NoError;
|
||||
break;
|
||||
case AssetServerError::PermissionDenied:
|
||||
case AssetUtils::AssetServerError::PermissionDenied:
|
||||
_error = PermissionDenied;
|
||||
break;
|
||||
default:
|
||||
|
@ -193,7 +193,7 @@ void SetMappingRequest::doStart() {
|
|||
});
|
||||
};
|
||||
|
||||
DeleteMappingsRequest::DeleteMappingsRequest(const AssetPathList& paths) : _paths(paths) {
|
||||
DeleteMappingsRequest::DeleteMappingsRequest(const AssetUtils::AssetPathList& paths) : _paths(paths) {
|
||||
for (auto& path : _paths) {
|
||||
path = path.trimmed();
|
||||
}
|
||||
|
@ -203,7 +203,7 @@ void DeleteMappingsRequest::doStart() {
|
|||
|
||||
// short circuit the request if any of the paths are invalid
|
||||
for (auto& path : _paths) {
|
||||
if (!isValidPath(path)) {
|
||||
if (!AssetUtils::isValidPath(path)) {
|
||||
_error = MappingRequest::InvalidPath;
|
||||
emit finished(this);
|
||||
return;
|
||||
|
@ -213,17 +213,17 @@ void DeleteMappingsRequest::doStart() {
|
|||
auto assetClient = DependencyManager::get<AssetClient>();
|
||||
|
||||
_mappingRequestID = assetClient->deleteAssetMappings(_paths,
|
||||
[this, assetClient](bool responseReceived, AssetServerError error, QSharedPointer<ReceivedMessage> message) {
|
||||
[this, assetClient](bool responseReceived, AssetUtils::AssetServerError error, QSharedPointer<ReceivedMessage> message) {
|
||||
|
||||
_mappingRequestID = INVALID_MESSAGE_ID;
|
||||
if (!responseReceived) {
|
||||
_error = NetworkError;
|
||||
} else {
|
||||
switch (error) {
|
||||
case AssetServerError::NoError:
|
||||
case AssetUtils::AssetServerError::NoError:
|
||||
_error = NoError;
|
||||
break;
|
||||
case AssetServerError::PermissionDenied:
|
||||
case AssetUtils::AssetServerError::PermissionDenied:
|
||||
_error = PermissionDenied;
|
||||
break;
|
||||
default:
|
||||
|
@ -236,7 +236,7 @@ void DeleteMappingsRequest::doStart() {
|
|||
});
|
||||
};
|
||||
|
||||
RenameMappingRequest::RenameMappingRequest(const AssetPath& oldPath, const AssetPath& newPath) :
|
||||
RenameMappingRequest::RenameMappingRequest(const AssetUtils::AssetPath& oldPath, const AssetUtils::AssetPath& newPath) :
|
||||
_oldPath(oldPath.trimmed()),
|
||||
_newPath(newPath.trimmed())
|
||||
{
|
||||
|
@ -246,7 +246,7 @@ RenameMappingRequest::RenameMappingRequest(const AssetPath& oldPath, const Asset
|
|||
void RenameMappingRequest::doStart() {
|
||||
|
||||
// short circuit the request if either of the paths are invalid
|
||||
if (!isValidFilePath(_oldPath) || !isValidFilePath(_newPath)) {
|
||||
if (!AssetUtils::isValidFilePath(_oldPath) || !AssetUtils::isValidFilePath(_newPath)) {
|
||||
_error = InvalidPath;
|
||||
emit finished(this);
|
||||
return;
|
||||
|
@ -255,17 +255,17 @@ void RenameMappingRequest::doStart() {
|
|||
auto assetClient = DependencyManager::get<AssetClient>();
|
||||
|
||||
_mappingRequestID = assetClient->renameAssetMapping(_oldPath, _newPath,
|
||||
[this, assetClient](bool responseReceived, AssetServerError error, QSharedPointer<ReceivedMessage> message) {
|
||||
[this, assetClient](bool responseReceived, AssetUtils::AssetServerError error, QSharedPointer<ReceivedMessage> message) {
|
||||
|
||||
_mappingRequestID = INVALID_MESSAGE_ID;
|
||||
if (!responseReceived) {
|
||||
_error = NetworkError;
|
||||
} else {
|
||||
switch (error) {
|
||||
case AssetServerError::NoError:
|
||||
case AssetUtils::AssetServerError::NoError:
|
||||
_error = NoError;
|
||||
break;
|
||||
case AssetServerError::PermissionDenied:
|
||||
case AssetUtils::AssetServerError::PermissionDenied:
|
||||
_error = PermissionDenied;
|
||||
break;
|
||||
default:
|
||||
|
@ -278,7 +278,7 @@ void RenameMappingRequest::doStart() {
|
|||
});
|
||||
}
|
||||
|
||||
SetBakingEnabledRequest::SetBakingEnabledRequest(const AssetPathList& paths, bool enabled) : _paths(paths), _enabled(enabled) {
|
||||
SetBakingEnabledRequest::SetBakingEnabledRequest(const AssetUtils::AssetPathList& paths, bool enabled) : _paths(paths), _enabled(enabled) {
|
||||
for (auto& path : _paths) {
|
||||
path = path.trimmed();
|
||||
}
|
||||
|
@ -288,7 +288,7 @@ void SetBakingEnabledRequest::doStart() {
|
|||
|
||||
// short circuit the request if any of the paths are invalid
|
||||
for (auto& path : _paths) {
|
||||
if (!isValidPath(path)) {
|
||||
if (!AssetUtils::isValidPath(path)) {
|
||||
_error = MappingRequest::InvalidPath;
|
||||
emit finished(this);
|
||||
return;
|
||||
|
@ -298,17 +298,17 @@ void SetBakingEnabledRequest::doStart() {
|
|||
auto assetClient = DependencyManager::get<AssetClient>();
|
||||
|
||||
_mappingRequestID = assetClient->setBakingEnabled(_paths, _enabled,
|
||||
[this, assetClient](bool responseReceived, AssetServerError error, QSharedPointer<ReceivedMessage> message) {
|
||||
[this, assetClient](bool responseReceived, AssetUtils::AssetServerError error, QSharedPointer<ReceivedMessage> message) {
|
||||
|
||||
_mappingRequestID = INVALID_MESSAGE_ID;
|
||||
if (!responseReceived) {
|
||||
_error = NetworkError;
|
||||
} else {
|
||||
switch (error) {
|
||||
case AssetServerError::NoError:
|
||||
case AssetUtils::AssetServerError::NoError:
|
||||
_error = NoError;
|
||||
break;
|
||||
case AssetServerError::PermissionDenied:
|
||||
case AssetUtils::AssetServerError::PermissionDenied:
|
||||
_error = PermissionDenied;
|
||||
break;
|
||||
default:
|
||||
|
@ -319,4 +319,4 @@ void SetBakingEnabledRequest::doStart() {
|
|||
|
||||
emit finished(this);
|
||||
});
|
||||
};
|
||||
};
|
||||
|
|
|
@ -50,10 +50,10 @@ private:
|
|||
class GetMappingRequest : public MappingRequest {
|
||||
Q_OBJECT
|
||||
public:
|
||||
GetMappingRequest(const AssetPath& path);
|
||||
GetMappingRequest(const AssetUtils::AssetPath& path);
|
||||
|
||||
AssetHash getHash() const { return _hash; }
|
||||
AssetPath getRedirectedPath() const { return _redirectedPath; }
|
||||
AssetUtils::AssetHash getHash() const { return _hash; }
|
||||
AssetUtils::AssetPath getRedirectedPath() const { return _redirectedPath; }
|
||||
bool wasRedirected() const { return _wasRedirected; }
|
||||
|
||||
signals:
|
||||
|
@ -62,21 +62,21 @@ signals:
|
|||
private:
|
||||
virtual void doStart() override;
|
||||
|
||||
AssetPath _path;
|
||||
AssetHash _hash;
|
||||
AssetUtils::AssetPath _path;
|
||||
AssetUtils::AssetHash _hash;
|
||||
|
||||
|
||||
AssetPath _redirectedPath;
|
||||
AssetUtils::AssetPath _redirectedPath;
|
||||
bool _wasRedirected { false };
|
||||
};
|
||||
|
||||
class SetMappingRequest : public MappingRequest {
|
||||
Q_OBJECT
|
||||
public:
|
||||
SetMappingRequest(const AssetPath& path, const AssetHash& hash);
|
||||
SetMappingRequest(const AssetUtils::AssetPath& path, const AssetUtils::AssetHash& hash);
|
||||
|
||||
AssetPath getPath() const { return _path; }
|
||||
AssetHash getHash() const { return _hash; }
|
||||
AssetUtils::AssetPath getPath() const { return _path; }
|
||||
AssetUtils::AssetHash getHash() const { return _hash; }
|
||||
|
||||
signals:
|
||||
void finished(SetMappingRequest* thisRequest);
|
||||
|
@ -84,14 +84,14 @@ signals:
|
|||
private:
|
||||
virtual void doStart() override;
|
||||
|
||||
AssetPath _path;
|
||||
AssetHash _hash;
|
||||
AssetUtils::AssetPath _path;
|
||||
AssetUtils::AssetHash _hash;
|
||||
};
|
||||
|
||||
class DeleteMappingsRequest : public MappingRequest {
|
||||
Q_OBJECT
|
||||
public:
|
||||
DeleteMappingsRequest(const AssetPathList& path);
|
||||
DeleteMappingsRequest(const AssetUtils::AssetPathList& path);
|
||||
|
||||
signals:
|
||||
void finished(DeleteMappingsRequest* thisRequest);
|
||||
|
@ -99,13 +99,13 @@ signals:
|
|||
private:
|
||||
virtual void doStart() override;
|
||||
|
||||
AssetPathList _paths;
|
||||
AssetUtils::AssetPathList _paths;
|
||||
};
|
||||
|
||||
class RenameMappingRequest : public MappingRequest {
|
||||
Q_OBJECT
|
||||
public:
|
||||
RenameMappingRequest(const AssetPath& oldPath, const AssetPath& newPath);
|
||||
RenameMappingRequest(const AssetUtils::AssetPath& oldPath, const AssetUtils::AssetPath& newPath);
|
||||
|
||||
signals:
|
||||
void finished(RenameMappingRequest* thisRequest);
|
||||
|
@ -113,8 +113,8 @@ signals:
|
|||
private:
|
||||
virtual void doStart() override;
|
||||
|
||||
AssetPath _oldPath;
|
||||
AssetPath _newPath;
|
||||
AssetUtils::AssetPath _oldPath;
|
||||
AssetUtils::AssetPath _newPath;
|
||||
};
|
||||
|
||||
class GetAllMappingsRequest : public MappingRequest {
|
||||
|
@ -122,7 +122,7 @@ class GetAllMappingsRequest : public MappingRequest {
|
|||
public:
|
||||
GetAllMappingsRequest();
|
||||
|
||||
AssetMapping getMappings() const { return _mappings; }
|
||||
AssetUtils::AssetMapping getMappings() const { return _mappings; }
|
||||
|
||||
signals:
|
||||
void finished(GetAllMappingsRequest* thisRequest);
|
||||
|
@ -130,13 +130,13 @@ signals:
|
|||
private:
|
||||
virtual void doStart() override;
|
||||
|
||||
AssetMapping _mappings;
|
||||
AssetUtils::AssetMapping _mappings;
|
||||
};
|
||||
|
||||
class SetBakingEnabledRequest : public MappingRequest {
|
||||
Q_OBJECT
|
||||
public:
|
||||
SetBakingEnabledRequest(const AssetPathList& path, bool enabled);
|
||||
SetBakingEnabledRequest(const AssetUtils::AssetPathList& path, bool enabled);
|
||||
|
||||
signals:
|
||||
void finished(SetBakingEnabledRequest* thisRequest);
|
||||
|
@ -144,7 +144,7 @@ signals:
|
|||
private:
|
||||
virtual void doStart() override;
|
||||
|
||||
AssetPathList _paths;
|
||||
AssetUtils::AssetPathList _paths;
|
||||
bool _enabled;
|
||||
};
|
||||
|
||||
|
|
|
@ -21,21 +21,25 @@
|
|||
<@include LightDirectional.slh@>
|
||||
|
||||
|
||||
<@func prepareGlobalLight(isScattering)@>
|
||||
// prepareGlobalLight
|
||||
// Transform directions to worldspace
|
||||
vec3 fragNormal = vec3((normal));
|
||||
vec3 fragEyeVector = vec3(invViewMat * vec4(-1.0*position, 0.0));
|
||||
vec3 fragEyeDir = normalize(fragEyeVector);
|
||||
|
||||
<@func fetchGlobalLight()@>
|
||||
// Get light
|
||||
Light light = getLight();
|
||||
Light light = getKeyLight();
|
||||
LightAmbient lightAmbient = getLightAmbient();
|
||||
|
||||
vec3 lightDirection = getLightDirection(light);
|
||||
vec3 lightIrradiance = getLightIrradiance(light);
|
||||
|
||||
vec3 color = vec3(0.0);
|
||||
<@endfunc@>
|
||||
|
||||
<@func prepareGlobalLight(isScattering)@>
|
||||
// prepareGlobalLight
|
||||
// Transform directions to worldspace
|
||||
vec3 fragNormal = vec3((normal));
|
||||
vec3 fragEyeVector = vec3(invViewMat * vec4(-position, 0.0));
|
||||
vec3 fragEyeDir = normalize(fragEyeVector);
|
||||
|
||||
<$fetchGlobalLight()$>
|
||||
|
||||
<@endfunc@>
|
||||
|
||||
|
@ -147,7 +151,7 @@ vec3 evalSkyboxGlobalColor(mat4 invViewMat, float shadowAttenuation, float obscu
|
|||
|
||||
<@func declareEvalLightmappedColor()@>
|
||||
vec3 evalLightmappedColor(mat4 invViewMat, float shadowAttenuation, float obscurance, vec3 normal, vec3 albedo, vec3 lightmap) {
|
||||
Light light = getLight();
|
||||
Light light = getKeyLight();
|
||||
LightAmbient ambient = getLightAmbient();
|
||||
|
||||
// Catch normals perpendicular to the projection plane, hence the magic number for the threshold
|
||||
|
@ -175,11 +179,12 @@ vec3 evalLightmappedColor(mat4 invViewMat, float shadowAttenuation, float obscur
|
|||
<$declareLightingAmbient(1, 1, 1)$>
|
||||
<$declareLightingDirectional()$>
|
||||
|
||||
vec3 evalGlobalLightingAlphaBlended(mat4 invViewMat, float shadowAttenuation, float obscurance, vec3 position, vec3 normal, vec3 albedo, vec3 fresnel, float metallic, vec3 emissive, float roughness, float opacity) {
|
||||
vec3 evalGlobalLightingAlphaBlended(mat4 invViewMat, float shadowAttenuation, float obscurance, vec3 position, vec3 normal, vec3 albedo, vec3 fresnel, float metallic, vec3 emissive, float roughness, float opacity, vec3 prevLighting) {
|
||||
<$prepareGlobalLight()$>
|
||||
|
||||
SurfaceData surface = initSurfaceData(roughness, fragNormal, fragEyeDir);
|
||||
|
||||
color = prevLighting;
|
||||
color += emissive * isEmissiveEnabled();
|
||||
|
||||
// Ambient
|
||||
|
@ -238,6 +243,44 @@ vec3 evalGlobalLightingAlphaBlendedWithHaze(
|
|||
|
||||
return color;
|
||||
}
|
||||
|
||||
vec3 evalGlobalLightingAlphaBlendedWithHaze(
|
||||
mat4 invViewMat, float shadowAttenuation, float obscurance, vec3 position,
|
||||
vec3 albedo, vec3 fresnel, float metallic, vec3 emissive, SurfaceData surface, float opacity, vec3 prevLighting)
|
||||
{
|
||||
<$fetchGlobalLight()$>
|
||||
|
||||
color = prevLighting;
|
||||
color += emissive * isEmissiveEnabled();
|
||||
|
||||
// Ambient
|
||||
vec3 ambientDiffuse;
|
||||
vec3 ambientSpecular;
|
||||
evalLightingAmbient(ambientDiffuse, ambientSpecular, lightAmbient, surface, metallic, fresnel, albedo, obscurance);
|
||||
|
||||
// Directional
|
||||
vec3 directionalDiffuse;
|
||||
vec3 directionalSpecular;
|
||||
evalLightingDirectional(directionalDiffuse, directionalSpecular, lightDirection, lightIrradiance, surface, metallic, fresnel, albedo, shadowAttenuation);
|
||||
|
||||
color += ambientDiffuse + directionalDiffuse;
|
||||
color += (ambientSpecular + directionalSpecular) / opacity;
|
||||
|
||||
// Haze
|
||||
if ((hazeParams.hazeMode & HAZE_MODE_IS_ACTIVE) == HAZE_MODE_IS_ACTIVE) {
|
||||
vec4 colorV4 = computeHazeColor(
|
||||
vec4(color, 1.0), // fragment original color
|
||||
position, // fragment position in eye coordinates
|
||||
surface.eyeDir, // fragment eye vector in world coordinates
|
||||
invViewMat[3].y, // eye height in world coordinates
|
||||
lightDirection // keylight direction vector
|
||||
);
|
||||
|
||||
color = colorV4.rgb;
|
||||
}
|
||||
|
||||
return color;
|
||||
}
|
||||
<@endfunc@>
|
||||
|
||||
|
||||
|
|
|
@ -45,6 +45,7 @@ using namespace render;
|
|||
|
||||
struct LightLocations {
|
||||
int radius{ -1 };
|
||||
int keyLightBufferUnit{ -1 };
|
||||
int lightBufferUnit{ -1 };
|
||||
int ambientBufferUnit { -1 };
|
||||
int lightIndexBufferUnit { -1 };
|
||||
|
@ -147,9 +148,31 @@ void DeferredLightingEffect::unsetKeyLightBatch(gpu::Batch& batch, int lightBuff
|
|||
}
|
||||
}
|
||||
|
||||
void DeferredLightingEffect::setupLocalLightsBatch(gpu::Batch& batch,
|
||||
int clusterGridBufferUnit, int clusterContentBufferUnit, int frustumGridBufferUnit,
|
||||
const LightClustersPointer& lightClusters) {
|
||||
// Bind the global list of lights and the visible lights this frame
|
||||
batch.setUniformBuffer(_localLightLocations->lightBufferUnit, lightClusters->_lightStage->getLightArrayBuffer());
|
||||
|
||||
batch.setUniformBuffer(frustumGridBufferUnit, lightClusters->_frustumGridBuffer);
|
||||
batch.setUniformBuffer(clusterGridBufferUnit, lightClusters->_clusterGridBuffer);
|
||||
batch.setUniformBuffer(clusterContentBufferUnit, lightClusters->_clusterContentBuffer);
|
||||
}
|
||||
|
||||
void DeferredLightingEffect::unsetLocalLightsBatch(gpu::Batch& batch, int clusterGridBufferUnit, int clusterContentBufferUnit, int frustumGridBufferUnit) {
|
||||
if (clusterGridBufferUnit >= 0) {
|
||||
batch.setUniformBuffer(clusterGridBufferUnit, nullptr);
|
||||
}
|
||||
if (clusterContentBufferUnit >= 0) {
|
||||
batch.setUniformBuffer(clusterContentBufferUnit, nullptr);
|
||||
}
|
||||
if (frustumGridBufferUnit >= 0) {
|
||||
batch.setUniformBuffer(frustumGridBufferUnit, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
static gpu::ShaderPointer makeLightProgram(const gpu::ShaderPointer& vertShader, const gpu::ShaderPointer& fragShader, LightLocationsPtr& locations) {
|
||||
gpu::ShaderPointer program = gpu::Shader::createProgram(vertShader, fragShader);
|
||||
|
||||
gpu::Shader::BindingSet slotBindings;
|
||||
slotBindings.insert(gpu::Shader::Binding(std::string("colorMap"), DEFERRED_BUFFER_COLOR_UNIT));
|
||||
slotBindings.insert(gpu::Shader::Binding(std::string("normalMap"), DEFERRED_BUFFER_NORMAL_UNIT));
|
||||
|
@ -186,6 +209,7 @@ static gpu::ShaderPointer makeLightProgram(const gpu::ShaderPointer& vertShader,
|
|||
|
||||
locations->texcoordFrameTransform = program->getUniforms().findLocation("texcoordFrameTransform");
|
||||
|
||||
locations->keyLightBufferUnit = program->getUniformBuffers().findLocation("keyLightBuffer");
|
||||
locations->lightBufferUnit = program->getUniformBuffers().findLocation("lightBuffer");
|
||||
locations->ambientBufferUnit = program->getUniformBuffers().findLocation("lightAmbientBuffer");
|
||||
locations->lightIndexBufferUnit = program->getUniformBuffers().findLocation("lightIndexBuffer");
|
||||
|
@ -558,7 +582,7 @@ void RenderDeferredSetup::run(const render::RenderContextPointer& renderContext,
|
|||
batch._glUniform4fv(locations->texcoordFrameTransform, 1, reinterpret_cast< const float* >(&textureFrameTransform));
|
||||
|
||||
// Setup the global lighting
|
||||
deferredLightingEffect->setupKeyLightBatch(args, batch, locations->lightBufferUnit, locations->ambientBufferUnit, SKYBOX_MAP_UNIT);
|
||||
deferredLightingEffect->setupKeyLightBatch(args, batch, locations->keyLightBufferUnit, locations->ambientBufferUnit, SKYBOX_MAP_UNIT);
|
||||
|
||||
// Haze
|
||||
if (haze) {
|
||||
|
@ -567,7 +591,7 @@ void RenderDeferredSetup::run(const render::RenderContextPointer& renderContext,
|
|||
|
||||
batch.draw(gpu::TRIANGLE_STRIP, 4);
|
||||
|
||||
deferredLightingEffect->unsetKeyLightBatch(batch, locations->lightBufferUnit, locations->ambientBufferUnit, SKYBOX_MAP_UNIT);
|
||||
deferredLightingEffect->unsetKeyLightBatch(batch, locations->keyLightBufferUnit, locations->ambientBufferUnit, SKYBOX_MAP_UNIT);
|
||||
|
||||
for (auto i = 0; i < SHADOW_CASCADE_MAX_COUNT; i++) {
|
||||
batch.setResourceTexture(SHADOW_MAP_UNIT+i, nullptr);
|
||||
|
@ -622,12 +646,8 @@ void RenderDeferredLocals::run(const render::RenderContextPointer& renderContext
|
|||
|
||||
auto& lightIndices = lightClusters->_visibleLightIndices;
|
||||
if (!lightIndices.empty() && lightIndices[0] > 0) {
|
||||
// Bind the global list of lights and the visible lights this frame
|
||||
batch.setUniformBuffer(deferredLightingEffect->_localLightLocations->lightBufferUnit, lightClusters->_lightStage->getLightArrayBuffer());
|
||||
|
||||
batch.setUniformBuffer(LIGHT_CLUSTER_GRID_FRUSTUM_GRID_SLOT, lightClusters->_frustumGridBuffer);
|
||||
batch.setUniformBuffer(LIGHT_CLUSTER_GRID_CLUSTER_GRID_SLOT, lightClusters->_clusterGridBuffer);
|
||||
batch.setUniformBuffer(LIGHT_CLUSTER_GRID_CLUSTER_CONTENT_SLOT, lightClusters->_clusterContentBuffer);
|
||||
deferredLightingEffect->setupLocalLightsBatch(batch, LIGHT_CLUSTER_GRID_CLUSTER_GRID_SLOT, LIGHT_CLUSTER_GRID_CLUSTER_CONTENT_SLOT, LIGHT_CLUSTER_GRID_FRUSTUM_GRID_SLOT,
|
||||
lightClusters);
|
||||
|
||||
// Local light pipeline
|
||||
batch.setPipeline(deferredLightingEffect->_localLight);
|
||||
|
|
|
@ -51,6 +51,9 @@ public:
|
|||
void setupKeyLightBatch(const RenderArgs* args, gpu::Batch& batch, int lightBufferUnit, int ambientBufferUnit, int skyboxCubemapUnit);
|
||||
void unsetKeyLightBatch(gpu::Batch& batch, int lightBufferUnit, int ambientBufferUnit, int skyboxCubemapUnit);
|
||||
|
||||
void setupLocalLightsBatch(gpu::Batch& batch, int clusterGridBufferUnit, int clusterContentBufferUnit, int frustumGridBufferUnit, const LightClustersPointer& lightClusters);
|
||||
void unsetLocalLightsBatch(gpu::Batch& batch, int clusterGridBufferUnit, int clusterContentBufferUnit, int frustumGridBufferUnit);
|
||||
|
||||
void setShadowMapEnabled(bool enable) { _shadowMapEnabled = enable; };
|
||||
void setAmbientOcclusionEnabled(bool enable) { _ambientOcclusionEnabled = enable; }
|
||||
bool isAmbientOcclusionEnabled() const { return _ambientOcclusionEnabled; }
|
||||
|
|
|
@ -147,7 +147,7 @@ void DrawHaze::run(const render::RenderContextPointer& renderContext, const Inpu
|
|||
slotBindings.insert(gpu::Shader::Binding(std::string("deferredFrameTransformBuffer"), HazeEffect_TransformBufferSlot));
|
||||
slotBindings.insert(gpu::Shader::Binding(std::string("colorMap"), HazeEffect_ColorMapSlot));
|
||||
slotBindings.insert(gpu::Shader::Binding(std::string("linearDepthMap"), HazeEffect_LinearDepthMapSlot));
|
||||
slotBindings.insert(gpu::Shader::Binding(std::string("lightBuffer"), HazeEffect_LightingMapSlot));
|
||||
slotBindings.insert(gpu::Shader::Binding(std::string("keyLightBuffer"), HazeEffect_LightingMapSlot));
|
||||
gpu::Shader::makeProgram(*program, slotBindings);
|
||||
|
||||
_hazePipeline = gpu::PipelinePointer(gpu::Pipeline::create(program, state));
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
vec3 fragEyeDir = normalize(fragEyeVector);
|
||||
|
||||
// Get light
|
||||
Light light = getLight();
|
||||
Light light = getKeyLight();
|
||||
LightAmbient lightAmbient = getLightAmbient();
|
||||
|
||||
vec3 lightDirection = getLightDirection(light);
|
||||
|
@ -143,7 +143,7 @@ vec3 evalSkyboxGlobalColor(mat4 invViewMat, float shadowAttenuation, float obscu
|
|||
|
||||
<@func declareEvalLightmappedColor()@>
|
||||
vec3 evalLightmappedColor(mat4 invViewMat, float shadowAttenuation, float obscurance, vec3 normal, vec3 albedo, vec3 lightmap) {
|
||||
Light light = getLight();
|
||||
Light light = getKeyLight();
|
||||
LightAmbient ambient = getLightAmbient();
|
||||
|
||||
// Catch normals perpendicular to the projection plane, hence the magic number for the threshold
|
||||
|
|
|
@ -53,7 +53,7 @@ void main(void) {
|
|||
vec4 worldFragPos = viewInverse * eyeFragPos;
|
||||
vec4 worldEyePos = viewInverse[3];
|
||||
|
||||
Light light = getLight();
|
||||
Light light = getKeyLight();
|
||||
vec3 lightDirection = getLightDirection(light);
|
||||
|
||||
outFragColor = computeHazeColor(fragColor, eyeFragPos.xyz, worldFragPos.xyz, worldEyePos.y, lightDirection);
|
||||
|
|
|
@ -86,4 +86,24 @@ int clusterGrid_getClusterLightId(int index, int offset) {
|
|||
return (((elementIndex & 0x00000001) == 1) ? (element >> 16) : element) & 0x0000FFFF;
|
||||
}
|
||||
|
||||
|
||||
<@func fetchClusterInfo(fragWorldPos)@>
|
||||
|
||||
// From frag world pos find the cluster
|
||||
vec4 clusterEyePos = frustumGrid_worldToEye(<$fragWorldPos$>);
|
||||
ivec3 clusterPos = frustumGrid_eyeToClusterPos(clusterEyePos.xyz);
|
||||
|
||||
ivec3 cluster = clusterGrid_getCluster(frustumGrid_clusterToIndex(clusterPos));
|
||||
int numLights = cluster.x + cluster.y;
|
||||
ivec3 dims = frustumGrid.dims.xyz;
|
||||
|
||||
<@endfunc@>
|
||||
|
||||
bool hasLocalLights(int numLights, ivec3 clusterPos, ivec3 dims) {
|
||||
return numLights>0
|
||||
&& all(greaterThanEqual(clusterPos, ivec3(0)))
|
||||
&& all(lessThan(clusterPos.xy, dims.xy))
|
||||
&& clusterPos.z <= dims.z;
|
||||
}
|
||||
|
||||
<@endif@>
|
||||
|
|
148
libraries/render-utils/src/LightLocal.slh
Normal file
148
libraries/render-utils/src/LightLocal.slh
Normal file
|
@ -0,0 +1,148 @@
|
|||
// Generated on <$_SCRIBE_DATE$>
|
||||
//
|
||||
// Created by Olivier Prat on 15/01/18.
|
||||
// Copyright 2018 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
// Everything about light
|
||||
<@include graphics/Light.slh@>
|
||||
<$declareLightBuffer(256)$>
|
||||
<@include LightingModel.slh@>
|
||||
|
||||
|
||||
<@include LightPoint.slh@>
|
||||
<$declareLightingPoint(supportScattering)$>
|
||||
<@include LightSpot.slh@>
|
||||
<$declareLightingSpot(supportScattering)$>
|
||||
|
||||
<@include LightClusterGrid.slh@>
|
||||
|
||||
vec4 evalLocalLighting(ivec3 cluster, int numLights, vec3 fragWorldPos, SurfaceData surface,
|
||||
float fragMetallic, vec3 fragFresnel, vec3 fragAlbedo, float fragScattering,
|
||||
vec4 midNormalCurvature, vec4 lowNormalCurvature, float opacity) {
|
||||
vec4 fragColor = vec4(0.0);
|
||||
vec3 fragSpecular = vec3(0.0);
|
||||
vec3 fragDiffuse = vec3(0.0);
|
||||
int lightClusterOffset = cluster.z;
|
||||
|
||||
// Compute the rougness into gloss2 once:
|
||||
bool withScattering = (fragScattering * isScatteringEnabled() > 0.0);
|
||||
|
||||
int numLightTouching = 0;
|
||||
for (int i = 0; i < cluster.x; i++) {
|
||||
// Need the light now
|
||||
int theLightIndex = clusterGrid_getClusterLightId(i, lightClusterOffset);
|
||||
Light light = getLight(theLightIndex);
|
||||
|
||||
// Clip againgst the light volume and Make the Light vector going from fragment to light center in world space
|
||||
vec4 fragLightVecLen2;
|
||||
vec4 fragLightDirLen;
|
||||
|
||||
if (!lightVolume_clipFragToLightVolumePoint(light.volume, fragWorldPos.xyz, fragLightVecLen2)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Allright we re in the light sphere volume
|
||||
fragLightDirLen.w = length(fragLightVecLen2.xyz);
|
||||
fragLightDirLen.xyz = fragLightVecLen2.xyz / fragLightDirLen.w;
|
||||
if (dot(surface.normal, fragLightDirLen.xyz) < 0.0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
numLightTouching++;
|
||||
|
||||
vec3 diffuse = vec3(1.0);
|
||||
vec3 specular = vec3(0.1);
|
||||
|
||||
// Allright we re valid in the volume
|
||||
float fragLightDistance = fragLightDirLen.w;
|
||||
vec3 fragLightDir = fragLightDirLen.xyz;
|
||||
|
||||
updateSurfaceDataWithLight(surface, fragLightDir);
|
||||
|
||||
// Eval attenuation
|
||||
float radialAttenuation = lightIrradiance_evalLightAttenuation(light.irradiance, fragLightDistance);
|
||||
vec3 lightEnergy = radialAttenuation * getLightIrradiance(light);
|
||||
|
||||
// Eval shading
|
||||
if (withScattering) {
|
||||
evalFragShadingScattering(diffuse, specular, fragMetallic, fragFresnel, surface, fragAlbedo,
|
||||
fragScattering, midNormalCurvature, lowNormalCurvature );
|
||||
} else {
|
||||
evalFragShadingGloss(diffuse, specular, fragMetallic, fragFresnel, surface, fragAlbedo);
|
||||
}
|
||||
|
||||
diffuse *= lightEnergy;
|
||||
specular *= lightEnergy;
|
||||
|
||||
fragDiffuse.rgb += diffuse;
|
||||
fragSpecular.rgb += specular;
|
||||
}
|
||||
|
||||
for (int i = cluster.x; i < numLights; i++) {
|
||||
// Need the light now
|
||||
int theLightIndex = clusterGrid_getClusterLightId(i, lightClusterOffset);
|
||||
Light light = getLight(theLightIndex);
|
||||
|
||||
// Clip againgst the light volume and Make the Light vector going from fragment to light center in world space
|
||||
vec4 fragLightVecLen2;
|
||||
vec4 fragLightDirLen;
|
||||
float cosSpotAngle;
|
||||
|
||||
if (!lightVolume_clipFragToLightVolumePoint(light.volume, fragWorldPos.xyz, fragLightVecLen2)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Allright we re in the light sphere volume
|
||||
fragLightDirLen.w = length(fragLightVecLen2.xyz);
|
||||
fragLightDirLen.xyz = fragLightVecLen2.xyz / fragLightDirLen.w;
|
||||
if (dot(surface.normal, fragLightDirLen.xyz) < 0.0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check spot
|
||||
if (!lightVolume_clipFragToLightVolumeSpotSide(light.volume, fragLightDirLen, cosSpotAngle)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
numLightTouching++;
|
||||
|
||||
vec3 diffuse = vec3(1.0);
|
||||
vec3 specular = vec3(0.1);
|
||||
|
||||
// Allright we re valid in the volume
|
||||
float fragLightDistance = fragLightDirLen.w;
|
||||
vec3 fragLightDir = fragLightDirLen.xyz;
|
||||
|
||||
updateSurfaceDataWithLight(surface, fragLightDir);
|
||||
|
||||
// Eval attenuation
|
||||
float radialAttenuation = lightIrradiance_evalLightAttenuation(light.irradiance, fragLightDistance);
|
||||
float angularAttenuation = lightIrradiance_evalLightSpotAttenuation(light.irradiance, cosSpotAngle);
|
||||
vec3 lightEnergy = radialAttenuation * angularAttenuation * getLightIrradiance(light);
|
||||
|
||||
// Eval shading
|
||||
if (withScattering) {
|
||||
evalFragShadingScattering(diffuse, specular, fragMetallic, fragFresnel, surface, fragAlbedo,
|
||||
fragScattering, midNormalCurvature, lowNormalCurvature );
|
||||
} else {
|
||||
evalFragShadingGloss(diffuse, specular, fragMetallic, fragFresnel, surface, fragAlbedo);
|
||||
}
|
||||
|
||||
diffuse *= lightEnergy;
|
||||
specular *= lightEnergy;
|
||||
|
||||
fragDiffuse.rgb += diffuse;
|
||||
fragSpecular.rgb += specular;
|
||||
}
|
||||
|
||||
fragDiffuse *= isDiffuseEnabled();
|
||||
fragSpecular *= isSpecularEnabled();
|
||||
|
||||
fragColor.rgb += fragDiffuse;
|
||||
fragColor.rgb += fragSpecular / opacity;
|
||||
return fragColor;
|
||||
}
|
|
@ -289,9 +289,8 @@ void evalFragShading(out vec3 diffuse, out vec3 specular,
|
|||
|
||||
|
||||
void evalFragShadingScattering(out vec3 diffuse, out vec3 specular,
|
||||
float metallic, vec3 fresnel, SurfaceData surface, vec3 albedo
|
||||
,float scattering, vec4 midNormalCurvature, vec4 lowNormalCurvature
|
||||
) {
|
||||
float metallic, vec3 fresnel, SurfaceData surface, vec3 albedo,
|
||||
float scattering, vec4 midNormalCurvature, vec4 lowNormalCurvature) {
|
||||
vec3 brdf = evalSkinBRDF(surface.lightDir, surface.normal, midNormalCurvature.xyz, lowNormalCurvature.xyz, lowNormalCurvature.w);
|
||||
float NdotL = surface.ndotl;
|
||||
diffuse = mix(vec3(NdotL), brdf, scattering);
|
||||
|
@ -305,8 +304,7 @@ void evalFragShadingScattering(out vec3 diffuse, out vec3 specular,
|
|||
}
|
||||
|
||||
void evalFragShadingGloss(out vec3 diffuse, out vec3 specular,
|
||||
float metallic, vec3 fresnel, SurfaceData surface, vec3 albedo
|
||||
) {
|
||||
float metallic, vec3 fresnel, SurfaceData surface, vec3 albedo) {
|
||||
vec4 shading = evalPBRShading(metallic, fresnel, surface);
|
||||
diffuse = vec3(shading.w);
|
||||
diffuse *= mix(vec3(1.0), albedo, isAlbedoEnabled());
|
||||
|
|
|
@ -422,8 +422,8 @@ void Model::initJointStates() {
|
|||
}
|
||||
|
||||
bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const glm::vec3& direction, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal,
|
||||
QString& extraInfo, bool pickAgainstTriangles, bool allowBackface) {
|
||||
BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo,
|
||||
bool pickAgainstTriangles, bool allowBackface) {
|
||||
|
||||
bool intersectedSomething = false;
|
||||
|
||||
|
@ -454,6 +454,10 @@ bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const g
|
|||
QMutexLocker locker(&_mutex);
|
||||
|
||||
float bestDistance = std::numeric_limits<float>::max();
|
||||
Triangle bestModelTriangle;
|
||||
Triangle bestWorldTriangle;
|
||||
int bestSubMeshIndex = 0;
|
||||
|
||||
int subMeshIndex = 0;
|
||||
const FBXGeometry& geometry = getFBXGeometry();
|
||||
|
||||
|
@ -471,8 +475,8 @@ bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const g
|
|||
for (auto& triangleSet : _modelSpaceMeshTriangleSets) {
|
||||
float triangleSetDistance = 0.0f;
|
||||
BoxFace triangleSetFace;
|
||||
glm::vec3 triangleSetNormal;
|
||||
if (triangleSet.findRayIntersection(meshFrameOrigin, meshFrameDirection, triangleSetDistance, triangleSetFace, triangleSetNormal, pickAgainstTriangles, allowBackface)) {
|
||||
Triangle triangleSetTriangle;
|
||||
if (triangleSet.findRayIntersection(meshFrameOrigin, meshFrameDirection, triangleSetDistance, triangleSetFace, triangleSetTriangle, pickAgainstTriangles, allowBackface)) {
|
||||
|
||||
glm::vec3 meshIntersectionPoint = meshFrameOrigin + (meshFrameDirection * triangleSetDistance);
|
||||
glm::vec3 worldIntersectionPoint = glm::vec3(meshToWorldMatrix * glm::vec4(meshIntersectionPoint, 1.0f));
|
||||
|
@ -482,8 +486,11 @@ bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const g
|
|||
bestDistance = worldDistance;
|
||||
intersectedSomething = true;
|
||||
face = triangleSetFace;
|
||||
surfaceNormal = glm::vec3(meshToWorldMatrix * glm::vec4(triangleSetNormal, 0.0f));
|
||||
extraInfo = geometry.getModelNameOfMesh(subMeshIndex);
|
||||
bestModelTriangle = triangleSetTriangle;
|
||||
bestWorldTriangle = triangleSetTriangle * meshToWorldMatrix;
|
||||
extraInfo["worldIntersectionPoint"] = vec3toVariant(worldIntersectionPoint);
|
||||
extraInfo["meshIntersectionPoint"] = vec3toVariant(meshIntersectionPoint);
|
||||
bestSubMeshIndex = subMeshIndex;
|
||||
}
|
||||
}
|
||||
subMeshIndex++;
|
||||
|
@ -491,9 +498,24 @@ bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const g
|
|||
|
||||
if (intersectedSomething) {
|
||||
distance = bestDistance;
|
||||
surfaceNormal = bestWorldTriangle.getNormal();
|
||||
if (pickAgainstTriangles) {
|
||||
extraInfo["subMeshIndex"] = bestSubMeshIndex;
|
||||
extraInfo["subMeshName"] = geometry.getModelNameOfMesh(bestSubMeshIndex);
|
||||
extraInfo["subMeshTriangleWorld"] = QVariantMap{
|
||||
{ "v0", vec3toVariant(bestWorldTriangle.v0) },
|
||||
{ "v1", vec3toVariant(bestWorldTriangle.v1) },
|
||||
{ "v2", vec3toVariant(bestWorldTriangle.v2) },
|
||||
};
|
||||
extraInfo["subMeshNormal"] = vec3toVariant(bestModelTriangle.getNormal());
|
||||
extraInfo["subMeshTriangle"] = QVariantMap{
|
||||
{ "v0", vec3toVariant(bestModelTriangle.v0) },
|
||||
{ "v1", vec3toVariant(bestModelTriangle.v1) },
|
||||
{ "v2", vec3toVariant(bestModelTriangle.v2) },
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return intersectedSomething;
|
||||
}
|
||||
|
||||
return intersectedSomething;
|
||||
|
|
|
@ -161,8 +161,8 @@ public:
|
|||
void setJointTranslation(int index, bool valid, const glm::vec3& translation, float priority);
|
||||
|
||||
bool findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const glm::vec3& direction, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal,
|
||||
QString& extraInfo, bool pickAgainstTriangles = false, bool allowBackface = false);
|
||||
BoxFace& face, glm::vec3& surfaceNormal,
|
||||
QVariantMap& extraInfo, bool pickAgainstTriangles = false, bool allowBackface = false);
|
||||
|
||||
void setOffset(const glm::vec3& offset);
|
||||
const glm::vec3& getOffset() const { return _offset; }
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
|
||||
#include "RenderDeferredTask.h"
|
||||
|
||||
#include <DependencyManager.h>
|
||||
|
||||
#include <PerfStat.h>
|
||||
#include <PathUtils.h>
|
||||
#include <ViewFrustum.h>
|
||||
|
@ -168,7 +170,7 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren
|
|||
task.addJob<DrawHaze>("DrawHazeDeferred", drawHazeInputs);
|
||||
|
||||
// Render transparent objects forward in LightingBuffer
|
||||
const auto transparentsInputs = DrawDeferred::Inputs(transparents, lightingModel).asVarying();
|
||||
const auto transparentsInputs = DrawDeferred::Inputs(transparents, lightingModel, lightClusters).asVarying();
|
||||
task.addJob<DrawDeferred>("DrawTransparentDeferred", transparentsInputs, shapePlumber);
|
||||
|
||||
// Light Cluster Grid Debuging job
|
||||
|
@ -298,6 +300,8 @@ void DrawDeferred::run(const RenderContextPointer& renderContext, const Inputs&
|
|||
|
||||
const auto& inItems = inputs.get0();
|
||||
const auto& lightingModel = inputs.get1();
|
||||
const auto& lightClusters = inputs.get2();
|
||||
auto deferredLightingEffect = DependencyManager::get<DeferredLightingEffect>();
|
||||
|
||||
RenderArgs* args = renderContext->args;
|
||||
|
||||
|
@ -319,7 +323,13 @@ void DrawDeferred::run(const RenderContextPointer& renderContext, const Inputs&
|
|||
// Setup lighting model for all items;
|
||||
batch.setUniformBuffer(render::ShapePipeline::Slot::LIGHTING_MODEL, lightingModel->getParametersBuffer());
|
||||
|
||||
// Setup haze iff current zone has haze
|
||||
deferredLightingEffect->setupLocalLightsBatch(batch,
|
||||
render::ShapePipeline::Slot::LIGHT_CLUSTER_GRID_CLUSTER_GRID_SLOT,
|
||||
render::ShapePipeline::Slot::LIGHT_CLUSTER_GRID_CLUSTER_CONTENT_SLOT,
|
||||
render::ShapePipeline::Slot::LIGHT_CLUSTER_GRID_FRUSTUM_GRID_SLOT,
|
||||
lightClusters);
|
||||
|
||||
// Setup haze if current zone has haze
|
||||
auto hazeStage = args->_scene->getStage<HazeStage>();
|
||||
if (hazeStage && hazeStage->_currentFrame._hazes.size() > 0) {
|
||||
graphics::HazePointer hazePointer = hazeStage->getHaze(hazeStage->_currentFrame._hazes.front());
|
||||
|
@ -341,6 +351,11 @@ void DrawDeferred::run(const RenderContextPointer& renderContext, const Inputs&
|
|||
|
||||
args->_batch = nullptr;
|
||||
args->_globalShapeKey = 0;
|
||||
|
||||
deferredLightingEffect->unsetLocalLightsBatch(batch,
|
||||
render::ShapePipeline::Slot::LIGHT_CLUSTER_GRID_CLUSTER_GRID_SLOT,
|
||||
render::ShapePipeline::Slot::LIGHT_CLUSTER_GRID_CLUSTER_CONTENT_SLOT,
|
||||
render::ShapePipeline::Slot::LIGHT_CLUSTER_GRID_FRUSTUM_GRID_SLOT);
|
||||
});
|
||||
|
||||
config->setNumDrawn((int)inItems.size());
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
#include <gpu/Pipeline.h>
|
||||
#include <render/RenderFetchCullSortTask.h>
|
||||
#include "LightingModel.h"
|
||||
#include "LightClusters.h"
|
||||
|
||||
class DrawDeferredConfig : public render::Job::Config {
|
||||
Q_OBJECT
|
||||
|
@ -40,7 +41,7 @@ protected:
|
|||
|
||||
class DrawDeferred {
|
||||
public:
|
||||
using Inputs = render::VaryingSet2<render::ItemBounds, LightingModelPointer>;
|
||||
using Inputs = render::VaryingSet3 <render::ItemBounds, LightingModelPointer, LightClustersPointer>;
|
||||
using Config = DrawDeferredConfig;
|
||||
using JobModel = render::Job::ModelI<DrawDeferred, Inputs, Config>;
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue