mirror of
https://github.com/HifiExperiments/overte.git
synced 2025-07-22 18:13:52 +02:00
Merge remote-tracking branch 'upstream/master' into avatar-verification
This commit is contained in:
commit
f3efee56c0
137 changed files with 4429 additions and 2192 deletions
|
@ -107,6 +107,10 @@ BakeVersion currentBakeVersionForAssetType(BakedAssetType type) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString getBakeMapping(const AssetUtils::AssetHash& hash, const QString& relativeFilePath) {
|
||||||
|
return AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER + hash + "/" + relativeFilePath;
|
||||||
|
}
|
||||||
|
|
||||||
const QString ASSET_SERVER_LOGGING_TARGET_NAME = "asset-server";
|
const QString ASSET_SERVER_LOGGING_TARGET_NAME = "asset-server";
|
||||||
|
|
||||||
void AssetServer::bakeAsset(const AssetUtils::AssetHash& assetHash, const AssetUtils::AssetPath& assetPath, const QString& filePath) {
|
void AssetServer::bakeAsset(const AssetUtils::AssetHash& assetHash, const AssetUtils::AssetPath& assetPath, const QString& filePath) {
|
||||||
|
@ -141,26 +145,27 @@ std::pair<AssetUtils::BakingStatus, QString> AssetServer::getAssetStatus(const A
|
||||||
return { AssetUtils::Baked, "" };
|
return { AssetUtils::Baked, "" };
|
||||||
}
|
}
|
||||||
|
|
||||||
auto dotIndex = path.lastIndexOf(".");
|
BakedAssetType type = assetTypeForFilename(path);
|
||||||
if (dotIndex == -1) {
|
if (type == BakedAssetType::Undefined) {
|
||||||
return { AssetUtils::Irrelevant, "" };
|
return { AssetUtils::Irrelevant, "" };
|
||||||
}
|
}
|
||||||
|
|
||||||
auto extension = path.mid(dotIndex + 1);
|
bool loaded;
|
||||||
|
AssetMeta meta;
|
||||||
|
std::tie(loaded, meta) = readMetaFile(hash);
|
||||||
|
|
||||||
QString bakedFilename;
|
// We create a meta file for Skyboxes at runtime when they get requested
|
||||||
|
// Otherwise, textures don't get baked by themselves.
|
||||||
if (BAKEABLE_MODEL_EXTENSIONS.contains(extension)) {
|
if (type == BakedAssetType::Texture && !loaded) {
|
||||||
bakedFilename = BAKED_MODEL_SIMPLE_NAME;
|
|
||||||
} else if (BAKEABLE_TEXTURE_EXTENSIONS.contains(extension.toLocal8Bit()) && hasMetaFile(hash)) {
|
|
||||||
bakedFilename = BAKED_TEXTURE_SIMPLE_NAME;
|
|
||||||
} else if (BAKEABLE_SCRIPT_EXTENSIONS.contains(extension)) {
|
|
||||||
bakedFilename = BAKED_SCRIPT_SIMPLE_NAME;
|
|
||||||
} else {
|
|
||||||
return { AssetUtils::Irrelevant, "" };
|
return { AssetUtils::Irrelevant, "" };
|
||||||
}
|
}
|
||||||
|
|
||||||
auto bakedPath = AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER + hash + "/" + bakedFilename;
|
QString bakedFilename = bakedFilenameForAssetType(type);
|
||||||
|
auto bakedPath = getBakeMapping(hash, bakedFilename);
|
||||||
|
if (loaded && !meta.redirectTarget.isEmpty()) {
|
||||||
|
bakedPath = meta.redirectTarget;
|
||||||
|
}
|
||||||
|
|
||||||
auto jt = _fileMappings.find(bakedPath);
|
auto jt = _fileMappings.find(bakedPath);
|
||||||
if (jt != _fileMappings.end()) {
|
if (jt != _fileMappings.end()) {
|
||||||
if (jt->second == hash) {
|
if (jt->second == hash) {
|
||||||
|
@ -168,14 +173,8 @@ std::pair<AssetUtils::BakingStatus, QString> AssetServer::getAssetStatus(const A
|
||||||
} else {
|
} else {
|
||||||
return { AssetUtils::Baked, "" };
|
return { AssetUtils::Baked, "" };
|
||||||
}
|
}
|
||||||
} else {
|
} else if (loaded && meta.failedLastBake) {
|
||||||
bool loaded;
|
return { AssetUtils::Error, meta.lastBakeErrors };
|
||||||
AssetMeta meta;
|
|
||||||
|
|
||||||
std::tie(loaded, meta) = readMetaFile(hash);
|
|
||||||
if (loaded && meta.failedLastBake) {
|
|
||||||
return { AssetUtils::Error, meta.lastBakeErrors };
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return { AssetUtils::Pending, "" };
|
return { AssetUtils::Pending, "" };
|
||||||
|
@ -227,8 +226,16 @@ bool AssetServer::needsToBeBaked(const AssetUtils::AssetPath& path, const AssetU
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool loaded;
|
||||||
|
AssetMeta meta;
|
||||||
|
std::tie(loaded, meta) = readMetaFile(assetHash);
|
||||||
|
|
||||||
QString bakedFilename = bakedFilenameForAssetType(type);
|
QString bakedFilename = bakedFilenameForAssetType(type);
|
||||||
auto bakedPath = AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER + assetHash + "/" + bakedFilename;
|
auto bakedPath = getBakeMapping(assetHash, bakedFilename);
|
||||||
|
if (loaded && !meta.redirectTarget.isEmpty()) {
|
||||||
|
bakedPath = meta.redirectTarget;
|
||||||
|
}
|
||||||
|
|
||||||
auto mappingIt = _fileMappings.find(bakedPath);
|
auto mappingIt = _fileMappings.find(bakedPath);
|
||||||
bool bakedMappingExists = mappingIt != _fileMappings.end();
|
bool bakedMappingExists = mappingIt != _fileMappings.end();
|
||||||
|
|
||||||
|
@ -238,10 +245,8 @@ bool AssetServer::needsToBeBaked(const AssetUtils::AssetPath& path, const AssetU
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool loaded;
|
// We create a meta file for Skyboxes at runtime when they get requested
|
||||||
AssetMeta meta;
|
// Otherwise, textures don't get baked by themselves.
|
||||||
std::tie(loaded, meta) = readMetaFile(assetHash);
|
|
||||||
|
|
||||||
if (type == BakedAssetType::Texture && !loaded) {
|
if (type == BakedAssetType::Texture && !loaded) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -633,36 +638,33 @@ void AssetServer::handleGetMappingOperation(ReceivedMessage& message, NLPacketLi
|
||||||
if (it != _fileMappings.end()) {
|
if (it != _fileMappings.end()) {
|
||||||
|
|
||||||
// check if we should re-direct to a baked asset
|
// check if we should re-direct to a baked asset
|
||||||
|
|
||||||
// first, figure out from the mapping extension what type of file this is
|
|
||||||
auto assetPathExtension = assetPath.mid(assetPath.lastIndexOf('.') + 1).toLower();
|
|
||||||
|
|
||||||
auto type = assetTypeForFilename(assetPath);
|
|
||||||
QString bakedRootFile = bakedFilenameForAssetType(type);
|
|
||||||
|
|
||||||
auto originalAssetHash = it->second;
|
auto originalAssetHash = it->second;
|
||||||
QString redirectedAssetHash;
|
QString redirectedAssetHash;
|
||||||
QString bakedAssetPath;
|
|
||||||
quint8 wasRedirected = false;
|
quint8 wasRedirected = false;
|
||||||
bool bakingDisabled = false;
|
bool bakingDisabled = false;
|
||||||
|
|
||||||
if (!bakedRootFile.isEmpty()) {
|
bool loaded;
|
||||||
// we ran into an asset for which we could have a baked version, let's check if it's ready
|
AssetMeta meta;
|
||||||
bakedAssetPath = AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER + originalAssetHash + "/" + bakedRootFile;
|
std::tie(loaded, meta) = readMetaFile(originalAssetHash);
|
||||||
auto bakedIt = _fileMappings.find(bakedAssetPath);
|
|
||||||
|
|
||||||
if (bakedIt != _fileMappings.end()) {
|
auto type = assetTypeForFilename(assetPath);
|
||||||
if (bakedIt->second != originalAssetHash) {
|
QString bakedRootFile = bakedFilenameForAssetType(type);
|
||||||
qDebug() << "Did find baked version for: " << originalAssetHash << assetPath;
|
QString bakedAssetPath = getBakeMapping(originalAssetHash, bakedRootFile);
|
||||||
// we found a baked version of the requested asset to serve, redirect to that
|
|
||||||
redirectedAssetHash = bakedIt->second;
|
if (loaded && !meta.redirectTarget.isEmpty()) {
|
||||||
wasRedirected = true;
|
bakedAssetPath = meta.redirectTarget;
|
||||||
} else {
|
}
|
||||||
qDebug() << "Did not find baked version for: " << originalAssetHash << assetPath << " (disabled)";
|
|
||||||
bakingDisabled = true;
|
auto bakedIt = _fileMappings.find(bakedAssetPath);
|
||||||
}
|
if (bakedIt != _fileMappings.end()) {
|
||||||
|
if (bakedIt->second != originalAssetHash) {
|
||||||
|
qDebug() << "Did find baked version for: " << originalAssetHash << assetPath;
|
||||||
|
// we found a baked version of the requested asset to serve, redirect to that
|
||||||
|
redirectedAssetHash = bakedIt->second;
|
||||||
|
wasRedirected = true;
|
||||||
} else {
|
} else {
|
||||||
qDebug() << "Did not find baked version for: " << originalAssetHash << assetPath;
|
qDebug() << "Did not find baked version for: " << originalAssetHash << assetPath << " (disabled)";
|
||||||
|
bakingDisabled = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -684,20 +686,13 @@ void AssetServer::handleGetMappingOperation(ReceivedMessage& message, NLPacketLi
|
||||||
|
|
||||||
auto query = QUrlQuery(url.query());
|
auto query = QUrlQuery(url.query());
|
||||||
bool isSkybox = query.hasQueryItem("skybox");
|
bool isSkybox = query.hasQueryItem("skybox");
|
||||||
if (isSkybox) {
|
if (isSkybox && !loaded) {
|
||||||
bool loaded;
|
AssetMeta needsBakingMeta;
|
||||||
AssetMeta meta;
|
needsBakingMeta.bakeVersion = NEEDS_BAKING_BAKE_VERSION;
|
||||||
std::tie(loaded, meta) = readMetaFile(originalAssetHash);
|
|
||||||
|
|
||||||
if (!loaded) {
|
|
||||||
AssetMeta needsBakingMeta;
|
|
||||||
needsBakingMeta.bakeVersion = NEEDS_BAKING_BAKE_VERSION;
|
|
||||||
|
|
||||||
writeMetaFile(originalAssetHash, needsBakingMeta);
|
|
||||||
if (!bakingDisabled) {
|
|
||||||
maybeBake(assetPath, originalAssetHash);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
writeMetaFile(originalAssetHash, needsBakingMeta);
|
||||||
|
if (!bakingDisabled) {
|
||||||
|
maybeBake(assetPath, originalAssetHash);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1297,14 +1292,6 @@ bool AssetServer::renameMapping(AssetUtils::AssetPath oldPath, AssetUtils::Asset
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 AssetUtils::AssetHash& hash, const QString& relativeFilePath) {
|
|
||||||
return AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER + hash + "/" + relativeFilePath;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AssetServer::handleFailedBake(QString originalAssetHash, QString assetPath, QString errors) {
|
void AssetServer::handleFailedBake(QString originalAssetHash, QString assetPath, QString errors) {
|
||||||
qDebug() << "Failed to bake: " << originalAssetHash << assetPath << "(" << errors << ")";
|
qDebug() << "Failed to bake: " << originalAssetHash << assetPath << "(" << errors << ")";
|
||||||
|
|
||||||
|
@ -1326,12 +1313,78 @@ void AssetServer::handleFailedBake(QString originalAssetHash, QString assetPath,
|
||||||
}
|
}
|
||||||
|
|
||||||
void AssetServer::handleCompletedBake(QString originalAssetHash, QString originalAssetPath,
|
void AssetServer::handleCompletedBake(QString originalAssetHash, QString originalAssetPath,
|
||||||
QString bakedTempOutputDir, QVector<QString> bakedFilePaths) {
|
QString bakedTempOutputDir) {
|
||||||
|
auto reportCompletion = [this, originalAssetPath, originalAssetHash](bool errorCompletingBake,
|
||||||
|
QString errorReason,
|
||||||
|
QString redirectTarget) {
|
||||||
|
auto type = assetTypeForFilename(originalAssetPath);
|
||||||
|
auto currentTypeVersion = currentBakeVersionForAssetType(type);
|
||||||
|
|
||||||
|
AssetMeta meta;
|
||||||
|
meta.bakeVersion = currentTypeVersion;
|
||||||
|
meta.failedLastBake = errorCompletingBake;
|
||||||
|
meta.redirectTarget = redirectTarget;
|
||||||
|
|
||||||
|
if (errorCompletingBake) {
|
||||||
|
qWarning() << "Could not complete bake for" << originalAssetHash;
|
||||||
|
meta.lastBakeErrors = errorReason;
|
||||||
|
}
|
||||||
|
|
||||||
|
writeMetaFile(originalAssetHash, meta);
|
||||||
|
|
||||||
|
_pendingBakes.remove(originalAssetHash);
|
||||||
|
};
|
||||||
|
|
||||||
bool errorCompletingBake { false };
|
bool errorCompletingBake { false };
|
||||||
QString errorReason;
|
QString errorReason;
|
||||||
|
QString redirectTarget;
|
||||||
|
|
||||||
qDebug() << "Completing bake for " << originalAssetHash;
|
qDebug() << "Completing bake for " << originalAssetHash;
|
||||||
|
|
||||||
|
// Find the directory containing the baked content
|
||||||
|
QDir outputDir(bakedTempOutputDir);
|
||||||
|
QString outputDirName = outputDir.dirName();
|
||||||
|
auto directories = outputDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
|
||||||
|
QString bakedDirectoryPath;
|
||||||
|
for (const auto& dirName : directories) {
|
||||||
|
outputDir.cd(dirName);
|
||||||
|
if (outputDir.exists("baked") && outputDir.exists("original")) {
|
||||||
|
bakedDirectoryPath = outputDir.filePath("baked");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
outputDir.cdUp();
|
||||||
|
}
|
||||||
|
if (bakedDirectoryPath.isEmpty()) {
|
||||||
|
errorCompletingBake = true;
|
||||||
|
errorReason = "Failed to find baking output";
|
||||||
|
|
||||||
|
// Cleanup temporary output directory
|
||||||
|
PathUtils::deleteMyTemporaryDir(outputDirName);
|
||||||
|
reportCompletion(errorCompletingBake, errorReason, redirectTarget);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compile list of all the baked files
|
||||||
|
QDirIterator it(bakedDirectoryPath, QDirIterator::Subdirectories);
|
||||||
|
QVector<QString> bakedFilePaths;
|
||||||
|
while (it.hasNext()) {
|
||||||
|
it.next();
|
||||||
|
if (it.fileInfo().isFile()) {
|
||||||
|
bakedFilePaths.push_back(it.filePath());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (bakedFilePaths.isEmpty()) {
|
||||||
|
errorCompletingBake = true;
|
||||||
|
errorReason = "Baking output has no files";
|
||||||
|
|
||||||
|
// Cleanup temporary output directory
|
||||||
|
PathUtils::deleteMyTemporaryDir(outputDirName);
|
||||||
|
reportCompletion(errorCompletingBake, errorReason, redirectTarget);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QDir bakedDirectory(bakedDirectoryPath);
|
||||||
|
|
||||||
for (auto& filePath : bakedFilePaths) {
|
for (auto& filePath : bakedFilePaths) {
|
||||||
// figure out the hash for the contents of this file
|
// figure out the hash for the contents of this file
|
||||||
QFile file(filePath);
|
QFile file(filePath);
|
||||||
|
@ -1340,89 +1393,72 @@ void AssetServer::handleCompletedBake(QString originalAssetHash, QString origina
|
||||||
|
|
||||||
AssetUtils::AssetHash bakedFileHash;
|
AssetUtils::AssetHash bakedFileHash;
|
||||||
|
|
||||||
if (file.open(QIODevice::ReadOnly)) {
|
if (!file.open(QIODevice::ReadOnly)) {
|
||||||
QCryptographicHash hasher(QCryptographicHash::Sha256);
|
|
||||||
|
|
||||||
if (hasher.addData(&file)) {
|
|
||||||
bakedFileHash = hasher.result().toHex();
|
|
||||||
} else {
|
|
||||||
// stop handling this bake, couldn't hash the contents of the file
|
|
||||||
errorCompletingBake = true;
|
|
||||||
errorReason = "Failed to finalize bake";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// first check that we don't already have this bake file in our list
|
|
||||||
auto bakeFileDestination = _filesDirectory.absoluteFilePath(bakedFileHash);
|
|
||||||
if (!QFile::exists(bakeFileDestination)) {
|
|
||||||
// copy each to our files folder (with the hash as their filename)
|
|
||||||
if (!file.copy(_filesDirectory.absoluteFilePath(bakedFileHash))) {
|
|
||||||
// stop handling this bake, couldn't copy the bake file into our files directory
|
|
||||||
errorCompletingBake = true;
|
|
||||||
errorReason = "Failed to copy baked assets to asset server";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// setup the mapping for this bake file
|
|
||||||
auto relativeFilePath = QUrl(filePath).fileName();
|
|
||||||
qDebug() << "Relative file path is: " << relativeFilePath;
|
|
||||||
if (relativeFilePath.endsWith(".fbx", Qt::CaseInsensitive)) {
|
|
||||||
// for an FBX file, we replace the filename with the simple name
|
|
||||||
// (to handle the case where two mapped assets have the same hash but different names)
|
|
||||||
relativeFilePath = BAKED_ASSET_SIMPLE_FBX_NAME;
|
|
||||||
} else if (relativeFilePath.endsWith(".js", Qt::CaseInsensitive)) {
|
|
||||||
relativeFilePath = BAKED_ASSET_SIMPLE_JS_NAME;
|
|
||||||
} else if (!originalAssetPath.endsWith(".fbx", Qt::CaseInsensitive)) {
|
|
||||||
relativeFilePath = BAKED_ASSET_SIMPLE_TEXTURE_NAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString bakeMapping = getBakeMapping(originalAssetHash, relativeFilePath);
|
|
||||||
|
|
||||||
// add a mapping (under the hidden baked folder) for this file resulting from the bake
|
|
||||||
if (setMapping(bakeMapping, bakedFileHash)) {
|
|
||||||
qDebug() << "Added" << bakeMapping << "for bake file" << bakedFileHash << "from bake of" << originalAssetHash;
|
|
||||||
} else {
|
|
||||||
qDebug() << "Failed to set mapping";
|
|
||||||
// stop handling this bake, couldn't add a mapping for this bake file
|
|
||||||
errorCompletingBake = true;
|
|
||||||
errorReason = "Failed to finalize bake";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
qDebug() << "Failed to open baked file: " << filePath;
|
qDebug() << "Failed to open baked file: " << filePath;
|
||||||
// stop handling this bake, we couldn't open one of the files for reading
|
// stop handling this bake, we couldn't open one of the files for reading
|
||||||
errorCompletingBake = true;
|
errorCompletingBake = true;
|
||||||
errorReason = "Failed to finalize bake";
|
errorReason = "Could not open baked file " + file.fileName();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
for (auto& filePath : bakedFilePaths) {
|
QCryptographicHash hasher(QCryptographicHash::Sha256);
|
||||||
QFile file(filePath);
|
|
||||||
if (!file.remove()) {
|
if (!hasher.addData(&file)) {
|
||||||
qWarning() << "Failed to remove temporary file:" << filePath;
|
// stop handling this bake, couldn't hash the contents of the file
|
||||||
|
errorCompletingBake = true;
|
||||||
|
errorReason = "Could not hash data for " + file.fileName();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (!QDir(bakedTempOutputDir).rmdir(".")) {
|
bakedFileHash = hasher.result().toHex();
|
||||||
qWarning() << "Failed to remove temporary directory:" << bakedTempOutputDir;
|
|
||||||
|
// first check that we don't already have this bake file in our list
|
||||||
|
auto bakeFileDestination = _filesDirectory.absoluteFilePath(bakedFileHash);
|
||||||
|
if (!QFile::exists(bakeFileDestination)) {
|
||||||
|
// copy each to our files folder (with the hash as their filename)
|
||||||
|
if (!file.copy(_filesDirectory.absoluteFilePath(bakedFileHash))) {
|
||||||
|
// stop handling this bake, couldn't copy the bake file into our files directory
|
||||||
|
errorCompletingBake = true;
|
||||||
|
errorReason = "Failed to copy baked assets to asset server";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// setup the mapping for this bake file
|
||||||
|
auto relativeFilePath = bakedDirectory.relativeFilePath(filePath);
|
||||||
|
|
||||||
|
QString bakeMapping = getBakeMapping(originalAssetHash, relativeFilePath);
|
||||||
|
|
||||||
|
// Check if this is the file we should redirect to when someone asks for the original asset
|
||||||
|
if ((relativeFilePath.endsWith(".baked.fst", Qt::CaseInsensitive) && originalAssetPath.endsWith(".fbx")) ||
|
||||||
|
(relativeFilePath.endsWith(".texmeta.json", Qt::CaseInsensitive) && !originalAssetPath.endsWith(".fbx"))) {
|
||||||
|
if (!redirectTarget.isEmpty()) {
|
||||||
|
qWarning() << "Found multiple baked redirect target for" << originalAssetPath;
|
||||||
|
}
|
||||||
|
redirectTarget = bakeMapping;
|
||||||
|
}
|
||||||
|
|
||||||
|
// add a mapping (under the hidden baked folder) for this file resulting from the bake
|
||||||
|
if (!setMapping(bakeMapping, bakedFileHash)) {
|
||||||
|
qDebug() << "Failed to set mapping";
|
||||||
|
// stop handling this bake, couldn't add a mapping for this bake file
|
||||||
|
errorCompletingBake = true;
|
||||||
|
errorReason = "Failed to set mapping for baked file " + file.fileName();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "Added" << bakeMapping << "for bake file" << bakedFileHash << "from bake of" << originalAssetHash;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto type = assetTypeForFilename(originalAssetPath);
|
|
||||||
auto currentTypeVersion = currentBakeVersionForAssetType(type);
|
|
||||||
|
|
||||||
AssetMeta meta;
|
if (redirectTarget.isEmpty()) {
|
||||||
meta.bakeVersion = currentTypeVersion;
|
errorCompletingBake = true;
|
||||||
meta.failedLastBake = errorCompletingBake;
|
errorReason = "Could not find root file for baked output";
|
||||||
|
|
||||||
if (errorCompletingBake) {
|
|
||||||
qWarning() << "Could not complete bake for" << originalAssetHash;
|
|
||||||
meta.lastBakeErrors = errorReason;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
writeMetaFile(originalAssetHash, meta);
|
// Cleanup temporary output directory
|
||||||
|
PathUtils::deleteMyTemporaryDir(outputDirName);
|
||||||
_pendingBakes.remove(originalAssetHash);
|
reportCompletion(errorCompletingBake, errorReason, redirectTarget);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AssetServer::handleAbortedBake(QString originalAssetHash, QString assetPath) {
|
void AssetServer::handleAbortedBake(QString originalAssetHash, QString assetPath) {
|
||||||
|
@ -1435,6 +1471,7 @@ void AssetServer::handleAbortedBake(QString originalAssetHash, QString assetPath
|
||||||
static const QString BAKE_VERSION_KEY = "bake_version";
|
static const QString BAKE_VERSION_KEY = "bake_version";
|
||||||
static const QString FAILED_LAST_BAKE_KEY = "failed_last_bake";
|
static const QString FAILED_LAST_BAKE_KEY = "failed_last_bake";
|
||||||
static const QString LAST_BAKE_ERRORS_KEY = "last_bake_errors";
|
static const QString LAST_BAKE_ERRORS_KEY = "last_bake_errors";
|
||||||
|
static const QString REDIRECT_TARGET_KEY = "redirect_target";
|
||||||
|
|
||||||
std::pair<bool, AssetMeta> AssetServer::readMetaFile(AssetUtils::AssetHash hash) {
|
std::pair<bool, AssetMeta> AssetServer::readMetaFile(AssetUtils::AssetHash hash) {
|
||||||
auto metaFilePath = AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER + hash + "/" + "meta.json";
|
auto metaFilePath = AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER + hash + "/" + "meta.json";
|
||||||
|
@ -1461,6 +1498,7 @@ std::pair<bool, AssetMeta> AssetServer::readMetaFile(AssetUtils::AssetHash hash)
|
||||||
auto bakeVersion = root[BAKE_VERSION_KEY];
|
auto bakeVersion = root[BAKE_VERSION_KEY];
|
||||||
auto failedLastBake = root[FAILED_LAST_BAKE_KEY];
|
auto failedLastBake = root[FAILED_LAST_BAKE_KEY];
|
||||||
auto lastBakeErrors = root[LAST_BAKE_ERRORS_KEY];
|
auto lastBakeErrors = root[LAST_BAKE_ERRORS_KEY];
|
||||||
|
auto redirectTarget = root[REDIRECT_TARGET_KEY];
|
||||||
|
|
||||||
if (bakeVersion.isDouble()
|
if (bakeVersion.isDouble()
|
||||||
&& failedLastBake.isBool()
|
&& failedLastBake.isBool()
|
||||||
|
@ -1470,6 +1508,7 @@ std::pair<bool, AssetMeta> AssetServer::readMetaFile(AssetUtils::AssetHash hash)
|
||||||
meta.bakeVersion = bakeVersion.toInt();
|
meta.bakeVersion = bakeVersion.toInt();
|
||||||
meta.failedLastBake = failedLastBake.toBool();
|
meta.failedLastBake = failedLastBake.toBool();
|
||||||
meta.lastBakeErrors = lastBakeErrors.toString();
|
meta.lastBakeErrors = lastBakeErrors.toString();
|
||||||
|
meta.redirectTarget = redirectTarget.toString();
|
||||||
|
|
||||||
return { true, meta };
|
return { true, meta };
|
||||||
} else {
|
} else {
|
||||||
|
@ -1488,6 +1527,7 @@ bool AssetServer::writeMetaFile(AssetUtils::AssetHash originalAssetHash, const A
|
||||||
metaFileObject[BAKE_VERSION_KEY] = (int)meta.bakeVersion;
|
metaFileObject[BAKE_VERSION_KEY] = (int)meta.bakeVersion;
|
||||||
metaFileObject[FAILED_LAST_BAKE_KEY] = meta.failedLastBake;
|
metaFileObject[FAILED_LAST_BAKE_KEY] = meta.failedLastBake;
|
||||||
metaFileObject[LAST_BAKE_ERRORS_KEY] = meta.lastBakeErrors;
|
metaFileObject[LAST_BAKE_ERRORS_KEY] = meta.lastBakeErrors;
|
||||||
|
metaFileObject[REDIRECT_TARGET_KEY] = meta.redirectTarget;
|
||||||
|
|
||||||
QJsonDocument metaFileDoc;
|
QJsonDocument metaFileDoc;
|
||||||
metaFileDoc.setObject(metaFileObject);
|
metaFileDoc.setObject(metaFileObject);
|
||||||
|
@ -1521,10 +1561,18 @@ bool AssetServer::setBakingEnabled(const AssetUtils::AssetPathList& paths, bool
|
||||||
if (type == BakedAssetType::Undefined) {
|
if (type == BakedAssetType::Undefined) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
QString bakedFilename = bakedFilenameForAssetType(type);
|
|
||||||
|
|
||||||
auto hash = it->second;
|
auto hash = it->second;
|
||||||
|
|
||||||
|
bool loaded;
|
||||||
|
AssetMeta meta;
|
||||||
|
std::tie(loaded, meta) = readMetaFile(hash);
|
||||||
|
|
||||||
|
QString bakedFilename = bakedFilenameForAssetType(type);
|
||||||
auto bakedMapping = getBakeMapping(hash, bakedFilename);
|
auto bakedMapping = getBakeMapping(hash, bakedFilename);
|
||||||
|
if (loaded && !meta.redirectTarget.isEmpty()) {
|
||||||
|
bakedMapping = meta.redirectTarget;
|
||||||
|
}
|
||||||
|
|
||||||
auto it = _fileMappings.find(bakedMapping);
|
auto it = _fileMappings.find(bakedMapping);
|
||||||
bool currentlyDisabled = (it != _fileMappings.end() && it->second == hash);
|
bool currentlyDisabled = (it != _fileMappings.end() && it->second == hash);
|
||||||
|
|
|
@ -62,12 +62,10 @@ enum class ScriptBakeVersion : BakeVersion {
|
||||||
};
|
};
|
||||||
|
|
||||||
struct AssetMeta {
|
struct AssetMeta {
|
||||||
AssetMeta() {
|
|
||||||
}
|
|
||||||
|
|
||||||
BakeVersion bakeVersion { INITIAL_BAKE_VERSION };
|
BakeVersion bakeVersion { INITIAL_BAKE_VERSION };
|
||||||
bool failedLastBake { false };
|
bool failedLastBake { false };
|
||||||
QString lastBakeErrors;
|
QString lastBakeErrors;
|
||||||
|
QString redirectTarget;
|
||||||
};
|
};
|
||||||
|
|
||||||
class BakeAssetTask;
|
class BakeAssetTask;
|
||||||
|
@ -139,8 +137,7 @@ private:
|
||||||
void bakeAsset(const AssetUtils::AssetHash& assetHash, const AssetUtils::AssetPath& assetPath, const QString& filePath);
|
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
|
/// Move baked content for asset to baked directory and update baked status
|
||||||
void handleCompletedBake(QString originalAssetHash, QString assetPath, QString bakedTempOutputDir,
|
void handleCompletedBake(QString originalAssetHash, QString assetPath, QString bakedTempOutputDir);
|
||||||
QVector<QString> bakedFilePaths);
|
|
||||||
void handleFailedBake(QString originalAssetHash, QString assetPath, QString errors);
|
void handleFailedBake(QString originalAssetHash, QString assetPath, QString errors);
|
||||||
void handleAbortedBake(QString originalAssetHash, QString assetPath);
|
void handleAbortedBake(QString originalAssetHash, QString assetPath);
|
||||||
|
|
||||||
|
|
|
@ -36,33 +36,38 @@ BakeAssetTask::BakeAssetTask(const AssetUtils::AssetHash& assetHash, const Asset
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void cleanupTempFiles(QString tempOutputDir, std::vector<QString> files) {
|
|
||||||
for (const auto& filename : files) {
|
|
||||||
QFile f { filename };
|
|
||||||
if (!f.remove()) {
|
|
||||||
qDebug() << "Failed to remove:" << filename;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!tempOutputDir.isEmpty()) {
|
|
||||||
QDir dir { tempOutputDir };
|
|
||||||
if (!dir.rmdir(".")) {
|
|
||||||
qDebug() << "Failed to remove temporary directory:" << tempOutputDir;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
void BakeAssetTask::run() {
|
void BakeAssetTask::run() {
|
||||||
if (_isBaking.exchange(true)) {
|
if (_isBaking.exchange(true)) {
|
||||||
qWarning() << "Tried to start bake asset task while already baking";
|
qWarning() << "Tried to start bake asset task while already baking";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Make a new temporary directory for the Oven to work in
|
||||||
QString tempOutputDir = PathUtils::generateTemporaryDir();
|
QString tempOutputDir = PathUtils::generateTemporaryDir();
|
||||||
|
QString tempOutputDirName = QDir(tempOutputDir).dirName();
|
||||||
|
if (tempOutputDir.isEmpty()) {
|
||||||
|
QString errors = "Could not create temporary working directory";
|
||||||
|
emit bakeFailed(_assetHash, _assetPath, errors);
|
||||||
|
PathUtils::deleteMyTemporaryDir(tempOutputDirName);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy file to bake the temporary dir and give a name the oven can work with
|
||||||
|
auto assetName = _assetPath.split("/").last();
|
||||||
|
auto tempAssetPath = tempOutputDir + "/" + assetName;
|
||||||
|
auto success = QFile::copy(_filePath, tempAssetPath);
|
||||||
|
if (!success) {
|
||||||
|
QString errors = "Couldn't copy file to bake to temporary directory";
|
||||||
|
emit bakeFailed(_assetHash, _assetPath, errors);
|
||||||
|
PathUtils::deleteMyTemporaryDir(tempOutputDirName);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
auto base = QFileInfo(QCoreApplication::applicationFilePath()).absoluteDir();
|
auto base = QFileInfo(QCoreApplication::applicationFilePath()).absoluteDir();
|
||||||
QString path = base.absolutePath() + "/oven";
|
QString path = base.absolutePath() + "/oven";
|
||||||
QString extension = _assetPath.mid(_assetPath.lastIndexOf('.') + 1);
|
QString extension = _assetPath.mid(_assetPath.lastIndexOf('.') + 1);
|
||||||
QStringList args {
|
QStringList args {
|
||||||
"-i", _filePath,
|
"-i", tempAssetPath,
|
||||||
"-o", tempOutputDir,
|
"-o", tempOutputDir,
|
||||||
"-t", extension,
|
"-t", extension,
|
||||||
};
|
};
|
||||||
|
@ -72,10 +77,11 @@ void BakeAssetTask::run() {
|
||||||
QEventLoop loop;
|
QEventLoop loop;
|
||||||
|
|
||||||
connect(_ovenProcess.get(), static_cast<void(QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished),
|
connect(_ovenProcess.get(), static_cast<void(QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished),
|
||||||
this, [&loop, this, tempOutputDir](int exitCode, QProcess::ExitStatus exitStatus) {
|
this, [&loop, this, tempOutputDir, tempAssetPath, tempOutputDirName](int exitCode, QProcess::ExitStatus exitStatus) {
|
||||||
qDebug() << "Baking process finished: " << exitCode << exitStatus;
|
qDebug() << "Baking process finished: " << exitCode << exitStatus;
|
||||||
|
|
||||||
if (exitStatus == QProcess::CrashExit) {
|
if (exitStatus == QProcess::CrashExit) {
|
||||||
|
PathUtils::deleteMyTemporaryDir(tempOutputDirName);
|
||||||
if (_wasAborted) {
|
if (_wasAborted) {
|
||||||
emit bakeAborted(_assetHash, _assetPath);
|
emit bakeAborted(_assetHash, _assetPath);
|
||||||
} else {
|
} else {
|
||||||
|
@ -83,16 +89,10 @@ void BakeAssetTask::run() {
|
||||||
emit bakeFailed(_assetHash, _assetPath, errors);
|
emit bakeFailed(_assetHash, _assetPath, errors);
|
||||||
}
|
}
|
||||||
} else if (exitCode == OVEN_STATUS_CODE_SUCCESS) {
|
} else if (exitCode == OVEN_STATUS_CODE_SUCCESS) {
|
||||||
QDir outputDir = tempOutputDir;
|
emit bakeComplete(_assetHash, _assetPath, tempOutputDir);
|
||||||
auto files = outputDir.entryInfoList(QDir::Files);
|
|
||||||
QVector<QString> outputFiles;
|
|
||||||
for (auto& file : files) {
|
|
||||||
outputFiles.push_back(file.absoluteFilePath());
|
|
||||||
}
|
|
||||||
|
|
||||||
emit bakeComplete(_assetHash, _assetPath, tempOutputDir, outputFiles);
|
|
||||||
} else if (exitStatus == QProcess::NormalExit && exitCode == OVEN_STATUS_CODE_ABORT) {
|
} else if (exitStatus == QProcess::NormalExit && exitCode == OVEN_STATUS_CODE_ABORT) {
|
||||||
_wasAborted.store(true);
|
_wasAborted.store(true);
|
||||||
|
PathUtils::deleteMyTemporaryDir(tempOutputDirName);
|
||||||
emit bakeAborted(_assetHash, _assetPath);
|
emit bakeAborted(_assetHash, _assetPath);
|
||||||
} else {
|
} else {
|
||||||
QString errors;
|
QString errors;
|
||||||
|
@ -107,6 +107,7 @@ void BakeAssetTask::run() {
|
||||||
errors = "Unknown error occurred while baking";
|
errors = "Unknown error occurred while baking";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
PathUtils::deleteMyTemporaryDir(tempOutputDirName);
|
||||||
emit bakeFailed(_assetHash, _assetPath, errors);
|
emit bakeFailed(_assetHash, _assetPath, errors);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -115,7 +116,10 @@ void BakeAssetTask::run() {
|
||||||
|
|
||||||
qDebug() << "Starting oven for " << _assetPath;
|
qDebug() << "Starting oven for " << _assetPath;
|
||||||
_ovenProcess->start(path, args, QIODevice::ReadOnly);
|
_ovenProcess->start(path, args, QIODevice::ReadOnly);
|
||||||
if (!_ovenProcess->waitForStarted(-1)) {
|
qDebug() << "Running:" << path << args;
|
||||||
|
if (!_ovenProcess->waitForStarted()) {
|
||||||
|
PathUtils::deleteMyTemporaryDir(tempOutputDirName);
|
||||||
|
|
||||||
QString errors = "Oven process failed to start";
|
QString errors = "Oven process failed to start";
|
||||||
emit bakeFailed(_assetHash, _assetPath, errors);
|
emit bakeFailed(_assetHash, _assetPath, errors);
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -37,7 +37,7 @@ public slots:
|
||||||
void abort();
|
void abort();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void bakeComplete(QString assetHash, QString assetPath, QString tempOutputDir, QVector<QString> outputFiles);
|
void bakeComplete(QString assetHash, QString assetPath, QString tempOutputDir);
|
||||||
void bakeFailed(QString assetHash, QString assetPath, QString errors);
|
void bakeFailed(QString assetHash, QString assetPath, QString errors);
|
||||||
void bakeAborted(QString assetHash, QString assetPath);
|
void bakeAborted(QString assetHash, QString assetPath);
|
||||||
|
|
||||||
|
|
4
cmake/externals/LibOVR/CMakeLists.txt
vendored
4
cmake/externals/LibOVR/CMakeLists.txt
vendored
|
@ -17,8 +17,8 @@ if (WIN32)
|
||||||
|
|
||||||
ExternalProject_Add(
|
ExternalProject_Add(
|
||||||
${EXTERNAL_NAME}
|
${EXTERNAL_NAME}
|
||||||
URL https://public.highfidelity.com/dependencies/ovr_sdk_win_1.26.0_public.zip
|
URL https://hifi-public.s3.amazonaws.com/dependencies/ovr_sdk_win_1.35.0.zip
|
||||||
URL_MD5 06804ff9727b910dcd04a37c800053b5
|
URL_MD5 1e3e8b2101387af07ff9c841d0ea285e
|
||||||
CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR>
|
CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR>
|
||||||
PATCH_COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_SOURCE_DIR}/LibOVRCMakeLists.txt" <SOURCE_DIR>/CMakeLists.txt
|
PATCH_COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_SOURCE_DIR}/LibOVRCMakeLists.txt" <SOURCE_DIR>/CMakeLists.txt
|
||||||
LOG_DOWNLOAD 1
|
LOG_DOWNLOAD 1
|
||||||
|
|
|
@ -23,15 +23,15 @@ Rectangle {
|
||||||
property bool ignoreRadiusEnabled: AvatarInputs.ignoreRadiusEnabled;
|
property bool ignoreRadiusEnabled: AvatarInputs.ignoreRadiusEnabled;
|
||||||
|
|
||||||
function updateOpacity() {
|
function updateOpacity() {
|
||||||
if (ignoreRadiusEnabled) {
|
var rectOpacity = ignoreRadiusEnabled ? 1.0 : (mouseArea.containsMouse ? 1.0 : 0.7);
|
||||||
bubbleRect.opacity = 1.0;
|
bubbleRect.opacity = rectOpacity;
|
||||||
} else {
|
|
||||||
bubbleRect.opacity = 0.7;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
updateOpacity();
|
updateOpacity();
|
||||||
|
AvatarInputs.ignoreRadiusEnabledChanged.connect(function() {
|
||||||
|
ignoreRadiusEnabled = AvatarInputs.ignoreRadiusEnabled;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onIgnoreRadiusEnabledChanged: {
|
onIgnoreRadiusEnabledChanged: {
|
||||||
|
@ -74,10 +74,10 @@ Rectangle {
|
||||||
}
|
}
|
||||||
drag.target: dragTarget;
|
drag.target: dragTarget;
|
||||||
onContainsMouseChanged: {
|
onContainsMouseChanged: {
|
||||||
var rectOpacity = (ignoreRadiusEnabled && containsMouse) ? 1.0 : (containsMouse ? 1.0 : 0.7);
|
|
||||||
if (containsMouse) {
|
if (containsMouse) {
|
||||||
Tablet.playSound(TabletEnums.ButtonHover);
|
Tablet.playSound(TabletEnums.ButtonHover);
|
||||||
}
|
}
|
||||||
|
var rectOpacity = ignoreRadiusEnabled ? 1.0 : (mouseArea.containsMouse ? 1.0 : 0.7);
|
||||||
bubbleRect.opacity = rectOpacity;
|
bubbleRect.opacity = rectOpacity;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -952,7 +952,7 @@ Rectangle {
|
||||||
text: "LOG IN"
|
text: "LOG IN"
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
sendToScript({method: 'needsLogIn_loginClicked'});
|
sendToScript({method: 'marketplace_loginClicked'});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -137,7 +137,7 @@ Item {
|
||||||
width: parent.width/2 - anchors.leftMargin*2;
|
width: parent.width/2 - anchors.leftMargin*2;
|
||||||
text: "Cancel"
|
text: "Cancel"
|
||||||
onClicked: {
|
onClicked: {
|
||||||
sendToScript({method: 'needsLogIn_cancelClicked'});
|
sendToScript({method: 'passphrasePopup_cancelClicked'});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -155,7 +155,7 @@ Item {
|
||||||
width: parent.width/2 - anchors.rightMargin*2;
|
width: parent.width/2 - anchors.rightMargin*2;
|
||||||
text: "Log In"
|
text: "Log In"
|
||||||
onClicked: {
|
onClicked: {
|
||||||
sendToScript({method: 'needsLogIn_loginClicked'});
|
sendToScript({method: 'marketplace_loginClicked'});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1861,12 +1861,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
||||||
|
|
||||||
if (Menu::getInstance()->isOptionChecked(MenuOption::FirstPerson)) {
|
if (Menu::getInstance()->isOptionChecked(MenuOption::FirstPerson)) {
|
||||||
getMyAvatar()->setBoomLength(MyAvatar::ZOOM_MIN); // So that camera doesn't auto-switch to third person.
|
getMyAvatar()->setBoomLength(MyAvatar::ZOOM_MIN); // So that camera doesn't auto-switch to third person.
|
||||||
} else if (Menu::getInstance()->isOptionChecked(MenuOption::IndependentMode)) {
|
|
||||||
Menu::getInstance()->setIsOptionChecked(MenuOption::ThirdPerson, true);
|
|
||||||
cameraMenuChanged();
|
|
||||||
} else if (Menu::getInstance()->isOptionChecked(MenuOption::CameraEntityMode)) {
|
|
||||||
Menu::getInstance()->setIsOptionChecked(MenuOption::ThirdPerson, true);
|
|
||||||
cameraMenuChanged();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -2337,7 +2331,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
||||||
DependencyManager::get<PickManager>()->setPrecisionPicking(rayPickID, value);
|
DependencyManager::get<PickManager>()->setPrecisionPicking(rayPickID, value);
|
||||||
});
|
});
|
||||||
|
|
||||||
EntityItem::setBillboardRotationOperator([this](const glm::vec3& position, const glm::quat& rotation, BillboardMode billboardMode, const glm::vec3& frustumPos) {
|
EntityItem::setBillboardRotationOperator([](const glm::vec3& position, const glm::quat& rotation, BillboardMode billboardMode, const glm::vec3& frustumPos) {
|
||||||
if (billboardMode == BillboardMode::YAW) {
|
if (billboardMode == BillboardMode::YAW) {
|
||||||
//rotate about vertical to face the camera
|
//rotate about vertical to face the camera
|
||||||
glm::vec3 dPosition = frustumPos - position;
|
glm::vec3 dPosition = frustumPos - position;
|
||||||
|
@ -2365,7 +2359,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
||||||
|
|
||||||
DependencyManager::get<UsersScriptingInterface>()->setKickConfirmationOperator([this] (const QUuid& nodeID) { userKickConfirmation(nodeID); });
|
DependencyManager::get<UsersScriptingInterface>()->setKickConfirmationOperator([this] (const QUuid& nodeID) { userKickConfirmation(nodeID); });
|
||||||
|
|
||||||
render::entities::WebEntityRenderer::setAcquireWebSurfaceOperator([this](const QString& url, bool htmlContent, QSharedPointer<OffscreenQmlSurface>& webSurface, bool& cachedWebSurface) {
|
render::entities::WebEntityRenderer::setAcquireWebSurfaceOperator([=](const QString& url, bool htmlContent, QSharedPointer<OffscreenQmlSurface>& webSurface, bool& cachedWebSurface) {
|
||||||
bool isTablet = url == TabletScriptingInterface::QML;
|
bool isTablet = url == TabletScriptingInterface::QML;
|
||||||
if (htmlContent) {
|
if (htmlContent) {
|
||||||
webSurface = DependencyManager::get<OffscreenQmlSurfaceCache>()->acquire(render::entities::WebEntityRenderer::QML);
|
webSurface = DependencyManager::get<OffscreenQmlSurfaceCache>()->acquire(render::entities::WebEntityRenderer::QML);
|
||||||
|
@ -2405,7 +2399,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
||||||
const uint8_t TABLET_FPS = 90;
|
const uint8_t TABLET_FPS = 90;
|
||||||
webSurface->setMaxFps(isTablet ? TABLET_FPS : DEFAULT_MAX_FPS);
|
webSurface->setMaxFps(isTablet ? TABLET_FPS : DEFAULT_MAX_FPS);
|
||||||
});
|
});
|
||||||
render::entities::WebEntityRenderer::setReleaseWebSurfaceOperator([this](QSharedPointer<OffscreenQmlSurface>& webSurface, bool& cachedWebSurface, std::vector<QMetaObject::Connection>& connections) {
|
render::entities::WebEntityRenderer::setReleaseWebSurfaceOperator([=](QSharedPointer<OffscreenQmlSurface>& webSurface, bool& cachedWebSurface, std::vector<QMetaObject::Connection>& connections) {
|
||||||
QQuickItem* rootItem = webSurface->getRootItem();
|
QQuickItem* rootItem = webSurface->getRootItem();
|
||||||
|
|
||||||
// Fix for crash in QtWebEngineCore when rapidly switching domains
|
// Fix for crash in QtWebEngineCore when rapidly switching domains
|
||||||
|
@ -5758,9 +5752,6 @@ void Application::cycleCamera() {
|
||||||
menu->setIsOptionChecked(MenuOption::ThirdPerson, false);
|
menu->setIsOptionChecked(MenuOption::ThirdPerson, false);
|
||||||
menu->setIsOptionChecked(MenuOption::FullscreenMirror, true);
|
menu->setIsOptionChecked(MenuOption::FullscreenMirror, true);
|
||||||
|
|
||||||
} else if (menu->isOptionChecked(MenuOption::IndependentMode) || menu->isOptionChecked(MenuOption::CameraEntityMode)) {
|
|
||||||
// do nothing if in independent or camera entity modes
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
cameraMenuChanged(); // handle the menu change
|
cameraMenuChanged(); // handle the menu change
|
||||||
}
|
}
|
||||||
|
@ -5776,12 +5767,6 @@ void Application::cameraModeChanged() {
|
||||||
case CAMERA_MODE_MIRROR:
|
case CAMERA_MODE_MIRROR:
|
||||||
Menu::getInstance()->setIsOptionChecked(MenuOption::FullscreenMirror, true);
|
Menu::getInstance()->setIsOptionChecked(MenuOption::FullscreenMirror, true);
|
||||||
break;
|
break;
|
||||||
case CAMERA_MODE_INDEPENDENT:
|
|
||||||
Menu::getInstance()->setIsOptionChecked(MenuOption::IndependentMode, true);
|
|
||||||
break;
|
|
||||||
case CAMERA_MODE_ENTITY:
|
|
||||||
Menu::getInstance()->setIsOptionChecked(MenuOption::CameraEntityMode, true);
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -5825,14 +5810,6 @@ void Application::cameraMenuChanged() {
|
||||||
getMyAvatar()->setBoomLength(MyAvatar::ZOOM_DEFAULT);
|
getMyAvatar()->setBoomLength(MyAvatar::ZOOM_DEFAULT);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (menu->isOptionChecked(MenuOption::IndependentMode)) {
|
|
||||||
if (_myCamera.getMode() != CAMERA_MODE_INDEPENDENT) {
|
|
||||||
_myCamera.setMode(CAMERA_MODE_INDEPENDENT);
|
|
||||||
}
|
|
||||||
} else if (menu->isOptionChecked(MenuOption::CameraEntityMode)) {
|
|
||||||
if (_myCamera.getMode() != CAMERA_MODE_ENTITY) {
|
|
||||||
_myCamera.setMode(CAMERA_MODE_ENTITY);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6747,6 +6724,11 @@ void Application::updateRenderArgs(float deltaTime) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
appRenderArgs._renderArgs._stencilMaskMode = getActiveDisplayPlugin()->getStencilMaskMode();
|
||||||
|
if (appRenderArgs._renderArgs._stencilMaskMode == StencilMaskMode::MESH) {
|
||||||
|
appRenderArgs._renderArgs._stencilMaskOperator = getActiveDisplayPlugin()->getStencilMaskMeshOperator();
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
QMutexLocker viewLocker(&_viewMutex);
|
QMutexLocker viewLocker(&_viewMutex);
|
||||||
_myCamera.loadViewFrustum(_displayViewFrustum);
|
_myCamera.loadViewFrustum(_displayViewFrustum);
|
||||||
|
@ -8434,11 +8416,23 @@ void Application::loadAvatarBrowser() const {
|
||||||
DependencyManager::get<HMDScriptingInterface>()->openTablet();
|
DependencyManager::get<HMDScriptingInterface>()->openTablet();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Application::addSnapshotOperator(const SnapshotOperator& snapshotOperator) {
|
||||||
|
std::lock_guard<std::mutex> lock(_snapshotMutex);
|
||||||
|
_snapshotOperators.push(snapshotOperator);
|
||||||
|
_hasPrimarySnapshot = _hasPrimarySnapshot || std::get<2>(snapshotOperator);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Application::takeSnapshotOperators(std::queue<SnapshotOperator>& snapshotOperators) {
|
||||||
|
std::lock_guard<std::mutex> lock(_snapshotMutex);
|
||||||
|
bool hasPrimarySnapshot = _hasPrimarySnapshot;
|
||||||
|
_hasPrimarySnapshot = false;
|
||||||
|
_snapshotOperators.swap(snapshotOperators);
|
||||||
|
return hasPrimarySnapshot;
|
||||||
|
}
|
||||||
|
|
||||||
void Application::takeSnapshot(bool notify, bool includeAnimated, float aspectRatio, const QString& filename) {
|
void Application::takeSnapshot(bool notify, bool includeAnimated, float aspectRatio, const QString& filename) {
|
||||||
postLambdaEvent([notify, includeAnimated, aspectRatio, filename, this] {
|
addSnapshotOperator(std::make_tuple([notify, includeAnimated, aspectRatio, filename](const QImage& snapshot) {
|
||||||
// Get a screenshot and save it
|
QString path = DependencyManager::get<Snapshot>()->saveSnapshot(snapshot, filename, TestScriptingInterface::getInstance()->getTestResultsLocation());
|
||||||
QString path = DependencyManager::get<Snapshot>()->saveSnapshot(getActiveDisplayPlugin()->getScreenshot(aspectRatio), filename,
|
|
||||||
TestScriptingInterface::getInstance()->getTestResultsLocation());
|
|
||||||
|
|
||||||
// If we're not doing an animated snapshot as well...
|
// If we're not doing an animated snapshot as well...
|
||||||
if (!includeAnimated) {
|
if (!includeAnimated) {
|
||||||
|
@ -8447,19 +8441,20 @@ void Application::takeSnapshot(bool notify, bool includeAnimated, float aspectRa
|
||||||
emit DependencyManager::get<WindowScriptingInterface>()->stillSnapshotTaken(path, notify);
|
emit DependencyManager::get<WindowScriptingInterface>()->stillSnapshotTaken(path, notify);
|
||||||
}
|
}
|
||||||
} else if (!SnapshotAnimated::isAlreadyTakingSnapshotAnimated()) {
|
} else if (!SnapshotAnimated::isAlreadyTakingSnapshotAnimated()) {
|
||||||
// Get an animated GIF snapshot and save it
|
qApp->postLambdaEvent([path, aspectRatio] {
|
||||||
SnapshotAnimated::saveSnapshotAnimated(path, aspectRatio, qApp, DependencyManager::get<WindowScriptingInterface>());
|
// Get an animated GIF snapshot and save it
|
||||||
|
SnapshotAnimated::saveSnapshotAnimated(path, aspectRatio, DependencyManager::get<WindowScriptingInterface>());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
}, aspectRatio, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::takeSecondaryCameraSnapshot(const bool& notify, const QString& filename) {
|
void Application::takeSecondaryCameraSnapshot(const bool& notify, const QString& filename) {
|
||||||
postLambdaEvent([notify, filename, this] {
|
addSnapshotOperator(std::make_tuple([notify, filename](const QImage& snapshot) {
|
||||||
QString snapshotPath = DependencyManager::get<Snapshot>()->saveSnapshot(getActiveDisplayPlugin()->getSecondaryCameraScreenshot(), filename,
|
QString snapshotPath = DependencyManager::get<Snapshot>()->saveSnapshot(snapshot, filename, TestScriptingInterface::getInstance()->getTestResultsLocation());
|
||||||
TestScriptingInterface::getInstance()->getTestResultsLocation());
|
|
||||||
|
|
||||||
emit DependencyManager::get<WindowScriptingInterface>()->stillSnapshotTaken(snapshotPath, notify);
|
emit DependencyManager::get<WindowScriptingInterface>()->stillSnapshotTaken(snapshotPath, notify);
|
||||||
});
|
}, 0.0f, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::takeSecondaryCamera360Snapshot(const glm::vec3& cameraPosition, const bool& cubemapOutputFormat, const bool& notify, const QString& filename) {
|
void Application::takeSecondaryCamera360Snapshot(const glm::vec3& cameraPosition, const bool& cubemapOutputFormat, const bool& notify, const QString& filename) {
|
||||||
|
|
|
@ -345,6 +345,10 @@ public:
|
||||||
void toggleAwayMode();
|
void toggleAwayMode();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
using SnapshotOperator = std::tuple<std::function<void(const QImage&)>, float, bool>;
|
||||||
|
void addSnapshotOperator(const SnapshotOperator& snapshotOperator);
|
||||||
|
bool takeSnapshotOperators(std::queue<SnapshotOperator>& snapshotOperators);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void svoImportRequested(const QString& url);
|
void svoImportRequested(const QString& url);
|
||||||
|
|
||||||
|
@ -789,6 +793,9 @@ private:
|
||||||
AudioInjectorPointer _snapshotSoundInjector;
|
AudioInjectorPointer _snapshotSoundInjector;
|
||||||
SharedSoundPointer _snapshotSound;
|
SharedSoundPointer _snapshotSound;
|
||||||
SharedSoundPointer _sampleSound;
|
SharedSoundPointer _sampleSound;
|
||||||
|
std::mutex _snapshotMutex;
|
||||||
|
std::queue<SnapshotOperator> _snapshotOperators;
|
||||||
|
bool _hasPrimarySnapshot { false };
|
||||||
|
|
||||||
DisplayPluginPointer _autoSwitchDisplayModeSupportedHMDPlugin;
|
DisplayPluginPointer _autoSwitchDisplayModeSupportedHMDPlugin;
|
||||||
QString _autoSwitchDisplayModeSupportedHMDPluginName;
|
QString _autoSwitchDisplayModeSupportedHMDPluginName;
|
||||||
|
|
|
@ -194,20 +194,6 @@ Menu::Menu() {
|
||||||
|
|
||||||
viewMirrorAction->setProperty(EXCLUSION_GROUP_KEY, QVariant::fromValue(cameraModeGroup));
|
viewMirrorAction->setProperty(EXCLUSION_GROUP_KEY, QVariant::fromValue(cameraModeGroup));
|
||||||
|
|
||||||
// View > Independent
|
|
||||||
auto viewIndependentAction = cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(viewMenu,
|
|
||||||
MenuOption::IndependentMode, 0,
|
|
||||||
false, qApp, SLOT(cameraMenuChanged())));
|
|
||||||
|
|
||||||
viewIndependentAction->setProperty(EXCLUSION_GROUP_KEY, QVariant::fromValue(cameraModeGroup));
|
|
||||||
|
|
||||||
// View > Entity Camera
|
|
||||||
auto viewEntityCameraAction = cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(viewMenu,
|
|
||||||
MenuOption::CameraEntityMode, 0,
|
|
||||||
false, qApp, SLOT(cameraMenuChanged())));
|
|
||||||
|
|
||||||
viewEntityCameraAction->setProperty(EXCLUSION_GROUP_KEY, QVariant::fromValue(cameraModeGroup));
|
|
||||||
|
|
||||||
viewMenu->addSeparator();
|
viewMenu->addSeparator();
|
||||||
|
|
||||||
// View > Center Player In View
|
// View > Center Player In View
|
||||||
|
|
|
@ -53,7 +53,6 @@ namespace MenuOption {
|
||||||
const QString BookmarkAvatarEntities = "Bookmark Avatar Entities";
|
const QString BookmarkAvatarEntities = "Bookmark Avatar Entities";
|
||||||
const QString BookmarkLocation = "Bookmark Location";
|
const QString BookmarkLocation = "Bookmark Location";
|
||||||
const QString CalibrateCamera = "Calibrate Camera";
|
const QString CalibrateCamera = "Calibrate Camera";
|
||||||
const QString CameraEntityMode = "Entity Mode";
|
|
||||||
const QString CenterPlayerInView = "Center Player In View";
|
const QString CenterPlayerInView = "Center Player In View";
|
||||||
const QString Chat = "Chat...";
|
const QString Chat = "Chat...";
|
||||||
const QString ClearDiskCache = "Clear Disk Cache";
|
const QString ClearDiskCache = "Clear Disk Cache";
|
||||||
|
@ -120,7 +119,6 @@ namespace MenuOption {
|
||||||
const QString Help = "Help...";
|
const QString Help = "Help...";
|
||||||
const QString HomeLocation = "Home ";
|
const QString HomeLocation = "Home ";
|
||||||
const QString IncreaseAvatarSize = "Increase Avatar Size";
|
const QString IncreaseAvatarSize = "Increase Avatar Size";
|
||||||
const QString IndependentMode = "Independent Mode";
|
|
||||||
const QString ActionMotorControl = "Enable Default Motor Control";
|
const QString ActionMotorControl = "Enable Default Motor Control";
|
||||||
const QString LastLocation = "Last Location";
|
const QString LastLocation = "Last Location";
|
||||||
const QString LoadScript = "Open and Run Script File...";
|
const QString LoadScript = "Open and Run Script File...";
|
||||||
|
|
|
@ -18,9 +18,6 @@
|
||||||
#include <QPushButton>
|
#include <QPushButton>
|
||||||
#include <QStandardPaths>
|
#include <QStandardPaths>
|
||||||
|
|
||||||
static const QString AVATAR_HEAD_AND_BODY_STRING = "Avatar Body with Head";
|
|
||||||
static const QString ENTITY_MODEL_STRING = "Entity Model";
|
|
||||||
|
|
||||||
ModelSelector::ModelSelector() {
|
ModelSelector::ModelSelector() {
|
||||||
QFormLayout* form = new QFormLayout(this);
|
QFormLayout* form = new QFormLayout(this);
|
||||||
|
|
||||||
|
|
|
@ -152,10 +152,12 @@ public:
|
||||||
_cachedArgsPointer->_viewport = args->_viewport;
|
_cachedArgsPointer->_viewport = args->_viewport;
|
||||||
_cachedArgsPointer->_displayMode = args->_displayMode;
|
_cachedArgsPointer->_displayMode = args->_displayMode;
|
||||||
_cachedArgsPointer->_renderMode = args->_renderMode;
|
_cachedArgsPointer->_renderMode = args->_renderMode;
|
||||||
|
_cachedArgsPointer->_stencilMaskMode = args->_stencilMaskMode;
|
||||||
args->_blitFramebuffer = destFramebuffer;
|
args->_blitFramebuffer = destFramebuffer;
|
||||||
args->_viewport = glm::ivec4(0, 0, destFramebuffer->getWidth(), destFramebuffer->getHeight());
|
args->_viewport = glm::ivec4(0, 0, destFramebuffer->getWidth(), destFramebuffer->getHeight());
|
||||||
args->_displayMode = RenderArgs::MONO;
|
args->_displayMode = RenderArgs::MONO;
|
||||||
args->_renderMode = RenderArgs::RenderMode::SECONDARY_CAMERA_RENDER_MODE;
|
args->_renderMode = RenderArgs::RenderMode::SECONDARY_CAMERA_RENDER_MODE;
|
||||||
|
args->_stencilMaskMode = StencilMaskMode::NONE;
|
||||||
|
|
||||||
gpu::doInBatch("SecondaryCameraJob::run", args->_context, [&](gpu::Batch& batch) {
|
gpu::doInBatch("SecondaryCameraJob::run", args->_context, [&](gpu::Batch& batch) {
|
||||||
batch.disableContextStereo();
|
batch.disableContextStereo();
|
||||||
|
@ -255,10 +257,11 @@ public:
|
||||||
void run(const render::RenderContextPointer& renderContext, const RenderArgsPointer& cachedArgs) {
|
void run(const render::RenderContextPointer& renderContext, const RenderArgsPointer& cachedArgs) {
|
||||||
auto args = renderContext->args;
|
auto args = renderContext->args;
|
||||||
if (cachedArgs) {
|
if (cachedArgs) {
|
||||||
args->_blitFramebuffer = cachedArgs->_blitFramebuffer;
|
args->_blitFramebuffer = cachedArgs->_blitFramebuffer;
|
||||||
args->_viewport = cachedArgs->_viewport;
|
args->_viewport = cachedArgs->_viewport;
|
||||||
args->_displayMode = cachedArgs->_displayMode;
|
args->_displayMode = cachedArgs->_displayMode;
|
||||||
args->_renderMode = cachedArgs->_renderMode;
|
args->_renderMode = cachedArgs->_renderMode;
|
||||||
|
args->_stencilMaskMode = cachedArgs->_stencilMaskMode;
|
||||||
}
|
}
|
||||||
args->popViewFrustum();
|
args->popViewFrustum();
|
||||||
|
|
||||||
|
|
|
@ -735,7 +735,7 @@ RayToAvatarIntersectionResult AvatarManager::findRayIntersectionVector(const Pic
|
||||||
boxHit._distance = FLT_MAX;
|
boxHit._distance = FLT_MAX;
|
||||||
|
|
||||||
for (size_t i = 0; i < hit._boundJoints.size(); i++) {
|
for (size_t i = 0; i < hit._boundJoints.size(); i++) {
|
||||||
assert(hit._boundJoints[i] < multiSpheres.size());
|
assert(hit._boundJoints[i] < (int)multiSpheres.size());
|
||||||
auto &mSphere = multiSpheres[hit._boundJoints[i]];
|
auto &mSphere = multiSpheres[hit._boundJoints[i]];
|
||||||
if (mSphere.isValid()) {
|
if (mSphere.isValid()) {
|
||||||
float boundDistance = FLT_MAX;
|
float boundDistance = FLT_MAX;
|
||||||
|
|
|
@ -1630,7 +1630,9 @@ void MyAvatar::handleChangedAvatarEntityData() {
|
||||||
if (!skip) {
|
if (!skip) {
|
||||||
sanitizeAvatarEntityProperties(properties);
|
sanitizeAvatarEntityProperties(properties);
|
||||||
entityTree->withWriteLock([&] {
|
entityTree->withWriteLock([&] {
|
||||||
entityTree->updateEntity(id, properties);
|
if (entityTree->updateEntity(id, properties)) {
|
||||||
|
packetSender->queueEditAvatarEntityMessage(entityTree, id);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5811,12 +5813,19 @@ void MyAvatar::releaseGrab(const QUuid& grabID) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void MyAvatar::addAvatarHandsToFlow(const std::shared_ptr<Avatar>& otherAvatar) {
|
void MyAvatar::addAvatarHandsToFlow(const std::shared_ptr<Avatar>& otherAvatar) {
|
||||||
|
if (QThread::currentThread() != thread()) {
|
||||||
|
QMetaObject::invokeMethod(this, "addAvatarHandsToFlow",
|
||||||
|
Q_ARG(const std::shared_ptr<Avatar>&, otherAvatar));
|
||||||
|
return;
|
||||||
|
}
|
||||||
auto &flow = _skeletonModel->getRig().getFlow();
|
auto &flow = _skeletonModel->getRig().getFlow();
|
||||||
for (auto &handJointName : HAND_COLLISION_JOINTS) {
|
if (otherAvatar != nullptr && flow.getActive()) {
|
||||||
int jointIndex = otherAvatar->getJointIndex(handJointName);
|
for (auto &handJointName : HAND_COLLISION_JOINTS) {
|
||||||
if (jointIndex != -1) {
|
int jointIndex = otherAvatar->getJointIndex(handJointName);
|
||||||
glm::vec3 position = otherAvatar->getJointPosition(jointIndex);
|
if (jointIndex != -1) {
|
||||||
flow.setOthersCollision(otherAvatar->getID(), jointIndex, position);
|
glm::vec3 position = otherAvatar->getJointPosition(jointIndex);
|
||||||
|
flow.setOthersCollision(otherAvatar->getID(), jointIndex, position);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,326 +41,246 @@
|
||||||
#include "ui/SecurityImageProvider.h"
|
#include "ui/SecurityImageProvider.h"
|
||||||
#include "scripting/HMDScriptingInterface.h"
|
#include "scripting/HMDScriptingInterface.h"
|
||||||
|
|
||||||
static const char* KEY_FILE = "hifikey";
|
namespace {
|
||||||
static const char* INSTRUCTIONS_FILE = "backup_instructions.html";
|
const char* KEY_FILE = "hifikey";
|
||||||
static const char* IMAGE_HEADER = "-----BEGIN SECURITY IMAGE-----\n";
|
const char* INSTRUCTIONS_FILE = "backup_instructions.html";
|
||||||
static const char* IMAGE_FOOTER = "-----END SECURITY IMAGE-----\n";
|
const char* IMAGE_HEADER = "-----BEGIN SECURITY IMAGE-----\n";
|
||||||
|
const char* IMAGE_FOOTER = "-----END SECURITY IMAGE-----\n";
|
||||||
|
|
||||||
void initialize() {
|
void initialize() {
|
||||||
static bool initialized = false;
|
static bool initialized = false;
|
||||||
if (!initialized) {
|
if (!initialized) {
|
||||||
SSL_load_error_strings();
|
SSL_load_error_strings();
|
||||||
SSL_library_init();
|
SSL_library_init();
|
||||||
OpenSSL_add_all_algorithms();
|
OpenSSL_add_all_algorithms();
|
||||||
initialized = true;
|
initialized = true;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QString keyFilePath() {
|
|
||||||
auto accountManager = DependencyManager::get<AccountManager>();
|
|
||||||
return PathUtils::getAppDataFilePath(QString("%1.%2").arg(accountManager->getAccountInfo().getUsername(), KEY_FILE));
|
|
||||||
}
|
|
||||||
bool Wallet::copyKeyFileFrom(const QString& pathname) {
|
|
||||||
QString existing = getKeyFilePath();
|
|
||||||
qCDebug(commerce) << "Old keyfile" << existing;
|
|
||||||
if (!existing.isEmpty()) {
|
|
||||||
QString backup = QString(existing).insert(existing.indexOf(KEY_FILE) - 1,
|
|
||||||
QDateTime::currentDateTime().toString(Qt::ISODate).replace(":", ""));
|
|
||||||
qCDebug(commerce) << "Renaming old keyfile to" << backup;
|
|
||||||
if (!QFile::rename(existing, backup)) {
|
|
||||||
qCCritical(commerce) << "Unable to backup" << existing << "to" << backup;
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
QString destination = keyFilePath();
|
|
||||||
bool result = QFile::copy(pathname, destination);
|
|
||||||
qCDebug(commerce) << "copy" << pathname << "to" << destination << "=>" << result;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// use the cached _passphrase if it exists, otherwise we need to prompt
|
QString keyFilePath() {
|
||||||
int passwordCallback(char* password, int maxPasswordSize, int rwFlag, void* u) {
|
auto accountManager = DependencyManager::get<AccountManager>();
|
||||||
// just return a hardcoded pwd for now
|
return PathUtils::getAppDataFilePath(QString("%1.%2").arg(accountManager->getAccountInfo().getUsername(), KEY_FILE));
|
||||||
auto wallet = DependencyManager::get<Wallet>();
|
|
||||||
auto passphrase = wallet->getPassphrase();
|
|
||||||
if (passphrase && !passphrase->isEmpty()) {
|
|
||||||
QString saltedPassphrase(*passphrase);
|
|
||||||
saltedPassphrase.append(wallet->getSalt());
|
|
||||||
strcpy(password, saltedPassphrase.toUtf8().constData());
|
|
||||||
return static_cast<int>(passphrase->size());
|
|
||||||
} else {
|
|
||||||
// this shouldn't happen - so lets log it to tell us we have
|
|
||||||
// a problem with the flow...
|
|
||||||
qCCritical(commerce) << "no cached passphrase while decrypting!";
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
EC_KEY* readKeys(QString filename) {
|
// use the cached _passphrase if it exists, otherwise we need to prompt
|
||||||
QFile file(filename);
|
int passwordCallback(char* password, int maxPasswordSize, int rwFlag, void* u) {
|
||||||
EC_KEY* key = NULL;
|
// just return a hardcoded pwd for now
|
||||||
if (file.open(QFile::ReadOnly)) {
|
auto wallet = DependencyManager::get<Wallet>();
|
||||||
// file opened successfully
|
auto passphrase = wallet->getPassphrase();
|
||||||
qCDebug(commerce) << "opened key file" << filename;
|
if (passphrase && !passphrase->isEmpty()) {
|
||||||
|
QString saltedPassphrase(*passphrase);
|
||||||
|
saltedPassphrase.append(wallet->getSalt());
|
||||||
|
strcpy(password, saltedPassphrase.toUtf8().constData());
|
||||||
|
return static_cast<int>(passphrase->size());
|
||||||
|
} else {
|
||||||
|
// this shouldn't happen - so lets log it to tell us we have
|
||||||
|
// a problem with the flow...
|
||||||
|
qCCritical(commerce) << "no cached passphrase while decrypting!";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
QByteArray pemKeyBytes = file.readAll();
|
EC_KEY* readKeys(QString filename) {
|
||||||
BIO* bufio = BIO_new_mem_buf((void*)pemKeyBytes.constData(), pemKeyBytes.length());
|
QFile file(filename);
|
||||||
if ((key = PEM_read_bio_EC_PUBKEY(bufio, NULL, NULL, NULL))) {
|
EC_KEY* key = NULL;
|
||||||
// now read private key
|
if (file.open(QFile::ReadOnly)) {
|
||||||
|
// file opened successfully
|
||||||
|
qCDebug(commerce) << "opened key file" << filename;
|
||||||
|
|
||||||
qCDebug(commerce) << "read public key";
|
QByteArray pemKeyBytes = file.readAll();
|
||||||
|
BIO* bufio = BIO_new_mem_buf((void*)pemKeyBytes.constData(), pemKeyBytes.length());
|
||||||
|
if ((key = PEM_read_bio_EC_PUBKEY(bufio, NULL, NULL, NULL))) {
|
||||||
|
// now read private key
|
||||||
|
|
||||||
if ((key = PEM_read_bio_ECPrivateKey(bufio, &key, passwordCallback, NULL))) {
|
qCDebug(commerce) << "read public key";
|
||||||
qCDebug(commerce) << "read private key";
|
|
||||||
BIO_free(bufio);
|
if ((key = PEM_read_bio_ECPrivateKey(bufio, &key, passwordCallback, NULL))) {
|
||||||
file.close();
|
qCDebug(commerce) << "read private key";
|
||||||
|
} else {
|
||||||
|
qCDebug(commerce) << "failed to read private key";
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
qCDebug(commerce) << "failed to read private key";
|
qCDebug(commerce) << "failed to read public key";
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
qCDebug(commerce) << "failed to read public key";
|
|
||||||
}
|
|
||||||
BIO_free(bufio);
|
|
||||||
file.close();
|
|
||||||
} else {
|
|
||||||
qCDebug(commerce) << "failed to open key file" << filename;
|
|
||||||
}
|
|
||||||
return key;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Wallet::writeBackupInstructions() {
|
|
||||||
QString inputFilename(PathUtils::resourcesPath() + "html/commerce/backup_instructions.html");
|
|
||||||
QString outputFilename = PathUtils::getAppDataFilePath(INSTRUCTIONS_FILE);
|
|
||||||
QFile inputFile(inputFilename);
|
|
||||||
QFile outputFile(outputFilename);
|
|
||||||
bool retval = false;
|
|
||||||
|
|
||||||
if (getKeyFilePath().isEmpty()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (QFile::exists(inputFilename) && inputFile.open(QIODevice::ReadOnly)) {
|
|
||||||
if (outputFile.open(QIODevice::ReadWrite)) {
|
|
||||||
// Read the data from the original file, then close it
|
|
||||||
QByteArray fileData = inputFile.readAll();
|
|
||||||
inputFile.close();
|
|
||||||
|
|
||||||
// Translate the data from the original file into a QString
|
|
||||||
QString text(fileData);
|
|
||||||
|
|
||||||
// Replace the necessary string
|
|
||||||
text.replace(QString("HIFIKEY_PATH_REPLACEME"), keyFilePath());
|
|
||||||
|
|
||||||
// Write the new text back to the file
|
|
||||||
outputFile.write(text.toUtf8());
|
|
||||||
|
|
||||||
// Close the output file
|
|
||||||
outputFile.close();
|
|
||||||
|
|
||||||
retval = true;
|
|
||||||
qCDebug(commerce) << "wrote html file successfully";
|
|
||||||
} else {
|
|
||||||
qCDebug(commerce) << "failed to open output html file" << outputFilename;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
qCDebug(commerce) << "failed to open input html file" << inputFilename;
|
|
||||||
}
|
|
||||||
return retval;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool writeKeys(QString filename, EC_KEY* keys) {
|
|
||||||
BIO* bio = BIO_new(BIO_s_mem());
|
|
||||||
bool retval = false;
|
|
||||||
if (!PEM_write_bio_EC_PUBKEY(bio, keys)) {
|
|
||||||
BIO_free(bio);
|
|
||||||
qCCritical(commerce) << "failed to write public key";
|
|
||||||
return retval;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!PEM_write_bio_ECPrivateKey(bio, keys, EVP_des_ede3_cbc(), NULL, 0, passwordCallback, NULL)) {
|
|
||||||
BIO_free(bio);
|
|
||||||
qCCritical(commerce) << "failed to write private key";
|
|
||||||
return retval;
|
|
||||||
}
|
|
||||||
|
|
||||||
QFile file(filename);
|
|
||||||
if (!file.open(QIODevice::WriteOnly)) {
|
|
||||||
const char* bio_data;
|
|
||||||
long bio_size = BIO_get_mem_data(bio, &bio_data);
|
|
||||||
|
|
||||||
QByteArray keyBytes(bio_data, bio_size);
|
|
||||||
file.write(keyBytes);
|
|
||||||
retval = true;
|
|
||||||
qCDebug(commerce) << "wrote keys successfully";
|
|
||||||
file.close();
|
|
||||||
} else {
|
|
||||||
qCDebug(commerce) << "failed to open key file" << filename;
|
|
||||||
}
|
|
||||||
BIO_free(bio);
|
|
||||||
return retval;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Wallet::setWallet(const QByteArray& wallet) {
|
|
||||||
QFile file(keyFilePath());
|
|
||||||
if (!file.open(QIODevice::WriteOnly)) {
|
|
||||||
qCCritical(commerce) << "Unable to open wallet for write in" << keyFilePath();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (file.write(wallet) != wallet.count()) {
|
|
||||||
qCCritical(commerce) << "Unable to write wallet in" << keyFilePath();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
file.close();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
QByteArray Wallet::getWallet() {
|
|
||||||
QFile file(keyFilePath());
|
|
||||||
if (!file.open(QIODevice::ReadOnly)) {
|
|
||||||
qCInfo(commerce) << "No existing wallet in" << keyFilePath();
|
|
||||||
return QByteArray();
|
|
||||||
}
|
|
||||||
QByteArray wallet = file.readAll();
|
|
||||||
file.close();
|
|
||||||
return wallet;
|
|
||||||
}
|
|
||||||
|
|
||||||
QPair<QByteArray*, QByteArray*> generateECKeypair() {
|
|
||||||
EC_KEY* keyPair = EC_KEY_new_by_curve_name(NID_secp256k1);
|
|
||||||
QPair<QByteArray*, QByteArray*> retval{};
|
|
||||||
|
|
||||||
EC_KEY_set_asn1_flag(keyPair, OPENSSL_EC_NAMED_CURVE);
|
|
||||||
if (!EC_KEY_generate_key(keyPair)) {
|
|
||||||
qCDebug(commerce) << "Error generating EC Keypair -" << ERR_get_error();
|
|
||||||
return retval;
|
|
||||||
}
|
|
||||||
|
|
||||||
// grab the public key and private key from the file
|
|
||||||
unsigned char* publicKeyDER = NULL;
|
|
||||||
int publicKeyLength = i2d_EC_PUBKEY(keyPair, &publicKeyDER);
|
|
||||||
|
|
||||||
unsigned char* privateKeyDER = NULL;
|
|
||||||
int privateKeyLength = i2d_ECPrivateKey(keyPair, &privateKeyDER);
|
|
||||||
|
|
||||||
if (publicKeyLength <= 0 || privateKeyLength <= 0) {
|
|
||||||
qCDebug(commerce) << "Error getting DER public or private key from EC struct -" << ERR_get_error();
|
|
||||||
|
|
||||||
// cleanup the EC struct
|
|
||||||
EC_KEY_free(keyPair);
|
|
||||||
|
|
||||||
// cleanup the public and private key DER data, if required
|
|
||||||
if (publicKeyLength > 0) {
|
|
||||||
OPENSSL_free(publicKeyDER);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (privateKeyLength > 0) {
|
|
||||||
OPENSSL_free(privateKeyDER);
|
|
||||||
}
|
|
||||||
|
|
||||||
return retval;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!writeKeys(keyFilePath(), keyPair)) {
|
|
||||||
qCDebug(commerce) << "couldn't save keys!";
|
|
||||||
return retval;
|
|
||||||
}
|
|
||||||
|
|
||||||
EC_KEY_free(keyPair);
|
|
||||||
|
|
||||||
// prepare the return values. TODO: Fix this - we probably don't really even want the
|
|
||||||
// private key at all (better to read it when we need it?). Or maybe we do, when we have
|
|
||||||
// multiple keys?
|
|
||||||
retval.first = new QByteArray(reinterpret_cast<char*>(publicKeyDER), publicKeyLength);
|
|
||||||
retval.second = new QByteArray(reinterpret_cast<char*>(privateKeyDER), privateKeyLength);
|
|
||||||
|
|
||||||
// cleanup the publicKeyDER and publicKeyDER data
|
|
||||||
OPENSSL_free(publicKeyDER);
|
|
||||||
OPENSSL_free(privateKeyDER);
|
|
||||||
return retval;
|
|
||||||
}
|
|
||||||
// END copied code (which will soon change)
|
|
||||||
|
|
||||||
// the public key can just go into a byte array
|
|
||||||
QByteArray readPublicKey(QString filename) {
|
|
||||||
QByteArray retval;
|
|
||||||
QFile file(filename);
|
|
||||||
if (file.open(QIODevice::ReadOnly)) {
|
|
||||||
// file opened successfully
|
|
||||||
qCDebug(commerce) << "opened key file" << filename;
|
|
||||||
|
|
||||||
QByteArray pemKeyBytes = file.readAll();
|
|
||||||
BIO* bufio = BIO_new_mem_buf((void*)pemKeyBytes.constData(), pemKeyBytes.length());
|
|
||||||
|
|
||||||
EC_KEY* key = PEM_read_bio_EC_PUBKEY(bufio, NULL, NULL, NULL);
|
|
||||||
if (key) {
|
|
||||||
// file read successfully
|
|
||||||
unsigned char* publicKeyDER = NULL;
|
|
||||||
int publicKeyLength = i2d_EC_PUBKEY(key, &publicKeyDER);
|
|
||||||
// TODO: check for 0 length?
|
|
||||||
|
|
||||||
// cleanup
|
|
||||||
EC_KEY_free(key);
|
|
||||||
|
|
||||||
qCDebug(commerce) << "parsed public key file successfully";
|
|
||||||
|
|
||||||
QByteArray retval((char*)publicKeyDER, publicKeyLength);
|
|
||||||
OPENSSL_free(publicKeyDER);
|
|
||||||
BIO_free(bufio);
|
BIO_free(bufio);
|
||||||
file.close();
|
file.close();
|
||||||
|
} else {
|
||||||
|
qCDebug(commerce) << "failed to open key file" << filename;
|
||||||
|
}
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool writeKeys(QString filename, EC_KEY* keys) {
|
||||||
|
BIO* bio = BIO_new(BIO_s_mem());
|
||||||
|
bool retval = false;
|
||||||
|
if (!PEM_write_bio_EC_PUBKEY(bio, keys)) {
|
||||||
|
BIO_free(bio);
|
||||||
|
qCCritical(commerce) << "failed to write public key";
|
||||||
return retval;
|
return retval;
|
||||||
} else {
|
|
||||||
qCDebug(commerce) << "couldn't parse" << filename;
|
|
||||||
}
|
}
|
||||||
BIO_free(bufio);
|
|
||||||
file.close();
|
|
||||||
} else {
|
|
||||||
qCDebug(commerce) << "couldn't open" << filename;
|
|
||||||
}
|
|
||||||
return QByteArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
// the private key should be read/copied into heap memory. For now, we need the EC_KEY struct
|
if (!PEM_write_bio_ECPrivateKey(bio, keys, EVP_des_ede3_cbc(), NULL, 0, passwordCallback, NULL)) {
|
||||||
// so I'll return that.
|
BIO_free(bio);
|
||||||
EC_KEY* readPrivateKey(QString filename) {
|
qCCritical(commerce) << "failed to write private key";
|
||||||
QFile file(filename);
|
return retval;
|
||||||
EC_KEY* key = NULL;
|
|
||||||
if (file.open(QIODevice::ReadOnly)) {
|
|
||||||
// file opened successfully
|
|
||||||
qCDebug(commerce) << "opened key file" << filename;
|
|
||||||
|
|
||||||
QByteArray pemKeyBytes = file.readAll();
|
|
||||||
BIO* bufio = BIO_new_mem_buf((void*)pemKeyBytes.constData(), pemKeyBytes.length());
|
|
||||||
|
|
||||||
if ((key = PEM_read_bio_ECPrivateKey(bufio, &key, passwordCallback, NULL))) {
|
|
||||||
qCDebug(commerce) << "parsed private key file successfully";
|
|
||||||
|
|
||||||
} else {
|
|
||||||
qCDebug(commerce) << "couldn't parse" << filename;
|
|
||||||
// if the passphrase is wrong, then let's not cache it
|
|
||||||
DependencyManager::get<Wallet>()->setPassphrase("");
|
|
||||||
}
|
}
|
||||||
BIO_free(bufio);
|
|
||||||
file.close();
|
|
||||||
} else {
|
|
||||||
qCDebug(commerce) << "couldn't open" << filename;
|
|
||||||
}
|
|
||||||
return key;
|
|
||||||
}
|
|
||||||
|
|
||||||
// QT's QByteArray will convert to Base64 without any embedded newlines. This just
|
QFile file(filename);
|
||||||
// writes it with embedded newlines, which is more readable.
|
if (!file.open(QIODevice::WriteOnly)) {
|
||||||
void outputBase64WithNewlines(QFile& file, const QByteArray& b64Array) {
|
const char* bio_data;
|
||||||
for (int i = 0; i < b64Array.size(); i += 64) {
|
long bio_size = BIO_get_mem_data(bio, &bio_data);
|
||||||
file.write(b64Array.mid(i, 64));
|
|
||||||
file.write("\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void initializeAESKeys(unsigned char* ivec, unsigned char* ckey, const QByteArray& salt) {
|
QByteArray keyBytes(bio_data, bio_size);
|
||||||
// use the ones in the wallet
|
file.write(keyBytes);
|
||||||
auto wallet = DependencyManager::get<Wallet>();
|
retval = true;
|
||||||
memcpy(ivec, wallet->getIv(), 16);
|
qCDebug(commerce) << "wrote keys successfully";
|
||||||
memcpy(ckey, wallet->getCKey(), 32);
|
file.close();
|
||||||
}
|
} else {
|
||||||
|
qCDebug(commerce) << "failed to open key file" << filename;
|
||||||
|
}
|
||||||
|
BIO_free(bio);
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
QPair<QByteArray*, QByteArray*> generateECKeypair() {
|
||||||
|
EC_KEY* keyPair = EC_KEY_new_by_curve_name(NID_secp256k1);
|
||||||
|
QPair<QByteArray*, QByteArray*> retval {};
|
||||||
|
|
||||||
|
EC_KEY_set_asn1_flag(keyPair, OPENSSL_EC_NAMED_CURVE);
|
||||||
|
if (!EC_KEY_generate_key(keyPair)) {
|
||||||
|
qCDebug(commerce) << "Error generating EC Keypair -" << ERR_get_error();
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
// grab the public key and private key from the file
|
||||||
|
unsigned char* publicKeyDER = NULL;
|
||||||
|
int publicKeyLength = i2d_EC_PUBKEY(keyPair, &publicKeyDER);
|
||||||
|
|
||||||
|
unsigned char* privateKeyDER = NULL;
|
||||||
|
int privateKeyLength = i2d_ECPrivateKey(keyPair, &privateKeyDER);
|
||||||
|
|
||||||
|
if (publicKeyLength <= 0 || privateKeyLength <= 0) {
|
||||||
|
qCDebug(commerce) << "Error getting DER public or private key from EC struct -" << ERR_get_error();
|
||||||
|
|
||||||
|
// cleanup the EC struct
|
||||||
|
EC_KEY_free(keyPair);
|
||||||
|
|
||||||
|
// cleanup the public and private key DER data, if required
|
||||||
|
if (publicKeyLength > 0) {
|
||||||
|
OPENSSL_free(publicKeyDER);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (privateKeyLength > 0) {
|
||||||
|
OPENSSL_free(privateKeyDER);
|
||||||
|
}
|
||||||
|
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!writeKeys(keyFilePath(), keyPair)) {
|
||||||
|
qCDebug(commerce) << "couldn't save keys!";
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
EC_KEY_free(keyPair);
|
||||||
|
|
||||||
|
// prepare the return values. TODO: Fix this - we probably don't really even want the
|
||||||
|
// private key at all (better to read it when we need it?). Or maybe we do, when we have
|
||||||
|
// multiple keys?
|
||||||
|
retval.first = new QByteArray(reinterpret_cast<char*>(publicKeyDER), publicKeyLength);
|
||||||
|
retval.second = new QByteArray(reinterpret_cast<char*>(privateKeyDER), privateKeyLength);
|
||||||
|
|
||||||
|
// cleanup the publicKeyDER and publicKeyDER data
|
||||||
|
OPENSSL_free(publicKeyDER);
|
||||||
|
OPENSSL_free(privateKeyDER);
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
// END copied code (which will soon change)
|
||||||
|
|
||||||
|
// the public key can just go into a byte array
|
||||||
|
QByteArray readPublicKey(QString filename) {
|
||||||
|
QByteArray retval;
|
||||||
|
QFile file(filename);
|
||||||
|
if (file.open(QIODevice::ReadOnly)) {
|
||||||
|
// file opened successfully
|
||||||
|
qCDebug(commerce) << "opened key file" << filename;
|
||||||
|
|
||||||
|
QByteArray pemKeyBytes = file.readAll();
|
||||||
|
BIO* bufio = BIO_new_mem_buf((void*)pemKeyBytes.constData(), pemKeyBytes.length());
|
||||||
|
|
||||||
|
EC_KEY* key = PEM_read_bio_EC_PUBKEY(bufio, NULL, NULL, NULL);
|
||||||
|
if (key) {
|
||||||
|
// file read successfully
|
||||||
|
unsigned char* publicKeyDER = NULL;
|
||||||
|
int publicKeyLength = i2d_EC_PUBKEY(key, &publicKeyDER);
|
||||||
|
// TODO: check for 0 length?
|
||||||
|
|
||||||
|
// cleanup
|
||||||
|
EC_KEY_free(key);
|
||||||
|
|
||||||
|
qCDebug(commerce) << "parsed public key file successfully";
|
||||||
|
|
||||||
|
QByteArray retval((char*)publicKeyDER, publicKeyLength);
|
||||||
|
OPENSSL_free(publicKeyDER);
|
||||||
|
BIO_free(bufio);
|
||||||
|
file.close();
|
||||||
|
return retval;
|
||||||
|
} else {
|
||||||
|
qCDebug(commerce) << "couldn't parse" << filename;
|
||||||
|
}
|
||||||
|
BIO_free(bufio);
|
||||||
|
file.close();
|
||||||
|
} else {
|
||||||
|
qCDebug(commerce) << "couldn't open" << filename;
|
||||||
|
}
|
||||||
|
return QByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
// the private key should be read/copied into heap memory. For now, we need the EC_KEY struct
|
||||||
|
// so I'll return that.
|
||||||
|
EC_KEY* readPrivateKey(QString filename) {
|
||||||
|
QFile file(filename);
|
||||||
|
EC_KEY* key = NULL;
|
||||||
|
if (file.open(QIODevice::ReadOnly)) {
|
||||||
|
// file opened successfully
|
||||||
|
qCDebug(commerce) << "opened key file" << filename;
|
||||||
|
|
||||||
|
QByteArray pemKeyBytes = file.readAll();
|
||||||
|
BIO* bufio = BIO_new_mem_buf((void*)pemKeyBytes.constData(), pemKeyBytes.length());
|
||||||
|
|
||||||
|
if ((key = PEM_read_bio_ECPrivateKey(bufio, &key, passwordCallback, NULL))) {
|
||||||
|
qCDebug(commerce) << "parsed private key file successfully";
|
||||||
|
|
||||||
|
} else {
|
||||||
|
qCDebug(commerce) << "couldn't parse" << filename;
|
||||||
|
// if the passphrase is wrong, then let's not cache it
|
||||||
|
DependencyManager::get<Wallet>()->setPassphrase("");
|
||||||
|
}
|
||||||
|
BIO_free(bufio);
|
||||||
|
file.close();
|
||||||
|
} else {
|
||||||
|
qCDebug(commerce) << "couldn't open" << filename;
|
||||||
|
}
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
// QT's QByteArray will convert to Base64 without any embedded newlines. This just
|
||||||
|
// writes it with embedded newlines, which is more readable.
|
||||||
|
void outputBase64WithNewlines(QFile& file, const QByteArray& b64Array) {
|
||||||
|
for (int i = 0; i < b64Array.size(); i += 64) {
|
||||||
|
file.write(b64Array.mid(i, 64));
|
||||||
|
file.write("\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void initializeAESKeys(unsigned char* ivec, unsigned char* ckey, const QByteArray& salt) {
|
||||||
|
// use the ones in the wallet
|
||||||
|
auto wallet = DependencyManager::get<Wallet>();
|
||||||
|
memcpy(ivec, wallet->getIv(), 16);
|
||||||
|
memcpy(ckey, wallet->getCKey(), 32);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // close unnamed namespace
|
||||||
|
|
||||||
Wallet::Wallet() {
|
Wallet::Wallet() {
|
||||||
auto nodeList = DependencyManager::get<NodeList>();
|
auto nodeList = DependencyManager::get<NodeList>();
|
||||||
|
@ -423,6 +343,88 @@ Wallet::~Wallet() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Wallet::setWallet(const QByteArray& wallet) {
|
||||||
|
QFile file(keyFilePath());
|
||||||
|
if (!file.open(QIODevice::WriteOnly)) {
|
||||||
|
qCCritical(commerce) << "Unable to open wallet for write in" << keyFilePath();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (file.write(wallet) != wallet.count()) {
|
||||||
|
qCCritical(commerce) << "Unable to write wallet in" << keyFilePath();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
file.close();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
QByteArray Wallet::getWallet() {
|
||||||
|
QFile file(keyFilePath());
|
||||||
|
if (!file.open(QIODevice::ReadOnly)) {
|
||||||
|
qCInfo(commerce) << "No existing wallet in" << keyFilePath();
|
||||||
|
return QByteArray();
|
||||||
|
}
|
||||||
|
QByteArray wallet = file.readAll();
|
||||||
|
file.close();
|
||||||
|
return wallet;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Wallet::copyKeyFileFrom(const QString& pathname) {
|
||||||
|
QString existing = getKeyFilePath();
|
||||||
|
qCDebug(commerce) << "Old keyfile" << existing;
|
||||||
|
if (!existing.isEmpty()) {
|
||||||
|
QString backup = QString(existing).insert(existing.indexOf(KEY_FILE) - 1,
|
||||||
|
QDateTime::currentDateTime().toString(Qt::ISODate).replace(":", ""));
|
||||||
|
qCDebug(commerce) << "Renaming old keyfile to" << backup;
|
||||||
|
if (!QFile::rename(existing, backup)) {
|
||||||
|
qCCritical(commerce) << "Unable to backup" << existing << "to" << backup;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
QString destination = keyFilePath();
|
||||||
|
bool result = QFile::copy(pathname, destination);
|
||||||
|
qCDebug(commerce) << "copy" << pathname << "to" << destination << "=>" << result;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Wallet::writeBackupInstructions() {
|
||||||
|
QString inputFilename(PathUtils::resourcesPath() + "html/commerce/backup_instructions.html");
|
||||||
|
QString outputFilename = PathUtils::getAppDataFilePath(INSTRUCTIONS_FILE);
|
||||||
|
QFile inputFile(inputFilename);
|
||||||
|
QFile outputFile(outputFilename);
|
||||||
|
bool retval = false;
|
||||||
|
|
||||||
|
if (getKeyFilePath().isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (QFile::exists(inputFilename) && inputFile.open(QIODevice::ReadOnly)) {
|
||||||
|
if (outputFile.open(QIODevice::ReadWrite)) {
|
||||||
|
// Read the data from the original file, then close it
|
||||||
|
QByteArray fileData = inputFile.readAll();
|
||||||
|
inputFile.close();
|
||||||
|
|
||||||
|
// Translate the data from the original file into a QString
|
||||||
|
QString text(fileData);
|
||||||
|
|
||||||
|
// Replace the necessary string
|
||||||
|
text.replace(QString("HIFIKEY_PATH_REPLACEME"), keyFilePath());
|
||||||
|
|
||||||
|
// Write the new text back to the file
|
||||||
|
outputFile.write(text.toUtf8());
|
||||||
|
|
||||||
|
// Close the output file
|
||||||
|
outputFile.close();
|
||||||
|
|
||||||
|
retval = true;
|
||||||
|
qCDebug(commerce) << "wrote html file successfully";
|
||||||
|
} else {
|
||||||
|
qCDebug(commerce) << "failed to open output html file" << outputFilename;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
qCDebug(commerce) << "failed to open input html file" << inputFilename;
|
||||||
|
}
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
bool Wallet::setPassphrase(const QString& passphrase) {
|
bool Wallet::setPassphrase(const QString& passphrase) {
|
||||||
if (_passphrase) {
|
if (_passphrase) {
|
||||||
delete _passphrase;
|
delete _passphrase;
|
||||||
|
|
|
@ -244,6 +244,7 @@ void GraphicsEngine::render_performFrame() {
|
||||||
finalFramebuffer = framebufferCache->getFramebuffer();
|
finalFramebuffer = framebufferCache->getFramebuffer();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::queue<Application::SnapshotOperator> snapshotOperators;
|
||||||
if (!_programsCompiled.load()) {
|
if (!_programsCompiled.load()) {
|
||||||
gpu::doInBatch("splashFrame", _gpuContext, [&](gpu::Batch& batch) {
|
gpu::doInBatch("splashFrame", _gpuContext, [&](gpu::Batch& batch) {
|
||||||
batch.setFramebuffer(finalFramebuffer);
|
batch.setFramebuffer(finalFramebuffer);
|
||||||
|
@ -271,6 +272,7 @@ void GraphicsEngine::render_performFrame() {
|
||||||
PROFILE_RANGE(render, "/runRenderFrame");
|
PROFILE_RANGE(render, "/runRenderFrame");
|
||||||
renderArgs._hudOperator = displayPlugin->getHUDOperator();
|
renderArgs._hudOperator = displayPlugin->getHUDOperator();
|
||||||
renderArgs._hudTexture = qApp->getApplicationOverlay().getOverlayTexture();
|
renderArgs._hudTexture = qApp->getApplicationOverlay().getOverlayTexture();
|
||||||
|
renderArgs._takingSnapshot = qApp->takeSnapshotOperators(snapshotOperators);
|
||||||
renderArgs._blitFramebuffer = finalFramebuffer;
|
renderArgs._blitFramebuffer = finalFramebuffer;
|
||||||
render_runRenderFrame(&renderArgs);
|
render_runRenderFrame(&renderArgs);
|
||||||
}
|
}
|
||||||
|
@ -285,6 +287,7 @@ void GraphicsEngine::render_performFrame() {
|
||||||
frameBufferCache->releaseFramebuffer(framebuffer);
|
frameBufferCache->releaseFramebuffer(framebuffer);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
frame->snapshotOperators = snapshotOperators;
|
||||||
// deliver final scene rendering commands to the display plugin
|
// deliver final scene rendering commands to the display plugin
|
||||||
{
|
{
|
||||||
PROFILE_RANGE(render, "/pluginOutput");
|
PROFILE_RANGE(render, "/pluginOutput");
|
||||||
|
|
|
@ -174,14 +174,10 @@ void Audio::setPTTDesktop(bool enabled) {
|
||||||
_pttDesktop = enabled;
|
_pttDesktop = enabled;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (!enabled) {
|
if (enabled || _settingsLoaded) {
|
||||||
// Set to default behavior (unmuted for Desktop) on Push-To-Talk disable.
|
// Set to default behavior (muted for Desktop) on Push-To-Talk disable or when enabled. Settings also need to be loaded.
|
||||||
setMutedDesktop(true);
|
|
||||||
} else {
|
|
||||||
// Should be muted when not pushing to talk while PTT is enabled.
|
|
||||||
setMutedDesktop(true);
|
setMutedDesktop(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (changed) {
|
if (changed) {
|
||||||
emit pushToTalkChanged(enabled);
|
emit pushToTalkChanged(enabled);
|
||||||
emit pushToTalkDesktopChanged(enabled);
|
emit pushToTalkDesktopChanged(enabled);
|
||||||
|
@ -202,12 +198,9 @@ void Audio::setPTTHMD(bool enabled) {
|
||||||
_pttHMD = enabled;
|
_pttHMD = enabled;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (!enabled) {
|
if (enabled || _settingsLoaded) {
|
||||||
// Set to default behavior (unmuted for HMD) on Push-To-Talk disable.
|
// Set to default behavior (unmuted for HMD) on Push-To-Talk disable or muted for when PTT is enabled.
|
||||||
setMutedHMD(false);
|
setMutedHMD(enabled);
|
||||||
} else {
|
|
||||||
// Should be muted when not pushing to talk while PTT is enabled.
|
|
||||||
setMutedHMD(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (changed) {
|
if (changed) {
|
||||||
|
@ -231,6 +224,7 @@ void Audio::loadData() {
|
||||||
|
|
||||||
auto client = DependencyManager::get<AudioClient>().data();
|
auto client = DependencyManager::get<AudioClient>().data();
|
||||||
QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted()), Q_ARG(bool, false));
|
QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted()), Q_ARG(bool, false));
|
||||||
|
_settingsLoaded = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Audio::getPTTHMD() const {
|
bool Audio::getPTTHMD() const {
|
||||||
|
@ -357,10 +351,12 @@ void Audio::onContextChanged() {
|
||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (isHMD) {
|
if (_settingsLoaded) {
|
||||||
setMuted(getMutedHMD());
|
bool isMuted = isHMD ? getMutedHMD() : getMutedDesktop();
|
||||||
} else {
|
setMuted(isMuted);
|
||||||
setMuted(getMutedDesktop());
|
// always set audio client muted state on context changed - sometimes setMuted does not catch it.
|
||||||
|
auto client = DependencyManager::get<AudioClient>().data();
|
||||||
|
QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted), Q_ARG(bool, false));
|
||||||
}
|
}
|
||||||
if (changed) {
|
if (changed) {
|
||||||
emit contextChanged(isHMD ? Audio::HMD : Audio::DESKTOP);
|
emit contextChanged(isHMD ? Audio::HMD : Audio::DESKTOP);
|
||||||
|
|
|
@ -409,6 +409,7 @@ protected:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
|
bool _settingsLoaded { false };
|
||||||
float _inputVolume { 1.0f };
|
float _inputVolume { 1.0f };
|
||||||
float _inputLevel { 0.0f };
|
float _inputLevel { 0.0f };
|
||||||
float _localInjectorGain { 0.0f }; // in dB
|
float _localInjectorGain { 0.0f }; // in dB
|
||||||
|
|
|
@ -138,7 +138,7 @@ void LoginDialog::login(const QString& username, const QString& password) const
|
||||||
void LoginDialog::loginThroughOculus() {
|
void LoginDialog::loginThroughOculus() {
|
||||||
qDebug() << "Attempting to login through Oculus";
|
qDebug() << "Attempting to login through Oculus";
|
||||||
if (auto oculusPlatformPlugin = PluginManager::getInstance()->getOculusPlatformPlugin()) {
|
if (auto oculusPlatformPlugin = PluginManager::getInstance()->getOculusPlatformPlugin()) {
|
||||||
oculusPlatformPlugin->requestNonceAndUserID([this] (QString nonce, QString oculusID) {
|
oculusPlatformPlugin->requestNonceAndUserID([] (QString nonce, QString oculusID) {
|
||||||
DependencyManager::get<AccountManager>()->requestAccessTokenWithOculus(nonce, oculusID);
|
DependencyManager::get<AccountManager>()->requestAccessTokenWithOculus(nonce, oculusID);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -159,47 +159,57 @@ void Snapshot::save360Snapshot(const glm::vec3& cameraPosition,
|
||||||
secondaryCameraRenderConfig->setOrientation(CAMERA_ORIENTATION_DOWN);
|
secondaryCameraRenderConfig->setOrientation(CAMERA_ORIENTATION_DOWN);
|
||||||
|
|
||||||
_snapshotIndex = 0;
|
_snapshotIndex = 0;
|
||||||
|
_taking360Snapshot = true;
|
||||||
|
|
||||||
_snapshotTimer.start(SNAPSHOT_360_TIMER_INTERVAL);
|
_snapshotTimer.start(SNAPSHOT_360_TIMER_INTERVAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Snapshot::takeNextSnapshot() {
|
void Snapshot::takeNextSnapshot() {
|
||||||
SecondaryCameraJobConfig* config =
|
if (_taking360Snapshot) {
|
||||||
static_cast<SecondaryCameraJobConfig*>(qApp->getRenderEngine()->getConfiguration()->getConfig("SecondaryCamera"));
|
if (!_waitingOnSnapshot) {
|
||||||
|
_waitingOnSnapshot = true;
|
||||||
|
qApp->addSnapshotOperator(std::make_tuple([this](const QImage& snapshot) {
|
||||||
|
// Order is:
|
||||||
|
// 0. Down
|
||||||
|
// 1. Front
|
||||||
|
// 2. Left
|
||||||
|
// 3. Back
|
||||||
|
// 4. Right
|
||||||
|
// 5. Up
|
||||||
|
if (_snapshotIndex < 6) {
|
||||||
|
_imageArray[_snapshotIndex] = snapshot;
|
||||||
|
}
|
||||||
|
|
||||||
// Order is:
|
SecondaryCameraJobConfig* config = static_cast<SecondaryCameraJobConfig*>(qApp->getRenderEngine()->getConfiguration()->getConfig("SecondaryCamera"));
|
||||||
// 0. Down
|
if (_snapshotIndex == 0) {
|
||||||
// 1. Front
|
// Setup for Front Image capture
|
||||||
// 2. Left
|
config->setOrientation(CAMERA_ORIENTATION_FRONT);
|
||||||
// 3. Back
|
} else if (_snapshotIndex == 1) {
|
||||||
// 4. Right
|
// Setup for Left Image capture
|
||||||
// 5. Up
|
config->setOrientation(CAMERA_ORIENTATION_LEFT);
|
||||||
if (_snapshotIndex < 6) {
|
} else if (_snapshotIndex == 2) {
|
||||||
_imageArray[_snapshotIndex] = qApp->getActiveDisplayPlugin()->getSecondaryCameraScreenshot();
|
// Setup for Back Image capture
|
||||||
}
|
config->setOrientation(CAMERA_ORIENTATION_BACK);
|
||||||
|
} else if (_snapshotIndex == 3) {
|
||||||
|
// Setup for Right Image capture
|
||||||
|
config->setOrientation(CAMERA_ORIENTATION_RIGHT);
|
||||||
|
} else if (_snapshotIndex == 4) {
|
||||||
|
// Setup for Up Image capture
|
||||||
|
config->setOrientation(CAMERA_ORIENTATION_UP);
|
||||||
|
} else if (_snapshotIndex == 5) {
|
||||||
|
_taking360Snapshot = false;
|
||||||
|
}
|
||||||
|
|
||||||
if (_snapshotIndex == 0) {
|
_waitingOnSnapshot = false;
|
||||||
// Setup for Front Image capture
|
_snapshotIndex++;
|
||||||
config->setOrientation(CAMERA_ORIENTATION_FRONT);
|
}, 0.0f, false));
|
||||||
} else if (_snapshotIndex == 1) {
|
}
|
||||||
// Setup for Left Image capture
|
} else {
|
||||||
config->setOrientation(CAMERA_ORIENTATION_LEFT);
|
|
||||||
} else if (_snapshotIndex == 2) {
|
|
||||||
// Setup for Back Image capture
|
|
||||||
config->setOrientation(CAMERA_ORIENTATION_BACK);
|
|
||||||
} else if (_snapshotIndex == 3) {
|
|
||||||
// Setup for Right Image capture
|
|
||||||
config->setOrientation(CAMERA_ORIENTATION_RIGHT);
|
|
||||||
} else if (_snapshotIndex == 4) {
|
|
||||||
// Setup for Up Image capture
|
|
||||||
config->setOrientation(CAMERA_ORIENTATION_UP);
|
|
||||||
} else if (_snapshotIndex > 5) {
|
|
||||||
_snapshotTimer.stop();
|
_snapshotTimer.stop();
|
||||||
|
|
||||||
// Reset secondary camera render config
|
// Reset secondary camera render config
|
||||||
static_cast<ToneMappingConfig*>(
|
SecondaryCameraJobConfig* config = static_cast<SecondaryCameraJobConfig*>(qApp->getRenderEngine()->getConfiguration()->getConfig("SecondaryCamera"));
|
||||||
qApp->getRenderEngine()->getConfiguration()->getConfig("SecondaryCameraJob.ToneMapping"))
|
static_cast<ToneMappingConfig*>(qApp->getRenderEngine()->getConfiguration()->getConfig("SecondaryCameraJob.ToneMapping"))->setCurve(1);
|
||||||
->setCurve(1);
|
|
||||||
config->resetSizeSpectatorCamera(qApp->getWindow()->geometry().width(), qApp->getWindow()->geometry().height());
|
config->resetSizeSpectatorCamera(qApp->getWindow()->geometry().width(), qApp->getWindow()->geometry().height());
|
||||||
config->setProperty("attachedEntityId", _oldAttachedEntityId);
|
config->setProperty("attachedEntityId", _oldAttachedEntityId);
|
||||||
config->setProperty("vFoV", _oldvFoV);
|
config->setProperty("vFoV", _oldvFoV);
|
||||||
|
@ -217,8 +227,6 @@ void Snapshot::takeNextSnapshot() {
|
||||||
QtConcurrent::run([this]() { convertToEquirectangular(); });
|
QtConcurrent::run([this]() { convertToEquirectangular(); });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_snapshotIndex++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Snapshot::convertToCubemap() {
|
void Snapshot::convertToCubemap() {
|
||||||
|
|
|
@ -97,6 +97,8 @@ private:
|
||||||
bool _cubemapOutputFormat;
|
bool _cubemapOutputFormat;
|
||||||
QTimer _snapshotTimer;
|
QTimer _snapshotTimer;
|
||||||
qint16 _snapshotIndex;
|
qint16 _snapshotIndex;
|
||||||
|
bool _waitingOnSnapshot { false };
|
||||||
|
bool _taking360Snapshot { false };
|
||||||
bool _oldEnabled;
|
bool _oldEnabled;
|
||||||
QVariant _oldAttachedEntityId;
|
QVariant _oldAttachedEntityId;
|
||||||
QVariant _oldOrientation;
|
QVariant _oldOrientation;
|
||||||
|
|
|
@ -27,7 +27,6 @@ QString SnapshotAnimated::snapshotAnimatedPath;
|
||||||
QString SnapshotAnimated::snapshotStillPath;
|
QString SnapshotAnimated::snapshotStillPath;
|
||||||
QVector<QImage> SnapshotAnimated::snapshotAnimatedFrameVector;
|
QVector<QImage> SnapshotAnimated::snapshotAnimatedFrameVector;
|
||||||
QVector<qint64> SnapshotAnimated::snapshotAnimatedFrameDelayVector;
|
QVector<qint64> SnapshotAnimated::snapshotAnimatedFrameDelayVector;
|
||||||
Application* SnapshotAnimated::app;
|
|
||||||
float SnapshotAnimated::aspectRatio;
|
float SnapshotAnimated::aspectRatio;
|
||||||
QSharedPointer<WindowScriptingInterface> SnapshotAnimated::snapshotAnimatedDM;
|
QSharedPointer<WindowScriptingInterface> SnapshotAnimated::snapshotAnimatedDM;
|
||||||
GifWriter SnapshotAnimated::snapshotAnimatedGifWriter;
|
GifWriter SnapshotAnimated::snapshotAnimatedGifWriter;
|
||||||
|
@ -36,12 +35,11 @@ GifWriter SnapshotAnimated::snapshotAnimatedGifWriter;
|
||||||
Setting::Handle<bool> SnapshotAnimated::alsoTakeAnimatedSnapshot("alsoTakeAnimatedSnapshot", true);
|
Setting::Handle<bool> SnapshotAnimated::alsoTakeAnimatedSnapshot("alsoTakeAnimatedSnapshot", true);
|
||||||
Setting::Handle<float> SnapshotAnimated::snapshotAnimatedDuration("snapshotAnimatedDuration", SNAPSNOT_ANIMATED_DURATION_SECS);
|
Setting::Handle<float> SnapshotAnimated::snapshotAnimatedDuration("snapshotAnimatedDuration", SNAPSNOT_ANIMATED_DURATION_SECS);
|
||||||
|
|
||||||
void SnapshotAnimated::saveSnapshotAnimated(QString pathStill, float aspectRatio, Application* app, QSharedPointer<WindowScriptingInterface> dm) {
|
void SnapshotAnimated::saveSnapshotAnimated(QString pathStill, float aspectRatio, QSharedPointer<WindowScriptingInterface> dm) {
|
||||||
// If we're not in the middle of capturing an animated snapshot...
|
// If we're not in the middle of capturing an animated snapshot...
|
||||||
if (SnapshotAnimated::snapshotAnimatedFirstFrameTimestamp == 0) {
|
if (SnapshotAnimated::snapshotAnimatedFirstFrameTimestamp == 0) {
|
||||||
SnapshotAnimated::snapshotAnimatedTimer = new QTimer();
|
SnapshotAnimated::snapshotAnimatedTimer = new QTimer();
|
||||||
SnapshotAnimated::aspectRatio = aspectRatio;
|
SnapshotAnimated::aspectRatio = aspectRatio;
|
||||||
SnapshotAnimated::app = app;
|
|
||||||
SnapshotAnimated::snapshotAnimatedDM = dm;
|
SnapshotAnimated::snapshotAnimatedDM = dm;
|
||||||
// Define the output location of the still and animated snapshots.
|
// Define the output location of the still and animated snapshots.
|
||||||
SnapshotAnimated::snapshotStillPath = pathStill;
|
SnapshotAnimated::snapshotStillPath = pathStill;
|
||||||
|
@ -62,44 +60,45 @@ void SnapshotAnimated::saveSnapshotAnimated(QString pathStill, float aspectRatio
|
||||||
|
|
||||||
void SnapshotAnimated::captureFrames() {
|
void SnapshotAnimated::captureFrames() {
|
||||||
if (SnapshotAnimated::snapshotAnimatedTimerRunning) {
|
if (SnapshotAnimated::snapshotAnimatedTimerRunning) {
|
||||||
// Get a screenshot from the display, then scale the screenshot down,
|
qApp->addSnapshotOperator(std::make_tuple([](const QImage& snapshot) {
|
||||||
// then convert it to the image format the GIF library needs,
|
// Get a screenshot from the display, then scale the screenshot down,
|
||||||
// then save all that to the QImage named "frame"
|
// then convert it to the image format the GIF library needs,
|
||||||
QImage frame(SnapshotAnimated::app->getActiveDisplayPlugin()->getScreenshot(SnapshotAnimated::aspectRatio));
|
// then save all that to the QImage named "frame"
|
||||||
frame = frame.scaledToWidth(SNAPSNOT_ANIMATED_WIDTH);
|
QImage frame = snapshot.scaledToWidth(SNAPSNOT_ANIMATED_WIDTH);
|
||||||
SnapshotAnimated::snapshotAnimatedFrameVector.append(frame);
|
SnapshotAnimated::snapshotAnimatedFrameVector.append(frame);
|
||||||
|
|
||||||
// If that was the first frame...
|
// If that was the first frame...
|
||||||
if (SnapshotAnimated::snapshotAnimatedFirstFrameTimestamp == 0) {
|
if (SnapshotAnimated::snapshotAnimatedFirstFrameTimestamp == 0) {
|
||||||
// Record the current frame timestamp
|
// Record the current frame timestamp
|
||||||
SnapshotAnimated::snapshotAnimatedTimestamp = QDateTime::currentMSecsSinceEpoch();
|
SnapshotAnimated::snapshotAnimatedTimestamp = QDateTime::currentMSecsSinceEpoch();
|
||||||
// Record the first frame timestamp
|
// Record the first frame timestamp
|
||||||
SnapshotAnimated::snapshotAnimatedFirstFrameTimestamp = SnapshotAnimated::snapshotAnimatedTimestamp;
|
SnapshotAnimated::snapshotAnimatedFirstFrameTimestamp = SnapshotAnimated::snapshotAnimatedTimestamp;
|
||||||
SnapshotAnimated::snapshotAnimatedFrameDelayVector.append(SNAPSNOT_ANIMATED_FRAME_DELAY_MSEC / 10);
|
SnapshotAnimated::snapshotAnimatedFrameDelayVector.append(SNAPSNOT_ANIMATED_FRAME_DELAY_MSEC / 10);
|
||||||
// If this is an intermediate or the final frame...
|
// If this is an intermediate or the final frame...
|
||||||
} else {
|
} else {
|
||||||
// Push the current frame delay onto the vector
|
// Push the current frame delay onto the vector
|
||||||
SnapshotAnimated::snapshotAnimatedFrameDelayVector.append(round(((float)(QDateTime::currentMSecsSinceEpoch() - SnapshotAnimated::snapshotAnimatedTimestamp)) / 10));
|
SnapshotAnimated::snapshotAnimatedFrameDelayVector.append(round(((float)(QDateTime::currentMSecsSinceEpoch() - SnapshotAnimated::snapshotAnimatedTimestamp)) / 10));
|
||||||
// Record the current frame timestamp
|
// Record the current frame timestamp
|
||||||
SnapshotAnimated::snapshotAnimatedTimestamp = QDateTime::currentMSecsSinceEpoch();
|
SnapshotAnimated::snapshotAnimatedTimestamp = QDateTime::currentMSecsSinceEpoch();
|
||||||
|
|
||||||
// If that was the last frame...
|
// If that was the last frame...
|
||||||
if ((SnapshotAnimated::snapshotAnimatedTimestamp - SnapshotAnimated::snapshotAnimatedFirstFrameTimestamp) >= (SnapshotAnimated::snapshotAnimatedDuration.get() * MSECS_PER_SECOND)) {
|
if ((SnapshotAnimated::snapshotAnimatedTimestamp - SnapshotAnimated::snapshotAnimatedFirstFrameTimestamp) >= (SnapshotAnimated::snapshotAnimatedDuration.get() * MSECS_PER_SECOND)) {
|
||||||
SnapshotAnimated::snapshotAnimatedTimerRunning = false;
|
SnapshotAnimated::snapshotAnimatedTimerRunning = false;
|
||||||
|
}
|
||||||
// Notify the user that we're processing the snapshot
|
|
||||||
// This also pops up the "Share" dialog. The unprocessed GIF will be visualized as a loading icon until processingGifCompleted() is called.
|
|
||||||
emit SnapshotAnimated::snapshotAnimatedDM->processingGifStarted(SnapshotAnimated::snapshotStillPath);
|
|
||||||
|
|
||||||
// Kick off the thread that'll pack the frames into the GIF
|
|
||||||
QtConcurrent::run(processFrames);
|
|
||||||
// Stop the snapshot QTimer. This action by itself DOES NOT GUARANTEE
|
|
||||||
// that the slot will not be called again in the future.
|
|
||||||
// See: http://lists.qt-project.org/pipermail/qt-interest-old/2009-October/013926.html
|
|
||||||
SnapshotAnimated::snapshotAnimatedTimer->stop();
|
|
||||||
delete SnapshotAnimated::snapshotAnimatedTimer;
|
|
||||||
}
|
}
|
||||||
}
|
}, SnapshotAnimated::aspectRatio, true));
|
||||||
|
} else {
|
||||||
|
// Notify the user that we're processing the snapshot
|
||||||
|
// This also pops up the "Share" dialog. The unprocessed GIF will be visualized as a loading icon until processingGifCompleted() is called.
|
||||||
|
emit SnapshotAnimated::snapshotAnimatedDM->processingGifStarted(SnapshotAnimated::snapshotStillPath);
|
||||||
|
|
||||||
|
// Kick off the thread that'll pack the frames into the GIF
|
||||||
|
QtConcurrent::run(processFrames);
|
||||||
|
// Stop the snapshot QTimer. This action by itself DOES NOT GUARANTEE
|
||||||
|
// that the slot will not be called again in the future.
|
||||||
|
// See: http://lists.qt-project.org/pipermail/qt-interest-old/2009-October/013926.html
|
||||||
|
SnapshotAnimated::snapshotAnimatedTimer->stop();
|
||||||
|
delete SnapshotAnimated::snapshotAnimatedTimer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -42,7 +42,6 @@ private:
|
||||||
static QVector<QImage> snapshotAnimatedFrameVector;
|
static QVector<QImage> snapshotAnimatedFrameVector;
|
||||||
static QVector<qint64> snapshotAnimatedFrameDelayVector;
|
static QVector<qint64> snapshotAnimatedFrameDelayVector;
|
||||||
static QSharedPointer<WindowScriptingInterface> snapshotAnimatedDM;
|
static QSharedPointer<WindowScriptingInterface> snapshotAnimatedDM;
|
||||||
static Application* app;
|
|
||||||
static float aspectRatio;
|
static float aspectRatio;
|
||||||
|
|
||||||
static GifWriter snapshotAnimatedGifWriter;
|
static GifWriter snapshotAnimatedGifWriter;
|
||||||
|
@ -51,7 +50,7 @@ private:
|
||||||
static void processFrames();
|
static void processFrames();
|
||||||
static void clearTempVariables();
|
static void clearTempVariables();
|
||||||
public:
|
public:
|
||||||
static void saveSnapshotAnimated(QString pathStill, float aspectRatio, Application* app, QSharedPointer<WindowScriptingInterface> dm);
|
static void saveSnapshotAnimated(QString pathStill, float aspectRatio, QSharedPointer<WindowScriptingInterface> dm);
|
||||||
static bool isAlreadyTakingSnapshotAnimated() { return snapshotAnimatedFirstFrameTimestamp != 0; };
|
static bool isAlreadyTakingSnapshotAnimated() { return snapshotAnimatedFirstFrameTimestamp != 0; };
|
||||||
static Setting::Handle<bool> alsoTakeAnimatedSnapshot;
|
static Setting::Handle<bool> alsoTakeAnimatedSnapshot;
|
||||||
static Setting::Handle<float> snapshotAnimatedDuration;
|
static Setting::Handle<float> snapshotAnimatedDuration;
|
||||||
|
|
|
@ -261,7 +261,7 @@ public:
|
||||||
qCDebug(animation) << " " << pair.first << "=" << pair.second.getString();
|
qCDebug(animation) << " " << pair.first << "=" << pair.second.getString();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
assert(("invalid AnimVariant::Type", false));
|
assert(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -142,6 +142,27 @@ void FBXBaker::rewriteAndBakeSceneModels(const QVector<hfm::Mesh>& meshes, const
|
||||||
} else if (object->name == "Texture" || object->name == "Video") {
|
} else if (object->name == "Texture" || object->name == "Video") {
|
||||||
// this is an embedded texture, we need to remove it from the FBX
|
// this is an embedded texture, we need to remove it from the FBX
|
||||||
object = rootChild.children.erase(object);
|
object = rootChild.children.erase(object);
|
||||||
|
} else if (object->name == "Material") {
|
||||||
|
for (FBXNode& materialChild : object->children) {
|
||||||
|
if (materialChild.name == "Properties60" || materialChild.name == "Properties70") {
|
||||||
|
// This is a properties node
|
||||||
|
// Remove the material texture scale because that is now included in the material JSON
|
||||||
|
// Texture nodes are removed, so their texture scale is effectively gone already
|
||||||
|
static const QVariant MAYA_UV_SCALE = hifi::ByteArray("Maya|uv_scale");
|
||||||
|
static const QVariant MAYA_UV_OFFSET = hifi::ByteArray("Maya|uv_offset");
|
||||||
|
for (int i = 0; i < materialChild.children.size(); i++) {
|
||||||
|
const auto& prop = materialChild.children[i];
|
||||||
|
const auto& propertyName = prop.properties.at(0);
|
||||||
|
if (propertyName == MAYA_UV_SCALE ||
|
||||||
|
propertyName == MAYA_UV_OFFSET) {
|
||||||
|
materialChild.children.removeAt(i);
|
||||||
|
--i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object++;
|
||||||
} else {
|
} else {
|
||||||
object++;
|
object++;
|
||||||
}
|
}
|
||||||
|
|
|
@ -276,4 +276,8 @@ void MaterialBaker::setMaterials(const QHash<QString, hfm::Material>& materials,
|
||||||
addTexture(material.name, image::TextureUsage::SCATTERING_TEXTURE, material.scatteringTexture);
|
addTexture(material.name, image::TextureUsage::SCATTERING_TEXTURE, material.scatteringTexture);
|
||||||
addTexture(material.name, image::TextureUsage::LIGHTMAP_TEXTURE, material.lightmapTexture);
|
addTexture(material.name, image::TextureUsage::LIGHTMAP_TEXTURE, material.lightmapTexture);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MaterialBaker::setMaterials(const NetworkMaterialResourcePointer& materialResource) {
|
||||||
|
_materialResource = materialResource;
|
||||||
}
|
}
|
|
@ -31,6 +31,9 @@ public:
|
||||||
QString getBakedMaterialData() const { return _bakedMaterialData; }
|
QString getBakedMaterialData() const { return _bakedMaterialData; }
|
||||||
|
|
||||||
void setMaterials(const QHash<QString, hfm::Material>& materials, const QString& baseURL);
|
void setMaterials(const QHash<QString, hfm::Material>& materials, const QString& baseURL);
|
||||||
|
void setMaterials(const NetworkMaterialResourcePointer& materialResource);
|
||||||
|
|
||||||
|
NetworkMaterialResourcePointer getNetworkMaterialResource() const { return _materialResource; }
|
||||||
|
|
||||||
static void setNextOvenWorkerThreadOperator(std::function<QThread*()> getNextOvenWorkerThreadOperator) { _getNextOvenWorkerThreadOperator = getNextOvenWorkerThreadOperator; }
|
static void setNextOvenWorkerThreadOperator(std::function<QThread*()> getNextOvenWorkerThreadOperator) { _getNextOvenWorkerThreadOperator = getNextOvenWorkerThreadOperator; }
|
||||||
|
|
||||||
|
|
|
@ -241,14 +241,12 @@ void ModelBaker::bakeSourceCopy() {
|
||||||
config->getJobConfig("BuildDracoMesh")->setEnabled(true);
|
config->getJobConfig("BuildDracoMesh")->setEnabled(true);
|
||||||
// Do not permit potentially lossy modification of joint data meant for runtime
|
// Do not permit potentially lossy modification of joint data meant for runtime
|
||||||
((PrepareJointsConfig*)config->getJobConfig("PrepareJoints"))->passthrough = true;
|
((PrepareJointsConfig*)config->getJobConfig("PrepareJoints"))->passthrough = true;
|
||||||
// The resources parsed from this job will not be used for now
|
|
||||||
// TODO: Proper full baking of all materials for a model
|
|
||||||
config->getJobConfig("ParseMaterialMapping")->setEnabled(false);
|
|
||||||
|
|
||||||
// Begin hfm baking
|
// Begin hfm baking
|
||||||
baker.run();
|
baker.run();
|
||||||
|
|
||||||
_hfmModel = baker.getHFMModel();
|
_hfmModel = baker.getHFMModel();
|
||||||
|
_materialMapping = baker.getMaterialMapping();
|
||||||
dracoMeshes = baker.getDracoMeshes();
|
dracoMeshes = baker.getDracoMeshes();
|
||||||
dracoMaterialLists = baker.getDracoMaterialLists();
|
dracoMaterialLists = baker.getDracoMaterialLists();
|
||||||
}
|
}
|
||||||
|
@ -260,7 +258,7 @@ void ModelBaker::bakeSourceCopy() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_hfmModel->materials.size() > 0) {
|
if (!_hfmModel->materials.isEmpty()) {
|
||||||
_materialBaker = QSharedPointer<MaterialBaker>(
|
_materialBaker = QSharedPointer<MaterialBaker>(
|
||||||
new MaterialBaker(_modelURL.fileName(), true, _bakedOutputDir),
|
new MaterialBaker(_modelURL.fileName(), true, _bakedOutputDir),
|
||||||
&MaterialBaker::deleteLater
|
&MaterialBaker::deleteLater
|
||||||
|
@ -269,7 +267,7 @@ void ModelBaker::bakeSourceCopy() {
|
||||||
connect(_materialBaker.data(), &MaterialBaker::finished, this, &ModelBaker::handleFinishedMaterialBaker);
|
connect(_materialBaker.data(), &MaterialBaker::finished, this, &ModelBaker::handleFinishedMaterialBaker);
|
||||||
_materialBaker->bake();
|
_materialBaker->bake();
|
||||||
} else {
|
} else {
|
||||||
outputBakedFST();
|
bakeMaterialMap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -285,26 +283,14 @@ void ModelBaker::handleFinishedMaterialBaker() {
|
||||||
auto baseName = relativeBakedMaterialURL.left(relativeBakedMaterialURL.lastIndexOf('.'));
|
auto baseName = relativeBakedMaterialURL.left(relativeBakedMaterialURL.lastIndexOf('.'));
|
||||||
relativeBakedMaterialURL = baseName + BAKED_MATERIAL_EXTENSION;
|
relativeBakedMaterialURL = baseName + BAKED_MATERIAL_EXTENSION;
|
||||||
|
|
||||||
// First we add the materials in the model
|
auto materialResource = baker->getNetworkMaterialResource();
|
||||||
QJsonArray materialMapping;
|
if (materialResource) {
|
||||||
for (auto material : _hfmModel->materials) {
|
for (auto materialName : materialResource->parsedMaterials.names) {
|
||||||
QJsonObject json;
|
|
||||||
json["mat::" + material.name] = relativeBakedMaterialURL + "#" + material.name;
|
|
||||||
materialMapping.push_back(json);
|
|
||||||
}
|
|
||||||
|
|
||||||
// The we add any existing mappings from the mapping
|
|
||||||
if (_mapping.contains(MATERIAL_MAPPING_FIELD)) {
|
|
||||||
QByteArray materialMapValue = _mapping[MATERIAL_MAPPING_FIELD].toByteArray();
|
|
||||||
QJsonObject oldMaterialMapping = QJsonDocument::fromJson(materialMapValue).object();
|
|
||||||
for (auto key : oldMaterialMapping.keys()) {
|
|
||||||
QJsonObject json;
|
QJsonObject json;
|
||||||
json[key] = oldMaterialMapping[key];
|
json[QString("mat::" + QString(materialName.c_str()))] = relativeBakedMaterialURL + "#" + materialName.c_str();
|
||||||
materialMapping.push_back(json);
|
_materialMappingJSON.push_back(json);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_mapping[MATERIAL_MAPPING_FIELD] = QJsonDocument(materialMapping).toJson(QJsonDocument::Compact);
|
|
||||||
} else {
|
} else {
|
||||||
// this material failed to bake - this doesn't fail the entire bake but we need to add the errors from
|
// this material failed to bake - this doesn't fail the entire bake but we need to add the errors from
|
||||||
// the material to our warnings
|
// the material to our warnings
|
||||||
|
@ -314,7 +300,62 @@ void ModelBaker::handleFinishedMaterialBaker() {
|
||||||
handleWarning("Failed to bake the materials for model with URL " + _modelURL.toString());
|
handleWarning("Failed to bake the materials for model with URL " + _modelURL.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
outputBakedFST();
|
bakeMaterialMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModelBaker::bakeMaterialMap() {
|
||||||
|
if (!_materialMapping.empty()) {
|
||||||
|
// TODO: The existing material map must be baked in order, so we do it all on this thread to preserve the order.
|
||||||
|
// It could be spread over multiple threads if we had a good way of preserving the order once all of the bakers are done
|
||||||
|
_materialBaker = QSharedPointer<MaterialBaker>(
|
||||||
|
new MaterialBaker("materialMap" + QString::number(_materialMapIndex++), true, _bakedOutputDir),
|
||||||
|
&MaterialBaker::deleteLater
|
||||||
|
);
|
||||||
|
_materialBaker->setMaterials(_materialMapping.front().second);
|
||||||
|
connect(_materialBaker.data(), &MaterialBaker::finished, this, &ModelBaker::handleFinishedMaterialMapBaker);
|
||||||
|
_materialBaker->bake();
|
||||||
|
} else {
|
||||||
|
outputBakedFST();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModelBaker::handleFinishedMaterialMapBaker() {
|
||||||
|
auto baker = qobject_cast<MaterialBaker*>(sender());
|
||||||
|
|
||||||
|
if (baker) {
|
||||||
|
if (!baker->hasErrors()) {
|
||||||
|
// this MaterialBaker is done and everything went according to plan
|
||||||
|
qCDebug(model_baking) << "Adding baked material to FST mapping " << baker->getBakedMaterialData();
|
||||||
|
|
||||||
|
QString materialName;
|
||||||
|
{
|
||||||
|
auto materialResource = baker->getNetworkMaterialResource();
|
||||||
|
if (materialResource) {
|
||||||
|
auto url = materialResource->getURL();
|
||||||
|
if (!url.isEmpty()) {
|
||||||
|
QString urlString = url.toDisplayString();
|
||||||
|
auto index = urlString.lastIndexOf("#");
|
||||||
|
if (index != -1) {
|
||||||
|
materialName = urlString.right(urlString.length() - index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject json;
|
||||||
|
json[QString(_materialMapping.front().first.c_str())] = baker->getMaterialData() + BAKED_MATERIAL_EXTENSION + materialName;
|
||||||
|
_materialMappingJSON.push_back(json);
|
||||||
|
} else {
|
||||||
|
// this material failed to bake - this doesn't fail the entire bake but we need to add the errors from
|
||||||
|
// the material to our warnings
|
||||||
|
_warningList << baker->getWarnings();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
handleWarning("Failed to bake the materialMap for model with URL " + _modelURL.toString() + " and mapping target " + _materialMapping.front().first.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
_materialMapping.erase(_materialMapping.begin());
|
||||||
|
bakeMaterialMap();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ModelBaker::outputUnbakedFST() {
|
void ModelBaker::outputUnbakedFST() {
|
||||||
|
@ -363,6 +404,9 @@ void ModelBaker::outputBakedFST() {
|
||||||
outputMapping[FILENAME_FIELD] = _bakedModelURL.fileName();
|
outputMapping[FILENAME_FIELD] = _bakedModelURL.fileName();
|
||||||
outputMapping.remove(TEXDIR_FIELD);
|
outputMapping.remove(TEXDIR_FIELD);
|
||||||
outputMapping.remove(COMMENT_FIELD);
|
outputMapping.remove(COMMENT_FIELD);
|
||||||
|
if (!_materialMappingJSON.isEmpty()) {
|
||||||
|
outputMapping[MATERIAL_MAPPING_FIELD] = QJsonDocument(_materialMappingJSON).toJson(QJsonDocument::Compact);
|
||||||
|
}
|
||||||
hifi::ByteArray fstOut = FSTReader::writeMapping(outputMapping);
|
hifi::ByteArray fstOut = FSTReader::writeMapping(outputMapping);
|
||||||
|
|
||||||
QFile fstOutputFile { outputFSTURL };
|
QFile fstOutputFile { outputFSTURL };
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
#include <QtCore/QDir>
|
#include <QtCore/QDir>
|
||||||
#include <QtCore/QUrl>
|
#include <QtCore/QUrl>
|
||||||
#include <QtNetwork/QNetworkReply>
|
#include <QtNetwork/QNetworkReply>
|
||||||
|
#include <QJsonArray>
|
||||||
|
|
||||||
#include "Baker.h"
|
#include "Baker.h"
|
||||||
#include "MaterialBaker.h"
|
#include "MaterialBaker.h"
|
||||||
|
@ -80,14 +81,19 @@ protected slots:
|
||||||
void handleModelNetworkReply();
|
void handleModelNetworkReply();
|
||||||
virtual void bakeSourceCopy();
|
virtual void bakeSourceCopy();
|
||||||
void handleFinishedMaterialBaker();
|
void handleFinishedMaterialBaker();
|
||||||
|
void handleFinishedMaterialMapBaker();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void outputUnbakedFST();
|
void outputUnbakedFST();
|
||||||
void outputBakedFST();
|
void outputBakedFST();
|
||||||
|
void bakeMaterialMap();
|
||||||
|
|
||||||
bool _hasBeenBaked { false };
|
bool _hasBeenBaked { false };
|
||||||
|
|
||||||
hfm::Model::Pointer _hfmModel;
|
hfm::Model::Pointer _hfmModel;
|
||||||
|
MaterialMapping _materialMapping;
|
||||||
|
int _materialMapIndex { 0 };
|
||||||
|
QJsonArray _materialMappingJSON;
|
||||||
QSharedPointer<MaterialBaker> _materialBaker;
|
QSharedPointer<MaterialBaker> _materialBaker;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -25,12 +25,4 @@ void NullDisplayPlugin::submitFrame(const gpu::FramePointer& frame) {
|
||||||
if (frame) {
|
if (frame) {
|
||||||
_gpuContext->consumeFrameUpdates(frame);
|
_gpuContext->consumeFrameUpdates(frame);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QImage NullDisplayPlugin::getScreenshot(float aspectRatio) const {
|
|
||||||
return QImage();
|
|
||||||
}
|
|
||||||
|
|
||||||
QImage NullDisplayPlugin::getSecondaryCameraScreenshot() const {
|
|
||||||
return QImage();
|
|
||||||
}
|
|
|
@ -17,8 +17,6 @@ public:
|
||||||
|
|
||||||
glm::uvec2 getRecommendedRenderSize() const override;
|
glm::uvec2 getRecommendedRenderSize() const override;
|
||||||
void submitFrame(const gpu::FramePointer& newFrame) override;
|
void submitFrame(const gpu::FramePointer& newFrame) override;
|
||||||
QImage getScreenshot(float aspectRatio = 0.0f) const override;
|
|
||||||
QImage getSecondaryCameraScreenshot() const override;
|
|
||||||
void copyTextureToQuickFramebuffer(NetworkTexturePointer source, QOpenGLFramebufferObject* target, GLsync* fenceSync) override {};
|
void copyTextureToQuickFramebuffer(NetworkTexturePointer source, QOpenGLFramebufferObject* target, GLsync* fenceSync) override {};
|
||||||
void pluginUpdate() override {};
|
void pluginUpdate() override {};
|
||||||
private:
|
private:
|
||||||
|
|
|
@ -721,6 +721,19 @@ void OpenGLDisplayPlugin::present() {
|
||||||
compositeLayers();
|
compositeLayers();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{ // If we have any snapshots this frame, handle them
|
||||||
|
PROFILE_RANGE_EX(render, "snapshotOperators", 0xffff00ff, frameId)
|
||||||
|
while (!_currentFrame->snapshotOperators.empty()) {
|
||||||
|
auto& snapshotOperator = _currentFrame->snapshotOperators.front();
|
||||||
|
if (std::get<2>(snapshotOperator)) {
|
||||||
|
std::get<0>(snapshotOperator)(getScreenshot(std::get<1>(snapshotOperator)));
|
||||||
|
} else {
|
||||||
|
std::get<0>(snapshotOperator)(getSecondaryCameraScreenshot());
|
||||||
|
}
|
||||||
|
_currentFrame->snapshotOperators.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Take the composite framebuffer and send it to the output device
|
// Take the composite framebuffer and send it to the output device
|
||||||
{
|
{
|
||||||
PROFILE_RANGE_EX(render, "internalPresent", 0xff00ffff, frameId)
|
PROFILE_RANGE_EX(render, "internalPresent", 0xff00ffff, frameId)
|
||||||
|
@ -785,7 +798,7 @@ bool OpenGLDisplayPlugin::setDisplayTexture(const QString& name) {
|
||||||
return !!_displayTexture;
|
return !!_displayTexture;
|
||||||
}
|
}
|
||||||
|
|
||||||
QImage OpenGLDisplayPlugin::getScreenshot(float aspectRatio) const {
|
QImage OpenGLDisplayPlugin::getScreenshot(float aspectRatio) {
|
||||||
auto size = _compositeFramebuffer->getSize();
|
auto size = _compositeFramebuffer->getSize();
|
||||||
if (isHmd()) {
|
if (isHmd()) {
|
||||||
size.x /= 2;
|
size.x /= 2;
|
||||||
|
@ -801,24 +814,18 @@ QImage OpenGLDisplayPlugin::getScreenshot(float aspectRatio) const {
|
||||||
corner.x = round((size.x - bestSize.x) / 2.0f);
|
corner.x = round((size.x - bestSize.x) / 2.0f);
|
||||||
corner.y = round((size.y - bestSize.y) / 2.0f);
|
corner.y = round((size.y - bestSize.y) / 2.0f);
|
||||||
}
|
}
|
||||||
auto glBackend = const_cast<OpenGLDisplayPlugin&>(*this).getGLBackend();
|
|
||||||
QImage screenshot(bestSize.x, bestSize.y, QImage::Format_ARGB32);
|
QImage screenshot(bestSize.x, bestSize.y, QImage::Format_ARGB32);
|
||||||
withOtherThreadContext([&] {
|
getGLBackend()->downloadFramebuffer(_compositeFramebuffer, ivec4(corner, bestSize), screenshot);
|
||||||
glBackend->downloadFramebuffer(_compositeFramebuffer, ivec4(corner, bestSize), screenshot);
|
|
||||||
});
|
|
||||||
return screenshot.mirrored(false, true);
|
return screenshot.mirrored(false, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
QImage OpenGLDisplayPlugin::getSecondaryCameraScreenshot() const {
|
QImage OpenGLDisplayPlugin::getSecondaryCameraScreenshot() {
|
||||||
auto textureCache = DependencyManager::get<TextureCache>();
|
auto textureCache = DependencyManager::get<TextureCache>();
|
||||||
auto secondaryCameraFramebuffer = textureCache->getSpectatorCameraFramebuffer();
|
auto secondaryCameraFramebuffer = textureCache->getSpectatorCameraFramebuffer();
|
||||||
gpu::Vec4i region(0, 0, secondaryCameraFramebuffer->getWidth(), secondaryCameraFramebuffer->getHeight());
|
gpu::Vec4i region(0, 0, secondaryCameraFramebuffer->getWidth(), secondaryCameraFramebuffer->getHeight());
|
||||||
|
|
||||||
auto glBackend = const_cast<OpenGLDisplayPlugin&>(*this).getGLBackend();
|
|
||||||
QImage screenshot(region.z, region.w, QImage::Format_ARGB32);
|
QImage screenshot(region.z, region.w, QImage::Format_ARGB32);
|
||||||
withOtherThreadContext([&] {
|
getGLBackend()->downloadFramebuffer(secondaryCameraFramebuffer, region, screenshot);
|
||||||
glBackend->downloadFramebuffer(secondaryCameraFramebuffer, region, screenshot);
|
|
||||||
});
|
|
||||||
return screenshot.mirrored(false, true);
|
return screenshot.mirrored(false, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -60,8 +60,6 @@ public:
|
||||||
|
|
||||||
virtual bool setDisplayTexture(const QString& name) override;
|
virtual bool setDisplayTexture(const QString& name) override;
|
||||||
virtual bool onDisplayTextureReset() { return false; };
|
virtual bool onDisplayTextureReset() { return false; };
|
||||||
QImage getScreenshot(float aspectRatio = 0.0f) const override;
|
|
||||||
QImage getSecondaryCameraScreenshot() const override;
|
|
||||||
|
|
||||||
float presentRate() const override;
|
float presentRate() const override;
|
||||||
|
|
||||||
|
@ -185,5 +183,8 @@ protected:
|
||||||
// be serialized through this mutex
|
// be serialized through this mutex
|
||||||
mutable Mutex _presentMutex;
|
mutable Mutex _presentMutex;
|
||||||
float _hudAlpha{ 1.0f };
|
float _hudAlpha{ 1.0f };
|
||||||
|
|
||||||
|
QImage getScreenshot(float aspectRatio);
|
||||||
|
QImage getSecondaryCameraScreenshot();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -199,7 +199,7 @@ void HmdDisplayPlugin::internalPresent() {
|
||||||
float newWidth = sourceSize.x - shiftLeftBy;
|
float newWidth = sourceSize.x - shiftLeftBy;
|
||||||
|
|
||||||
// Experimentally adjusted the region presented in preview to avoid seeing the masked pixels and recenter the center...
|
// Experimentally adjusted the region presented in preview to avoid seeing the masked pixels and recenter the center...
|
||||||
static float SCALE_WIDTH = 0.9f;
|
static float SCALE_WIDTH = 0.8f;
|
||||||
static float SCALE_OFFSET = 2.0f;
|
static float SCALE_OFFSET = 2.0f;
|
||||||
newWidth *= SCALE_WIDTH;
|
newWidth *= SCALE_WIDTH;
|
||||||
shiftLeftBy *= SCALE_OFFSET;
|
shiftLeftBy *= SCALE_OFFSET;
|
||||||
|
|
|
@ -48,6 +48,8 @@ public:
|
||||||
|
|
||||||
void pluginUpdate() override {};
|
void pluginUpdate() override {};
|
||||||
|
|
||||||
|
virtual StencilMaskMode getStencilMaskMode() const override { return StencilMaskMode::PAINT; }
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void hmdMountedChanged();
|
void hmdMountedChanged();
|
||||||
void hmdVisibleChanged(bool visible);
|
void hmdVisibleChanged(bool visible);
|
||||||
|
|
|
@ -313,11 +313,9 @@ void MaterialEntityRenderer::doRender(RenderArgs* args) {
|
||||||
|
|
||||||
batch.setModelTransform(renderTransform);
|
batch.setModelTransform(renderTransform);
|
||||||
|
|
||||||
if (args->_renderMode != render::Args::RenderMode::SHADOW_RENDER_MODE) {
|
drawMaterial->setTextureTransforms(textureTransform, MaterialMappingMode::UV, true);
|
||||||
drawMaterial->setTextureTransforms(textureTransform, MaterialMappingMode::UV, true);
|
// bind the material
|
||||||
|
if (RenderPipelines::bindMaterial(drawMaterial, batch, args->_renderMode, args->_enableTexturing)) {
|
||||||
// bind the material
|
|
||||||
RenderPipelines::bindMaterial(drawMaterial, batch, args->_enableTexturing);
|
|
||||||
args->_details._materialSwitches++;
|
args->_details._materialSwitches++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -291,8 +291,7 @@ void ShapeEntityRenderer::doRender(RenderArgs* args) {
|
||||||
geometryCache->renderSolidShapeInstance(args, batch, geometryShape, outColor, pipeline);
|
geometryCache->renderSolidShapeInstance(args, batch, geometryShape, outColor, pipeline);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (args->_renderMode != render::Args::RenderMode::SHADOW_RENDER_MODE) {
|
if (RenderPipelines::bindMaterials(materials, batch, args->_renderMode, args->_enableTexturing)) {
|
||||||
RenderPipelines::bindMaterials(materials, batch, args->_enableTexturing);
|
|
||||||
args->_details._materialSwitches++;
|
args->_details._materialSwitches++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -791,7 +791,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
|
||||||
bool otherOverwrites = overwriteLocalData && !weOwnSimulation;
|
bool otherOverwrites = overwriteLocalData && !weOwnSimulation;
|
||||||
// calculate hasGrab once outside the lambda rather than calling it every time inside
|
// calculate hasGrab once outside the lambda rather than calling it every time inside
|
||||||
bool hasGrab = stillHasGrabAction();
|
bool hasGrab = stillHasGrabAction();
|
||||||
auto shouldUpdate = [this, lastEdited, otherOverwrites, filterRejection, hasGrab](quint64 updatedTimestamp, bool valueChanged) {
|
auto shouldUpdate = [lastEdited, otherOverwrites, filterRejection, hasGrab](quint64 updatedTimestamp, bool valueChanged) {
|
||||||
if (hasGrab) {
|
if (hasGrab) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -909,7 +909,6 @@ bool GLTFSerializer::buildGeometry(HFMModel& hfmModel, const hifi::URL& url) {
|
||||||
auto& node = _file.nodes[nodeIndex];
|
auto& node = _file.nodes[nodeIndex];
|
||||||
|
|
||||||
if (node.defined["mesh"]) {
|
if (node.defined["mesh"]) {
|
||||||
qCDebug(modelformat) << "node_transforms" << node.transforms;
|
|
||||||
foreach(auto &primitive, _file.meshes[node.mesh].primitives) {
|
foreach(auto &primitive, _file.meshes[node.mesh].primitives) {
|
||||||
hfmModel.meshes.append(HFMMesh());
|
hfmModel.meshes.append(HFMMesh());
|
||||||
HFMMesh& mesh = hfmModel.meshes[hfmModel.meshes.size() - 1];
|
HFMMesh& mesh = hfmModel.meshes[hfmModel.meshes.size() - 1];
|
||||||
|
@ -1276,7 +1275,6 @@ HFMTexture GLTFSerializer::getHFMTexture(const GLTFTexture& texture) {
|
||||||
|
|
||||||
QString fname = hifi::URL(url).fileName();
|
QString fname = hifi::URL(url).fileName();
|
||||||
hifi::URL textureUrl = _url.resolved(url);
|
hifi::URL textureUrl = _url.resolved(url);
|
||||||
qCDebug(modelformat) << "fname: " << fname;
|
|
||||||
fbxtex.name = fname;
|
fbxtex.name = fname;
|
||||||
fbxtex.filename = textureUrl.toEncoded();
|
fbxtex.filename = textureUrl.toEncoded();
|
||||||
|
|
||||||
|
@ -1370,10 +1368,7 @@ bool GLTFSerializer::readArray(const hifi::ByteArray& bin, int byteOffset, int c
|
||||||
blobstream.setByteOrder(QDataStream::LittleEndian);
|
blobstream.setByteOrder(QDataStream::LittleEndian);
|
||||||
blobstream.setVersion(QDataStream::Qt_5_9);
|
blobstream.setVersion(QDataStream::Qt_5_9);
|
||||||
blobstream.setFloatingPointPrecision(QDataStream::FloatingPointPrecision::SinglePrecision);
|
blobstream.setFloatingPointPrecision(QDataStream::FloatingPointPrecision::SinglePrecision);
|
||||||
|
blobstream.skipRawData(byteOffset);
|
||||||
qCDebug(modelformat) << "size1: " << count;
|
|
||||||
int dataskipped = blobstream.skipRawData(byteOffset);
|
|
||||||
qCDebug(modelformat) << "dataskipped: " << dataskipped;
|
|
||||||
|
|
||||||
int bufferCount = 0;
|
int bufferCount = 0;
|
||||||
switch (accessorType) {
|
switch (accessorType) {
|
||||||
|
@ -1467,6 +1462,38 @@ void GLTFSerializer::retriangulate(const QVector<int>& inIndices, const QVector<
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GLTFSerializer::glTFDebugDump() {
|
||||||
|
qCDebug(modelformat) << "---------------- Nodes ----------------";
|
||||||
|
for (GLTFNode node : _file.nodes) {
|
||||||
|
if (node.defined["mesh"]) {
|
||||||
|
qCDebug(modelformat) << "\n";
|
||||||
|
qCDebug(modelformat) << " node_transforms" << node.transforms;
|
||||||
|
qCDebug(modelformat) << "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
qCDebug(modelformat) << "---------------- Accessors ----------------";
|
||||||
|
for (GLTFAccessor accessor : _file.accessors) {
|
||||||
|
qCDebug(modelformat) << "\n";
|
||||||
|
qCDebug(modelformat) << "count: " << accessor.count;
|
||||||
|
qCDebug(modelformat) << "byteOffset: " << accessor.byteOffset;
|
||||||
|
qCDebug(modelformat) << "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
qCDebug(modelformat) << "---------------- Textures ----------------";
|
||||||
|
for (GLTFTexture texture : _file.textures) {
|
||||||
|
if (texture.defined["source"]) {
|
||||||
|
qCDebug(modelformat) << "\n";
|
||||||
|
QString url = _file.images[texture.source].uri;
|
||||||
|
QString fname = hifi::URL(url).fileName();
|
||||||
|
qCDebug(modelformat) << "fname: " << fname;
|
||||||
|
qCDebug(modelformat) << "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
qCDebug(modelformat) << "\n";
|
||||||
|
}
|
||||||
|
|
||||||
void GLTFSerializer::hfmDebugDump(const HFMModel& hfmModel) {
|
void GLTFSerializer::hfmDebugDump(const HFMModel& hfmModel) {
|
||||||
qCDebug(modelformat) << "---------------- hfmModel ----------------";
|
qCDebug(modelformat) << "---------------- hfmModel ----------------";
|
||||||
qCDebug(modelformat) << " hasSkeletonJoints =" << hfmModel.hasSkeletonJoints;
|
qCDebug(modelformat) << " hasSkeletonJoints =" << hfmModel.hasSkeletonJoints;
|
||||||
|
@ -1607,5 +1634,8 @@ void GLTFSerializer::hfmDebugDump(const HFMModel& hfmModel) {
|
||||||
qCDebug(modelformat) << "\n";
|
qCDebug(modelformat) << "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
qCDebug(modelformat) << "---------------- GLTF Model ----------------";
|
||||||
|
glTFDebugDump();
|
||||||
|
|
||||||
qCDebug(modelformat) << "\n";
|
qCDebug(modelformat) << "\n";
|
||||||
}
|
}
|
||||||
|
|
|
@ -784,6 +784,7 @@ private:
|
||||||
|
|
||||||
void setHFMMaterial(HFMMaterial& fbxmat, const GLTFMaterial& material);
|
void setHFMMaterial(HFMMaterial& fbxmat, const GLTFMaterial& material);
|
||||||
HFMTexture getHFMTexture(const GLTFTexture& texture);
|
HFMTexture getHFMTexture(const GLTFTexture& texture);
|
||||||
|
void glTFDebugDump();
|
||||||
void hfmDebugDump(const HFMModel& hfmModel);
|
void hfmDebugDump(const HFMModel& hfmModel);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
#define hifi_gpu_Frame_h
|
#define hifi_gpu_Frame_h
|
||||||
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
#include <queue>
|
||||||
|
|
||||||
#include "Forward.h"
|
#include "Forward.h"
|
||||||
#include "Batch.h"
|
#include "Batch.h"
|
||||||
|
@ -41,6 +42,8 @@ namespace gpu {
|
||||||
/// How to process the framebuffer when the frame dies. MUST BE THREAD SAFE
|
/// How to process the framebuffer when the frame dies. MUST BE THREAD SAFE
|
||||||
FramebufferRecycler framebufferRecycler;
|
FramebufferRecycler framebufferRecycler;
|
||||||
|
|
||||||
|
std::queue<std::tuple<std::function<void(const QImage&)>, float, bool>> snapshotOperators;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
friend class Deserializer;
|
friend class Deserializer;
|
||||||
|
|
||||||
|
|
|
@ -59,8 +59,8 @@ namespace scriptable {
|
||||||
* @property {string} occlusionMap
|
* @property {string} occlusionMap
|
||||||
* @property {string} lightmapMap
|
* @property {string} lightmapMap
|
||||||
* @property {string} scatteringMap
|
* @property {string} scatteringMap
|
||||||
* @property {string} texCoordTransform0
|
* @property {Mat4|string} texCoordTransform0
|
||||||
* @property {string} texCoordTransform1
|
* @property {Mat4|string} texCoordTransform1
|
||||||
* @property {string} lightmapParams
|
* @property {string} lightmapParams
|
||||||
* @property {string} materialParams
|
* @property {string} materialParams
|
||||||
* @property {boolean} defaultFallthrough
|
* @property {boolean} defaultFallthrough
|
||||||
|
@ -93,6 +93,7 @@ namespace scriptable {
|
||||||
QString occlusionMap;
|
QString occlusionMap;
|
||||||
QString lightmapMap;
|
QString lightmapMap;
|
||||||
QString scatteringMap;
|
QString scatteringMap;
|
||||||
|
std::array<glm::mat4, graphics::Material::NUM_TEXCOORD_TRANSFORMS> texCoordTransforms;
|
||||||
|
|
||||||
bool defaultFallthrough;
|
bool defaultFallthrough;
|
||||||
std::unordered_map<uint, bool> propertyFallthroughs; // not actually exposed to script
|
std::unordered_map<uint, bool> propertyFallthroughs; // not actually exposed to script
|
||||||
|
|
|
@ -470,9 +470,13 @@ namespace scriptable {
|
||||||
// These need to be implemented, but set the fallthrough for now
|
// These need to be implemented, but set the fallthrough for now
|
||||||
if (hasPropertyFallthroughs && material.propertyFallthroughs.at(graphics::Material::TEXCOORDTRANSFORM0)) {
|
if (hasPropertyFallthroughs && material.propertyFallthroughs.at(graphics::Material::TEXCOORDTRANSFORM0)) {
|
||||||
obj.setProperty("texCoordTransform0", FALLTHROUGH);
|
obj.setProperty("texCoordTransform0", FALLTHROUGH);
|
||||||
|
} else if (material.texCoordTransforms[0] != mat4()) {
|
||||||
|
obj.setProperty("texCoordTransform0", mat4toScriptValue(engine, material.texCoordTransforms[0]));
|
||||||
}
|
}
|
||||||
if (hasPropertyFallthroughs && material.propertyFallthroughs.at(graphics::Material::TEXCOORDTRANSFORM1)) {
|
if (hasPropertyFallthroughs && material.propertyFallthroughs.at(graphics::Material::TEXCOORDTRANSFORM1)) {
|
||||||
obj.setProperty("texCoordTransform1", FALLTHROUGH);
|
obj.setProperty("texCoordTransform1", FALLTHROUGH);
|
||||||
|
} else if (material.texCoordTransforms[1] != mat4()) {
|
||||||
|
obj.setProperty("texCoordTransform1", mat4toScriptValue(engine, material.texCoordTransforms[1]));
|
||||||
}
|
}
|
||||||
if (hasPropertyFallthroughs && material.propertyFallthroughs.at(graphics::Material::LIGHTMAP_PARAMS)) {
|
if (hasPropertyFallthroughs && material.propertyFallthroughs.at(graphics::Material::LIGHTMAP_PARAMS)) {
|
||||||
obj.setProperty("lightmapParams", FALLTHROUGH);
|
obj.setProperty("lightmapParams", FALLTHROUGH);
|
||||||
|
|
|
@ -119,6 +119,10 @@ scriptable::ScriptableMaterial::ScriptableMaterial(const graphics::MaterialPoint
|
||||||
if (map && map->getTextureSource()) {
|
if (map && map->getTextureSource()) {
|
||||||
scatteringMap = map->getTextureSource()->getUrl().toString();
|
scatteringMap = map->getTextureSource()->getUrl().toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < graphics::Material::NUM_TEXCOORD_TRANSFORMS; i++) {
|
||||||
|
texCoordTransforms[i] = material->getTexCoordTransform(i);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -73,6 +73,22 @@ bool Light::getCastShadows() const {
|
||||||
return _castShadows;
|
return _castShadows;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Light::setShadowsMaxDistance(const float maxDistance) {
|
||||||
|
_shadowsMaxDistance = std::max(0.0f, maxDistance);
|
||||||
|
}
|
||||||
|
|
||||||
|
float Light::getShadowsMaxDistance() const {
|
||||||
|
return _shadowsMaxDistance;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Light::setShadowsBiasScale(const float scale) {
|
||||||
|
_shadowsBiasScale = std::max(0.0f, scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
float Light::getShadowsBiasScale() const {
|
||||||
|
return _shadowsBiasScale;
|
||||||
|
}
|
||||||
|
|
||||||
void Light::setColor(const Color& color) {
|
void Light::setColor(const Color& color) {
|
||||||
_lightSchemaBuffer.edit().irradiance.color = color;
|
_lightSchemaBuffer.edit().irradiance.color = color;
|
||||||
updateLightRadius();
|
updateLightRadius();
|
||||||
|
|
|
@ -106,6 +106,12 @@ public:
|
||||||
void setCastShadows(const bool castShadows);
|
void setCastShadows(const bool castShadows);
|
||||||
bool getCastShadows() const;
|
bool getCastShadows() const;
|
||||||
|
|
||||||
|
void setShadowsMaxDistance(const float maxDistance);
|
||||||
|
float getShadowsMaxDistance() const;
|
||||||
|
|
||||||
|
void setShadowsBiasScale(const float scale);
|
||||||
|
float getShadowsBiasScale() const;
|
||||||
|
|
||||||
void setOrientation(const Quat& orientation);
|
void setOrientation(const Quat& orientation);
|
||||||
const glm::quat& getOrientation() const { return _transform.getRotation(); }
|
const glm::quat& getOrientation() const { return _transform.getRotation(); }
|
||||||
|
|
||||||
|
@ -192,10 +198,11 @@ protected:
|
||||||
Type _type { SUN };
|
Type _type { SUN };
|
||||||
float _spotCos { -1.0f }; // stored here to be able to reset the spot angle when turning the type spot on/off
|
float _spotCos { -1.0f }; // stored here to be able to reset the spot angle when turning the type spot on/off
|
||||||
|
|
||||||
void updateLightRadius();
|
float _shadowsMaxDistance{ 40.0f };
|
||||||
|
float _shadowsBiasScale{ 1.0f };
|
||||||
bool _castShadows{ false };
|
bool _castShadows{ false };
|
||||||
|
|
||||||
|
void updateLightRadius();
|
||||||
};
|
};
|
||||||
typedef std::shared_ptr< Light > LightPointer;
|
typedef std::shared_ptr< Light > LightPointer;
|
||||||
|
|
||||||
|
|
|
@ -324,6 +324,7 @@ public:
|
||||||
void setModel(const std::string& model) { _model = model; }
|
void setModel(const std::string& model) { _model = model; }
|
||||||
|
|
||||||
glm::mat4 getTexCoordTransform(uint i) const { return _texcoordTransforms[i]; }
|
glm::mat4 getTexCoordTransform(uint i) const { return _texcoordTransforms[i]; }
|
||||||
|
void setTexCoordTransform(uint i, const glm::mat4& mat4) { _texcoordTransforms[i] = mat4; }
|
||||||
glm::vec2 getLightmapParams() const { return _lightmapParams; }
|
glm::vec2 getLightmapParams() const { return _lightmapParams; }
|
||||||
glm::vec2 getMaterialParams() const { return _materialParams; }
|
glm::vec2 getMaterialParams() const { return _materialParams; }
|
||||||
|
|
||||||
|
|
|
@ -149,7 +149,6 @@ float fetchScatteringMap(vec2 uv) {
|
||||||
|
|
||||||
<@endfunc@>
|
<@endfunc@>
|
||||||
|
|
||||||
|
|
||||||
<@func fetchMaterialTexturesCoord0(matKey, texcoord0, albedo, roughness, normal, metallic, emissive, scattering)@>
|
<@func fetchMaterialTexturesCoord0(matKey, texcoord0, albedo, roughness, normal, metallic, emissive, scattering)@>
|
||||||
if (getTexMapArray()._materialParams.y != 1.0 && clamp(<$texcoord0$>, vec2(0.0), vec2(1.0)) != <$texcoord0$>) {
|
if (getTexMapArray()._materialParams.y != 1.0 && clamp(<$texcoord0$>, vec2(0.0), vec2(1.0)) != <$texcoord0$>) {
|
||||||
discard;
|
discard;
|
||||||
|
|
|
@ -177,6 +177,8 @@ std::pair<std::string, std::shared_ptr<NetworkMaterial>> NetworkMaterialResource
|
||||||
material->setModel(modelString);
|
material->setModel(modelString);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::array<glm::mat4, graphics::Material::NUM_TEXCOORD_TRANSFORMS> texcoordTransforms;
|
||||||
|
|
||||||
if (modelString == HIFI_PBR) {
|
if (modelString == HIFI_PBR) {
|
||||||
const QString FALLTHROUGH("fallthrough");
|
const QString FALLTHROUGH("fallthrough");
|
||||||
for (auto& key : materialJSON.keys()) {
|
for (auto& key : materialJSON.keys()) {
|
||||||
|
@ -372,8 +374,11 @@ std::pair<std::string, std::shared_ptr<NetworkMaterial>> NetworkMaterialResource
|
||||||
if (valueString == FALLTHROUGH) {
|
if (valueString == FALLTHROUGH) {
|
||||||
material->setPropertyDoesFallthrough(graphics::Material::ExtraFlagBit::TEXCOORDTRANSFORM0);
|
material->setPropertyDoesFallthrough(graphics::Material::ExtraFlagBit::TEXCOORDTRANSFORM0);
|
||||||
}
|
}
|
||||||
|
} else if (value.isObject()) {
|
||||||
|
auto valueVariant = value.toVariant();
|
||||||
|
glm::mat4 transform = mat4FromVariant(valueVariant);
|
||||||
|
texcoordTransforms[0] = transform;
|
||||||
}
|
}
|
||||||
// TODO: implement texCoordTransform0
|
|
||||||
} else if (key == "texCoordTransform1") {
|
} else if (key == "texCoordTransform1") {
|
||||||
auto value = materialJSON.value(key);
|
auto value = materialJSON.value(key);
|
||||||
if (value.isString()) {
|
if (value.isString()) {
|
||||||
|
@ -381,8 +386,11 @@ std::pair<std::string, std::shared_ptr<NetworkMaterial>> NetworkMaterialResource
|
||||||
if (valueString == FALLTHROUGH) {
|
if (valueString == FALLTHROUGH) {
|
||||||
material->setPropertyDoesFallthrough(graphics::Material::ExtraFlagBit::TEXCOORDTRANSFORM1);
|
material->setPropertyDoesFallthrough(graphics::Material::ExtraFlagBit::TEXCOORDTRANSFORM1);
|
||||||
}
|
}
|
||||||
|
} else if (value.isObject()) {
|
||||||
|
auto valueVariant = value.toVariant();
|
||||||
|
glm::mat4 transform = mat4FromVariant(valueVariant);
|
||||||
|
texcoordTransforms[1] = transform;
|
||||||
}
|
}
|
||||||
// TODO: implement texCoordTransform1
|
|
||||||
} else if (key == "lightmapParams") {
|
} else if (key == "lightmapParams") {
|
||||||
auto value = materialJSON.value(key);
|
auto value = materialJSON.value(key);
|
||||||
if (value.isString()) {
|
if (value.isString()) {
|
||||||
|
@ -409,6 +417,15 @@ std::pair<std::string, std::shared_ptr<NetworkMaterial>> NetworkMaterialResource
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Do this after the texture maps are defined, so it overrides the default transforms
|
||||||
|
for (int i = 0; i < graphics::Material::NUM_TEXCOORD_TRANSFORMS; i++) {
|
||||||
|
mat4 newTransform = texcoordTransforms[i];
|
||||||
|
if (newTransform != mat4() || newTransform != material->getTexCoordTransform(i)) {
|
||||||
|
material->setTexCoordTransform(i, newTransform);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return std::pair<std::string, std::shared_ptr<NetworkMaterial>>(name, material);
|
return std::pair<std::string, std::shared_ptr<NetworkMaterial>>(name, material);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -108,6 +108,7 @@ private:
|
||||||
|
|
||||||
using NetworkMaterialResourcePointer = QSharedPointer<NetworkMaterialResource>;
|
using NetworkMaterialResourcePointer = QSharedPointer<NetworkMaterialResource>;
|
||||||
using MaterialMapping = std::vector<std::pair<std::string, NetworkMaterialResourcePointer>>;
|
using MaterialMapping = std::vector<std::pair<std::string, NetworkMaterialResourcePointer>>;
|
||||||
|
Q_DECLARE_METATYPE(MaterialMapping)
|
||||||
|
|
||||||
class MaterialCache : public ResourceCache {
|
class MaterialCache : public ResourceCache {
|
||||||
public:
|
public:
|
||||||
|
|
|
@ -635,11 +635,9 @@ void NetworkTexture::makeLocalRequest() {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool NetworkTexture::handleFailedRequest(ResourceRequest::Result result) {
|
bool NetworkTexture::handleFailedRequest(ResourceRequest::Result result) {
|
||||||
if (_currentlyLoadingResourceType != ResourceType::KTX
|
if (_shouldFailOnRedirect && result == ResourceRequest::Result::RedirectFail) {
|
||||||
&& result == ResourceRequest::Result::RedirectFail) {
|
|
||||||
|
|
||||||
auto newPath = _request->getRelativePathUrl();
|
auto newPath = _request->getRelativePathUrl();
|
||||||
if (newPath.fileName().endsWith(".ktx")) {
|
if (newPath.fileName().toLower().endsWith(".ktx")) {
|
||||||
_currentlyLoadingResourceType = ResourceType::KTX;
|
_currentlyLoadingResourceType = ResourceType::KTX;
|
||||||
_activeUrl = newPath;
|
_activeUrl = newPath;
|
||||||
_shouldFailOnRedirect = false;
|
_shouldFailOnRedirect = false;
|
||||||
|
|
|
@ -52,7 +52,7 @@ std::vector<hifi::ByteArray> createMaterialList(const hfm::Mesh& mesh) {
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<draco::Mesh> createDracoMesh(const hfm::Mesh& mesh, const std::vector<glm::vec3>& normals, const std::vector<glm::vec3>& tangents, const std::vector<hifi::ByteArray>& materialList) {
|
std::unique_ptr<draco::Mesh> createDracoMesh(const hfm::Mesh& mesh, const std::vector<glm::vec3>& normals, const std::vector<glm::vec3>& tangents, const std::vector<hifi::ByteArray>& materialList) {
|
||||||
Q_ASSERT(normals.size() == 0 || normals.size() == mesh.vertices.size());
|
Q_ASSERT(normals.size() == 0 || (int)normals.size() == mesh.vertices.size());
|
||||||
Q_ASSERT(mesh.colors.size() == 0 || mesh.colors.size() == mesh.vertices.size());
|
Q_ASSERT(mesh.colors.size() == 0 || mesh.colors.size() == mesh.vertices.size());
|
||||||
Q_ASSERT(mesh.texCoords.size() == 0 || mesh.texCoords.size() == mesh.vertices.size());
|
Q_ASSERT(mesh.texCoords.size() == 0 || mesh.texCoords.size() == mesh.vertices.size());
|
||||||
|
|
||||||
|
|
|
@ -31,8 +31,6 @@
|
||||||
|
|
||||||
Q_LOGGING_CATEGORY(trace_resource_parse_geometry, "trace.resource.parse.geometry")
|
Q_LOGGING_CATEGORY(trace_resource_parse_geometry, "trace.resource.parse.geometry")
|
||||||
|
|
||||||
class GeometryReader;
|
|
||||||
|
|
||||||
class GeometryExtra {
|
class GeometryExtra {
|
||||||
public:
|
public:
|
||||||
const GeometryMappingPair& mapping;
|
const GeometryMappingPair& mapping;
|
||||||
|
@ -87,113 +85,6 @@ namespace std {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
QUrl resolveTextureBaseUrl(const QUrl& url, const QUrl& textureBaseUrl) {
|
|
||||||
return textureBaseUrl.isValid() ? textureBaseUrl : url;
|
|
||||||
}
|
|
||||||
|
|
||||||
class GeometryMappingResource : public GeometryResource {
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
GeometryMappingResource(const QUrl& url) : GeometryResource(url) {};
|
|
||||||
|
|
||||||
QString getType() const override { return "GeometryMapping"; }
|
|
||||||
|
|
||||||
virtual void downloadFinished(const QByteArray& data) override;
|
|
||||||
|
|
||||||
private slots:
|
|
||||||
void onGeometryMappingLoaded(bool success);
|
|
||||||
|
|
||||||
private:
|
|
||||||
GeometryResource::Pointer _geometryResource;
|
|
||||||
QMetaObject::Connection _connection;
|
|
||||||
};
|
|
||||||
|
|
||||||
void GeometryMappingResource::downloadFinished(const QByteArray& data) {
|
|
||||||
PROFILE_ASYNC_BEGIN(resource_parse_geometry, "GeometryMappingResource::downloadFinished", _url.toString(),
|
|
||||||
{ { "url", _url.toString() } });
|
|
||||||
|
|
||||||
// store parsed contents of FST file
|
|
||||||
_mapping = FSTReader::readMapping(data);
|
|
||||||
|
|
||||||
QString filename = _mapping.value("filename").toString();
|
|
||||||
|
|
||||||
if (filename.isNull()) {
|
|
||||||
finishedLoading(false);
|
|
||||||
} else {
|
|
||||||
const QString baseURL = _mapping.value("baseURL").toString();
|
|
||||||
const QUrl base = _effectiveBaseURL.resolved(baseURL);
|
|
||||||
QUrl url = base.resolved(filename);
|
|
||||||
|
|
||||||
QString texdir = _mapping.value(TEXDIR_FIELD).toString();
|
|
||||||
if (!texdir.isNull()) {
|
|
||||||
if (!texdir.endsWith('/')) {
|
|
||||||
texdir += '/';
|
|
||||||
}
|
|
||||||
_textureBaseUrl = resolveTextureBaseUrl(url, base.resolved(texdir));
|
|
||||||
} else {
|
|
||||||
_textureBaseUrl = url.resolved(QUrl("."));
|
|
||||||
}
|
|
||||||
|
|
||||||
auto scripts = FSTReader::getScripts(base, _mapping);
|
|
||||||
if (scripts.size() > 0) {
|
|
||||||
_mapping.remove(SCRIPT_FIELD);
|
|
||||||
for (auto &scriptPath : scripts) {
|
|
||||||
_mapping.insertMulti(SCRIPT_FIELD, scriptPath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto animGraphVariant = _mapping.value("animGraphUrl");
|
|
||||||
|
|
||||||
if (animGraphVariant.isValid()) {
|
|
||||||
QUrl fstUrl(animGraphVariant.toString());
|
|
||||||
if (fstUrl.isValid()) {
|
|
||||||
_animGraphOverrideUrl = base.resolved(fstUrl);
|
|
||||||
} else {
|
|
||||||
_animGraphOverrideUrl = QUrl();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
_animGraphOverrideUrl = QUrl();
|
|
||||||
}
|
|
||||||
|
|
||||||
auto modelCache = DependencyManager::get<ModelCache>();
|
|
||||||
GeometryExtra extra { GeometryMappingPair(base, _mapping), _textureBaseUrl, false };
|
|
||||||
|
|
||||||
// Get the raw GeometryResource
|
|
||||||
_geometryResource = modelCache->getResource(url, QUrl(), &extra, std::hash<GeometryExtra>()(extra)).staticCast<GeometryResource>();
|
|
||||||
// Avoid caching nested resources - their references will be held by the parent
|
|
||||||
_geometryResource->_isCacheable = false;
|
|
||||||
|
|
||||||
if (_geometryResource->isLoaded()) {
|
|
||||||
onGeometryMappingLoaded(!_geometryResource->getURL().isEmpty());
|
|
||||||
} else {
|
|
||||||
if (_connection) {
|
|
||||||
disconnect(_connection);
|
|
||||||
}
|
|
||||||
|
|
||||||
_connection = connect(_geometryResource.data(), &Resource::finished,
|
|
||||||
this, &GeometryMappingResource::onGeometryMappingLoaded);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void GeometryMappingResource::onGeometryMappingLoaded(bool success) {
|
|
||||||
if (success && _geometryResource) {
|
|
||||||
_hfmModel = _geometryResource->_hfmModel;
|
|
||||||
_materialMapping = _geometryResource->_materialMapping;
|
|
||||||
_meshParts = _geometryResource->_meshParts;
|
|
||||||
_meshes = _geometryResource->_meshes;
|
|
||||||
_materials = _geometryResource->_materials;
|
|
||||||
|
|
||||||
// Avoid holding onto extra references
|
|
||||||
_geometryResource.reset();
|
|
||||||
// Make sure connection will not trigger again
|
|
||||||
disconnect(_connection); // FIXME Should not have to do this
|
|
||||||
}
|
|
||||||
|
|
||||||
PROFILE_ASYNC_END(resource_parse_geometry, "GeometryMappingResource::downloadFinished", _url.toString());
|
|
||||||
finishedLoading(success);
|
|
||||||
}
|
|
||||||
|
|
||||||
class GeometryReader : public QRunnable {
|
class GeometryReader : public QRunnable {
|
||||||
public:
|
public:
|
||||||
GeometryReader(const ModelLoader& modelLoader, QWeakPointer<Resource>& resource, const QUrl& url, const GeometryMappingPair& mapping,
|
GeometryReader(const ModelLoader& modelLoader, QWeakPointer<Resource>& resource, const QUrl& url, const GeometryMappingPair& mapping,
|
||||||
|
@ -282,8 +173,16 @@ void GeometryReader::run() {
|
||||||
hfmModel->scripts.push_back(script.toString());
|
hfmModel->scripts.push_back(script.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Do processing on the model
|
||||||
|
baker::Baker modelBaker(hfmModel, _mapping.second, _mapping.first);
|
||||||
|
modelBaker.run();
|
||||||
|
|
||||||
|
auto processedHFMModel = modelBaker.getHFMModel();
|
||||||
|
auto materialMapping = modelBaker.getMaterialMapping();
|
||||||
|
|
||||||
QMetaObject::invokeMethod(resource.data(), "setGeometryDefinition",
|
QMetaObject::invokeMethod(resource.data(), "setGeometryDefinition",
|
||||||
Q_ARG(HFMModel::Pointer, hfmModel), Q_ARG(GeometryMappingPair, _mapping));
|
Q_ARG(HFMModel::Pointer, processedHFMModel), Q_ARG(MaterialMapping, materialMapping));
|
||||||
} catch (const std::exception&) {
|
} catch (const std::exception&) {
|
||||||
auto resource = _resource.toStrongRef();
|
auto resource = _resource.toStrongRef();
|
||||||
if (resource) {
|
if (resource) {
|
||||||
|
@ -300,60 +199,133 @@ void GeometryReader::run() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class GeometryDefinitionResource : public GeometryResource {
|
QUrl resolveTextureBaseUrl(const QUrl& url, const QUrl& textureBaseUrl) {
|
||||||
Q_OBJECT
|
return textureBaseUrl.isValid() ? textureBaseUrl : url;
|
||||||
public:
|
}
|
||||||
GeometryDefinitionResource(const ModelLoader& modelLoader, const QUrl& url) : GeometryResource(url), _modelLoader(modelLoader) {}
|
|
||||||
GeometryDefinitionResource(const GeometryDefinitionResource& other) :
|
|
||||||
GeometryResource(other),
|
|
||||||
_modelLoader(other._modelLoader),
|
|
||||||
_mapping(other._mapping),
|
|
||||||
_combineParts(other._combineParts) {}
|
|
||||||
|
|
||||||
QString getType() const override { return "GeometryDefinition"; }
|
GeometryResource::GeometryResource(const GeometryResource& other) :
|
||||||
|
Resource(other),
|
||||||
|
Geometry(other),
|
||||||
|
_modelLoader(other._modelLoader),
|
||||||
|
_mappingPair(other._mappingPair),
|
||||||
|
_textureBaseURL(other._textureBaseURL),
|
||||||
|
_combineParts(other._combineParts),
|
||||||
|
_isCacheable(other._isCacheable)
|
||||||
|
{
|
||||||
|
if (other._geometryResource) {
|
||||||
|
_startedLoading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
virtual void downloadFinished(const QByteArray& data) override;
|
void GeometryResource::downloadFinished(const QByteArray& data) {
|
||||||
|
if (_effectiveBaseURL.fileName().toLower().endsWith(".fst")) {
|
||||||
|
PROFILE_ASYNC_BEGIN(resource_parse_geometry, "GeometryResource::downloadFinished", _url.toString(), { { "url", _url.toString() } });
|
||||||
|
|
||||||
void setExtra(void* extra) override;
|
// store parsed contents of FST file
|
||||||
|
_mapping = FSTReader::readMapping(data);
|
||||||
|
|
||||||
protected:
|
QString filename = _mapping.value("filename").toString();
|
||||||
Q_INVOKABLE void setGeometryDefinition(HFMModel::Pointer hfmModel, const GeometryMappingPair& mapping);
|
|
||||||
|
|
||||||
private:
|
if (filename.isNull()) {
|
||||||
ModelLoader _modelLoader;
|
finishedLoading(false);
|
||||||
GeometryMappingPair _mapping;
|
} else {
|
||||||
bool _combineParts;
|
const QString baseURL = _mapping.value("baseURL").toString();
|
||||||
};
|
const QUrl base = _effectiveBaseURL.resolved(baseURL);
|
||||||
|
QUrl url = base.resolved(filename);
|
||||||
|
|
||||||
void GeometryDefinitionResource::setExtra(void* extra) {
|
QString texdir = _mapping.value(TEXDIR_FIELD).toString();
|
||||||
|
if (!texdir.isNull()) {
|
||||||
|
if (!texdir.endsWith('/')) {
|
||||||
|
texdir += '/';
|
||||||
|
}
|
||||||
|
_textureBaseURL = resolveTextureBaseUrl(url, base.resolved(texdir));
|
||||||
|
} else {
|
||||||
|
_textureBaseURL = url.resolved(QUrl("."));
|
||||||
|
}
|
||||||
|
|
||||||
|
auto scripts = FSTReader::getScripts(base, _mapping);
|
||||||
|
if (scripts.size() > 0) {
|
||||||
|
_mapping.remove(SCRIPT_FIELD);
|
||||||
|
for (auto &scriptPath : scripts) {
|
||||||
|
_mapping.insertMulti(SCRIPT_FIELD, scriptPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto animGraphVariant = _mapping.value("animGraphUrl");
|
||||||
|
|
||||||
|
if (animGraphVariant.isValid()) {
|
||||||
|
QUrl fstUrl(animGraphVariant.toString());
|
||||||
|
if (fstUrl.isValid()) {
|
||||||
|
_animGraphOverrideUrl = base.resolved(fstUrl);
|
||||||
|
} else {
|
||||||
|
_animGraphOverrideUrl = QUrl();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_animGraphOverrideUrl = QUrl();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto modelCache = DependencyManager::get<ModelCache>();
|
||||||
|
GeometryExtra extra { GeometryMappingPair(base, _mapping), _textureBaseURL, false };
|
||||||
|
|
||||||
|
// Get the raw GeometryResource
|
||||||
|
_geometryResource = modelCache->getResource(url, QUrl(), &extra, std::hash<GeometryExtra>()(extra)).staticCast<GeometryResource>();
|
||||||
|
// Avoid caching nested resources - their references will be held by the parent
|
||||||
|
_geometryResource->_isCacheable = false;
|
||||||
|
|
||||||
|
if (_geometryResource->isLoaded()) {
|
||||||
|
onGeometryMappingLoaded(!_geometryResource->getURL().isEmpty());
|
||||||
|
} else {
|
||||||
|
if (_connection) {
|
||||||
|
disconnect(_connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
_connection = connect(_geometryResource.data(), &Resource::finished, this, &GeometryResource::onGeometryMappingLoaded);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (_url != _effectiveBaseURL) {
|
||||||
|
_url = _effectiveBaseURL;
|
||||||
|
_textureBaseURL = _effectiveBaseURL;
|
||||||
|
}
|
||||||
|
QThreadPool::globalInstance()->start(new GeometryReader(_modelLoader, _self, _effectiveBaseURL, _mappingPair, data, _combineParts, _request->getWebMediaType()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GeometryResource::onGeometryMappingLoaded(bool success) {
|
||||||
|
if (success && _geometryResource) {
|
||||||
|
_hfmModel = _geometryResource->_hfmModel;
|
||||||
|
_materialMapping = _geometryResource->_materialMapping;
|
||||||
|
_meshParts = _geometryResource->_meshParts;
|
||||||
|
_meshes = _geometryResource->_meshes;
|
||||||
|
_materials = _geometryResource->_materials;
|
||||||
|
|
||||||
|
// Avoid holding onto extra references
|
||||||
|
_geometryResource.reset();
|
||||||
|
// Make sure connection will not trigger again
|
||||||
|
disconnect(_connection); // FIXME Should not have to do this
|
||||||
|
}
|
||||||
|
|
||||||
|
PROFILE_ASYNC_END(resource_parse_geometry, "GeometryResource::downloadFinished", _url.toString());
|
||||||
|
finishedLoading(success);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GeometryResource::setExtra(void* extra) {
|
||||||
const GeometryExtra* geometryExtra = static_cast<const GeometryExtra*>(extra);
|
const GeometryExtra* geometryExtra = static_cast<const GeometryExtra*>(extra);
|
||||||
_mapping = geometryExtra ? geometryExtra->mapping : GeometryMappingPair(QUrl(), QVariantHash());
|
_mappingPair = geometryExtra ? geometryExtra->mapping : GeometryMappingPair(QUrl(), QVariantHash());
|
||||||
_textureBaseUrl = geometryExtra ? resolveTextureBaseUrl(_url, geometryExtra->textureBaseUrl) : QUrl();
|
_textureBaseURL = geometryExtra ? resolveTextureBaseUrl(_url, geometryExtra->textureBaseUrl) : QUrl();
|
||||||
_combineParts = geometryExtra ? geometryExtra->combineParts : true;
|
_combineParts = geometryExtra ? geometryExtra->combineParts : true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void GeometryDefinitionResource::downloadFinished(const QByteArray& data) {
|
void GeometryResource::setGeometryDefinition(HFMModel::Pointer hfmModel, const MaterialMapping& materialMapping) {
|
||||||
if (_url != _effectiveBaseURL) {
|
|
||||||
_url = _effectiveBaseURL;
|
|
||||||
_textureBaseUrl = _effectiveBaseURL;
|
|
||||||
}
|
|
||||||
QThreadPool::globalInstance()->start(new GeometryReader(_modelLoader, _self, _effectiveBaseURL, _mapping, data, _combineParts, _request->getWebMediaType()));
|
|
||||||
}
|
|
||||||
|
|
||||||
void GeometryDefinitionResource::setGeometryDefinition(HFMModel::Pointer hfmModel, const GeometryMappingPair& mapping) {
|
|
||||||
// Do processing on the model
|
|
||||||
baker::Baker modelBaker(hfmModel, mapping.second, mapping.first);
|
|
||||||
modelBaker.run();
|
|
||||||
|
|
||||||
// Assume ownership of the processed HFMModel
|
// Assume ownership of the processed HFMModel
|
||||||
_hfmModel = modelBaker.getHFMModel();
|
_hfmModel = hfmModel;
|
||||||
_materialMapping = modelBaker.getMaterialMapping();
|
_materialMapping = materialMapping;
|
||||||
|
|
||||||
// Copy materials
|
// Copy materials
|
||||||
QHash<QString, size_t> materialIDAtlas;
|
QHash<QString, size_t> materialIDAtlas;
|
||||||
for (const HFMMaterial& material : _hfmModel->materials) {
|
for (const HFMMaterial& material : _hfmModel->materials) {
|
||||||
materialIDAtlas[material.materialID] = _materials.size();
|
materialIDAtlas[material.materialID] = _materials.size();
|
||||||
_materials.push_back(std::make_shared<NetworkMaterial>(material, _textureBaseUrl));
|
_materials.push_back(std::make_shared<NetworkMaterial>(material, _textureBaseURL));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<GeometryMeshes> meshes = std::make_shared<GeometryMeshes>();
|
std::shared_ptr<GeometryMeshes> meshes = std::make_shared<GeometryMeshes>();
|
||||||
|
@ -376,6 +348,23 @@ void GeometryDefinitionResource::setGeometryDefinition(HFMModel::Pointer hfmMode
|
||||||
finishedLoading(true);
|
finishedLoading(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GeometryResource::deleter() {
|
||||||
|
resetTextures();
|
||||||
|
Resource::deleter();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GeometryResource::setTextures() {
|
||||||
|
if (_hfmModel) {
|
||||||
|
for (const HFMMaterial& material : _hfmModel->materials) {
|
||||||
|
_materials.push_back(std::make_shared<NetworkMaterial>(material, _textureBaseURL));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GeometryResource::resetTextures() {
|
||||||
|
_materials.clear();
|
||||||
|
}
|
||||||
|
|
||||||
ModelCache::ModelCache() {
|
ModelCache::ModelCache() {
|
||||||
const qint64 GEOMETRY_DEFAULT_UNUSED_MAX_SIZE = DEFAULT_UNUSED_MAX_SIZE;
|
const qint64 GEOMETRY_DEFAULT_UNUSED_MAX_SIZE = DEFAULT_UNUSED_MAX_SIZE;
|
||||||
setUnusedResourceCacheSize(GEOMETRY_DEFAULT_UNUSED_MAX_SIZE);
|
setUnusedResourceCacheSize(GEOMETRY_DEFAULT_UNUSED_MAX_SIZE);
|
||||||
|
@ -388,26 +377,14 @@ ModelCache::ModelCache() {
|
||||||
}
|
}
|
||||||
|
|
||||||
QSharedPointer<Resource> ModelCache::createResource(const QUrl& url) {
|
QSharedPointer<Resource> ModelCache::createResource(const QUrl& url) {
|
||||||
Resource* resource = nullptr;
|
return QSharedPointer<Resource>(new GeometryResource(url, _modelLoader), &GeometryResource::deleter);
|
||||||
if (url.path().toLower().endsWith(".fst")) {
|
|
||||||
resource = new GeometryMappingResource(url);
|
|
||||||
} else {
|
|
||||||
resource = new GeometryDefinitionResource(_modelLoader, url);
|
|
||||||
}
|
|
||||||
|
|
||||||
return QSharedPointer<Resource>(resource, &Resource::deleter);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QSharedPointer<Resource> ModelCache::createResourceCopy(const QSharedPointer<Resource>& resource) {
|
QSharedPointer<Resource> ModelCache::createResourceCopy(const QSharedPointer<Resource>& resource) {
|
||||||
if (resource->getURL().path().toLower().endsWith(".fst")) {
|
return QSharedPointer<Resource>(new GeometryResource(*resource.staticCast<GeometryResource>()), &GeometryResource::deleter);
|
||||||
return QSharedPointer<Resource>(new GeometryMappingResource(*resource.staticCast<GeometryMappingResource>()), &Resource::deleter);
|
|
||||||
} else {
|
|
||||||
return QSharedPointer<Resource>(new GeometryDefinitionResource(*resource.staticCast<GeometryDefinitionResource>()), &Resource::deleter);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
GeometryResource::Pointer ModelCache::getGeometryResource(const QUrl& url,
|
GeometryResource::Pointer ModelCache::getGeometryResource(const QUrl& url, const GeometryMappingPair& mapping, const QUrl& textureBaseUrl) {
|
||||||
const GeometryMappingPair& mapping, const QUrl& textureBaseUrl) {
|
|
||||||
bool combineParts = true;
|
bool combineParts = true;
|
||||||
GeometryExtra geometryExtra = { mapping, textureBaseUrl, combineParts };
|
GeometryExtra geometryExtra = { mapping, textureBaseUrl, combineParts };
|
||||||
GeometryResource::Pointer resource = getResource(url, QUrl(), &geometryExtra, std::hash<GeometryExtra>()(geometryExtra)).staticCast<GeometryResource>();
|
GeometryResource::Pointer resource = getResource(url, QUrl(), &geometryExtra, std::hash<GeometryExtra>()(geometryExtra)).staticCast<GeometryResource>();
|
||||||
|
@ -498,6 +475,20 @@ bool Geometry::areTexturesLoaded() const {
|
||||||
material->checkResetOpacityMap();
|
material->checkResetOpacityMap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (auto& materialMapping : _materialMapping) {
|
||||||
|
if (materialMapping.second) {
|
||||||
|
for (auto& materialPair : materialMapping.second->parsedMaterials.networkMaterials) {
|
||||||
|
if (materialPair.second) {
|
||||||
|
if (materialPair.second->isMissingTexture()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
materialPair.second->checkResetOpacityMap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_areTexturesLoaded = true;
|
_areTexturesLoaded = true;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
@ -513,23 +504,6 @@ const std::shared_ptr<NetworkMaterial> Geometry::getShapeMaterial(int partID) co
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void GeometryResource::deleter() {
|
|
||||||
resetTextures();
|
|
||||||
Resource::deleter();
|
|
||||||
}
|
|
||||||
|
|
||||||
void GeometryResource::setTextures() {
|
|
||||||
if (_hfmModel) {
|
|
||||||
for (const HFMMaterial& material : _hfmModel->materials) {
|
|
||||||
_materials.push_back(std::make_shared<NetworkMaterial>(material, _textureBaseUrl));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void GeometryResource::resetTextures() {
|
|
||||||
_materials.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
void GeometryResourceWatcher::startWatching() {
|
void GeometryResourceWatcher::startWatching() {
|
||||||
connect(_resource.data(), &Resource::finished, this, &GeometryResourceWatcher::resourceFinished);
|
connect(_resource.data(), &Resource::finished, this, &GeometryResourceWatcher::resourceFinished);
|
||||||
connect(_resource.data(), &Resource::onRefresh, this, &GeometryResourceWatcher::resourceRefreshed);
|
connect(_resource.data(), &Resource::onRefresh, this, &GeometryResourceWatcher::resourceRefreshed);
|
||||||
|
|
|
@ -24,8 +24,6 @@
|
||||||
|
|
||||||
class MeshPart;
|
class MeshPart;
|
||||||
|
|
||||||
class GeometryMappingResource;
|
|
||||||
|
|
||||||
using GeometryMappingPair = std::pair<QUrl, QVariantHash>;
|
using GeometryMappingPair = std::pair<QUrl, QVariantHash>;
|
||||||
Q_DECLARE_METATYPE(GeometryMappingPair)
|
Q_DECLARE_METATYPE(GeometryMappingPair)
|
||||||
|
|
||||||
|
@ -60,8 +58,6 @@ public:
|
||||||
const QVariantHash& getMapping() const { return _mapping; }
|
const QVariantHash& getMapping() const { return _mapping; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
friend class GeometryMappingResource;
|
|
||||||
|
|
||||||
// Shared across all geometries, constant throughout lifetime
|
// Shared across all geometries, constant throughout lifetime
|
||||||
std::shared_ptr<const HFMModel> _hfmModel;
|
std::shared_ptr<const HFMModel> _hfmModel;
|
||||||
MaterialMapping _materialMapping;
|
MaterialMapping _materialMapping;
|
||||||
|
@ -80,23 +76,29 @@ private:
|
||||||
|
|
||||||
/// A geometry loaded from the network.
|
/// A geometry loaded from the network.
|
||||||
class GeometryResource : public Resource, public Geometry {
|
class GeometryResource : public Resource, public Geometry {
|
||||||
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
using Pointer = QSharedPointer<GeometryResource>;
|
using Pointer = QSharedPointer<GeometryResource>;
|
||||||
|
|
||||||
GeometryResource(const QUrl& url) : Resource(url) {}
|
GeometryResource(const QUrl& url, const ModelLoader& modelLoader) : Resource(url), _modelLoader(modelLoader) {}
|
||||||
GeometryResource(const GeometryResource& other) :
|
GeometryResource(const GeometryResource& other);
|
||||||
Resource(other),
|
|
||||||
Geometry(other),
|
|
||||||
_textureBaseUrl(other._textureBaseUrl),
|
|
||||||
_isCacheable(other._isCacheable) {}
|
|
||||||
|
|
||||||
virtual bool areTexturesLoaded() const override { return isLoaded() && Geometry::areTexturesLoaded(); }
|
QString getType() const override { return "Geometry"; }
|
||||||
|
|
||||||
virtual void deleter() override;
|
virtual void deleter() override;
|
||||||
|
|
||||||
|
virtual void downloadFinished(const QByteArray& data) override;
|
||||||
|
void setExtra(void* extra) override;
|
||||||
|
|
||||||
|
virtual bool areTexturesLoaded() const override { return isLoaded() && Geometry::areTexturesLoaded(); }
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void onGeometryMappingLoaded(bool success);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
friend class ModelCache;
|
friend class ModelCache;
|
||||||
friend class GeometryMappingResource;
|
|
||||||
|
Q_INVOKABLE void setGeometryDefinition(HFMModel::Pointer hfmModel, const MaterialMapping& materialMapping);
|
||||||
|
|
||||||
// Geometries may not hold onto textures while cached - that is for the texture cache
|
// Geometries may not hold onto textures while cached - that is for the texture cache
|
||||||
// Instead, these methods clear and reset textures from the geometry when caching/loading
|
// Instead, these methods clear and reset textures from the geometry when caching/loading
|
||||||
|
@ -104,10 +106,18 @@ protected:
|
||||||
void setTextures();
|
void setTextures();
|
||||||
void resetTextures();
|
void resetTextures();
|
||||||
|
|
||||||
QUrl _textureBaseUrl;
|
|
||||||
|
|
||||||
virtual bool isCacheable() const override { return _loaded && _isCacheable; }
|
virtual bool isCacheable() const override { return _loaded && _isCacheable; }
|
||||||
bool _isCacheable { true };
|
|
||||||
|
private:
|
||||||
|
ModelLoader _modelLoader;
|
||||||
|
GeometryMappingPair _mappingPair;
|
||||||
|
QUrl _textureBaseURL;
|
||||||
|
bool _combineParts;
|
||||||
|
|
||||||
|
GeometryResource::Pointer _geometryResource;
|
||||||
|
QMetaObject::Connection _connection;
|
||||||
|
|
||||||
|
bool _isCacheable{ true };
|
||||||
};
|
};
|
||||||
|
|
||||||
class GeometryResourceWatcher : public QObject {
|
class GeometryResourceWatcher : public QObject {
|
||||||
|
@ -158,7 +168,7 @@ public:
|
||||||
const QUrl& textureBaseUrl = QUrl());
|
const QUrl& textureBaseUrl = QUrl());
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
friend class GeometryMappingResource;
|
friend class GeometryResource;
|
||||||
|
|
||||||
virtual QSharedPointer<Resource> createResource(const QUrl& url) override;
|
virtual QSharedPointer<Resource> createResource(const QUrl& url) override;
|
||||||
QSharedPointer<Resource> createResourceCopy(const QSharedPointer<Resource>& resource) override;
|
QSharedPointer<Resource> createResourceCopy(const QSharedPointer<Resource>& resource) override;
|
||||||
|
|
|
@ -576,6 +576,7 @@ Resource::Resource(const Resource& other) :
|
||||||
|
|
||||||
Resource::Resource(const QUrl& url) :
|
Resource::Resource(const QUrl& url) :
|
||||||
_url(url),
|
_url(url),
|
||||||
|
_effectiveBaseURL(url),
|
||||||
_activeUrl(url),
|
_activeUrl(url),
|
||||||
_requestID(++requestID) {
|
_requestID(++requestID) {
|
||||||
init();
|
init();
|
||||||
|
|
|
@ -28,7 +28,7 @@
|
||||||
|
|
||||||
static AAssetManager* ASSET_MANAGER = nullptr;
|
static AAssetManager* ASSET_MANAGER = nullptr;
|
||||||
|
|
||||||
#define USE_BLIT_PRESENT 0
|
#define USE_BLIT_PRESENT 1
|
||||||
|
|
||||||
#if !USE_BLIT_PRESENT
|
#if !USE_BLIT_PRESENT
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,7 @@
|
||||||
#include <SimpleMovingAverage.h>
|
#include <SimpleMovingAverage.h>
|
||||||
#include <gpu/Forward.h>
|
#include <gpu/Forward.h>
|
||||||
#include "Plugin.h"
|
#include "Plugin.h"
|
||||||
|
#include "StencilMaskMode.h"
|
||||||
|
|
||||||
class QOpenGLFramebufferObject;
|
class QOpenGLFramebufferObject;
|
||||||
|
|
||||||
|
@ -172,10 +173,6 @@ public:
|
||||||
return QRect(0, 0, recommendedSize.x, recommendedSize.y);
|
return QRect(0, 0, recommendedSize.x, recommendedSize.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch the most recently displayed image as a QImage
|
|
||||||
virtual QImage getScreenshot(float aspectRatio = 0.0f) const = 0;
|
|
||||||
virtual QImage getSecondaryCameraScreenshot() const = 0;
|
|
||||||
|
|
||||||
// will query the underlying hmd api to compute the most recent head pose
|
// will query the underlying hmd api to compute the most recent head pose
|
||||||
virtual bool beginFrameRender(uint32_t frameIndex) { return true; }
|
virtual bool beginFrameRender(uint32_t frameIndex) { return true; }
|
||||||
|
|
||||||
|
@ -221,6 +218,10 @@ public:
|
||||||
// for updating plugin-related commands. Mimics the input plugin.
|
// for updating plugin-related commands. Mimics the input plugin.
|
||||||
virtual void pluginUpdate() = 0;
|
virtual void pluginUpdate() = 0;
|
||||||
|
|
||||||
|
virtual StencilMaskMode getStencilMaskMode() const { return StencilMaskMode::NONE; }
|
||||||
|
using StencilMaskMeshOperator = std::function<void(gpu::Batch&)>;
|
||||||
|
virtual StencilMaskMeshOperator getStencilMaskMeshOperator() { return nullptr; }
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void recommendedFramebufferSizeChanged(const QSize& size);
|
void recommendedFramebufferSizeChanged(const QSize& size);
|
||||||
void resetSensorsRequested();
|
void resetSensorsRequested();
|
||||||
|
|
|
@ -120,7 +120,7 @@ float LightStage::Shadow::Cascade::computeFarDistance(const ViewFrustum& viewFru
|
||||||
return far;
|
return far;
|
||||||
}
|
}
|
||||||
|
|
||||||
LightStage::Shadow::Shadow(graphics::LightPointer light, float maxDistance, unsigned int cascadeCount) :
|
LightStage::Shadow::Shadow(graphics::LightPointer light, unsigned int cascadeCount) :
|
||||||
_light{ light } {
|
_light{ light } {
|
||||||
cascadeCount = std::min(cascadeCount, (unsigned int)SHADOW_CASCADE_MAX_COUNT);
|
cascadeCount = std::min(cascadeCount, (unsigned int)SHADOW_CASCADE_MAX_COUNT);
|
||||||
Schema schema;
|
Schema schema;
|
||||||
|
@ -149,70 +149,79 @@ LightStage::Shadow::Shadow(graphics::LightPointer light, float maxDistance, unsi
|
||||||
cascade.framebuffer->setDepthBuffer(map, depthFormat, cascadeIndex);
|
cascade.framebuffer->setDepthBuffer(map, depthFormat, cascadeIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
setMaxDistance(maxDistance);
|
if (light) {
|
||||||
|
setMaxDistance(light->getShadowsMaxDistance());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void LightStage::Shadow::setLight(graphics::LightPointer light) {
|
void LightStage::Shadow::setLight(graphics::LightPointer light) {
|
||||||
_light = light;
|
_light = light;
|
||||||
|
if (light) {
|
||||||
|
setMaxDistance(light->getShadowsMaxDistance());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void LightStage::Shadow::setMaxDistance(float value) {
|
void LightStage::Shadow::setMaxDistance(float value) {
|
||||||
// This overlaping factor isn't really used directly for blending of shadow cascades. It
|
static const auto MINIMUM_MAXDISTANCE = 1e-3f;
|
||||||
// just there to be sure the cascades do overlap. The blending width used is relative
|
|
||||||
// to the UV space and is set in the Schema with invCascadeBlendWidth.
|
|
||||||
static const auto OVERLAP_FACTOR = 1.0f / 5.0f;
|
|
||||||
|
|
||||||
_maxDistance = std::max(0.0f, value);
|
value = std::max(MINIMUM_MAXDISTANCE, value);
|
||||||
|
if (value != _maxDistance) {
|
||||||
|
// This overlaping factor isn't really used directly for blending of shadow cascades. It's
|
||||||
|
// just there to be sure the cascades do overlap. The blending width used is relative
|
||||||
|
// to the UV space and is set in the Schema with invCascadeBlendWidth.
|
||||||
|
static const auto OVERLAP_FACTOR = 1.0f / 5.0f;
|
||||||
|
|
||||||
if (_cascades.size() == 1) {
|
_maxDistance = value;
|
||||||
_cascades.front().setMinDistance(0.0f);
|
|
||||||
_cascades.front().setMaxDistance(_maxDistance);
|
|
||||||
} else {
|
|
||||||
// Distribute the cascades along that distance
|
|
||||||
// TODO : these parameters should be exposed to the user as part of the light entity parameters, no?
|
|
||||||
static const auto LOW_MAX_DISTANCE = 2.0f;
|
|
||||||
static const auto MAX_RESOLUTION_LOSS = 0.6f; // Between 0 and 1, 0 giving tighter distributions
|
|
||||||
|
|
||||||
// The max cascade distance is computed by multiplying the previous cascade's max distance by a certain
|
if (_cascades.size() == 1) {
|
||||||
// factor. There is a "user" factor that is computed from a desired max resolution loss in the shadow
|
_cascades.front().setMinDistance(0.0f);
|
||||||
// and an optimal one based on the global min and max shadow distance, all cascades considered. The final
|
_cascades.front().setMaxDistance(_maxDistance);
|
||||||
// distance is a gradual blend between the two
|
} else {
|
||||||
const auto userDistanceScale = 1.0f / (1.0f - MAX_RESOLUTION_LOSS);
|
// Distribute the cascades along that distance
|
||||||
const auto optimalDistanceScale = powf(_maxDistance / LOW_MAX_DISTANCE, 1.0f / (_cascades.size() - 1));
|
// TODO : these parameters should be exposed to the user as part of the light entity parameters, no?
|
||||||
|
static const auto LOW_MAX_DISTANCE = 2.0f;
|
||||||
|
static const auto MAX_RESOLUTION_LOSS = 0.6f; // Between 0 and 1, 0 giving tighter distributions
|
||||||
|
|
||||||
float maxCascadeUserDistance = LOW_MAX_DISTANCE;
|
// The max cascade distance is computed by multiplying the previous cascade's max distance by a certain
|
||||||
float maxCascadeOptimalDistance = LOW_MAX_DISTANCE;
|
// factor. There is a "user" factor that is computed from a desired max resolution loss in the shadow
|
||||||
float minCascadeDistance = 0.0f;
|
// and an optimal one based on the global min and max shadow distance, all cascades considered. The final
|
||||||
|
// distance is a gradual blend between the two
|
||||||
|
const auto userDistanceScale = 1.0f / (1.0f - MAX_RESOLUTION_LOSS);
|
||||||
|
const auto optimalDistanceScale = powf(_maxDistance / LOW_MAX_DISTANCE, 1.0f / (_cascades.size() - 1));
|
||||||
|
|
||||||
for (size_t cascadeIndex = 0; cascadeIndex < _cascades.size(); ++cascadeIndex) {
|
float maxCascadeUserDistance = LOW_MAX_DISTANCE;
|
||||||
float blendFactor = cascadeIndex / float(_cascades.size() - 1);
|
float maxCascadeOptimalDistance = LOW_MAX_DISTANCE;
|
||||||
float maxCascadeDistance;
|
float minCascadeDistance = 0.0f;
|
||||||
|
|
||||||
if (cascadeIndex == size_t(_cascades.size() - 1)) {
|
for (size_t cascadeIndex = 0; cascadeIndex < _cascades.size(); ++cascadeIndex) {
|
||||||
maxCascadeDistance = _maxDistance;
|
float blendFactor = cascadeIndex / float(_cascades.size() - 1);
|
||||||
} else {
|
float maxCascadeDistance;
|
||||||
maxCascadeDistance = maxCascadeUserDistance + (maxCascadeOptimalDistance - maxCascadeUserDistance)*blendFactor*blendFactor;
|
|
||||||
|
if (cascadeIndex == size_t(_cascades.size() - 1)) {
|
||||||
|
maxCascadeDistance = _maxDistance;
|
||||||
|
} else {
|
||||||
|
maxCascadeDistance = maxCascadeUserDistance + (maxCascadeOptimalDistance - maxCascadeUserDistance)*blendFactor*blendFactor;
|
||||||
|
}
|
||||||
|
|
||||||
|
float shadowOverlapDistance = maxCascadeDistance * OVERLAP_FACTOR;
|
||||||
|
|
||||||
|
_cascades[cascadeIndex].setMinDistance(minCascadeDistance);
|
||||||
|
_cascades[cascadeIndex].setMaxDistance(maxCascadeDistance + shadowOverlapDistance);
|
||||||
|
|
||||||
|
// Compute distances for next cascade
|
||||||
|
minCascadeDistance = maxCascadeDistance;
|
||||||
|
maxCascadeUserDistance = maxCascadeUserDistance * userDistanceScale;
|
||||||
|
maxCascadeOptimalDistance = maxCascadeOptimalDistance * optimalDistanceScale;
|
||||||
|
maxCascadeUserDistance = std::min(maxCascadeUserDistance, _maxDistance);
|
||||||
}
|
}
|
||||||
|
|
||||||
float shadowOverlapDistance = maxCascadeDistance * OVERLAP_FACTOR;
|
|
||||||
|
|
||||||
_cascades[cascadeIndex].setMinDistance(minCascadeDistance);
|
|
||||||
_cascades[cascadeIndex].setMaxDistance(maxCascadeDistance + shadowOverlapDistance);
|
|
||||||
|
|
||||||
// Compute distances for next cascade
|
|
||||||
minCascadeDistance = maxCascadeDistance;
|
|
||||||
maxCascadeUserDistance = maxCascadeUserDistance * userDistanceScale;
|
|
||||||
maxCascadeOptimalDistance = maxCascadeOptimalDistance * optimalDistanceScale;
|
|
||||||
maxCascadeUserDistance = std::min(maxCascadeUserDistance, _maxDistance);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Update the buffer
|
// Update the buffer
|
||||||
const auto& lastCascade = _cascades.back();
|
const auto& lastCascade = _cascades.back();
|
||||||
auto& schema = _schemaBuffer.edit<Schema>();
|
auto& schema = _schemaBuffer.edit<Schema>();
|
||||||
schema.maxDistance = _maxDistance;
|
schema.maxDistance = _maxDistance;
|
||||||
schema.invFalloffDistance = 1.0f / (OVERLAP_FACTOR*lastCascade.getMaxDistance());
|
schema.invFalloffDistance = 1.0f / (OVERLAP_FACTOR*lastCascade.getMaxDistance());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void LightStage::Shadow::setKeylightFrustum(const ViewFrustum& viewFrustum,
|
void LightStage::Shadow::setKeylightFrustum(const ViewFrustum& viewFrustum,
|
||||||
|
|
|
@ -74,7 +74,7 @@ public:
|
||||||
float left, float right, float bottom, float top, float viewMaxShadowDistance) const;
|
float left, float right, float bottom, float top, float viewMaxShadowDistance) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
Shadow(graphics::LightPointer light, float maxDistance, unsigned int cascadeCount = 1);
|
Shadow(graphics::LightPointer light, unsigned int cascadeCount = 1);
|
||||||
|
|
||||||
void setLight(graphics::LightPointer light);
|
void setLight(graphics::LightPointer light);
|
||||||
|
|
||||||
|
@ -104,16 +104,14 @@ public:
|
||||||
};
|
};
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
|
|
||||||
using Cascades = std::vector<Cascade>;
|
using Cascades = std::vector<Cascade>;
|
||||||
|
|
||||||
static const glm::mat4 _biasMatrix;
|
static const glm::mat4 _biasMatrix;
|
||||||
|
|
||||||
graphics::LightPointer _light;
|
graphics::LightPointer _light;
|
||||||
float _maxDistance;
|
float _maxDistance{ 0.0f };
|
||||||
Cascades _cascades;
|
Cascades _cascades;
|
||||||
|
|
||||||
|
|
||||||
UniformBufferView _schemaBuffer = nullptr;
|
UniformBufferView _schemaBuffer = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -154,8 +154,7 @@ void MeshPartPayload::render(RenderArgs* args) {
|
||||||
bindMesh(batch);
|
bindMesh(batch);
|
||||||
|
|
||||||
// apply material properties
|
// apply material properties
|
||||||
if (args->_renderMode != render::Args::RenderMode::SHADOW_RENDER_MODE) {
|
if (RenderPipelines::bindMaterials(_drawMaterials, batch, args->_renderMode, args->_enableTexturing)) {
|
||||||
RenderPipelines::bindMaterials(_drawMaterials, batch, args->_enableTexturing);
|
|
||||||
args->_details._materialSwitches++;
|
args->_details._materialSwitches++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -434,8 +433,7 @@ void ModelMeshPartPayload::render(RenderArgs* args) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// apply material properties
|
// apply material properties
|
||||||
if (args->_renderMode != render::Args::RenderMode::SHADOW_RENDER_MODE) {
|
if (RenderPipelines::bindMaterials(_drawMaterials, batch, args->_renderMode, args->_enableTexturing)) {
|
||||||
RenderPipelines::bindMaterials(_drawMaterials, batch, args->_enableTexturing);
|
|
||||||
args->_details._materialSwitches++;
|
args->_details._materialSwitches++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -364,7 +364,7 @@ void RenderDeferredTaskDebug::build(JobModel& task, const render::Varying& input
|
||||||
sprintf(jobName, "DrawShadowFrustum%d", i);
|
sprintf(jobName, "DrawShadowFrustum%d", i);
|
||||||
task.addJob<DrawFrustum>(jobName, shadowFrustum, glm::vec3(0.0f, tint, 1.0f));
|
task.addJob<DrawFrustum>(jobName, shadowFrustum, glm::vec3(0.0f, tint, 1.0f));
|
||||||
if (!renderShadowTaskOut.isNull()) {
|
if (!renderShadowTaskOut.isNull()) {
|
||||||
const auto& shadowCascadeSceneBBoxes = renderShadowTaskOut;
|
const auto& shadowCascadeSceneBBoxes = renderShadowTaskOut.get<RenderShadowTask::CascadeBoxes>();
|
||||||
const auto shadowBBox = shadowCascadeSceneBBoxes[ExtractFrustums::SHADOW_CASCADE0_FRUSTUM + i];
|
const auto shadowBBox = shadowCascadeSceneBBoxes[ExtractFrustums::SHADOW_CASCADE0_FRUSTUM + i];
|
||||||
sprintf(jobName, "DrawShadowBBox%d", i);
|
sprintf(jobName, "DrawShadowBBox%d", i);
|
||||||
task.addJob<DrawAABox>(jobName, shadowBBox, glm::vec3(1.0f, tint, 0.0f));
|
task.addJob<DrawAABox>(jobName, shadowBBox, glm::vec3(1.0f, tint, 0.0f));
|
||||||
|
|
|
@ -373,10 +373,10 @@ void initZPassPipelines(ShapePlumber& shapePlumber, gpu::StatePointer state, con
|
||||||
gpu::Shader::createProgram(deformed_model_shadow_fade_dq), state, extraBatchSetter, itemSetter);
|
gpu::Shader::createProgram(deformed_model_shadow_fade_dq), state, extraBatchSetter, itemSetter);
|
||||||
}
|
}
|
||||||
|
|
||||||
void RenderPipelines::bindMaterial(graphics::MaterialPointer& material, gpu::Batch& batch, bool enableTextures) {
|
bool RenderPipelines::bindMaterial(graphics::MaterialPointer& material, gpu::Batch& batch, render::Args::RenderMode renderMode, bool enableTextures) {
|
||||||
graphics::MultiMaterial multiMaterial;
|
graphics::MultiMaterial multiMaterial;
|
||||||
multiMaterial.push(graphics::MaterialLayer(material, 0));
|
multiMaterial.push(graphics::MaterialLayer(material, 0));
|
||||||
bindMaterials(multiMaterial, batch, enableTextures);
|
return bindMaterials(multiMaterial, batch, renderMode, enableTextures);
|
||||||
}
|
}
|
||||||
|
|
||||||
void RenderPipelines::updateMultiMaterial(graphics::MultiMaterial& multiMaterial) {
|
void RenderPipelines::updateMultiMaterial(graphics::MultiMaterial& multiMaterial) {
|
||||||
|
@ -730,7 +730,7 @@ void RenderPipelines::updateMultiMaterial(graphics::MultiMaterial& multiMaterial
|
||||||
multiMaterial.setInitialized();
|
multiMaterial.setInitialized();
|
||||||
}
|
}
|
||||||
|
|
||||||
void RenderPipelines::bindMaterials(graphics::MultiMaterial& multiMaterial, gpu::Batch& batch, bool enableTextures) {
|
bool RenderPipelines::bindMaterials(graphics::MultiMaterial& multiMaterial, gpu::Batch& batch, render::Args::RenderMode renderMode, bool enableTextures) {
|
||||||
if (multiMaterial.shouldUpdate()) {
|
if (multiMaterial.shouldUpdate()) {
|
||||||
updateMultiMaterial(multiMaterial);
|
updateMultiMaterial(multiMaterial);
|
||||||
}
|
}
|
||||||
|
@ -738,8 +738,13 @@ void RenderPipelines::bindMaterials(graphics::MultiMaterial& multiMaterial, gpu:
|
||||||
auto textureCache = DependencyManager::get<TextureCache>();
|
auto textureCache = DependencyManager::get<TextureCache>();
|
||||||
|
|
||||||
static gpu::TextureTablePointer defaultMaterialTextures = std::make_shared<gpu::TextureTable>();
|
static gpu::TextureTablePointer defaultMaterialTextures = std::make_shared<gpu::TextureTable>();
|
||||||
|
static gpu::BufferView defaultMaterialSchema;
|
||||||
|
|
||||||
static std::once_flag once;
|
static std::once_flag once;
|
||||||
std::call_once(once, [textureCache] {
|
std::call_once(once, [textureCache] {
|
||||||
|
graphics::MultiMaterial::Schema schema;
|
||||||
|
defaultMaterialSchema = gpu::BufferView(std::make_shared<gpu::Buffer>(sizeof(schema), (const gpu::Byte*) &schema, sizeof(schema)));
|
||||||
|
|
||||||
defaultMaterialTextures->setTexture(gr::Texture::MaterialAlbedo, textureCache->getWhiteTexture());
|
defaultMaterialTextures->setTexture(gr::Texture::MaterialAlbedo, textureCache->getWhiteTexture());
|
||||||
defaultMaterialTextures->setTexture(gr::Texture::MaterialMetallic, textureCache->getBlackTexture());
|
defaultMaterialTextures->setTexture(gr::Texture::MaterialMetallic, textureCache->getBlackTexture());
|
||||||
defaultMaterialTextures->setTexture(gr::Texture::MaterialRoughness, textureCache->getWhiteTexture());
|
defaultMaterialTextures->setTexture(gr::Texture::MaterialRoughness, textureCache->getWhiteTexture());
|
||||||
|
@ -749,17 +754,29 @@ void RenderPipelines::bindMaterials(graphics::MultiMaterial& multiMaterial, gpu:
|
||||||
// MaterialEmissiveLightmap has to be set later
|
// MaterialEmissiveLightmap has to be set later
|
||||||
});
|
});
|
||||||
|
|
||||||
auto& schemaBuffer = multiMaterial.getSchemaBuffer();
|
// For shadows, we only need opacity mask information
|
||||||
batch.setUniformBuffer(gr::Buffer::Material, schemaBuffer);
|
auto key = multiMaterial.getMaterialKey();
|
||||||
if (enableTextures) {
|
if (renderMode != render::Args::RenderMode::SHADOW_RENDER_MODE || key.isOpacityMaskMap()) {
|
||||||
batch.setResourceTextureTable(multiMaterial.getTextureTable());
|
auto& schemaBuffer = multiMaterial.getSchemaBuffer();
|
||||||
} else {
|
batch.setUniformBuffer(gr::Buffer::Material, schemaBuffer);
|
||||||
auto key = multiMaterial.getMaterialKey();
|
if (enableTextures) {
|
||||||
if (key.isLightmapMap()) {
|
batch.setResourceTextureTable(multiMaterial.getTextureTable());
|
||||||
defaultMaterialTextures->setTexture(gr::Texture::MaterialEmissiveLightmap, textureCache->getBlackTexture());
|
} else {
|
||||||
} else if (key.isEmissiveMap()) {
|
if (renderMode != render::Args::RenderMode::SHADOW_RENDER_MODE) {
|
||||||
defaultMaterialTextures->setTexture(gr::Texture::MaterialEmissiveLightmap, textureCache->getGrayTexture());
|
if (key.isLightmapMap()) {
|
||||||
|
defaultMaterialTextures->setTexture(gr::Texture::MaterialEmissiveLightmap, textureCache->getBlackTexture());
|
||||||
|
} else if (key.isEmissiveMap()) {
|
||||||
|
defaultMaterialTextures->setTexture(gr::Texture::MaterialEmissiveLightmap, textureCache->getGrayTexture());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
batch.setResourceTextureTable(defaultMaterialTextures);
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
batch.setResourceTextureTable(defaultMaterialTextures);
|
batch.setResourceTextureTable(defaultMaterialTextures);
|
||||||
|
batch.setUniformBuffer(gr::Buffer::Material, defaultMaterialSchema);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,12 +12,13 @@
|
||||||
#define hifi_RenderPipelines_h
|
#define hifi_RenderPipelines_h
|
||||||
|
|
||||||
#include <graphics/Material.h>
|
#include <graphics/Material.h>
|
||||||
|
#include <render/Args.h>
|
||||||
|
|
||||||
class RenderPipelines {
|
class RenderPipelines {
|
||||||
public:
|
public:
|
||||||
static void bindMaterial(graphics::MaterialPointer& material, gpu::Batch& batch, bool enableTextures);
|
|
||||||
static void updateMultiMaterial(graphics::MultiMaterial& multiMaterial);
|
static void updateMultiMaterial(graphics::MultiMaterial& multiMaterial);
|
||||||
static void bindMaterials(graphics::MultiMaterial& multiMaterial, gpu::Batch& batch, bool enableTextures);
|
static bool bindMaterial(graphics::MaterialPointer& material, gpu::Batch& batch, render::Args::RenderMode renderMode, bool enableTextures);
|
||||||
|
static bool bindMaterials(graphics::MultiMaterial& multiMaterial, gpu::Batch& batch, render::Args::RenderMode renderMode, bool enableTextures);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,6 @@
|
||||||
#define SHADOW_FRUSTUM_NEAR 1.0f
|
#define SHADOW_FRUSTUM_NEAR 1.0f
|
||||||
#define SHADOW_FRUSTUM_FAR 500.0f
|
#define SHADOW_FRUSTUM_FAR 500.0f
|
||||||
static const unsigned int SHADOW_CASCADE_COUNT{ 4 };
|
static const unsigned int SHADOW_CASCADE_COUNT{ 4 };
|
||||||
static const float SHADOW_MAX_DISTANCE{ 40.0f };
|
|
||||||
|
|
||||||
using namespace render;
|
using namespace render;
|
||||||
|
|
||||||
|
@ -90,7 +89,7 @@ void RenderShadowTask::build(JobModel& task, const render::Varying& input, rende
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
render::VaryingArray<AABox,4> cascadeSceneBBoxes;
|
CascadeBoxes cascadeSceneBBoxes;
|
||||||
|
|
||||||
for (auto i = 0; i < SHADOW_CASCADE_MAX_COUNT; i++) {
|
for (auto i = 0; i < SHADOW_CASCADE_MAX_COUNT; i++) {
|
||||||
char jobName[64];
|
char jobName[64];
|
||||||
|
@ -336,7 +335,7 @@ void RenderShadowSetup::setConstantBias(int cascadeIndex, float value) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void RenderShadowSetup::setSlopeBias(int cascadeIndex, float value) {
|
void RenderShadowSetup::setSlopeBias(int cascadeIndex, float value) {
|
||||||
_bias[cascadeIndex]._slope = value * value * value * 0.01f;
|
_bias[cascadeIndex]._slope = value * value * value * 0.001f;
|
||||||
}
|
}
|
||||||
|
|
||||||
void RenderShadowSetup::run(const render::RenderContextPointer& renderContext, const Input& input, Output& output) {
|
void RenderShadowSetup::run(const render::RenderContextPointer& renderContext, const Input& input, Output& output) {
|
||||||
|
@ -367,7 +366,7 @@ void RenderShadowSetup::run(const render::RenderContextPointer& renderContext, c
|
||||||
output.edit2() = _cameraFrustum;
|
output.edit2() = _cameraFrustum;
|
||||||
|
|
||||||
if (!_globalShadowObject) {
|
if (!_globalShadowObject) {
|
||||||
_globalShadowObject = std::make_shared<LightStage::Shadow>(graphics::LightPointer(), SHADOW_MAX_DISTANCE, SHADOW_CASCADE_COUNT);
|
_globalShadowObject = std::make_shared<LightStage::Shadow>(currentKeyLight, SHADOW_CASCADE_COUNT);
|
||||||
}
|
}
|
||||||
|
|
||||||
_globalShadowObject->setLight(currentKeyLight);
|
_globalShadowObject->setLight(currentKeyLight);
|
||||||
|
@ -378,11 +377,12 @@ void RenderShadowSetup::run(const render::RenderContextPointer& renderContext, c
|
||||||
unsigned int cascadeIndex;
|
unsigned int cascadeIndex;
|
||||||
|
|
||||||
// Adjust each cascade frustum
|
// Adjust each cascade frustum
|
||||||
|
const auto biasScale = currentKeyLight->getShadowsBiasScale();
|
||||||
for (cascadeIndex = 0; cascadeIndex < _globalShadowObject->getCascadeCount(); ++cascadeIndex) {
|
for (cascadeIndex = 0; cascadeIndex < _globalShadowObject->getCascadeCount(); ++cascadeIndex) {
|
||||||
auto& bias = _bias[cascadeIndex];
|
auto& bias = _bias[cascadeIndex];
|
||||||
_globalShadowObject->setKeylightCascadeFrustum(cascadeIndex, args->getViewFrustum(),
|
_globalShadowObject->setKeylightCascadeFrustum(cascadeIndex, args->getViewFrustum(),
|
||||||
SHADOW_FRUSTUM_NEAR, SHADOW_FRUSTUM_FAR,
|
SHADOW_FRUSTUM_NEAR, SHADOW_FRUSTUM_FAR,
|
||||||
bias._constant, bias._slope);
|
bias._constant, bias._slope * biasScale);
|
||||||
}
|
}
|
||||||
|
|
||||||
_shadowFrameCache->pushShadow(_globalShadowObject);
|
_shadowFrameCache->pushShadow(_globalShadowObject);
|
||||||
|
|
|
@ -52,8 +52,9 @@ class RenderShadowTask {
|
||||||
public:
|
public:
|
||||||
|
|
||||||
// There is one AABox per shadow cascade
|
// There is one AABox per shadow cascade
|
||||||
|
using CascadeBoxes = render::VaryingArray<AABox, SHADOW_CASCADE_MAX_COUNT>;
|
||||||
using Input = render::VaryingSet2<LightStage::FramePointer, LightingModelPointer>;
|
using Input = render::VaryingSet2<LightStage::FramePointer, LightingModelPointer>;
|
||||||
using Output = render::VaryingSet2<render::VaryingArray<AABox, SHADOW_CASCADE_MAX_COUNT>, LightStage::ShadowFramePointer>;
|
using Output = render::VaryingSet2<CascadeBoxes, LightStage::ShadowFramePointer>;
|
||||||
using Config = RenderShadowTaskConfig;
|
using Config = RenderShadowTaskConfig;
|
||||||
using JobModel = render::Task::ModelIO<RenderShadowTask, Input, Output, Config>;
|
using JobModel = render::Task::ModelIO<RenderShadowTask, Input, Output, Config>;
|
||||||
|
|
||||||
|
@ -92,10 +93,10 @@ public:
|
||||||
float constantBias1{ 0.15f };
|
float constantBias1{ 0.15f };
|
||||||
float constantBias2{ 0.175f };
|
float constantBias2{ 0.175f };
|
||||||
float constantBias3{ 0.2f };
|
float constantBias3{ 0.2f };
|
||||||
float slopeBias0{ 0.6f };
|
float slopeBias0{ 0.4f };
|
||||||
float slopeBias1{ 0.6f };
|
float slopeBias1{ 0.45f };
|
||||||
float slopeBias2{ 0.7f };
|
float slopeBias2{ 0.65f };
|
||||||
float slopeBias3{ 0.82f };
|
float slopeBias3{ 0.7f };
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void dirty();
|
void dirty();
|
||||||
|
|
|
@ -90,8 +90,8 @@ float evalShadowAttenuationPCF(int cascadeIndex, ShadowSampleOffsets offsets, ve
|
||||||
return shadowAttenuation;
|
return shadowAttenuation;
|
||||||
}
|
}
|
||||||
|
|
||||||
float evalShadowCascadeAttenuation(int cascadeIndex, ShadowSampleOffsets offsets, vec4 shadowTexcoord, float oneMinusNdotL) {
|
float evalShadowCascadeAttenuation(int cascadeIndex, ShadowSampleOffsets offsets, vec4 shadowTexcoord, float slopeNdotL) {
|
||||||
float bias = getShadowFixedBias(cascadeIndex) + getShadowSlopeBias(cascadeIndex) * oneMinusNdotL;
|
float bias = getShadowFixedBias(cascadeIndex) + getShadowSlopeBias(cascadeIndex) * slopeNdotL;
|
||||||
return evalShadowAttenuationPCF(cascadeIndex, offsets, shadowTexcoord, bias);
|
return evalShadowAttenuationPCF(cascadeIndex, offsets, shadowTexcoord, bias);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,7 +104,8 @@ float evalShadowAttenuation(vec3 worldLightDir, vec4 worldPosition, float viewDe
|
||||||
vec3 cascadeMix;
|
vec3 cascadeMix;
|
||||||
bvec4 isPixelOnCascade;
|
bvec4 isPixelOnCascade;
|
||||||
int cascadeIndex;
|
int cascadeIndex;
|
||||||
float oneMinusNdotL = 1.0 - clamp(dot(worldLightDir, worldNormal), 0.0, 1.0);
|
float NdotL = clamp(dot(worldLightDir, worldNormal), 0.0, 1.0);
|
||||||
|
float slopeNdotL = min(2.0, sqrt(1.0-NdotL*NdotL) / NdotL);
|
||||||
|
|
||||||
for (cascadeIndex=0 ; cascadeIndex<getShadowCascadeCount() ; cascadeIndex++) {
|
for (cascadeIndex=0 ; cascadeIndex<getShadowCascadeCount() ; cascadeIndex++) {
|
||||||
cascadeShadowCoords[cascadeIndex] = evalShadowTexcoord(cascadeIndex, worldPosition);
|
cascadeShadowCoords[cascadeIndex] = evalShadowTexcoord(cascadeIndex, worldPosition);
|
||||||
|
@ -115,10 +116,10 @@ float evalShadowAttenuation(vec3 worldLightDir, vec4 worldPosition, float viewDe
|
||||||
isPixelOnCascade.z = isShadowCascadeProjectedOnPixel(cascadeShadowCoords[2]);
|
isPixelOnCascade.z = isShadowCascadeProjectedOnPixel(cascadeShadowCoords[2]);
|
||||||
isPixelOnCascade.w = isShadowCascadeProjectedOnPixel(cascadeShadowCoords[3]);
|
isPixelOnCascade.w = isShadowCascadeProjectedOnPixel(cascadeShadowCoords[3]);
|
||||||
|
|
||||||
cascadeAttenuations.x = mix(1.0, evalShadowCascadeAttenuation(0, offsets, cascadeShadowCoords[0], oneMinusNdotL), float(isPixelOnCascade.x));
|
cascadeAttenuations.x = mix(1.0, evalShadowCascadeAttenuation(0, offsets, cascadeShadowCoords[0], slopeNdotL), float(isPixelOnCascade.x));
|
||||||
cascadeAttenuations.y = mix(1.0, evalShadowCascadeAttenuation(1, offsets, cascadeShadowCoords[1], oneMinusNdotL), float(isPixelOnCascade.y));
|
cascadeAttenuations.y = mix(1.0, evalShadowCascadeAttenuation(1, offsets, cascadeShadowCoords[1], slopeNdotL), float(isPixelOnCascade.y));
|
||||||
cascadeAttenuations.z = mix(1.0, evalShadowCascadeAttenuation(2, offsets, cascadeShadowCoords[2], oneMinusNdotL), float(isPixelOnCascade.z));
|
cascadeAttenuations.z = mix(1.0, evalShadowCascadeAttenuation(2, offsets, cascadeShadowCoords[2], slopeNdotL), float(isPixelOnCascade.z));
|
||||||
cascadeAttenuations.w = mix(1.0, evalShadowCascadeAttenuation(3, offsets, cascadeShadowCoords[3], oneMinusNdotL), float(isPixelOnCascade.w));
|
cascadeAttenuations.w = mix(1.0, evalShadowCascadeAttenuation(3, offsets, cascadeShadowCoords[3], slopeNdotL), float(isPixelOnCascade.w));
|
||||||
|
|
||||||
cascadeWeights.x = evalShadowCascadeWeight(cascadeShadowCoords[0]);
|
cascadeWeights.x = evalShadowCascadeWeight(cascadeShadowCoords[0]);
|
||||||
cascadeWeights.y = evalShadowCascadeWeight(cascadeShadowCoords[1]);
|
cascadeWeights.y = evalShadowCascadeWeight(cascadeShadowCoords[1]);
|
||||||
|
|
|
@ -19,7 +19,6 @@ using namespace render;
|
||||||
|
|
||||||
void PrepareStencil::configure(const Config& config) {
|
void PrepareStencil::configure(const Config& config) {
|
||||||
_maskMode = config.maskMode;
|
_maskMode = config.maskMode;
|
||||||
_forceDraw = config.forceDraw;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
graphics::MeshPointer PrepareStencil::getMesh() {
|
graphics::MeshPointer PrepareStencil::getMesh() {
|
||||||
|
@ -43,6 +42,7 @@ gpu::PipelinePointer PrepareStencil::getMeshStencilPipeline() {
|
||||||
auto state = std::make_shared<gpu::State>();
|
auto state = std::make_shared<gpu::State>();
|
||||||
drawMask(*state);
|
drawMask(*state);
|
||||||
state->setColorWriteMask(gpu::State::WRITE_NONE);
|
state->setColorWriteMask(gpu::State::WRITE_NONE);
|
||||||
|
state->setCullMode(gpu::State::CullMode::CULL_NONE);
|
||||||
|
|
||||||
_meshStencilPipeline = gpu::Pipeline::create(program, state);
|
_meshStencilPipeline = gpu::Pipeline::create(program, state);
|
||||||
}
|
}
|
||||||
|
@ -64,8 +64,28 @@ gpu::PipelinePointer PrepareStencil::getPaintStencilPipeline() {
|
||||||
void PrepareStencil::run(const RenderContextPointer& renderContext, const gpu::FramebufferPointer& srcFramebuffer) {
|
void PrepareStencil::run(const RenderContextPointer& renderContext, const gpu::FramebufferPointer& srcFramebuffer) {
|
||||||
RenderArgs* args = renderContext->args;
|
RenderArgs* args = renderContext->args;
|
||||||
|
|
||||||
// Only draw the stencil mask if in HMD mode or not forced.
|
if (args->_takingSnapshot) {
|
||||||
if (!_forceDraw && (args->_displayMode != RenderArgs::STEREO_HMD)) {
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
StencilMaskMode maskMode = _maskMode;
|
||||||
|
std::function<void(gpu::Batch&)> maskOperator = [this](gpu::Batch& batch) {
|
||||||
|
auto mesh = getMesh();
|
||||||
|
batch.setIndexBuffer(mesh->getIndexBuffer());
|
||||||
|
batch.setInputFormat((mesh->getVertexFormat()));
|
||||||
|
batch.setInputStream(0, mesh->getVertexStream());
|
||||||
|
|
||||||
|
// Draw
|
||||||
|
auto part = mesh->getPartBuffer().get<graphics::Mesh::Part>(0);
|
||||||
|
batch.drawIndexed(gpu::TRIANGLES, part._numIndices, part._startIndex);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (maskMode == StencilMaskMode::NONE) {
|
||||||
|
maskMode = args->_stencilMaskMode;
|
||||||
|
maskOperator = args->_stencilMaskOperator;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (maskMode == StencilMaskMode::NONE || (maskMode == StencilMaskMode::MESH && !maskOperator)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,20 +94,12 @@ void PrepareStencil::run(const RenderContextPointer& renderContext, const gpu::F
|
||||||
|
|
||||||
batch.setViewportTransform(args->_viewport);
|
batch.setViewportTransform(args->_viewport);
|
||||||
|
|
||||||
if (_maskMode < 0) {
|
if (maskMode == StencilMaskMode::PAINT) {
|
||||||
batch.setPipeline(getMeshStencilPipeline());
|
|
||||||
|
|
||||||
auto mesh = getMesh();
|
|
||||||
batch.setIndexBuffer(mesh->getIndexBuffer());
|
|
||||||
batch.setInputFormat((mesh->getVertexFormat()));
|
|
||||||
batch.setInputStream(0, mesh->getVertexStream());
|
|
||||||
|
|
||||||
// Draw
|
|
||||||
auto part = mesh->getPartBuffer().get<graphics::Mesh::Part>(0);
|
|
||||||
batch.drawIndexed(gpu::TRIANGLES, part._numIndices, part._startIndex);
|
|
||||||
} else {
|
|
||||||
batch.setPipeline(getPaintStencilPipeline());
|
batch.setPipeline(getPaintStencilPipeline());
|
||||||
batch.draw(gpu::TRIANGLE_STRIP, 4);
|
batch.draw(gpu::TRIANGLE_STRIP, 4);
|
||||||
|
} else if (maskMode == StencilMaskMode::MESH) {
|
||||||
|
batch.setPipeline(getMeshStencilPipeline());
|
||||||
|
maskOperator(batch);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,17 +15,19 @@
|
||||||
#include <render/Engine.h>
|
#include <render/Engine.h>
|
||||||
#include <gpu/Pipeline.h>
|
#include <gpu/Pipeline.h>
|
||||||
#include <graphics/Geometry.h>
|
#include <graphics/Geometry.h>
|
||||||
|
#include <StencilMaskMode.h>
|
||||||
|
|
||||||
class PrepareStencilConfig : public render::Job::Config {
|
class PrepareStencilConfig : public render::Job::Config {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
Q_PROPERTY(int maskMode MEMBER maskMode NOTIFY dirty)
|
Q_PROPERTY(StencilMaskMode maskMode MEMBER maskMode NOTIFY dirty)
|
||||||
Q_PROPERTY(bool forceDraw MEMBER forceDraw NOTIFY dirty)
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
PrepareStencilConfig(bool enabled = true) : JobConfig(enabled) {}
|
PrepareStencilConfig(bool enabled = true) : JobConfig(enabled) {}
|
||||||
|
|
||||||
int maskMode { 0 };
|
// -1 -> don't force drawing (fallback to render args mode)
|
||||||
bool forceDraw { false };
|
// 0 -> force draw without mesh
|
||||||
|
// 1 -> force draw with mesh
|
||||||
|
StencilMaskMode maskMode { StencilMaskMode::NONE };
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void dirty();
|
void dirty();
|
||||||
|
@ -66,8 +68,7 @@ private:
|
||||||
graphics::MeshPointer _mesh;
|
graphics::MeshPointer _mesh;
|
||||||
graphics::MeshPointer getMesh();
|
graphics::MeshPointer getMesh();
|
||||||
|
|
||||||
int _maskMode { 0 };
|
StencilMaskMode _maskMode { StencilMaskMode::NONE };
|
||||||
bool _forceDraw { false };
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -18,9 +18,15 @@
|
||||||
<$declareMeshDeformer(_SCRIBE_NULL, _SCRIBE_NULL, 1, _SCRIBE_NULL, 1)$>
|
<$declareMeshDeformer(_SCRIBE_NULL, _SCRIBE_NULL, 1, _SCRIBE_NULL, 1)$>
|
||||||
<$declareMeshDeformerActivation(1, 1)$>
|
<$declareMeshDeformerActivation(1, 1)$>
|
||||||
|
|
||||||
|
<@include graphics/Material.slh@>
|
||||||
|
<@include graphics/MaterialTextures.slh@>
|
||||||
|
|
||||||
|
<$declareMaterialTexMapArrayBuffer()$>
|
||||||
|
|
||||||
<@include render-utils/ShaderConstants.h@>
|
<@include render-utils/ShaderConstants.h@>
|
||||||
|
|
||||||
layout(location=RENDER_UTILS_ATTR_POSITION_WS) out vec4 _positionWS;
|
layout(location=RENDER_UTILS_ATTR_POSITION_WS) out vec4 _positionWS;
|
||||||
|
layout(location=RENDER_UTILS_ATTR_TEXCOORD01) out vec4 _texCoord01;
|
||||||
|
|
||||||
void main(void) {
|
void main(void) {
|
||||||
vec4 deformedPosition = vec4(0.0, 0.0, 0.0, 0.0);
|
vec4 deformedPosition = vec4(0.0, 0.0, 0.0, 0.0);
|
||||||
|
@ -33,5 +39,14 @@ void main(void) {
|
||||||
TransformObject obj = getTransformObject();
|
TransformObject obj = getTransformObject();
|
||||||
<$transformModelToClipPos(cam, obj, deformedPosition, gl_Position)$>
|
<$transformModelToClipPos(cam, obj, deformedPosition, gl_Position)$>
|
||||||
<$transformModelToWorldPos(obj, deformedPosition, _positionWS)$>
|
<$transformModelToWorldPos(obj, deformedPosition, _positionWS)$>
|
||||||
|
|
||||||
|
Material mat = getMaterial();
|
||||||
|
BITFIELD matKey = getMaterialKey(mat);
|
||||||
|
_texCoord01 = vec4(0.0, 0.0, 0.0, 0.0);
|
||||||
|
// If we have an opacity mask than we need the first tex coord
|
||||||
|
if ((matKey & OPACITY_MASK_MAP_BIT) != 0) {
|
||||||
|
TexMapArray texMapArray = getTexMapArray();
|
||||||
|
<$evalTexMapArrayTexcoord0(texMapArray, inTexCoord0, _positionWS, _texCoord01.xy)$>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,9 +18,15 @@
|
||||||
<$declareMeshDeformer(_SCRIBE_NULL, _SCRIBE_NULL, 1, 1, 1)$>
|
<$declareMeshDeformer(_SCRIBE_NULL, _SCRIBE_NULL, 1, 1, 1)$>
|
||||||
<$declareMeshDeformerActivation(1, 1)$>
|
<$declareMeshDeformerActivation(1, 1)$>
|
||||||
|
|
||||||
|
<@include graphics/Material.slh@>
|
||||||
|
<@include graphics/MaterialTextures.slh@>
|
||||||
|
|
||||||
|
<$declareMaterialTexMapArrayBuffer()$>
|
||||||
|
|
||||||
<@include render-utils/ShaderConstants.h@>
|
<@include render-utils/ShaderConstants.h@>
|
||||||
|
|
||||||
layout(location=RENDER_UTILS_ATTR_POSITION_WS) out vec4 _positionWS;
|
layout(location=RENDER_UTILS_ATTR_POSITION_WS) out vec4 _positionWS;
|
||||||
|
layout(location=RENDER_UTILS_ATTR_TEXCOORD01) out vec4 _texCoord01;
|
||||||
|
|
||||||
void main(void) {
|
void main(void) {
|
||||||
vec4 deformedPosition = vec4(0.0, 0.0, 0.0, 0.0);
|
vec4 deformedPosition = vec4(0.0, 0.0, 0.0, 0.0);
|
||||||
|
@ -33,4 +39,13 @@ void main(void) {
|
||||||
TransformObject obj = getTransformObject();
|
TransformObject obj = getTransformObject();
|
||||||
<$transformModelToClipPos(cam, obj, deformedPosition, gl_Position)$>
|
<$transformModelToClipPos(cam, obj, deformedPosition, gl_Position)$>
|
||||||
<$transformModelToWorldPos(obj, deformedPosition, _positionWS)$>
|
<$transformModelToWorldPos(obj, deformedPosition, _positionWS)$>
|
||||||
|
|
||||||
|
Material mat = getMaterial();
|
||||||
|
BITFIELD matKey = getMaterialKey(mat);
|
||||||
|
_texCoord01 = vec4(0.0, 0.0, 0.0, 0.0);
|
||||||
|
// If we have an opacity mask than we need the first tex coord
|
||||||
|
if ((matKey & OPACITY_MASK_MAP_BIT) != 0) {
|
||||||
|
TexMapArray texMapArray = getTexMapArray();
|
||||||
|
<$evalTexMapArrayTexcoord0(texMapArray, inTexCoord0, _positionWS, _texCoord01.xy)$>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,7 @@ void main(void) {
|
||||||
<$fetchMaterialTexturesCoord1(matKey, _texCoord1, occlusionTex)$>
|
<$fetchMaterialTexturesCoord1(matKey, _texCoord1, occlusionTex)$>
|
||||||
|
|
||||||
float opacity = 1.0;
|
float opacity = 1.0;
|
||||||
<$evalMaterialOpacity(albedoTex.a, opacity, matKey, opacity)&>;
|
<$evalMaterialOpacity(albedoTex.a, opacity, matKey, opacity)$>;
|
||||||
<$discardTransparent(opacity)$>;
|
<$discardTransparent(opacity)$>;
|
||||||
|
|
||||||
vec3 albedo = getMaterialAlbedo(mat);
|
vec3 albedo = getMaterialAlbedo(mat);
|
||||||
|
|
|
@ -9,10 +9,26 @@
|
||||||
// Distributed under the Apache License, Version 2.0.
|
// Distributed under the Apache License, Version 2.0.
|
||||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
//
|
//
|
||||||
|
<@include graphics/Material.slh@>
|
||||||
|
<@include graphics/MaterialTextures.slh@>
|
||||||
|
<@include render-utils/ShaderConstants.h@>
|
||||||
|
|
||||||
|
<$declareMaterialTextures(ALBEDO, _SCRIBE_NULL, _SCRIBE_NULL, _SCRIBE_NULL, _SCRIBE_NULL, _SCRIBE_NULL, _SCRIBE_NULL)$>
|
||||||
|
|
||||||
|
layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01;
|
||||||
|
#define _texCoord0 _texCoord01.xy
|
||||||
|
|
||||||
layout(location=0) out vec4 _fragColor;
|
layout(location=0) out vec4 _fragColor;
|
||||||
|
|
||||||
void main(void) {
|
void main(void) {
|
||||||
|
Material mat = getMaterial();
|
||||||
|
BITFIELD matKey = getMaterialKey(mat);
|
||||||
|
<$fetchMaterialTexturesCoord0(matKey, _texCoord0, albedoTex, _SCRIBE_NULL, _SCRIBE_NULL, _SCRIBE_NULL, _SCRIBE_NULL, _SCRIBE_NULL)$>
|
||||||
|
|
||||||
|
float opacity = 1.0;
|
||||||
|
<$evalMaterialOpacity(albedoTex.a, opacity, matKey, opacity)$>;
|
||||||
|
<$discardTransparent(opacity)$>;
|
||||||
|
|
||||||
// pass-through to set z-buffer
|
// pass-through to set z-buffer
|
||||||
_fragColor = vec4(1.0, 1.0, 1.0, 0.0);
|
_fragColor = vec4(1.0, 1.0, 1.0, 0.0);
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,12 +13,16 @@
|
||||||
<@include gpu/Inputs.slh@>
|
<@include gpu/Inputs.slh@>
|
||||||
|
|
||||||
<@include gpu/Transform.slh@>
|
<@include gpu/Transform.slh@>
|
||||||
|
<@include graphics/Material.slh@>
|
||||||
|
<@include graphics/MaterialTextures.slh@>
|
||||||
|
|
||||||
<$declareStandardTransform()$>
|
<$declareStandardTransform()$>
|
||||||
|
<$declareMaterialTexMapArrayBuffer()$>
|
||||||
|
|
||||||
<@include render-utils/ShaderConstants.h@>
|
<@include render-utils/ShaderConstants.h@>
|
||||||
|
|
||||||
layout(location=RENDER_UTILS_ATTR_POSITION_WS) out vec4 _positionWS;
|
layout(location=RENDER_UTILS_ATTR_POSITION_WS) out vec4 _positionWS;
|
||||||
|
layout(location=RENDER_UTILS_ATTR_TEXCOORD01) out vec4 _texCoord01;
|
||||||
|
|
||||||
void main(void) {
|
void main(void) {
|
||||||
// standard transform
|
// standard transform
|
||||||
|
@ -26,4 +30,13 @@ void main(void) {
|
||||||
TransformObject obj = getTransformObject();
|
TransformObject obj = getTransformObject();
|
||||||
<$transformModelToClipPos(cam, obj, inPosition, gl_Position)$>
|
<$transformModelToClipPos(cam, obj, inPosition, gl_Position)$>
|
||||||
<$transformModelToWorldPos(obj, inPosition, _positionWS)$>
|
<$transformModelToWorldPos(obj, inPosition, _positionWS)$>
|
||||||
|
|
||||||
|
Material mat = getMaterial();
|
||||||
|
BITFIELD matKey = getMaterialKey(mat);
|
||||||
|
_texCoord01 = vec4(0.0, 0.0, 0.0, 0.0);
|
||||||
|
// If we have an opacity mask than we need the first tex coord
|
||||||
|
if ((matKey & OPACITY_MASK_MAP_BIT) != 0) {
|
||||||
|
TexMapArray texMapArray = getTexMapArray();
|
||||||
|
<$evalTexMapArrayTexcoord0(texMapArray, inTexCoord0, _positionWS, _texCoord01.xy)$>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,13 +9,18 @@
|
||||||
// Distributed under the Apache License, Version 2.0.
|
// Distributed under the Apache License, Version 2.0.
|
||||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
//
|
//
|
||||||
|
<@include graphics/Material.slh@>
|
||||||
|
<@include graphics/MaterialTextures.slh@>
|
||||||
<@include render-utils/ShaderConstants.h@>
|
<@include render-utils/ShaderConstants.h@>
|
||||||
|
|
||||||
|
<$declareMaterialTextures(ALBEDO, _SCRIBE_NULL, _SCRIBE_NULL, _SCRIBE_NULL, _SCRIBE_NULL, _SCRIBE_NULL, _SCRIBE_NULL)$>
|
||||||
|
|
||||||
<@include Fade.slh@>
|
<@include Fade.slh@>
|
||||||
<$declareFadeFragment()$>
|
<$declareFadeFragment()$>
|
||||||
|
|
||||||
layout(location=RENDER_UTILS_ATTR_POSITION_WS) in vec4 _positionWS;
|
layout(location=RENDER_UTILS_ATTR_POSITION_WS) in vec4 _positionWS;
|
||||||
|
layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01;
|
||||||
|
#define _texCoord0 _texCoord01.xy
|
||||||
|
|
||||||
layout(location=0) out vec4 _fragColor;
|
layout(location=0) out vec4 _fragColor;
|
||||||
|
|
||||||
|
@ -24,6 +29,14 @@ void main(void) {
|
||||||
<$fetchFadeObjectParams(fadeParams)$>
|
<$fetchFadeObjectParams(fadeParams)$>
|
||||||
applyFadeClip(fadeParams, _positionWS.xyz);
|
applyFadeClip(fadeParams, _positionWS.xyz);
|
||||||
|
|
||||||
|
Material mat = getMaterial();
|
||||||
|
BITFIELD matKey = getMaterialKey(mat);
|
||||||
|
<$fetchMaterialTexturesCoord0(matKey, _texCoord0, albedoTex, _SCRIBE_NULL, _SCRIBE_NULL, _SCRIBE_NULL, _SCRIBE_NULL, _SCRIBE_NULL)$>
|
||||||
|
|
||||||
|
float opacity = 1.0;
|
||||||
|
<$evalMaterialOpacity(albedoTex.a, opacity, matKey, opacity)$>;
|
||||||
|
<$discardTransparent(opacity)$>;
|
||||||
|
|
||||||
// pass-through to set z-buffer
|
// pass-through to set z-buffer
|
||||||
_fragColor = vec4(1.0, 1.0, 1.0, 0.0);
|
_fragColor = vec4(1.0, 1.0, 1.0, 0.0);
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
#include <GLMHelpers.h>
|
#include <GLMHelpers.h>
|
||||||
#include <ViewFrustum.h>
|
#include <ViewFrustum.h>
|
||||||
|
#include <StencilMaskMode.h>
|
||||||
|
|
||||||
#include <gpu/Forward.h>
|
#include <gpu/Forward.h>
|
||||||
#include "Forward.h"
|
#include "Forward.h"
|
||||||
|
@ -133,6 +134,10 @@ namespace render {
|
||||||
|
|
||||||
std::function<void(gpu::Batch&, const gpu::TexturePointer&, bool mirror)> _hudOperator;
|
std::function<void(gpu::Batch&, const gpu::TexturePointer&, bool mirror)> _hudOperator;
|
||||||
gpu::TexturePointer _hudTexture;
|
gpu::TexturePointer _hudTexture;
|
||||||
|
|
||||||
|
bool _takingSnapshot { false };
|
||||||
|
StencilMaskMode _stencilMaskMode { StencilMaskMode::NONE };
|
||||||
|
std::function<void(gpu::Batch&)> _stencilMaskOperator;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -185,6 +185,30 @@ QString PathUtils::generateTemporaryDir() {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool PathUtils::deleteMyTemporaryDir(QString dirName) {
|
||||||
|
QDir rootTempDir = QDir::tempPath();
|
||||||
|
|
||||||
|
QString appName = qApp->applicationName();
|
||||||
|
QRegularExpression re { "^" + QRegularExpression::escape(appName) + "\\-(?<pid>\\d+)\\-(?<timestamp>\\d+)$" };
|
||||||
|
|
||||||
|
auto match = re.match(dirName);
|
||||||
|
auto pid = match.capturedRef("pid").toLongLong();
|
||||||
|
|
||||||
|
if (match.hasMatch() && rootTempDir.exists(dirName) && pid == qApp->applicationPid()) {
|
||||||
|
auto absoluteDirPath = QDir(rootTempDir.absoluteFilePath(dirName));
|
||||||
|
|
||||||
|
bool success = absoluteDirPath.removeRecursively();
|
||||||
|
if (success) {
|
||||||
|
qDebug() << " Removing temporary directory: " << absoluteDirPath.absolutePath();
|
||||||
|
} else {
|
||||||
|
qDebug() << " Failed to remove temporary directory: " << absoluteDirPath.absolutePath();
|
||||||
|
}
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Delete all temporary directories for an application
|
// Delete all temporary directories for an application
|
||||||
int PathUtils::removeTemporaryApplicationDirs(QString appName) {
|
int PathUtils::removeTemporaryApplicationDirs(QString appName) {
|
||||||
if (appName.isNull()) {
|
if (appName.isNull()) {
|
||||||
|
|
|
@ -56,6 +56,7 @@ public:
|
||||||
static QString getAppLocalDataFilePath(const QString& filename);
|
static QString getAppLocalDataFilePath(const QString& filename);
|
||||||
|
|
||||||
static QString generateTemporaryDir();
|
static QString generateTemporaryDir();
|
||||||
|
static bool deleteMyTemporaryDir(QString dirName);
|
||||||
|
|
||||||
static int removeTemporaryApplicationDirs(QString appName = QString::null);
|
static int removeTemporaryApplicationDirs(QString appName = QString::null);
|
||||||
|
|
||||||
|
|
|
@ -40,6 +40,7 @@ int qMapURLStringMetaTypeId = qRegisterMetaType<QMap<QUrl,QString>>();
|
||||||
int socketErrorMetaTypeId = qRegisterMetaType<QAbstractSocket::SocketError>();
|
int socketErrorMetaTypeId = qRegisterMetaType<QAbstractSocket::SocketError>();
|
||||||
int voidLambdaType = qRegisterMetaType<std::function<void()>>();
|
int voidLambdaType = qRegisterMetaType<std::function<void()>>();
|
||||||
int variantLambdaType = qRegisterMetaType<std::function<QVariant()>>();
|
int variantLambdaType = qRegisterMetaType<std::function<QVariant()>>();
|
||||||
|
int stencilModeMetaTypeId = qRegisterMetaType<StencilMaskMode>();
|
||||||
|
|
||||||
void registerMetaTypes(QScriptEngine* engine) {
|
void registerMetaTypes(QScriptEngine* engine) {
|
||||||
qScriptRegisterMetaType(engine, vec2ToScriptValue, vec2FromScriptValue);
|
qScriptRegisterMetaType(engine, vec2ToScriptValue, vec2FromScriptValue);
|
||||||
|
@ -64,6 +65,8 @@ void registerMetaTypes(QScriptEngine* engine) {
|
||||||
qScriptRegisterMetaType(engine, collisionToScriptValue, collisionFromScriptValue);
|
qScriptRegisterMetaType(engine, collisionToScriptValue, collisionFromScriptValue);
|
||||||
qScriptRegisterMetaType(engine, quuidToScriptValue, quuidFromScriptValue);
|
qScriptRegisterMetaType(engine, quuidToScriptValue, quuidFromScriptValue);
|
||||||
qScriptRegisterMetaType(engine, aaCubeToScriptValue, aaCubeFromScriptValue);
|
qScriptRegisterMetaType(engine, aaCubeToScriptValue, aaCubeFromScriptValue);
|
||||||
|
|
||||||
|
qScriptRegisterMetaType(engine, stencilMaskModeToScriptValue, stencilMaskModeFromScriptValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
QScriptValue vec2ToScriptValue(QScriptEngine* engine, const glm::vec2& vec2) {
|
QScriptValue vec2ToScriptValue(QScriptEngine* engine, const glm::vec2& vec2) {
|
||||||
|
@ -636,6 +639,81 @@ void mat4FromScriptValue(const QScriptValue& object, glm::mat4& mat4) {
|
||||||
mat4[3][3] = object.property("r3c3").toVariant().toFloat();
|
mat4[3][3] = object.property("r3c3").toVariant().toFloat();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QVariant mat4ToVariant(const glm::mat4& mat4) {
|
||||||
|
if (mat4 != mat4) {
|
||||||
|
// NaN
|
||||||
|
return QVariant();
|
||||||
|
}
|
||||||
|
QVariantMap object;
|
||||||
|
|
||||||
|
object["r0c0"] = mat4[0][0];
|
||||||
|
object["r1c0"] = mat4[0][1];
|
||||||
|
object["r2c0"] = mat4[0][2];
|
||||||
|
object["r3c0"] = mat4[0][3];
|
||||||
|
object["r0c1"] = mat4[1][0];
|
||||||
|
object["r1c1"] = mat4[1][1];
|
||||||
|
object["r2c1"] = mat4[1][2];
|
||||||
|
object["r3c1"] = mat4[1][3];
|
||||||
|
object["r0c2"] = mat4[2][0];
|
||||||
|
object["r1c2"] = mat4[2][1];
|
||||||
|
object["r2c2"] = mat4[2][2];
|
||||||
|
object["r3c2"] = mat4[2][3];
|
||||||
|
object["r0c3"] = mat4[3][0];
|
||||||
|
object["r1c3"] = mat4[3][1];
|
||||||
|
object["r2c3"] = mat4[3][2];
|
||||||
|
object["r3c3"] = mat4[3][3];
|
||||||
|
|
||||||
|
return object;
|
||||||
|
}
|
||||||
|
|
||||||
|
glm::mat4 mat4FromVariant(const QVariant& object, bool& valid) {
|
||||||
|
glm::mat4 mat4;
|
||||||
|
valid = false;
|
||||||
|
if (!object.isValid() || object.isNull()) {
|
||||||
|
return mat4;
|
||||||
|
} else {
|
||||||
|
const static auto getElement = [](const QVariantMap& map, const char * key, float& value, bool& everyConversionValid) {
|
||||||
|
auto variantValue = map[key];
|
||||||
|
if (variantValue.canConvert<float>()) {
|
||||||
|
value = variantValue.toFloat();
|
||||||
|
} else {
|
||||||
|
everyConversionValid = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
auto map = object.toMap();
|
||||||
|
bool everyConversionValid = true;
|
||||||
|
|
||||||
|
getElement(map, "r0c0", mat4[0][0], everyConversionValid);
|
||||||
|
getElement(map, "r1c0", mat4[0][1], everyConversionValid);
|
||||||
|
getElement(map, "r2c0", mat4[0][2], everyConversionValid);
|
||||||
|
getElement(map, "r3c0", mat4[0][3], everyConversionValid);
|
||||||
|
getElement(map, "r0c1", mat4[1][0], everyConversionValid);
|
||||||
|
getElement(map, "r1c1", mat4[1][1], everyConversionValid);
|
||||||
|
getElement(map, "r2c1", mat4[1][2], everyConversionValid);
|
||||||
|
getElement(map, "r3c1", mat4[1][3], everyConversionValid);
|
||||||
|
getElement(map, "r0c2", mat4[2][0], everyConversionValid);
|
||||||
|
getElement(map, "r1c2", mat4[2][1], everyConversionValid);
|
||||||
|
getElement(map, "r2c2", mat4[2][2], everyConversionValid);
|
||||||
|
getElement(map, "r3c2", mat4[2][3], everyConversionValid);
|
||||||
|
getElement(map, "r0c3", mat4[3][0], everyConversionValid);
|
||||||
|
getElement(map, "r1c3", mat4[3][1], everyConversionValid);
|
||||||
|
getElement(map, "r2c3", mat4[3][2], everyConversionValid);
|
||||||
|
getElement(map, "r3c3", mat4[3][3], everyConversionValid);
|
||||||
|
|
||||||
|
if (everyConversionValid) {
|
||||||
|
valid = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return mat4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
glm::mat4 mat4FromVariant(const QVariant& object) {
|
||||||
|
bool valid = false;
|
||||||
|
return mat4FromVariant(object, valid);
|
||||||
|
}
|
||||||
|
|
||||||
QScriptValue qVectorVec3ColorToScriptValue(QScriptEngine* engine, const QVector<glm::vec3>& vector) {
|
QScriptValue qVectorVec3ColorToScriptValue(QScriptEngine* engine, const QVector<glm::vec3>& vector) {
|
||||||
QScriptValue array = engine->newArray();
|
QScriptValue array = engine->newArray();
|
||||||
for (int i = 0; i < vector.size(); i++) {
|
for (int i = 0; i < vector.size(); i++) {
|
||||||
|
@ -1283,4 +1361,12 @@ QVariantMap parseTexturesToMap(QString newTextures, const QVariantMap& defaultTe
|
||||||
}
|
}
|
||||||
|
|
||||||
return toReturn;
|
return toReturn;
|
||||||
|
}
|
||||||
|
|
||||||
|
QScriptValue stencilMaskModeToScriptValue(QScriptEngine* engine, const StencilMaskMode& stencilMode) {
|
||||||
|
return engine->newVariant((int)stencilMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
void stencilMaskModeFromScriptValue(const QScriptValue& object, StencilMaskMode& stencilMode) {
|
||||||
|
stencilMode = StencilMaskMode(object.toVariant().toInt());
|
||||||
}
|
}
|
|
@ -25,6 +25,7 @@
|
||||||
#include "shared/Bilateral.h"
|
#include "shared/Bilateral.h"
|
||||||
#include "Transform.h"
|
#include "Transform.h"
|
||||||
#include "PhysicsCollisionGroups.h"
|
#include "PhysicsCollisionGroups.h"
|
||||||
|
#include "StencilMaskMode.h"
|
||||||
|
|
||||||
class QColor;
|
class QColor;
|
||||||
class QUrl;
|
class QUrl;
|
||||||
|
@ -67,6 +68,10 @@ void registerMetaTypes(QScriptEngine* engine);
|
||||||
QScriptValue mat4toScriptValue(QScriptEngine* engine, const glm::mat4& mat4);
|
QScriptValue mat4toScriptValue(QScriptEngine* engine, const glm::mat4& mat4);
|
||||||
void mat4FromScriptValue(const QScriptValue& object, glm::mat4& mat4);
|
void mat4FromScriptValue(const QScriptValue& object, glm::mat4& mat4);
|
||||||
|
|
||||||
|
QVariant mat4ToVariant(const glm::mat4& mat4);
|
||||||
|
glm::mat4 mat4FromVariant(const QVariant& object, bool& valid);
|
||||||
|
glm::mat4 mat4FromVariant(const QVariant& object);
|
||||||
|
|
||||||
/**jsdoc
|
/**jsdoc
|
||||||
* A 2-dimensional vector.
|
* A 2-dimensional vector.
|
||||||
*
|
*
|
||||||
|
@ -729,5 +734,8 @@ void qVectorMeshFaceFromScriptValue(const QScriptValue& array, QVector<MeshFace>
|
||||||
|
|
||||||
QVariantMap parseTexturesToMap(QString textures, const QVariantMap& defaultTextures);
|
QVariantMap parseTexturesToMap(QString textures, const QVariantMap& defaultTextures);
|
||||||
|
|
||||||
|
Q_DECLARE_METATYPE(StencilMaskMode)
|
||||||
|
QScriptValue stencilMaskModeToScriptValue(QScriptEngine* engine, const StencilMaskMode& stencilMode);
|
||||||
|
void stencilMaskModeFromScriptValue(const QScriptValue& object, StencilMaskMode& stencilMode);
|
||||||
|
|
||||||
#endif // hifi_RegisteredMetaTypes_h
|
#endif // hifi_RegisteredMetaTypes_h
|
||||||
|
|
18
libraries/shared/src/StencilMaskMode.h
Normal file
18
libraries/shared/src/StencilMaskMode.h
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
//
|
||||||
|
// Created by Sam Gondelman on 3/26/19.
|
||||||
|
// Copyright 2019 High Fidelity, Inc.
|
||||||
|
//
|
||||||
|
// Distributed under the Apache License, Version 2.0.
|
||||||
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef hifi_StencilMaskMode_h
|
||||||
|
#define hifi_StencilMaskMode_h
|
||||||
|
|
||||||
|
enum class StencilMaskMode {
|
||||||
|
NONE = -1, // for legacy reasons, this is -1
|
||||||
|
PAINT = 0,
|
||||||
|
MESH = 1
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // hifi_StencilMaskMode_h
|
|
@ -44,7 +44,7 @@ QScriptValue variantToScriptValue(QVariant& qValue, QScriptEngine& scriptEngine)
|
||||||
if (qValue.canConvert<float>()) {
|
if (qValue.canConvert<float>()) {
|
||||||
return qValue.toFloat();
|
return qValue.toFloat();
|
||||||
}
|
}
|
||||||
qCDebug(shared) << "unhandled QScript type" << qValue.type();
|
//qCDebug(shared) << "unhandled QScript type" << qValue.type();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -152,6 +152,7 @@ public:
|
||||||
|
|
||||||
template <class... A>
|
template <class... A>
|
||||||
static std::shared_ptr<Model> create(const std::string& name, const Varying& input, A&&... args) {
|
static std::shared_ptr<Model> create(const std::string& name, const Varying& input, A&&... args) {
|
||||||
|
assert(input.canCast<I>());
|
||||||
return std::make_shared<Model>(name, input, std::make_shared<C>(), std::forward<A>(args)...);
|
return std::make_shared<Model>(name, input, std::make_shared<C>(), std::forward<A>(args)...);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@ if (WIN32 AND (NOT USE_GLES))
|
||||||
link_hifi_libraries(
|
link_hifi_libraries(
|
||||||
shared task gl shaders gpu ${PLATFORM_GL_BACKEND} controllers ui qml
|
shared task gl shaders gpu ${PLATFORM_GL_BACKEND} controllers ui qml
|
||||||
plugins ui-plugins display-plugins input-plugins
|
plugins ui-plugins display-plugins input-plugins
|
||||||
audio-client networking render-utils
|
audio-client networking render-utils graphics
|
||||||
${PLATFORM_GL_BACKEND}
|
${PLATFORM_GL_BACKEND}
|
||||||
)
|
)
|
||||||
include_hifi_library_headers(octree)
|
include_hifi_library_headers(octree)
|
||||||
|
|
|
@ -227,3 +227,66 @@ QVector<glm::vec3> OculusBaseDisplayPlugin::getSensorPositions() {
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DisplayPlugin::StencilMaskMeshOperator OculusBaseDisplayPlugin::getStencilMaskMeshOperator() {
|
||||||
|
if (_session) {
|
||||||
|
if (!_stencilMeshesInitialized) {
|
||||||
|
_stencilMeshesInitialized = true;
|
||||||
|
ovr::for_each_eye([&](ovrEyeType eye) {
|
||||||
|
ovrFovStencilDesc stencilDesc = {
|
||||||
|
ovrFovStencil_HiddenArea, 0, eye,
|
||||||
|
_eyeRenderDescs[eye].Fov, _eyeRenderDescs[eye].HmdToEyePose.Orientation
|
||||||
|
};
|
||||||
|
// First we get the size of the buffer we need
|
||||||
|
ovrFovStencilMeshBuffer buffer = { 0, 0, nullptr, 0, 0, nullptr };
|
||||||
|
ovrResult result = ovr_GetFovStencil(_session, &stencilDesc, &buffer);
|
||||||
|
if (!OVR_SUCCESS(result)) {
|
||||||
|
_stencilMeshesInitialized = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<ovrVector2f> ovrVertices(buffer.UsedVertexCount);
|
||||||
|
std::vector<uint16_t> ovrIndices(buffer.UsedIndexCount);
|
||||||
|
|
||||||
|
// Now we populate the actual buffer
|
||||||
|
buffer = { (int)ovrVertices.size(), 0, ovrVertices.data(), (int)ovrIndices.size(), 0, ovrIndices.data() };
|
||||||
|
result = ovr_GetFovStencil(_session, &stencilDesc, &buffer);
|
||||||
|
|
||||||
|
if (!OVR_SUCCESS(result)) {
|
||||||
|
_stencilMeshesInitialized = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<glm::vec3> vertices;
|
||||||
|
vertices.reserve(ovrVertices.size());
|
||||||
|
for (auto& ovrVertex : ovrVertices) {
|
||||||
|
// We need the vertices in clip space
|
||||||
|
vertices.emplace_back(ovrVertex.x - (1.0f - (float)eye), 2.0f * ovrVertex.y - 1.0f, 0.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint32_t> indices;
|
||||||
|
indices.reserve(ovrIndices.size());
|
||||||
|
for (auto& ovrIndex : ovrIndices) {
|
||||||
|
indices.push_back(ovrIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
_stencilMeshes[eye] = graphics::Mesh::createIndexedTriangles_P3F((uint32_t)vertices.size(), (uint32_t)indices.size(), vertices.data(), indices.data());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_stencilMeshesInitialized) {
|
||||||
|
return [&](gpu::Batch& batch) {
|
||||||
|
for (auto& mesh : _stencilMeshes) {
|
||||||
|
batch.setIndexBuffer(mesh->getIndexBuffer());
|
||||||
|
batch.setInputFormat((mesh->getVertexFormat()));
|
||||||
|
batch.setInputStream(0, mesh->getVertexStream());
|
||||||
|
|
||||||
|
// Draw
|
||||||
|
auto part = mesh->getPartBuffer().get<graphics::Mesh::Part>(0);
|
||||||
|
batch.drawIndexed(gpu::TRIANGLES, part._numIndices, part._startIndex);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
|
@ -16,6 +16,8 @@
|
||||||
#define OVRPL_DISABLED
|
#define OVRPL_DISABLED
|
||||||
#include <OVR_Platform.h>
|
#include <OVR_Platform.h>
|
||||||
|
|
||||||
|
#include <graphics/Geometry.h>
|
||||||
|
|
||||||
class OculusBaseDisplayPlugin : public HmdDisplayPlugin {
|
class OculusBaseDisplayPlugin : public HmdDisplayPlugin {
|
||||||
using Parent = HmdDisplayPlugin;
|
using Parent = HmdDisplayPlugin;
|
||||||
public:
|
public:
|
||||||
|
@ -34,6 +36,9 @@ public:
|
||||||
QRectF getPlayAreaRect() override;
|
QRectF getPlayAreaRect() override;
|
||||||
QVector<glm::vec3> getSensorPositions() override;
|
QVector<glm::vec3> getSensorPositions() override;
|
||||||
|
|
||||||
|
virtual StencilMaskMode getStencilMaskMode() const override { return StencilMaskMode::MESH; }
|
||||||
|
virtual StencilMaskMeshOperator getStencilMaskMeshOperator() override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void customizeContext() override;
|
void customizeContext() override;
|
||||||
void uncustomizeContext() override;
|
void uncustomizeContext() override;
|
||||||
|
@ -52,4 +57,7 @@ protected:
|
||||||
// ovrLayerEyeFovDepth _depthLayer;
|
// ovrLayerEyeFovDepth _depthLayer;
|
||||||
bool _hmdMounted { false };
|
bool _hmdMounted { false };
|
||||||
bool _visible { true };
|
bool _visible { true };
|
||||||
|
|
||||||
|
std::array<graphics::MeshPointer, 2> _stencilMeshes;
|
||||||
|
bool _stencilMeshesInitialized { false };
|
||||||
};
|
};
|
||||||
|
|
|
@ -784,3 +784,48 @@ QRectF OpenVrDisplayPlugin::getPlayAreaRect() {
|
||||||
|
|
||||||
return QRectF(center.x, center.y, dimensions.x, dimensions.y);
|
return QRectF(center.x, center.y, dimensions.x, dimensions.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DisplayPlugin::StencilMaskMeshOperator OpenVrDisplayPlugin::getStencilMaskMeshOperator() {
|
||||||
|
if (_system) {
|
||||||
|
if (!_stencilMeshesInitialized) {
|
||||||
|
_stencilMeshesInitialized = true;
|
||||||
|
for (auto eye : VR_EYES) {
|
||||||
|
vr::HiddenAreaMesh_t stencilMesh = _system->GetHiddenAreaMesh(eye);
|
||||||
|
if (stencilMesh.pVertexData && stencilMesh.unTriangleCount > 0) {
|
||||||
|
std::vector<glm::vec3> vertices;
|
||||||
|
std::vector<uint32_t> indices;
|
||||||
|
|
||||||
|
const int NUM_INDICES_PER_TRIANGLE = 3;
|
||||||
|
int numIndices = stencilMesh.unTriangleCount * NUM_INDICES_PER_TRIANGLE;
|
||||||
|
vertices.reserve(numIndices);
|
||||||
|
indices.reserve(numIndices);
|
||||||
|
for (int i = 0; i < numIndices; i++) {
|
||||||
|
vr::HmdVector2_t vertex2D = stencilMesh.pVertexData[i];
|
||||||
|
// We need the vertices in clip space
|
||||||
|
vertices.emplace_back(vertex2D.v[0] - (1.0f - (float)eye), 2.0f * vertex2D.v[1] - 1.0f, 0.0f);
|
||||||
|
indices.push_back(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
_stencilMeshes[eye] = graphics::Mesh::createIndexedTriangles_P3F((uint32_t)vertices.size(), (uint32_t)indices.size(), vertices.data(), indices.data());
|
||||||
|
} else {
|
||||||
|
_stencilMeshesInitialized = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_stencilMeshesInitialized) {
|
||||||
|
return [&](gpu::Batch& batch) {
|
||||||
|
for (auto& mesh : _stencilMeshes) {
|
||||||
|
batch.setIndexBuffer(mesh->getIndexBuffer());
|
||||||
|
batch.setInputFormat((mesh->getVertexFormat()));
|
||||||
|
batch.setInputStream(0, mesh->getVertexStream());
|
||||||
|
|
||||||
|
// Draw
|
||||||
|
auto part = mesh->getPartBuffer().get<graphics::Mesh::Part>(0);
|
||||||
|
batch.drawIndexed(gpu::TRIANGLES, part._numIndices, part._startIndex);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
|
@ -13,6 +13,8 @@
|
||||||
|
|
||||||
#include <display-plugins/hmd/HmdDisplayPlugin.h>
|
#include <display-plugins/hmd/HmdDisplayPlugin.h>
|
||||||
|
|
||||||
|
#include <graphics/Geometry.h>
|
||||||
|
|
||||||
const float TARGET_RATE_OpenVr = 90.0f; // FIXME: get from sdk tracked device property? This number is vive-only.
|
const float TARGET_RATE_OpenVr = 90.0f; // FIXME: get from sdk tracked device property? This number is vive-only.
|
||||||
|
|
||||||
namespace gl {
|
namespace gl {
|
||||||
|
@ -67,6 +69,9 @@ public:
|
||||||
|
|
||||||
QRectF getPlayAreaRect() override;
|
QRectF getPlayAreaRect() override;
|
||||||
|
|
||||||
|
virtual StencilMaskMode getStencilMaskMode() const override { return StencilMaskMode::MESH; }
|
||||||
|
virtual StencilMaskMeshOperator getStencilMaskMeshOperator() override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
bool internalActivate() override;
|
bool internalActivate() override;
|
||||||
void internalDeactivate() override;
|
void internalDeactivate() override;
|
||||||
|
@ -94,4 +99,7 @@ private:
|
||||||
bool _asyncReprojectionActive { false };
|
bool _asyncReprojectionActive { false };
|
||||||
|
|
||||||
bool _hmdMounted { false };
|
bool _hmdMounted { false };
|
||||||
|
|
||||||
|
std::array<graphics::MeshPointer, 2> _stencilMeshes;
|
||||||
|
bool _stencilMeshesInitialized { false };
|
||||||
};
|
};
|
||||||
|
|
|
@ -10,7 +10,12 @@
|
||||||
//
|
//
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
// traverse task tree
|
// traverse task tree recursively
|
||||||
|
//
|
||||||
|
// @param root: the root job config from where to traverse
|
||||||
|
// @param functor: the functor function() which is applied on every subjobs of root traversed
|
||||||
|
// if return true, then 'task_tree' is called recursively on that subjob
|
||||||
|
// @param depth: the depth of the recurse loop since the initial call.
|
||||||
function task_traverse(root, functor, depth) {
|
function task_traverse(root, functor, depth) {
|
||||||
if (root.isTask()) {
|
if (root.isTask()) {
|
||||||
depth++;
|
depth++;
|
||||||
|
@ -22,6 +27,9 @@ function task_traverse(root, functor, depth) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// same function as 'task_traverse' with the depth being 0
|
||||||
|
// and visisting the root job first.
|
||||||
function task_traverseTree(root, functor) {
|
function task_traverseTree(root, functor) {
|
||||||
if (functor(root, 0, 0)) {
|
if (functor(root, 0, 0)) {
|
||||||
task_traverse(root, functor, 0)
|
task_traverse(root, functor, 0)
|
||||||
|
|
123
scripts/developer/utilities/lib/jet/qml/TaskPropView.qml
Normal file
123
scripts/developer/utilities/lib/jet/qml/TaskPropView.qml
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
//
|
||||||
|
// jet/TaskPropView.qml
|
||||||
|
//
|
||||||
|
// Created by Sam Gateau, 2018/05/09
|
||||||
|
// 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.7
|
||||||
|
import QtQuick.Controls 1.4 as Original
|
||||||
|
import QtQuick.Controls.Styles 1.4
|
||||||
|
|
||||||
|
import stylesUit 1.0
|
||||||
|
import controlsUit 1.0 as HifiControls
|
||||||
|
|
||||||
|
import "../../prop" as Prop
|
||||||
|
|
||||||
|
import "../jet.js" as Jet
|
||||||
|
|
||||||
|
Prop.PropGroup {
|
||||||
|
|
||||||
|
id: root;
|
||||||
|
|
||||||
|
property var rootConfig : Render
|
||||||
|
property var jobPath: ""
|
||||||
|
property alias label: root.label
|
||||||
|
property alias indentDepth: root.indentDepth
|
||||||
|
|
||||||
|
property var showProps: true
|
||||||
|
property var showSubs: true
|
||||||
|
property var jobEnabled: rootConfig.getConfig(jobPath).enabled
|
||||||
|
property var jobCpuTime: pullCpuTime()
|
||||||
|
|
||||||
|
function pullCpuTime() {
|
||||||
|
if (jobEnabled) {
|
||||||
|
return rootConfig.getConfig(jobPath).cpuRunTime.toPrecision(3);
|
||||||
|
} else {
|
||||||
|
return '.'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
property var toggleJobActivation: function() {
|
||||||
|
console.log("the button has been pressed and jobEnabled is " + jobEnabled )
|
||||||
|
jobEnabled = !jobEnabled;
|
||||||
|
rootConfig.getConfig(jobPath).enabled = jobEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Panel Header Data Component
|
||||||
|
panelHeaderData: Component {
|
||||||
|
Item {
|
||||||
|
id: header
|
||||||
|
Prop.PropLabel {
|
||||||
|
text: root.label
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: cpuTime.left
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
Prop.PropLabel {
|
||||||
|
id: cpuTime
|
||||||
|
visible: false // root.jobEnabled
|
||||||
|
width: 50
|
||||||
|
text: jobCpuTime
|
||||||
|
horizontalAlignment: Text.AlignLeft
|
||||||
|
anchors.rightMargin: 5
|
||||||
|
anchors.right:enabledIcon.right
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
Prop.PropCanvasIcon {
|
||||||
|
id: enabledIcon
|
||||||
|
anchors.right:parent.right
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
filled: root.jobEnabled
|
||||||
|
fillColor: (root.jobEnabled ? global.colorGreenHighlight : global.colorOrangeAccent)
|
||||||
|
icon: 5
|
||||||
|
iconMouseArea.onClicked: { toggleJobActivation() }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function populatePropItems() {
|
||||||
|
var propsModel = []
|
||||||
|
var props = Jet.job_propKeys(rootConfig.getConfig(jobPath));
|
||||||
|
// console.log(JSON.stringify(props));
|
||||||
|
if (showProps) {
|
||||||
|
for (var p in props) {
|
||||||
|
propsModel.push({"object": rootConfig.getConfig(jobPath), "property":props[p] })
|
||||||
|
}
|
||||||
|
root.updatePropItems(root.propItemsPanel, propsModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showSubs) {
|
||||||
|
Jet.task_traverse(rootConfig.getConfig(jobPath),
|
||||||
|
function(job, depth, index) {
|
||||||
|
var component = Qt.createComponent("./TaskPropView.qml");
|
||||||
|
component.createObject(root.propItemsPanel, {
|
||||||
|
"label": job.objectName,
|
||||||
|
"rootConfig": root.rootConfig,
|
||||||
|
"jobPath": root.jobPath + '.' + job.objectName,
|
||||||
|
"showProps": root.showProps,
|
||||||
|
"showSubs": root.showSubs,
|
||||||
|
"indentDepth": root.indentDepth + 1,
|
||||||
|
})
|
||||||
|
/* var component = Qt.createComponent("../../prop/PropItem.qml");
|
||||||
|
component.createObject(root.propItemsPanel, {
|
||||||
|
"label": root.jobPath + '.' + job.objectName + ' num=' + index,
|
||||||
|
})*/
|
||||||
|
// propsModel.push({"type": "printLabel", "label": root.jobPath + '.' + job.objectName + ' num=' + index })
|
||||||
|
|
||||||
|
return (depth < 1);
|
||||||
|
}, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
populatePropItems()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
TaskList 1.0 TaskList.qml
|
TaskList 1.0 TaskList.qml
|
||||||
TaskViewList 1.0 TaskViewList.qml
|
TaskViewList 1.0 TaskViewList.qml
|
||||||
TaskTimeFrameView 1.0 TaskTimeFrameView.qml
|
TaskTimeFrameView 1.0 TaskTimeFrameView.qml
|
||||||
|
TaskPropView 1.0 TaskPropView.qml
|
32
scripts/developer/utilities/lib/prop/PropBool.qml
Normal file
32
scripts/developer/utilities/lib/prop/PropBool.qml
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
//
|
||||||
|
// PropBool.qml
|
||||||
|
//
|
||||||
|
// Created by Sam Gateau on 3/2/2019
|
||||||
|
// Copyright 2019 High Fidelity, Inc.
|
||||||
|
//
|
||||||
|
// Distributed under the Apache License, Version 2.0.
|
||||||
|
// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
//
|
||||||
|
|
||||||
|
import QtQuick 2.7
|
||||||
|
|
||||||
|
PropItem {
|
||||||
|
Global { id: global }
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property alias valueVar : checkboxControl.checked
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
valueVar = root.valueVarGetter();
|
||||||
|
}
|
||||||
|
|
||||||
|
PropCheckBox {
|
||||||
|
id: checkboxControl
|
||||||
|
|
||||||
|
anchors.left: root.splitter.right
|
||||||
|
anchors.verticalCenter: root.verticalCenter
|
||||||
|
width: root.width * global.valueAreaWidthScale
|
||||||
|
|
||||||
|
onCheckedChanged: { root.valueVarSetter(checked); }
|
||||||
|
}
|
||||||
|
}
|
0
scripts/developer/utilities/lib/prop/PropColor.qml
Normal file
0
scripts/developer/utilities/lib/prop/PropColor.qml
Normal file
38
scripts/developer/utilities/lib/prop/PropEnum.qml
Normal file
38
scripts/developer/utilities/lib/prop/PropEnum.qml
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
//
|
||||||
|
// PropEnum.qml
|
||||||
|
//
|
||||||
|
// Created by Sam Gateau on 3/2/2019
|
||||||
|
// Copyright 2019 High Fidelity, Inc.
|
||||||
|
//
|
||||||
|
// Distributed under the Apache License, Version 2.0.
|
||||||
|
// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
//
|
||||||
|
|
||||||
|
import QtQuick 2.7
|
||||||
|
import QtQuick.Controls 2.2
|
||||||
|
|
||||||
|
PropItem {
|
||||||
|
Global { id: global }
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property alias valueVar : valueCombo.currentIndex
|
||||||
|
property alias enums : valueCombo.model
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
// valueVar = root.valueVarGetter();
|
||||||
|
}
|
||||||
|
|
||||||
|
PropComboBox {
|
||||||
|
id: valueCombo
|
||||||
|
|
||||||
|
flat: true
|
||||||
|
|
||||||
|
anchors.left: root.splitter.right
|
||||||
|
anchors.right: root.right
|
||||||
|
anchors.verticalCenter: root.verticalCenter
|
||||||
|
height: global.slimHeight
|
||||||
|
|
||||||
|
currentIndex: root.valueVarGetter()
|
||||||
|
onCurrentIndexChanged: { root.valueVarSetter(currentIndex); }
|
||||||
|
}
|
||||||
|
}
|
102
scripts/developer/utilities/lib/prop/PropGroup.qml
Normal file
102
scripts/developer/utilities/lib/prop/PropGroup.qml
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
//
|
||||||
|
// PropGroup.qml
|
||||||
|
//
|
||||||
|
// Created by Sam Gateau on 3/2/2019
|
||||||
|
// Copyright 2019 High Fidelity, Inc.
|
||||||
|
//
|
||||||
|
// Distributed under the Apache License, Version 2.0.
|
||||||
|
// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
//
|
||||||
|
|
||||||
|
import QtQuick 2.7
|
||||||
|
|
||||||
|
// PropGroup is mostly reusing the look and feel of the PropFolderPanel
|
||||||
|
// It is populated by calling "updatePropItems"
|
||||||
|
// or adding manually new Items to the "propItemsPanel"
|
||||||
|
PropFolderPanel {
|
||||||
|
Global { id: global }
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property alias propItemsPanel: root.panelFrameContent
|
||||||
|
|
||||||
|
// Prop Group is designed to author an array of ProItems, they are defined with an array of the tuplets describing each individual item:
|
||||||
|
// [ ..., PropItemInfo, ...]
|
||||||
|
// PropItemInfo {
|
||||||
|
// type: "PropXXXX", object: JSobject, property: "propName"
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
function updatePropItems(propItemsContainer, propItemsModel) {
|
||||||
|
root.hasContent = false
|
||||||
|
for (var i = 0; i < propItemsModel.length; i++) {
|
||||||
|
var proItem = propItemsModel[i];
|
||||||
|
// valid object
|
||||||
|
if (proItem['object'] !== undefined && proItem['object'] !== null ) {
|
||||||
|
// valid property
|
||||||
|
if (proItem['property'] !== undefined && proItem.object[proItem.property] !== undefined) {
|
||||||
|
// check type
|
||||||
|
if (proItem['type'] === undefined) {
|
||||||
|
proItem['type'] = typeof(proItem.object[proItem.property])
|
||||||
|
}
|
||||||
|
switch(proItem.type) {
|
||||||
|
case 'boolean':
|
||||||
|
case 'PropBool': {
|
||||||
|
var component = Qt.createComponent("PropBool.qml");
|
||||||
|
component.createObject(propItemsContainer, {
|
||||||
|
"label": proItem.property,
|
||||||
|
"object": proItem.object,
|
||||||
|
"property": proItem.property
|
||||||
|
})
|
||||||
|
} break;
|
||||||
|
case 'number':
|
||||||
|
case 'PropScalar': {
|
||||||
|
var component = Qt.createComponent("PropScalar.qml");
|
||||||
|
component.createObject(propItemsContainer, {
|
||||||
|
"label": proItem.property,
|
||||||
|
"object": proItem.object,
|
||||||
|
"property": proItem.property,
|
||||||
|
"min": (proItem["min"] !== undefined ? proItem.min : 0.0),
|
||||||
|
"max": (proItem["max"] !== undefined ? proItem.max : 1.0),
|
||||||
|
"integer": (proItem["integral"] !== undefined ? proItem.integral : false),
|
||||||
|
})
|
||||||
|
} break;
|
||||||
|
case 'PropEnum': {
|
||||||
|
var component = Qt.createComponent("PropEnum.qml");
|
||||||
|
component.createObject(propItemsContainer, {
|
||||||
|
"label": proItem.property,
|
||||||
|
"object": proItem.object,
|
||||||
|
"property": proItem.property,
|
||||||
|
"enums": (proItem["enums"] !== undefined ? proItem.enums : ["Undefined Enums !!!"]),
|
||||||
|
})
|
||||||
|
} break;
|
||||||
|
case 'object': {
|
||||||
|
var component = Qt.createComponent("PropItem.qml");
|
||||||
|
component.createObject(propItemsContainer, {
|
||||||
|
"label": proItem.property,
|
||||||
|
"object": proItem.object,
|
||||||
|
"property": proItem.property,
|
||||||
|
})
|
||||||
|
} break;
|
||||||
|
case 'printLabel': {
|
||||||
|
var component = Qt.createComponent("PropItem.qml");
|
||||||
|
component.createObject(propItemsContainer, {
|
||||||
|
"label": proItem.property
|
||||||
|
})
|
||||||
|
} break;
|
||||||
|
}
|
||||||
|
root.hasContent = true
|
||||||
|
} else {
|
||||||
|
console.log('Invalid property: ' + JSON.stringify(proItem));
|
||||||
|
}
|
||||||
|
} else if (proItem['type'] === 'printLabel') {
|
||||||
|
var component = Qt.createComponent("PropItem.qml");
|
||||||
|
component.createObject(propItemsContainer, {
|
||||||
|
"label": proItem.label
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
console.log('Invalid object: ' + JSON.stringify(proItem));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Component.onCompleted: {
|
||||||
|
}
|
||||||
|
}
|
63
scripts/developer/utilities/lib/prop/PropItem.qml
Normal file
63
scripts/developer/utilities/lib/prop/PropItem.qml
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
//
|
||||||
|
// PropItem.qml
|
||||||
|
//
|
||||||
|
// Created by Sam Gateau on 3/2/2019
|
||||||
|
// Copyright 2019 High Fidelity, Inc.
|
||||||
|
//
|
||||||
|
// Distributed under the Apache License, Version 2.0.
|
||||||
|
// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
//
|
||||||
|
|
||||||
|
import QtQuick 2.7
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Global { id: global }
|
||||||
|
|
||||||
|
id: root
|
||||||
|
|
||||||
|
// Prop item is designed to author an object[property]:
|
||||||
|
property var object: {}
|
||||||
|
property string property: ""
|
||||||
|
|
||||||
|
// value is accessed through the "valueVarSetter" and "valueVarGetter"
|
||||||
|
// By default, these just go get or set the value from the object[property]
|
||||||
|
//
|
||||||
|
function defaultGet() { return root.object[root.property]; }
|
||||||
|
function defaultSet(value) { root.object[root.property] = value; }
|
||||||
|
property var valueVarSetter: defaultSet
|
||||||
|
property var valueVarGetter: defaultGet
|
||||||
|
|
||||||
|
// PropItem is stretching horizontally accross its parent
|
||||||
|
// Fixed height
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
height: global.lineHeight
|
||||||
|
anchors.leftMargin: global.horizontalMargin
|
||||||
|
anchors.rightMargin: global.horizontalMargin
|
||||||
|
|
||||||
|
// LabelControl And SplitterControl are on the left side of the PropItem
|
||||||
|
property bool showLabel: true
|
||||||
|
property alias labelControl: labelControl
|
||||||
|
property alias label: labelControl.text
|
||||||
|
|
||||||
|
property var labelAreaWidth: root.width * global.splitterRightWidthScale - global.splitterWidth
|
||||||
|
|
||||||
|
PropText {
|
||||||
|
id: labelControl
|
||||||
|
text: root.label
|
||||||
|
enabled: root.showLabel
|
||||||
|
|
||||||
|
anchors.left: root.left
|
||||||
|
anchors.verticalCenter: root.verticalCenter
|
||||||
|
width: labelAreaWidth
|
||||||
|
}
|
||||||
|
|
||||||
|
property alias splitter: splitterControl
|
||||||
|
PropSplitter {
|
||||||
|
id: splitterControl
|
||||||
|
|
||||||
|
anchors.left: labelControl.right
|
||||||
|
size: global.splitterWidth
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
70
scripts/developer/utilities/lib/prop/PropScalar.qml
Normal file
70
scripts/developer/utilities/lib/prop/PropScalar.qml
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
//
|
||||||
|
// PropItem.qml
|
||||||
|
//
|
||||||
|
// Created by Sam Gateau on 3/2/2019
|
||||||
|
// Copyright 2019 High Fidelity, Inc.
|
||||||
|
//
|
||||||
|
// Distributed under the Apache License, Version 2.0.
|
||||||
|
// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
//
|
||||||
|
|
||||||
|
import QtQuick 2.7
|
||||||
|
|
||||||
|
import controlsUit 1.0 as HifiControls
|
||||||
|
|
||||||
|
PropItem {
|
||||||
|
Global { id: global }
|
||||||
|
id: root
|
||||||
|
|
||||||
|
// Scalar Prop
|
||||||
|
property bool integral: false
|
||||||
|
property var numDigits: 2
|
||||||
|
|
||||||
|
|
||||||
|
property alias valueVar : sliderControl.value
|
||||||
|
property alias min: sliderControl.minimumValue
|
||||||
|
property alias max: sliderControl.maximumValue
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
property bool showValue: true
|
||||||
|
|
||||||
|
|
||||||
|
signal valueChanged(real value)
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
valueVar = root.valueVarGetter();
|
||||||
|
}
|
||||||
|
|
||||||
|
PropLabel {
|
||||||
|
id: valueLabel
|
||||||
|
enabled: root.showValue
|
||||||
|
|
||||||
|
anchors.left: root.splitter.right
|
||||||
|
anchors.verticalCenter: root.verticalCenter
|
||||||
|
width: root.width * global.valueAreaWidthScale
|
||||||
|
horizontalAlignment: global.valueTextAlign
|
||||||
|
height: global.slimHeight
|
||||||
|
|
||||||
|
text: sliderControl.value.toFixed(root.integral ? 0 : root.numDigits)
|
||||||
|
|
||||||
|
background: Rectangle {
|
||||||
|
color: global.color
|
||||||
|
border.color: global.colorBorderLight
|
||||||
|
border.width: global.valueBorderWidth
|
||||||
|
radius: global.valueBorderRadius
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HifiControls.Slider {
|
||||||
|
id: sliderControl
|
||||||
|
stepSize: root.integral ? 1.0 : 0.0
|
||||||
|
anchors.left: valueLabel.right
|
||||||
|
anchors.right: root.right
|
||||||
|
anchors.verticalCenter: root.verticalCenter
|
||||||
|
|
||||||
|
onValueChanged: { root.valueVarSetter(value) }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
14
scripts/developer/utilities/lib/prop/qmldir
Normal file
14
scripts/developer/utilities/lib/prop/qmldir
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
Module Prop
|
||||||
|
Global 1.0 style/Global.qml
|
||||||
|
PropText 1.0 style/PiText.qml
|
||||||
|
PropLabel 1.0 style/PiLabel.qml
|
||||||
|
PropSplitter 1.0 style/PiSplitter.qml
|
||||||
|
PropComboBox 1.0 style/PiComboBox.qml
|
||||||
|
PropCanvasIcon 1.0 style/PiCanvasIcon.qml
|
||||||
|
PropCheckBox 1.0 style/PiCheckBox.qml
|
||||||
|
PropFolderPanel 1.0 style/PiFolderPanel.qml
|
||||||
|
|
||||||
|
PropItem 1.0 PropItem.qml
|
||||||
|
PropScalar 1.0 PropScalar.qml
|
||||||
|
PropEnum 1.0 PropEnum.qml
|
||||||
|
PropColor 1.0 PropColor.qml
|
54
scripts/developer/utilities/lib/prop/style/Global.qml
Normal file
54
scripts/developer/utilities/lib/prop/style/Global.qml
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
//
|
||||||
|
// Prop/style/Global.qml
|
||||||
|
//
|
||||||
|
// Created by Sam Gateau on 3/2/2019
|
||||||
|
// Copyright 2019 High Fidelity, Inc.
|
||||||
|
//
|
||||||
|
// Distributed under the Apache License, Version 2.0.
|
||||||
|
// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
//
|
||||||
|
|
||||||
|
import QtQuick 2.7
|
||||||
|
|
||||||
|
import stylesUit 1.0
|
||||||
|
import controlsUit 1.0 as HifiControls
|
||||||
|
|
||||||
|
|
||||||
|
Item {
|
||||||
|
HifiConstants { id: hifi }
|
||||||
|
id: root
|
||||||
|
|
||||||
|
readonly property real lineHeight: 32
|
||||||
|
readonly property real slimHeight: 24
|
||||||
|
|
||||||
|
readonly property real horizontalMargin: 4
|
||||||
|
|
||||||
|
readonly property color color: hifi.colors.baseGray
|
||||||
|
readonly property color colorBackShadow: hifi.colors.baseGrayShadow
|
||||||
|
readonly property color colorBackHighlight: hifi.colors.baseGrayHighlight
|
||||||
|
readonly property color colorBorderLight: hifi.colors.lightGray
|
||||||
|
readonly property color colorBorderHighight: hifi.colors.blueHighlight
|
||||||
|
|
||||||
|
readonly property color colorOrangeAccent: "#FF6309"
|
||||||
|
readonly property color colorRedAccent: "#C62147"
|
||||||
|
readonly property color colorGreenHighlight: "#1ac567"
|
||||||
|
|
||||||
|
readonly property real fontSize: 12
|
||||||
|
readonly property var fontFamily: "Raleway"
|
||||||
|
readonly property var fontWeight: Font.DemiBold
|
||||||
|
readonly property color fontColor: hifi.colors.faintGray
|
||||||
|
|
||||||
|
readonly property var splitterRightWidthScale: 0.45
|
||||||
|
readonly property real splitterWidth: 8
|
||||||
|
|
||||||
|
readonly property real iconWidth: fontSize
|
||||||
|
readonly property real iconHeight: fontSize
|
||||||
|
|
||||||
|
readonly property var labelTextAlign: Text.AlignRight
|
||||||
|
readonly property var labelTextElide: Text.ElideMiddle
|
||||||
|
|
||||||
|
readonly property var valueAreaWidthScale: 0.3 * (1.0 - splitterRightWidthScale)
|
||||||
|
readonly property var valueTextAlign: Text.AlignHCenter
|
||||||
|
readonly property real valueBorderWidth: 1
|
||||||
|
readonly property real valueBorderRadius: 2
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue