Merged with master

This commit is contained in:
Olivier Prat 2018-01-30 09:52:30 +01:00
commit 5e6300ce46
147 changed files with 3611 additions and 977 deletions

View file

@ -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()) {

View file

@ -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 };

View file

@ -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)

View file

@ -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 };

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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) {

View file

@ -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));

View file

@ -193,7 +193,7 @@ Item {
color: hifi.colors.white;
}
// "Change Passphrase" button
// "Change Security Pic" button
HifiControlsUit.Button {
id: changeSecurityImageButton;
color: hifi.buttons.blue;

View file

@ -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();
}

View file

@ -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

View file

@ -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";
}

View file

@ -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
}
}

View file

@ -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

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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());

View 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);
}
}

View 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

View file

@ -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, [] {

View file

@ -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";

View file

@ -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;
}
}

View file

@ -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) {

View file

@ -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);

View file

@ -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

View file

@ -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.
*/

View file

@ -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());
}

View file

@ -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;
}

View file

@ -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;
}
}

View file

@ -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());
}

View file

@ -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);
}
}
}

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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;

View file

@ -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) {

View file

@ -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;
};

View file

@ -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 {

View file

@ -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;
}

View file

@ -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();
}

View file

@ -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);

View file

@ -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;

View file

@ -982,6 +982,7 @@ RayToAvatarIntersectionResult() : intersects(false), avatarID(), distance(0) {}
QUuid avatarID;
float distance;
glm::vec3 intersection;
QVariantMap extraInfo;
};
Q_DECLARE_METATYPE(RayToAvatarIntersectionResult)

View file

@ -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 };

View file

@ -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,

View file

@ -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();

View file

@ -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;

View file

@ -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) {

View file

@ -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;

View file

@ -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; }

View file

@ -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) {

View file

@ -62,7 +62,7 @@ public:
BoxFace face;
glm::vec3 intersection;
glm::vec3 surfaceNormal;
EntityItemPointer entity;
QVariantMap extraInfo;
};
Q_DECLARE_METATYPE(RayToEntityIntersectionResult)

View file

@ -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;
}

View file

@ -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; }

View file

@ -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

View file

@ -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;

View file

@ -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

View file

@ -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

View file

@ -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();

View file

@ -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!

View file

@ -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;

View file

@ -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);

View file

@ -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;

View file

@ -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();

View file

@ -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);

View file

@ -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();

View file

@ -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;

View file

@ -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;
}

View file

@ -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;

View file

@ -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);
}

View file

@ -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;

View file

@ -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() {}
};

View file

@ -56,10 +56,10 @@ Light getLight(int index) {
}
<@else@>
uniform lightBuffer {
uniform keyLightBuffer {
Light light;
};
Light getLight() {
Light getKeyLight() {
return light;
}

View file

@ -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();
}

View file

@ -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);

View file

@ -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;
}

View file

@ -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; }

View file

@ -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);

View file

@ -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 };

View file

@ -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);

View file

@ -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

View file

@ -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

View 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;
}

View 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

View file

@ -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);
});
};
};

View file

@ -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;
};

View file

@ -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@>

View file

@ -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);

View file

@ -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; }

View file

@ -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));

View file

@ -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

View file

@ -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);

View file

@ -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@>

View 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;
}

View file

@ -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());

View file

@ -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;

View file

@ -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; }

View file

@ -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());

View file

@ -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