mirror of
https://github.com/AleziaKurdis/overte.git
synced 2025-04-08 18:32:34 +02:00
Merge branch 'master' of github.com:highfidelity/hifi into 22007-hifiQtBuildv2
This commit is contained in:
commit
88a89de7c6
278 changed files with 7295 additions and 2895 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";
|
||||
|
||||
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, "" };
|
||||
}
|
||||
|
||||
auto dotIndex = path.lastIndexOf(".");
|
||||
if (dotIndex == -1) {
|
||||
BakedAssetType type = assetTypeForFilename(path);
|
||||
if (type == BakedAssetType::Undefined) {
|
||||
return { AssetUtils::Irrelevant, "" };
|
||||
}
|
||||
|
||||
auto extension = path.mid(dotIndex + 1);
|
||||
bool loaded;
|
||||
AssetMeta meta;
|
||||
std::tie(loaded, meta) = readMetaFile(hash);
|
||||
|
||||
QString bakedFilename;
|
||||
|
||||
if (BAKEABLE_MODEL_EXTENSIONS.contains(extension)) {
|
||||
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 {
|
||||
// We create a meta file for Skyboxes at runtime when they get requested
|
||||
// Otherwise, textures don't get baked by themselves.
|
||||
if (type == BakedAssetType::Texture && !loaded) {
|
||||
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);
|
||||
if (jt != _fileMappings.end()) {
|
||||
if (jt->second == hash) {
|
||||
|
@ -168,14 +173,8 @@ std::pair<AssetUtils::BakingStatus, QString> AssetServer::getAssetStatus(const A
|
|||
} else {
|
||||
return { AssetUtils::Baked, "" };
|
||||
}
|
||||
} else {
|
||||
bool loaded;
|
||||
AssetMeta meta;
|
||||
|
||||
std::tie(loaded, meta) = readMetaFile(hash);
|
||||
if (loaded && meta.failedLastBake) {
|
||||
return { AssetUtils::Error, meta.lastBakeErrors };
|
||||
}
|
||||
} else if (loaded && meta.failedLastBake) {
|
||||
return { AssetUtils::Error, meta.lastBakeErrors };
|
||||
}
|
||||
|
||||
return { AssetUtils::Pending, "" };
|
||||
|
@ -227,8 +226,16 @@ bool AssetServer::needsToBeBaked(const AssetUtils::AssetPath& path, const AssetU
|
|||
return false;
|
||||
}
|
||||
|
||||
bool loaded;
|
||||
AssetMeta meta;
|
||||
std::tie(loaded, meta) = readMetaFile(assetHash);
|
||||
|
||||
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);
|
||||
bool bakedMappingExists = mappingIt != _fileMappings.end();
|
||||
|
||||
|
@ -238,10 +245,8 @@ bool AssetServer::needsToBeBaked(const AssetUtils::AssetPath& path, const AssetU
|
|||
return false;
|
||||
}
|
||||
|
||||
bool loaded;
|
||||
AssetMeta meta;
|
||||
std::tie(loaded, meta) = readMetaFile(assetHash);
|
||||
|
||||
// We create a meta file for Skyboxes at runtime when they get requested
|
||||
// Otherwise, textures don't get baked by themselves.
|
||||
if (type == BakedAssetType::Texture && !loaded) {
|
||||
return false;
|
||||
}
|
||||
|
@ -633,36 +638,33 @@ void AssetServer::handleGetMappingOperation(ReceivedMessage& message, NLPacketLi
|
|||
if (it != _fileMappings.end()) {
|
||||
|
||||
// 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;
|
||||
QString redirectedAssetHash;
|
||||
QString bakedAssetPath;
|
||||
quint8 wasRedirected = false;
|
||||
bool bakingDisabled = false;
|
||||
|
||||
if (!bakedRootFile.isEmpty()) {
|
||||
// we ran into an asset for which we could have a baked version, let's check if it's ready
|
||||
bakedAssetPath = AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER + originalAssetHash + "/" + bakedRootFile;
|
||||
auto bakedIt = _fileMappings.find(bakedAssetPath);
|
||||
bool loaded;
|
||||
AssetMeta meta;
|
||||
std::tie(loaded, meta) = readMetaFile(originalAssetHash);
|
||||
|
||||
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 {
|
||||
qDebug() << "Did not find baked version for: " << originalAssetHash << assetPath << " (disabled)";
|
||||
bakingDisabled = true;
|
||||
}
|
||||
auto type = assetTypeForFilename(assetPath);
|
||||
QString bakedRootFile = bakedFilenameForAssetType(type);
|
||||
QString bakedAssetPath = getBakeMapping(originalAssetHash, bakedRootFile);
|
||||
|
||||
if (loaded && !meta.redirectTarget.isEmpty()) {
|
||||
bakedAssetPath = meta.redirectTarget;
|
||||
}
|
||||
|
||||
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 {
|
||||
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());
|
||||
bool isSkybox = query.hasQueryItem("skybox");
|
||||
if (isSkybox) {
|
||||
bool loaded;
|
||||
AssetMeta meta;
|
||||
std::tie(loaded, meta) = readMetaFile(originalAssetHash);
|
||||
|
||||
if (!loaded) {
|
||||
AssetMeta needsBakingMeta;
|
||||
needsBakingMeta.bakeVersion = NEEDS_BAKING_BAKE_VERSION;
|
||||
|
||||
writeMetaFile(originalAssetHash, needsBakingMeta);
|
||||
if (!bakingDisabled) {
|
||||
maybeBake(assetPath, originalAssetHash);
|
||||
}
|
||||
if (isSkybox && !loaded) {
|
||||
AssetMeta needsBakingMeta;
|
||||
needsBakingMeta.bakeVersion = NEEDS_BAKING_BAKE_VERSION;
|
||||
|
||||
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) {
|
||||
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,
|
||||
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 };
|
||||
QString errorReason;
|
||||
QString redirectTarget;
|
||||
|
||||
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) {
|
||||
// figure out the hash for the contents of this file
|
||||
QFile file(filePath);
|
||||
|
@ -1340,89 +1393,72 @@ void AssetServer::handleCompletedBake(QString originalAssetHash, QString origina
|
|||
|
||||
AssetUtils::AssetHash bakedFileHash;
|
||||
|
||||
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 {
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
qDebug() << "Failed to open baked file: " << filePath;
|
||||
// stop handling this bake, we couldn't open one of the files for reading
|
||||
errorCompletingBake = true;
|
||||
errorReason = "Failed to finalize bake";
|
||||
errorReason = "Could not open baked file " + file.fileName();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& filePath : bakedFilePaths) {
|
||||
QFile file(filePath);
|
||||
if (!file.remove()) {
|
||||
qWarning() << "Failed to remove temporary file:" << filePath;
|
||||
QCryptographicHash hasher(QCryptographicHash::Sha256);
|
||||
|
||||
if (!hasher.addData(&file)) {
|
||||
// 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(".")) {
|
||||
qWarning() << "Failed to remove temporary directory:" << bakedTempOutputDir;
|
||||
|
||||
bakedFileHash = hasher.result().toHex();
|
||||
|
||||
// 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;
|
||||
meta.bakeVersion = currentTypeVersion;
|
||||
meta.failedLastBake = errorCompletingBake;
|
||||
|
||||
if (errorCompletingBake) {
|
||||
qWarning() << "Could not complete bake for" << originalAssetHash;
|
||||
meta.lastBakeErrors = errorReason;
|
||||
if (redirectTarget.isEmpty()) {
|
||||
errorCompletingBake = true;
|
||||
errorReason = "Could not find root file for baked output";
|
||||
}
|
||||
|
||||
writeMetaFile(originalAssetHash, meta);
|
||||
|
||||
_pendingBakes.remove(originalAssetHash);
|
||||
// Cleanup temporary output directory
|
||||
PathUtils::deleteMyTemporaryDir(outputDirName);
|
||||
reportCompletion(errorCompletingBake, errorReason, redirectTarget);
|
||||
}
|
||||
|
||||
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 FAILED_LAST_BAKE_KEY = "failed_last_bake";
|
||||
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) {
|
||||
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 failedLastBake = root[FAILED_LAST_BAKE_KEY];
|
||||
auto lastBakeErrors = root[LAST_BAKE_ERRORS_KEY];
|
||||
auto redirectTarget = root[REDIRECT_TARGET_KEY];
|
||||
|
||||
if (bakeVersion.isDouble()
|
||||
&& failedLastBake.isBool()
|
||||
|
@ -1470,6 +1508,7 @@ std::pair<bool, AssetMeta> AssetServer::readMetaFile(AssetUtils::AssetHash hash)
|
|||
meta.bakeVersion = bakeVersion.toInt();
|
||||
meta.failedLastBake = failedLastBake.toBool();
|
||||
meta.lastBakeErrors = lastBakeErrors.toString();
|
||||
meta.redirectTarget = redirectTarget.toString();
|
||||
|
||||
return { true, meta };
|
||||
} else {
|
||||
|
@ -1488,6 +1527,7 @@ bool AssetServer::writeMetaFile(AssetUtils::AssetHash originalAssetHash, const A
|
|||
metaFileObject[BAKE_VERSION_KEY] = (int)meta.bakeVersion;
|
||||
metaFileObject[FAILED_LAST_BAKE_KEY] = meta.failedLastBake;
|
||||
metaFileObject[LAST_BAKE_ERRORS_KEY] = meta.lastBakeErrors;
|
||||
metaFileObject[REDIRECT_TARGET_KEY] = meta.redirectTarget;
|
||||
|
||||
QJsonDocument metaFileDoc;
|
||||
metaFileDoc.setObject(metaFileObject);
|
||||
|
@ -1521,10 +1561,18 @@ bool AssetServer::setBakingEnabled(const AssetUtils::AssetPathList& paths, bool
|
|||
if (type == BakedAssetType::Undefined) {
|
||||
continue;
|
||||
}
|
||||
QString bakedFilename = bakedFilenameForAssetType(type);
|
||||
|
||||
auto hash = it->second;
|
||||
|
||||
bool loaded;
|
||||
AssetMeta meta;
|
||||
std::tie(loaded, meta) = readMetaFile(hash);
|
||||
|
||||
QString bakedFilename = bakedFilenameForAssetType(type);
|
||||
auto bakedMapping = getBakeMapping(hash, bakedFilename);
|
||||
if (loaded && !meta.redirectTarget.isEmpty()) {
|
||||
bakedMapping = meta.redirectTarget;
|
||||
}
|
||||
|
||||
auto it = _fileMappings.find(bakedMapping);
|
||||
bool currentlyDisabled = (it != _fileMappings.end() && it->second == hash);
|
||||
|
|
|
@ -62,12 +62,10 @@ enum class ScriptBakeVersion : BakeVersion {
|
|||
};
|
||||
|
||||
struct AssetMeta {
|
||||
AssetMeta() {
|
||||
}
|
||||
|
||||
BakeVersion bakeVersion { INITIAL_BAKE_VERSION };
|
||||
bool failedLastBake { false };
|
||||
QString lastBakeErrors;
|
||||
QString redirectTarget;
|
||||
};
|
||||
|
||||
class BakeAssetTask;
|
||||
|
@ -139,8 +137,7 @@ private:
|
|||
void bakeAsset(const AssetUtils::AssetHash& assetHash, const AssetUtils::AssetPath& assetPath, const QString& filePath);
|
||||
|
||||
/// Move baked content for asset to baked directory and update baked status
|
||||
void handleCompletedBake(QString originalAssetHash, QString assetPath, QString bakedTempOutputDir,
|
||||
QVector<QString> bakedFilePaths);
|
||||
void handleCompletedBake(QString originalAssetHash, QString assetPath, QString bakedTempOutputDir);
|
||||
void handleFailedBake(QString originalAssetHash, QString assetPath, QString errors);
|
||||
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() {
|
||||
if (_isBaking.exchange(true)) {
|
||||
qWarning() << "Tried to start bake asset task while already baking";
|
||||
return;
|
||||
}
|
||||
|
||||
// Make a new temporary directory for the Oven to work in
|
||||
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();
|
||||
QString path = base.absolutePath() + "/oven";
|
||||
QString extension = _assetPath.mid(_assetPath.lastIndexOf('.') + 1);
|
||||
QStringList args {
|
||||
"-i", _filePath,
|
||||
"-i", tempAssetPath,
|
||||
"-o", tempOutputDir,
|
||||
"-t", extension,
|
||||
};
|
||||
|
@ -72,10 +77,11 @@ void BakeAssetTask::run() {
|
|||
QEventLoop loop;
|
||||
|
||||
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;
|
||||
|
||||
if (exitStatus == QProcess::CrashExit) {
|
||||
PathUtils::deleteMyTemporaryDir(tempOutputDirName);
|
||||
if (_wasAborted) {
|
||||
emit bakeAborted(_assetHash, _assetPath);
|
||||
} else {
|
||||
|
@ -83,16 +89,10 @@ void BakeAssetTask::run() {
|
|||
emit bakeFailed(_assetHash, _assetPath, errors);
|
||||
}
|
||||
} else if (exitCode == OVEN_STATUS_CODE_SUCCESS) {
|
||||
QDir outputDir = 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);
|
||||
emit bakeComplete(_assetHash, _assetPath, tempOutputDir);
|
||||
} else if (exitStatus == QProcess::NormalExit && exitCode == OVEN_STATUS_CODE_ABORT) {
|
||||
_wasAborted.store(true);
|
||||
PathUtils::deleteMyTemporaryDir(tempOutputDirName);
|
||||
emit bakeAborted(_assetHash, _assetPath);
|
||||
} else {
|
||||
QString errors;
|
||||
|
@ -107,6 +107,7 @@ void BakeAssetTask::run() {
|
|||
errors = "Unknown error occurred while baking";
|
||||
}
|
||||
}
|
||||
PathUtils::deleteMyTemporaryDir(tempOutputDirName);
|
||||
emit bakeFailed(_assetHash, _assetPath, errors);
|
||||
}
|
||||
|
||||
|
@ -115,7 +116,10 @@ void BakeAssetTask::run() {
|
|||
|
||||
qDebug() << "Starting oven for " << _assetPath;
|
||||
_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";
|
||||
emit bakeFailed(_assetHash, _assetPath, errors);
|
||||
return;
|
||||
|
|
|
@ -37,7 +37,7 @@ public slots:
|
|||
void abort();
|
||||
|
||||
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 bakeAborted(QString assetHash, QString assetPath);
|
||||
|
||||
|
|
|
@ -155,6 +155,12 @@ int AvatarMixerClientData::parseData(ReceivedMessage& message, const SlaveShared
|
|||
void AvatarMixerClientData::processSetTraitsMessage(ReceivedMessage& message,
|
||||
const SlaveSharedData& slaveSharedData,
|
||||
Node& sendingNode) {
|
||||
// Trying to read more bytes than available, bail
|
||||
if (message.getBytesLeftToRead() < qint64(sizeof(AvatarTraits::TraitVersion))) {
|
||||
qWarning() << "Refusing to process malformed traits packet from" << message.getSenderSockAddr();
|
||||
return;
|
||||
}
|
||||
|
||||
// pull the trait version from the message
|
||||
AvatarTraits::TraitVersion packetTraitVersion;
|
||||
message.readPrimitive(&packetTraitVersion);
|
||||
|
@ -164,10 +170,22 @@ void AvatarMixerClientData::processSetTraitsMessage(ReceivedMessage& message,
|
|||
while (message.getBytesLeftToRead() > 0) {
|
||||
// for each trait in the packet, apply it if the trait version is newer than what we have
|
||||
|
||||
// Trying to read more bytes than available, bail
|
||||
if (message.getBytesLeftToRead() < qint64(sizeof(AvatarTraits::TraitType))) {
|
||||
qWarning() << "Refusing to process malformed traits packet from" << message.getSenderSockAddr();
|
||||
return;
|
||||
}
|
||||
|
||||
AvatarTraits::TraitType traitType;
|
||||
message.readPrimitive(&traitType);
|
||||
|
||||
if (AvatarTraits::isSimpleTrait(traitType)) {
|
||||
// Trying to read more bytes than available, bail
|
||||
if (message.getBytesLeftToRead() < qint64(sizeof(AvatarTraits::TraitWireSize))) {
|
||||
qWarning() << "Refusing to process malformed traits packet from" << message.getSenderSockAddr();
|
||||
return;
|
||||
}
|
||||
|
||||
AvatarTraits::TraitWireSize traitSize;
|
||||
message.readPrimitive(&traitSize);
|
||||
|
||||
|
@ -179,7 +197,6 @@ void AvatarMixerClientData::processSetTraitsMessage(ReceivedMessage& message,
|
|||
if (packetTraitVersion > _lastReceivedTraitVersions[traitType]) {
|
||||
_avatar->processTrait(traitType, message.read(traitSize));
|
||||
_lastReceivedTraitVersions[traitType] = packetTraitVersion;
|
||||
|
||||
if (traitType == AvatarTraits::SkeletonModelURL) {
|
||||
// special handling for skeleton model URL, since we need to make sure it is in the whitelist
|
||||
checkSkeletonURLAgainstWhitelist(slaveSharedData, sendingNode, packetTraitVersion);
|
||||
|
@ -190,13 +207,15 @@ void AvatarMixerClientData::processSetTraitsMessage(ReceivedMessage& message,
|
|||
message.seek(message.getPosition() + traitSize);
|
||||
}
|
||||
} else {
|
||||
AvatarTraits::TraitInstanceID instanceID = QUuid::fromRfc4122(message.readWithoutCopy(NUM_BYTES_RFC4122_UUID));
|
||||
|
||||
if (message.getBytesLeftToRead() == 0) {
|
||||
qWarning() << "Received an instanced trait with no size from" << message.getSenderSockAddr();
|
||||
break;
|
||||
// Trying to read more bytes than available, bail
|
||||
if (message.getBytesLeftToRead() < qint64(NUM_BYTES_RFC4122_UUID +
|
||||
sizeof(AvatarTraits::TraitWireSize))) {
|
||||
qWarning() << "Refusing to process malformed traits packet from" << message.getSenderSockAddr();
|
||||
return;
|
||||
}
|
||||
|
||||
AvatarTraits::TraitInstanceID instanceID = QUuid::fromRfc4122(message.readWithoutCopy(NUM_BYTES_RFC4122_UUID));
|
||||
|
||||
AvatarTraits::TraitWireSize traitSize;
|
||||
message.readPrimitive(&traitSize);
|
||||
|
||||
|
|
|
@ -62,8 +62,8 @@
|
|||
* @property {boolean} lookAtSnappingEnabled=true - <code>true</code> if the avatar's eyes snap to look at another avatar's
|
||||
* eyes when the other avatar is in the line of sight and also has <code>lookAtSnappingEnabled == true</code>.
|
||||
* @property {string} skeletonModelURL - The avatar's FST file.
|
||||
* @property {AttachmentData[]} attachmentData - Information on the avatar's attachments.<br />
|
||||
* <strong>Deprecated:</strong> Use avatar entities instead.
|
||||
* @property {AttachmentData[]} attachmentData - Information on the avatar's attachments.
|
||||
* <p class="important">Deprecated: This property is deprecated and will be removed. Use avatar entities instead.</p>
|
||||
* @property {string[]} jointNames - The list of joints in the current avatar model. <em>Read-only.</em>
|
||||
* @property {Uuid} sessionUUID - Unique ID of the avatar in the domain. <em>Read-only.</em>
|
||||
* @property {Mat4} sensorToWorldMatrix - The scale, rotation, and translation transform from the user's real world to the
|
||||
|
|
|
@ -56,7 +56,8 @@ public slots:
|
|||
/**jsdoc
|
||||
* @function EntityViewer.setKeyholeRadius
|
||||
* @param {number} radius
|
||||
* @deprecated Use {@link EntityViewer.setCenterRadius|setCenterRadius} instead.
|
||||
* @deprecated This function is deprecated and will be removed. Use {@link EntityViewer.setCenterRadius|setCenterRadius}
|
||||
* instead.
|
||||
*/
|
||||
void setKeyholeRadius(float radius) { _hasViewFrustum = true; _viewFrustum.setCenterRadius(radius); } // TODO: remove this legacy support
|
||||
|
||||
|
|
|
@ -89,7 +89,7 @@ if (APPLE)
|
|||
string(REGEX MATCH "^[0-9]+\\.[0-9]+" OSX_VERSION ${OSX_VERSION})
|
||||
message(STATUS "Detected OS X version = ${OSX_VERSION}")
|
||||
|
||||
set(OSX_SDK "${OSX_VERSION}" CACHE String "OS X SDK version to look for inside Xcode bundle or at OSX_SDK_PATH")
|
||||
set(OSX_SDK "${OSX_VERSION}" CACHE STRING "OS X SDK version to look for inside Xcode bundle or at OSX_SDK_PATH")
|
||||
|
||||
# set our OS X deployment target
|
||||
set(CMAKE_OSX_DEPLOYMENT_TARGET 10.9)
|
||||
|
|
26
cmake/externals/LibOVR/CMakeLists.txt
vendored
26
cmake/externals/LibOVR/CMakeLists.txt
vendored
|
@ -17,8 +17,8 @@ if (WIN32)
|
|||
|
||||
ExternalProject_Add(
|
||||
${EXTERNAL_NAME}
|
||||
URL https://public.highfidelity.com/dependencies/ovr_sdk_win_1.26.0_public.zip
|
||||
URL_MD5 06804ff9727b910dcd04a37c800053b5
|
||||
URL https://hifi-public.s3.amazonaws.com/dependencies/ovr_sdk_win_1.35.0.zip
|
||||
URL_MD5 1e3e8b2101387af07ff9c841d0ea285e
|
||||
CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR>
|
||||
PATCH_COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_SOURCE_DIR}/LibOVRCMakeLists.txt" <SOURCE_DIR>/CMakeLists.txt
|
||||
LOG_DOWNLOAD 1
|
||||
|
@ -27,12 +27,12 @@ if (WIN32)
|
|||
ExternalProject_Get_Property(${EXTERNAL_NAME} SOURCE_DIR)
|
||||
ExternalProject_Get_Property(${EXTERNAL_NAME} INSTALL_DIR)
|
||||
set(LIBOVR_DIR ${INSTALL_DIR})
|
||||
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${LIBOVR_DIR}/Include CACHE TYPE INTERNAL)
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG ${LIBOVR_DIR}/Lib/LibOVRd.lib CACHE TYPE INTERNAL)
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${LIBOVR_DIR}/Lib/LibOVR.lib CACHE TYPE INTERNAL)
|
||||
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${LIBOVR_DIR}/Include CACHE STRING INTERNAL)
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG ${LIBOVR_DIR}/Lib/LibOVRd.lib CACHE STRING INTERNAL)
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${LIBOVR_DIR}/Lib/LibOVR.lib CACHE STRING INTERNAL)
|
||||
include(SelectLibraryConfigurations)
|
||||
select_library_configurations(LIBOVR)
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${${EXTERNAL_NAME_UPPER}_LIBRARIES} CACHE TYPE INTERNAL)
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${${EXTERNAL_NAME_UPPER}_LIBRARIES} CACHE STRING INTERNAL)
|
||||
|
||||
elseif(APPLE)
|
||||
|
||||
|
@ -50,11 +50,11 @@ elseif(APPLE)
|
|||
# In theory we should use the Headers path inside the framework, as seen here
|
||||
# but unfortunately Oculus doesn't seem to have figured out automated testing
|
||||
# so they released a framework with missing headers.
|
||||
#set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/LibOVR/Lib/Mac/Release/LibOVR.framework/Headers/ CACHE TYPE INTERNAL)
|
||||
#set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/LibOVR/Lib/Mac/Release/LibOVR.framework/Headers/ CACHE STRING INTERNAL)
|
||||
|
||||
# Work around the broken framework by using a different path for the headers.
|
||||
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/LibOVR/Include CACHE TYPE INTERNAL)
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${SOURCE_DIR}/LibOVR/Lib/Mac/Release/LibOVR.framework/LibOVR CACHE TYPE INTERNAL)
|
||||
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/LibOVR/Include CACHE STRING INTERNAL)
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${SOURCE_DIR}/LibOVR/Lib/Mac/Release/LibOVR.framework/LibOVR CACHE STRING INTERNAL)
|
||||
|
||||
|
||||
|
||||
|
@ -74,8 +74,8 @@ elseif(NOT ANDROID)
|
|||
ExternalProject_Get_Property(${EXTERNAL_NAME} SOURCE_DIR)
|
||||
ExternalProject_Get_Property(${EXTERNAL_NAME} INSTALL_DIR)
|
||||
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${INSTALL_DIR}/lib/libovr.a CACHE TYPE INTERNAL)
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG "" CACHE TYPE INTERNAL)
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${INSTALL_DIR}/lib/libovr.a CACHE STRING INTERNAL)
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG "" CACHE STRING INTERNAL)
|
||||
|
||||
find_package(Threads REQUIRED)
|
||||
find_package(X11 REQUIRED)
|
||||
|
@ -89,8 +89,8 @@ elseif(NOT ANDROID)
|
|||
|
||||
select_library_configurations(${EXTERNAL_NAME_UPPER})
|
||||
|
||||
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/LibOVR/Include ${SOURCE_DIR}/LibOVR/Src CACHE TYPE INTERNAL)
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${${EXTERNAL_NAME_UPPER}_LIBRARY} ${${EXTERNAL_NAME_UPPER}_LIBRARY_EXTRAS} CACHE TYPE INTERNAL)
|
||||
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/LibOVR/Include ${SOURCE_DIR}/LibOVR/Src CACHE STRING INTERNAL)
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${${EXTERNAL_NAME_UPPER}_LIBRARY} ${${EXTERNAL_NAME_UPPER}_LIBRARY_EXTRAS} CACHE STRING INTERNAL)
|
||||
endif()
|
||||
|
||||
# Hide this external target (for ide users)
|
||||
|
|
|
@ -20,12 +20,12 @@ if (WIN32)
|
|||
ExternalProject_Get_Property(${EXTERNAL_NAME} SOURCE_DIR)
|
||||
|
||||
if ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8")
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${SOURCE_DIR}/Windows/LibOVRPlatform64_1.lib CACHE TYPE INTERNAL)
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${SOURCE_DIR}/Windows/LibOVRPlatform64_1.lib CACHE STRING INTERNAL)
|
||||
else()
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${SOURCE_DIR}/Windows/LibOVRPlatform32_1.lib CACHE TYPE INTERNAL)
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${SOURCE_DIR}/Windows/LibOVRPlatform32_1.lib CACHE STRING INTERNAL)
|
||||
endif()
|
||||
|
||||
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/Include CACHE TYPE INTERNAL)
|
||||
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/Include CACHE STRING INTERNAL)
|
||||
endif ()
|
||||
|
||||
# Hide this external target (for ide users)
|
||||
|
|
|
@ -36,10 +36,10 @@ set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals")
|
|||
|
||||
ExternalProject_Get_Property(${EXTERNAL_NAME} SOURCE_DIR)
|
||||
|
||||
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/include CACHE TYPE INTERNAL)
|
||||
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/include CACHE STRING INTERNAL)
|
||||
|
||||
if (WIN32)
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${SOURCE_DIR}/Release/audio.lib CACHE TYPE INTERNAL)
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${SOURCE_DIR}/Release/audio.lib CACHE STRING INTERNAL)
|
||||
else()
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${SOURCE_DIR}/Release/libaudio.a CACHE TYPE INTERNAL)
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${SOURCE_DIR}/Release/libaudio.a CACHE STRING INTERNAL)
|
||||
endif()
|
||||
|
|
12
cmake/externals/neuron/CMakeLists.txt
vendored
12
cmake/externals/neuron/CMakeLists.txt
vendored
|
@ -21,9 +21,9 @@ set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals")
|
|||
|
||||
# set include dir
|
||||
if(WIN32)
|
||||
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS "${SOURCE_DIR}/NeuronDataReader_Windows/include" CACHE TYPE INTERNAL)
|
||||
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS "${SOURCE_DIR}/NeuronDataReader_Windows/include" CACHE STRING INTERNAL)
|
||||
elseif(APPLE)
|
||||
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS "${SOURCE_DIR}/NeuronDataReader_Mac/include" CACHE TYPE INTERNAL)
|
||||
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS "${SOURCE_DIR}/NeuronDataReader_Mac/include" CACHE STRING INTERNAL)
|
||||
else()
|
||||
# Unsupported
|
||||
endif()
|
||||
|
@ -37,16 +37,16 @@ if(WIN32)
|
|||
endif()
|
||||
|
||||
set(${EXTERNAL_NAME_UPPER}_LIB_PATH "${SOURCE_DIR}/NeuronDataReader_Windows/lib/${ARCH_DIR}")
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE "${${EXTERNAL_NAME_UPPER}_LIB_PATH}/NeuronDataReader.lib" CACHE TYPE INTERNAL)
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARIES "${${EXTERNAL_NAME_UPPER}_LIB_PATH}/NeuronDataReader.lib" CACHE TYPE INTERNAL)
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE "${${EXTERNAL_NAME_UPPER}_LIB_PATH}/NeuronDataReader.lib" CACHE STRING INTERNAL)
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARIES "${${EXTERNAL_NAME_UPPER}_LIB_PATH}/NeuronDataReader.lib" CACHE STRING INTERNAL)
|
||||
|
||||
add_paths_to_fixup_libs("${${EXTERNAL_NAME_UPPER}_LIB_PATH}")
|
||||
|
||||
elseif(APPLE)
|
||||
|
||||
set(${EXTERNAL_NAME_UPPER}_LIB_PATH "${SOURCE_DIR}/NeuronDataReader_Mac/dylib")
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE "${${EXTERNAL_NAME_UPPER}_LIB_PATH}/NeuronDataReader.dylib" CACHE TYPE INTERNAL)
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARIES "${${EXTERNAL_NAME_UPPER}_LIB_PATH}/NeuronDataReader.dylib" CACHE TYPE INTERNAL)
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE "${${EXTERNAL_NAME_UPPER}_LIB_PATH}/NeuronDataReader.dylib" CACHE STRING INTERNAL)
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARIES "${${EXTERNAL_NAME_UPPER}_LIB_PATH}/NeuronDataReader.dylib" CACHE STRING INTERNAL)
|
||||
|
||||
add_paths_to_fixup_libs("${${EXTERNAL_NAME_UPPER}_LIB_PATH}")
|
||||
|
||||
|
|
6
cmake/externals/sixense/CMakeLists.txt
vendored
6
cmake/externals/sixense/CMakeLists.txt
vendored
|
@ -30,7 +30,7 @@ set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals")
|
|||
|
||||
ExternalProject_Get_Property(${EXTERNAL_NAME} SOURCE_DIR)
|
||||
|
||||
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/include CACHE TYPE INTERNAL)
|
||||
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/include CACHE STRING INTERNAL)
|
||||
|
||||
if (WIN32)
|
||||
|
||||
|
@ -52,7 +52,7 @@ if (WIN32)
|
|||
set(${EXTERNAL_NAME_UPPER}_LIB_PATH "${SOURCE_DIR}/lib/${ARCH_DIR}/release_dll")
|
||||
endif()
|
||||
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE "${${EXTERNAL_NAME_UPPER}_LIB_PATH}/sixense${ARCH_SUFFIX}.lib" CACHE TYPE INTERNAL)
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE "${${EXTERNAL_NAME_UPPER}_LIB_PATH}/sixense${ARCH_SUFFIX}.lib" CACHE STRING INTERNAL)
|
||||
add_paths_to_fixup_libs("${${EXTERNAL_NAME_UPPER}_DLL_PATH}")
|
||||
|
||||
elseif(APPLE)
|
||||
|
@ -62,7 +62,7 @@ elseif(APPLE)
|
|||
elseif(NOT ANDROID)
|
||||
|
||||
# FIXME need to account for different architectures
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${SOURCE_DIR}/lib/linux_x64/release/libsixense_x64.so CACHE TYPE INTERNAL)
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${SOURCE_DIR}/lib/linux_x64/release/libsixense_x64.so CACHE STRING INTERNAL)
|
||||
|
||||
endif()
|
||||
|
||||
|
|
8
cmake/externals/steamworks/CMakeLists.txt
vendored
8
cmake/externals/steamworks/CMakeLists.txt
vendored
|
@ -21,7 +21,7 @@ set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals")
|
|||
|
||||
ExternalProject_Get_Property(${EXTERNAL_NAME} SOURCE_DIR)
|
||||
|
||||
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/public CACHE TYPE INTERNAL)
|
||||
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/public CACHE STRING INTERNAL)
|
||||
|
||||
if (WIN32)
|
||||
|
||||
|
@ -36,12 +36,12 @@ if (WIN32)
|
|||
set(${EXTERNAL_NAME_UPPER}_DLL_PATH ${ARCH_DIR})
|
||||
set(${EXTERNAL_NAME_UPPER}_LIB_PATH ${ARCH_DIR})
|
||||
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE "${${EXTERNAL_NAME_UPPER}_LIB_PATH}/steam_api${ARCH_SUFFIX}.lib" CACHE TYPE INTERNAL)
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE "${${EXTERNAL_NAME_UPPER}_LIB_PATH}/steam_api${ARCH_SUFFIX}.lib" CACHE STRING INTERNAL)
|
||||
add_paths_to_fixup_libs("${${EXTERNAL_NAME_UPPER}_DLL_PATH}")
|
||||
|
||||
elseif(APPLE)
|
||||
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${SOURCE_DIR}/redistributable_bin/osx32/libsteam_api.dylib CACHE TYPE INTERNAL)
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${SOURCE_DIR}/redistributable_bin/osx32/libsteam_api.dylib CACHE STRING INTERNAL)
|
||||
|
||||
set(_STEAMWORKS_LIB_DIR "${SOURCE_DIR}/redistributable_bin/osx32")
|
||||
ExternalProject_Add_Step(
|
||||
|
@ -57,6 +57,6 @@ elseif(APPLE)
|
|||
elseif(NOT ANDROID)
|
||||
|
||||
# FIXME need to account for different architectures
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${SOURCE_DIR}/redistributable_bin/linux64/libsteam_api.so CACHE TYPE INTERNAL)
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${SOURCE_DIR}/redistributable_bin/linux64/libsteam_api.so CACHE STRING INTERNAL)
|
||||
|
||||
endif()
|
||||
|
|
2
cmake/externals/tbb/CMakeLists.txt
vendored
2
cmake/externals/tbb/CMakeLists.txt
vendored
|
@ -102,6 +102,6 @@ if (DEFINED _TBB_LIB_DIR)
|
|||
endif ()
|
||||
|
||||
if (DEFINED ${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE)
|
||||
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/include CACHE TYPE "List of tbb include directories")
|
||||
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/include CACHE STRING "List of tbb include directories")
|
||||
endif ()
|
||||
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 153 KiB After Width: | Height: | Size: 299 KiB |
|
@ -8,7 +8,7 @@
|
|||
macro(TARGET_BREAKPAD)
|
||||
if (ANDROID)
|
||||
set(INSTALL_DIR ${HIFI_ANDROID_PRECOMPILED}/breakpad)
|
||||
set(BREAKPAD_INCLUDE_DIRS "${INSTALL_DIR}/include" CACHE TYPE INTERNAL)
|
||||
set(BREAKPAD_INCLUDE_DIRS "${INSTALL_DIR}/include" CACHE STRING INTERNAL)
|
||||
set(LIB_DIR ${INSTALL_DIR}/lib)
|
||||
list(APPEND BREAKPAD_LIBRARIES ${LIB_DIR}/libbreakpad_client.a)
|
||||
target_include_directories(${TARGET_NAME} SYSTEM PRIVATE ${BREAKPAD_INCLUDE_DIRS})
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
macro(TARGET_BULLET)
|
||||
if (ANDROID)
|
||||
set(INSTALL_DIR ${HIFI_ANDROID_PRECOMPILED}/bullet)
|
||||
set(BULLET_INCLUDE_DIRS "${INSTALL_DIR}/include/bullet" CACHE TYPE INTERNAL)
|
||||
set(BULLET_INCLUDE_DIRS "${INSTALL_DIR}/include/bullet" CACHE STRING INTERNAL)
|
||||
|
||||
set(LIB_DIR ${INSTALL_DIR}/lib)
|
||||
list(APPEND BULLET_LIBRARIES ${LIB_DIR}/libBulletDynamics.a)
|
||||
|
|
|
@ -3,7 +3,7 @@ macro(TARGET_DRACO)
|
|||
find_library(LIBPATH ${LIB} PATHS )
|
||||
if (ANDROID)
|
||||
set(INSTALL_DIR ${HIFI_ANDROID_PRECOMPILED}/draco)
|
||||
set(DRACO_INCLUDE_DIRS "${INSTALL_DIR}/include" CACHE TYPE INTERNAL)
|
||||
set(DRACO_INCLUDE_DIRS "${INSTALL_DIR}/include" CACHE STRING INTERNAL)
|
||||
set(LIB_DIR ${INSTALL_DIR}/lib)
|
||||
list(APPEND DRACO_LIBRARIES ${LIB_DIR}/libdraco.a)
|
||||
list(APPEND DRACO_LIBRARIES ${LIB_DIR}/libdracodec.a)
|
||||
|
@ -21,4 +21,4 @@ macro(TARGET_DRACO)
|
|||
select_library_configurations(DRACO)
|
||||
target_link_libraries(${TARGET_NAME} ${DRACO_LIBRARY})
|
||||
endif()
|
||||
endmacro()
|
||||
endmacro()
|
||||
|
|
|
@ -9,9 +9,9 @@ macro(TARGET_HIFIAUDIOCODEC)
|
|||
if (ANDROID)
|
||||
set(HIFIAC_INSTALL_DIR ${HIFI_ANDROID_PRECOMPILED}/hifiAC/codecSDK)
|
||||
set(HIFIAC_LIB_DIR "${HIFIAC_INSTALL_DIR}/Release")
|
||||
set(HIFIAC_INCLUDE_DIRS "${HIFIAC_INSTALL_DIR}/include" CACHE TYPE INTERNAL)
|
||||
set(HIFIAC_INCLUDE_DIRS "${HIFIAC_INSTALL_DIR}/include" CACHE STRING INTERNAL)
|
||||
list(APPEND HIFIAC_LIBS "${HIFIAC_LIB_DIR}/libaudio.a")
|
||||
set(HIFIAC_LIBRARIES ${HIFIAC_LIBS} CACHE TYPE INTERNAL)
|
||||
set(HIFIAC_LIBRARIES ${HIFIAC_LIBS} CACHE STRING INTERNAL)
|
||||
else()
|
||||
add_dependency_external_projects(hifiAudioCodec)
|
||||
target_include_directories(${TARGET_NAME} PRIVATE ${HIFIAUDIOCODEC_INCLUDE_DIRS})
|
||||
|
|
|
@ -9,12 +9,12 @@ macro(TARGET_NVTT)
|
|||
if (ANDROID)
|
||||
set(NVTT_INSTALL_DIR ${HIFI_ANDROID_PRECOMPILED}/nvtt)
|
||||
set(NVTT_LIB_DIR "${NVTT_INSTALL_DIR}/lib")
|
||||
set(NVTT_INCLUDE_DIRS "${NVTT_INSTALL_DIR}/include" CACHE TYPE INTERNAL)
|
||||
set(NVTT_INCLUDE_DIRS "${NVTT_INSTALL_DIR}/include" CACHE STRING INTERNAL)
|
||||
list(APPEND NVTT_LIBS "${NVTT_LIB_DIR}/libnvcore.so")
|
||||
list(APPEND NVTT_LIBS "${NVTT_LIB_DIR}/libnvmath.so")
|
||||
list(APPEND NVTT_LIBS "${NVTT_LIB_DIR}/libnvimage.so")
|
||||
list(APPEND NVTT_LIBS "${NVTT_LIB_DIR}/libnvtt.so")
|
||||
set(NVTT_LIBRARIES ${NVTT_LIBS} CACHE TYPE INTERNAL)
|
||||
set(NVTT_LIBRARIES ${NVTT_LIBS} CACHE STRING INTERNAL)
|
||||
target_include_directories(${TARGET_NAME} PRIVATE ${NVTT_INCLUDE_DIRS})
|
||||
else()
|
||||
find_library(NVTT_LIBRARY_RELEASE nvtt PATHS ${VCPKG_INSTALL_ROOT}/lib NO_DEFAULT_PATH)
|
||||
|
|
|
@ -8,8 +8,8 @@
|
|||
macro(TARGET_OPENSSL)
|
||||
if (ANDROID)
|
||||
set(OPENSSL_INSTALL_DIR ${HIFI_ANDROID_PRECOMPILED}/openssl)
|
||||
set(OPENSSL_INCLUDE_DIR "${OPENSSL_INSTALL_DIR}/include" CACHE TYPE INTERNAL)
|
||||
set(OPENSSL_LIBRARIES "${OPENSSL_INSTALL_DIR}/lib/libcrypto.a;${OPENSSL_INSTALL_DIR}/lib/libssl.a" CACHE TYPE INTERNAL)
|
||||
set(OPENSSL_INCLUDE_DIR "${OPENSSL_INSTALL_DIR}/include" CACHE STRING INTERNAL)
|
||||
set(OPENSSL_LIBRARIES "${OPENSSL_INSTALL_DIR}/lib/libcrypto.a;${OPENSSL_INSTALL_DIR}/lib/libssl.a" CACHE STRING INTERNAL)
|
||||
else()
|
||||
# using VCPKG for OpenSSL
|
||||
find_package(OpenSSL REQUIRED)
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
macro(TARGET_POLYVOX)
|
||||
if (ANDROID)
|
||||
set(INSTALL_DIR ${HIFI_ANDROID_PRECOMPILED}/polyvox)
|
||||
set(POLYVOX_INCLUDE_DIRS "${INSTALL_DIR}/include" CACHE TYPE INTERNAL)
|
||||
set(POLYVOX_INCLUDE_DIRS "${INSTALL_DIR}/include" CACHE STRING INTERNAL)
|
||||
set(LIB_DIR ${INSTALL_DIR}/lib)
|
||||
list(APPEND POLYVOX_LIBRARIES ${LIB_DIR}/libPolyVoxUtil.so)
|
||||
list(APPEND POLYVOX_LIBRARIES ${LIB_DIR}/Release/libPolyVoxCore.so)
|
||||
|
|
|
@ -115,7 +115,6 @@ void DomainMetadata::securityChanged(bool send) {
|
|||
auto& state = *static_cast<QVariantMap*>(_metadata[DESCRIPTORS].data());
|
||||
|
||||
const QString RESTRICTION_OPEN = "open";
|
||||
const QString RESTRICTION_ANON = "anon";
|
||||
const QString RESTRICTION_HIFI = "hifi";
|
||||
const QString RESTRICTION_ACL = "acl";
|
||||
|
||||
|
@ -127,7 +126,7 @@ void DomainMetadata::securityChanged(bool send) {
|
|||
bool hasHifiAccess = settingsManager.getStandardPermissionsForName(NodePermissions::standardNameLoggedIn).can(
|
||||
NodePermissions::Permission::canConnectToDomain);
|
||||
if (hasAnonymousAccess) {
|
||||
restriction = hasHifiAccess ? RESTRICTION_OPEN : RESTRICTION_ANON;
|
||||
restriction = RESTRICTION_OPEN;
|
||||
} else if (hasHifiAccess) {
|
||||
restriction = RESTRICTION_HIFI;
|
||||
} else {
|
||||
|
|
Binary file not shown.
Binary file not shown.
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 39 KiB |
Binary file not shown.
Binary file not shown.
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 30 KiB |
BIN
interface/resources/avatar/animations/idleWS.fbx
Normal file
BIN
interface/resources/avatar/animations/idleWS.fbx
Normal file
Binary file not shown.
BIN
interface/resources/avatar/animations/idleWS_all.fbx
Normal file
BIN
interface/resources/avatar/animations/idleWS_all.fbx
Normal file
Binary file not shown.
BIN
interface/resources/avatar/animations/idle_LFF_all.fbx
Normal file
BIN
interface/resources/avatar/animations/idle_LFF_all.fbx
Normal file
Binary file not shown.
BIN
interface/resources/avatar/animations/idle_RFF_all.fbx
Normal file
BIN
interface/resources/avatar/animations/idle_RFF_all.fbx
Normal file
Binary file not shown.
BIN
interface/resources/avatar/animations/idle_lookaround01.fbx
Normal file
BIN
interface/resources/avatar/animations/idle_lookaround01.fbx
Normal file
Binary file not shown.
BIN
interface/resources/avatar/animations/idle_once_armstretch.fbx
Normal file
BIN
interface/resources/avatar/animations/idle_once_armstretch.fbx
Normal file
Binary file not shown.
BIN
interface/resources/avatar/animations/idle_once_bigstretch.fbx
Normal file
BIN
interface/resources/avatar/animations/idle_once_bigstretch.fbx
Normal file
Binary file not shown.
BIN
interface/resources/avatar/animations/idle_once_checkwatch.fbx
Normal file
BIN
interface/resources/avatar/animations/idle_once_checkwatch.fbx
Normal file
Binary file not shown.
BIN
interface/resources/avatar/animations/idle_once_headtilt.fbx
Normal file
BIN
interface/resources/avatar/animations/idle_once_headtilt.fbx
Normal file
Binary file not shown.
BIN
interface/resources/avatar/animations/idle_once_lookaround.fbx
Normal file
BIN
interface/resources/avatar/animations/idle_once_lookaround.fbx
Normal file
Binary file not shown.
BIN
interface/resources/avatar/animations/idle_once_neckstretch.fbx
Normal file
BIN
interface/resources/avatar/animations/idle_once_neckstretch.fbx
Normal file
Binary file not shown.
BIN
interface/resources/avatar/animations/idle_once_slownod.fbx
Normal file
BIN
interface/resources/avatar/animations/idle_once_slownod.fbx
Normal file
Binary file not shown.
BIN
interface/resources/avatar/animations/talk04.fbx
Normal file
BIN
interface/resources/avatar/animations/talk04.fbx
Normal file
Binary file not shown.
BIN
interface/resources/avatar/animations/talk_righthand.fbx
Normal file
BIN
interface/resources/avatar/animations/talk_righthand.fbx
Normal file
Binary file not shown.
File diff suppressed because it is too large
Load diff
|
@ -23,15 +23,15 @@ Rectangle {
|
|||
property bool ignoreRadiusEnabled: AvatarInputs.ignoreRadiusEnabled;
|
||||
|
||||
function updateOpacity() {
|
||||
if (ignoreRadiusEnabled) {
|
||||
bubbleRect.opacity = 1.0;
|
||||
} else {
|
||||
bubbleRect.opacity = 0.7;
|
||||
}
|
||||
var rectOpacity = ignoreRadiusEnabled ? 1.0 : (mouseArea.containsMouse ? 1.0 : 0.7);
|
||||
bubbleRect.opacity = rectOpacity;
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
updateOpacity();
|
||||
AvatarInputs.ignoreRadiusEnabledChanged.connect(function() {
|
||||
ignoreRadiusEnabled = AvatarInputs.ignoreRadiusEnabled;
|
||||
});
|
||||
}
|
||||
|
||||
onIgnoreRadiusEnabledChanged: {
|
||||
|
@ -74,10 +74,10 @@ Rectangle {
|
|||
}
|
||||
drag.target: dragTarget;
|
||||
onContainsMouseChanged: {
|
||||
var rectOpacity = (ignoreRadiusEnabled && containsMouse) ? 1.0 : (containsMouse ? 1.0 : 0.7);
|
||||
if (containsMouse) {
|
||||
Tablet.playSound(TabletEnums.ButtonHover);
|
||||
}
|
||||
var rectOpacity = ignoreRadiusEnabled ? 1.0 : (mouseArea.containsMouse ? 1.0 : 0.7);
|
||||
bubbleRect.opacity = rectOpacity;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -398,7 +398,7 @@ Item {
|
|||
lineHeight: 1
|
||||
lineHeightMode: Text.ProportionalHeight
|
||||
|
||||
onLinkActivated: loginDialog.openUrl(link);
|
||||
onLinkActivated: Window.openUrl(link);
|
||||
|
||||
Component.onCompleted: {
|
||||
if (termsTextMetrics.width > root.bannerWidth) {
|
||||
|
|
|
@ -363,7 +363,7 @@ Item {
|
|||
linkColor: hifi.colors.blueAccent
|
||||
onLinkActivated: {
|
||||
Tablet.playSound(TabletEnums.ButtonClick);
|
||||
loginDialog.openUrl(link);
|
||||
Window.openUrl(link);
|
||||
lightboxPopup.titleText = "Can't Access Account";
|
||||
lightboxPopup.bodyText = lightboxPopup.cantAccessBodyText;
|
||||
lightboxPopup.button2text = "CLOSE";
|
||||
|
|
|
@ -411,7 +411,7 @@ Item {
|
|||
lineHeight: 1
|
||||
lineHeightMode: Text.ProportionalHeight
|
||||
|
||||
onLinkActivated: loginDialog.openUrl(link);
|
||||
onLinkActivated: Window.openUrl(link);
|
||||
|
||||
Component.onCompleted: {
|
||||
if (termsTextMetrics.width > root.bannerWidth) {
|
||||
|
|
|
@ -234,7 +234,7 @@ Item {
|
|||
lineHeight: 1
|
||||
lineHeightMode: Text.ProportionalHeight
|
||||
|
||||
onLinkActivated: loginDialog.openUrl(link);
|
||||
onLinkActivated: Window.openUrl(link);
|
||||
|
||||
Component.onCompleted: {
|
||||
if (termsTextMetrics.width > root.bannerWidth) {
|
||||
|
|
|
@ -57,6 +57,14 @@ Item {
|
|||
StatText {
|
||||
text: "Avatars: " + root.avatarCount
|
||||
}
|
||||
StatText {
|
||||
visible: true
|
||||
text: "Refresh: " + root.refreshRateRegime + " - " + root.refreshRateTarget
|
||||
}
|
||||
StatText {
|
||||
visible: root.expanded
|
||||
text:" " + root.refreshRateMode + " - " + root.uxMode;
|
||||
}
|
||||
StatText {
|
||||
text: "Game Rate: " + root.gameLoopRate
|
||||
}
|
||||
|
|
|
@ -254,7 +254,7 @@ Rectangle {
|
|||
switchWidth: root.switchWidth;
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
labelTextOn: qsTr("Warn when muted in HMD");
|
||||
labelTextOn: qsTr("HMD Mute Warning");
|
||||
labelTextSize: 16;
|
||||
backgroundOnColor: "#E3E3E3";
|
||||
checked: AudioScriptingInterface.warnWhenMuted;
|
||||
|
|
|
@ -952,7 +952,7 @@ Rectangle {
|
|||
text: "LOG IN"
|
||||
|
||||
onClicked: {
|
||||
sendToScript({method: 'needsLogIn_loginClicked'});
|
||||
sendToScript({method: 'marketplace_loginClicked'});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -137,7 +137,7 @@ Item {
|
|||
width: parent.width/2 - anchors.leftMargin*2;
|
||||
text: "Cancel"
|
||||
onClicked: {
|
||||
sendToScript({method: 'needsLogIn_cancelClicked'});
|
||||
sendToScript({method: 'passphrasePopup_cancelClicked'});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -155,7 +155,7 @@ Item {
|
|||
width: parent.width/2 - anchors.rightMargin*2;
|
||||
text: "Log In"
|
||||
onClicked: {
|
||||
sendToScript({method: 'needsLogIn_loginClicked'});
|
||||
sendToScript({method: 'marketplace_loginClicked'});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -150,6 +150,7 @@
|
|||
#include <Preferences.h>
|
||||
#include <display-plugins/CompositorHelper.h>
|
||||
#include <display-plugins/hmd/HmdDisplayPlugin.h>
|
||||
#include <display-plugins/RefreshRateController.h>
|
||||
#include <trackers/EyeTracker.h>
|
||||
#include <avatars-renderer/ScriptAvatar.h>
|
||||
#include <RenderableEntityItem.h>
|
||||
|
@ -192,6 +193,7 @@
|
|||
#include "scripting/WalletScriptingInterface.h"
|
||||
#include "scripting/TTSScriptingInterface.h"
|
||||
#include "scripting/KeyboardScriptingInterface.h"
|
||||
#include "scripting/RefreshRateScriptingInterface.h"
|
||||
|
||||
|
||||
|
||||
|
@ -662,9 +664,10 @@ private:
|
|||
|
||||
/**jsdoc
|
||||
* <p>The <code>Controller.Hardware.Application</code> object has properties representing Interface's state. The property
|
||||
* values are integer IDs, uniquely identifying each output. <em>Read-only.</em> These can be mapped to actions or functions or
|
||||
* <code>Controller.Standard</code> items in a {@link RouteObject} mapping (e.g., using the {@link RouteObject#when} method).
|
||||
* Each data value is either <code>1.0</code> for "true" or <code>0.0</code> for "false".</p>
|
||||
* values are integer IDs, uniquely identifying each output. <em>Read-only.</em></p>
|
||||
* <p>These states can be mapped to actions or functions or <code>Controller.Standard</code> items in a {@link RouteObject}
|
||||
* mapping (e.g., using the {@link RouteObject#when} method). Each data value is either <code>1.0</code> for "true" or
|
||||
* <code>0.0</code> for "false".</p>
|
||||
* <table>
|
||||
* <thead>
|
||||
* <tr><th>Property</th><th>Type</th><th>Data</th><th>Description</th></tr>
|
||||
|
@ -678,13 +681,17 @@ private:
|
|||
* <tr><td><code>CameraIndependent</code></td><td>number</td><td>number</td><td>The camera is in independent mode.</td></tr>
|
||||
* <tr><td><code>CameraEntity</code></td><td>number</td><td>number</td><td>The camera is in entity mode.</td></tr>
|
||||
* <tr><td><code>InHMD</code></td><td>number</td><td>number</td><td>The user is in HMD mode.</td></tr>
|
||||
* <tr><td><code>AdvancedMovement</code></td><td>number</td><td>number</td><td>Advanced movement controls are enabled.
|
||||
* </td></tr>
|
||||
* <tr><td><code>AdvancedMovement</code></td><td>number</td><td>number</td><td>Advanced movement (walking) controls are
|
||||
* enabled.</td></tr>
|
||||
* <tr><td><code>StrafeEnabled</code></td><td>number</td><td>number</td><td>Strafing is enabled</td></tr>
|
||||
* <tr><td><code>LeftHandDominant</code></td><td>number</td><td>number</td><td>Dominant hand set to left.</td></tr>
|
||||
* <tr><td><code>RightHandDominant</code></td><td>number</td><td>number</td><td>Dominant hand set to right.</td></tr>
|
||||
* <tr><td><code>SnapTurn</code></td><td>number</td><td>number</td><td>Snap turn is enabled.</td></tr>
|
||||
* <tr><td><code>Grounded</code></td><td>number</td><td>number</td><td>The user's avatar is on the ground.</td></tr>
|
||||
* <tr><td><code>NavigationFocused</code></td><td>number</td><td>number</td><td><em>Not used.</em></td></tr>
|
||||
* <tr><td><code>PlatformWindows</code></td><td>number</td><td>number</td><td>The operating system is Windows.</td></tr>
|
||||
* <tr><td><code>PlatformMac</code></td><td>number</td><td>number</td><td>The operating system is Mac.</td></tr>
|
||||
* <tr><td><code>PlatformAndroid</code></td><td>number</td><td>number</td><td>The operating system is Android.</td></tr>
|
||||
* </tbody>
|
||||
* </table>
|
||||
* @typedef {object} Controller.Hardware-Application
|
||||
|
@ -820,7 +827,7 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) {
|
|||
audioDLLPath += "/audioWin7";
|
||||
}
|
||||
QCoreApplication::addLibraryPath(audioDLLPath);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
DependencyManager::registerInheritance<LimitedNodeList, NodeList>();
|
||||
DependencyManager::registerInheritance<AvatarHashMap, AvatarManager>();
|
||||
|
@ -1328,7 +1335,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
setCrashAnnotation("avatar", avatarURL.toString().toStdString());
|
||||
});
|
||||
|
||||
|
||||
// Inititalize sample before registering
|
||||
_sampleSound = DependencyManager::get<SoundCache>()->getSound(PathUtils::resourcesUrl("sounds/sample.wav"));
|
||||
|
||||
|
@ -1419,6 +1425,10 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
initializeDisplayPlugins();
|
||||
qCDebug(interfaceapp, "Initialized Display");
|
||||
|
||||
if (_displayPlugin && !_displayPlugin->isHmd()) {
|
||||
_preferredCursor.set(Cursor::Manager::getIconName(Cursor::Icon::SYSTEM));
|
||||
showCursor(Cursor::Manager::lookupIcon(_preferredCursor.get()));
|
||||
}
|
||||
// An audio device changed signal received before the display plugins are set up will cause a crash,
|
||||
// so we defer the setup of the `scripting::Audio` class until this point
|
||||
{
|
||||
|
@ -1438,8 +1448,11 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
audioScriptingInterface->environmentMuted();
|
||||
}
|
||||
});
|
||||
connect(this, &Application::activeDisplayPluginChanged,
|
||||
reinterpret_cast<scripting::Audio*>(audioScriptingInterface.data()), &scripting::Audio::onContextChanged);
|
||||
QSharedPointer<scripting::Audio> scriptingAudioSharedPointer = qSharedPointerDynamicCast<scripting::Audio>(DependencyManager::get<AudioScriptingInterface>());
|
||||
if (scriptingAudioSharedPointer) {
|
||||
connect(this, &Application::activeDisplayPluginChanged,
|
||||
scriptingAudioSharedPointer.data(), &scripting::Audio::onContextChanged);
|
||||
}
|
||||
}
|
||||
|
||||
// Create the rendering engine. This can be slow on some machines due to lots of
|
||||
|
@ -1632,7 +1645,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
connect(userInputMapper.data(), &UserInputMapper::actionEvent, [this](int action, float state) {
|
||||
using namespace controller;
|
||||
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
|
||||
auto audioScriptingInterface = reinterpret_cast<scripting::Audio*>(DependencyManager::get<AudioScriptingInterface>().data());
|
||||
QSharedPointer<scripting::Audio> audioScriptingInterface = qSharedPointerDynamicCast<scripting::Audio>(DependencyManager::get<AudioScriptingInterface>());
|
||||
{
|
||||
auto actionEnum = static_cast<Action>(action);
|
||||
int key = Qt::Key_unknown;
|
||||
|
@ -1640,10 +1653,12 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
bool navAxis = false;
|
||||
switch (actionEnum) {
|
||||
case Action::TOGGLE_PUSHTOTALK:
|
||||
if (state > 0.0f) {
|
||||
audioScriptingInterface->setPushingToTalk(true);
|
||||
} else if (state <= 0.0f) {
|
||||
audioScriptingInterface->setPushingToTalk(false);
|
||||
if (audioScriptingInterface) {
|
||||
if (state > 0.0f) {
|
||||
audioScriptingInterface->setPushingToTalk(true);
|
||||
} else if (state <= 0.0f) {
|
||||
audioScriptingInterface->setPushingToTalk(false);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
|
@ -1813,6 +1828,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
});
|
||||
|
||||
|
||||
getRefreshRateManager().setRefreshRateRegime(RefreshRateManager::RefreshRateRegime::STARTUP);
|
||||
|
||||
// Setup the _keyboardMouseDevice, _touchscreenDevice, _touchscreenVirtualPadDevice and the user input mapper with the default bindings
|
||||
userInputMapper->registerDevice(_keyboardMouseDevice->getInputDevice());
|
||||
// if the _touchscreenDevice is not supported it will not be registered
|
||||
|
@ -1861,12 +1878,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::FirstPerson)) {
|
||||
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 +2348,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
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) {
|
||||
//rotate about vertical to face the camera
|
||||
glm::vec3 dPosition = frustumPos - position;
|
||||
|
@ -2365,7 +2376,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
|
||||
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;
|
||||
if (htmlContent) {
|
||||
webSurface = DependencyManager::get<OffscreenQmlSurfaceCache>()->acquire(render::entities::WebEntityRenderer::QML);
|
||||
|
@ -2405,7 +2416,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
const uint8_t TABLET_FPS = 90;
|
||||
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();
|
||||
|
||||
// Fix for crash in QtWebEngineCore when rapidly switching domains
|
||||
|
@ -2443,6 +2454,14 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
DependencyManager::get<TabletScriptingInterface>()->preloadSounds();
|
||||
DependencyManager::get<Keyboard>()->createKeyboard();
|
||||
|
||||
FileDialogHelper::setOpenDirectoryOperator([this](const QString& path) { openDirectory(path); });
|
||||
QDesktopServices::setUrlHandler("file", this, "showUrlHandler");
|
||||
QDesktopServices::setUrlHandler("", this, "showUrlHandler");
|
||||
auto drives = QDir::drives();
|
||||
for (auto drive : drives) {
|
||||
QDesktopServices::setUrlHandler(QUrl(drive.absolutePath()).scheme(), this, "showUrlHandler");
|
||||
}
|
||||
|
||||
_pendingIdleEvent = false;
|
||||
_graphicsEngine.startup();
|
||||
|
||||
|
@ -2619,6 +2638,8 @@ void Application::onAboutToQuit() {
|
|||
_aboutToQuit = true;
|
||||
|
||||
cleanupBeforeQuit();
|
||||
|
||||
getRefreshRateManager().setRefreshRateRegime(RefreshRateManager::RefreshRateRegime::SHUTDOWN);
|
||||
}
|
||||
|
||||
void Application::cleanupBeforeQuit() {
|
||||
|
@ -3228,6 +3249,7 @@ void Application::onDesktopRootContextCreated(QQmlContext* surfaceContext) {
|
|||
|
||||
surfaceContext->setContextProperty("Controller", DependencyManager::get<controller::ScriptingInterface>().data());
|
||||
surfaceContext->setContextProperty("Entities", DependencyManager::get<EntityScriptingInterface>().data());
|
||||
surfaceContext->setContextProperty("RefreshRate", new RefreshRateScriptingInterface());
|
||||
_fileDownload = new FileScriptingInterface(engine);
|
||||
surfaceContext->setContextProperty("File", _fileDownload);
|
||||
connect(_fileDownload, &FileScriptingInterface::unzipResult, this, &Application::handleUnzip);
|
||||
|
@ -3376,6 +3398,7 @@ void Application::setupQmlSurface(QQmlContext* surfaceContext, bool setAdditiona
|
|||
|
||||
surfaceContext->setContextProperty("Settings", SettingsScriptingInterface::getInstance());
|
||||
surfaceContext->setContextProperty("MenuInterface", MenuScriptingInterface::getInstance());
|
||||
surfaceContext->setContextProperty("RefreshRate", new RefreshRateScriptingInterface());
|
||||
|
||||
surfaceContext->setContextProperty("Account", AccountServicesScriptingInterface::getInstance()); // DEPRECATED - TO BE REMOVED
|
||||
surfaceContext->setContextProperty("GlobalServices", AccountServicesScriptingInterface::getInstance()); // DEPRECATED - TO BE REMOVED
|
||||
|
@ -3581,7 +3604,14 @@ void Application::setPreferAvatarFingerOverStylus(bool value) {
|
|||
|
||||
void Application::setPreferredCursor(const QString& cursorName) {
|
||||
qCDebug(interfaceapp) << "setPreferredCursor" << cursorName;
|
||||
_preferredCursor.set(cursorName.isEmpty() ? DEFAULT_CURSOR_NAME : cursorName);
|
||||
|
||||
if (_displayPlugin && _displayPlugin->isHmd()) {
|
||||
_preferredCursor.set(cursorName.isEmpty() ? DEFAULT_CURSOR_NAME : cursorName);
|
||||
}
|
||||
else {
|
||||
_preferredCursor.set(cursorName.isEmpty() ? Cursor::Manager::getIconName(Cursor::Icon::SYSTEM) : cursorName);
|
||||
}
|
||||
|
||||
showCursor(Cursor::Manager::lookupIcon(_preferredCursor.get()));
|
||||
}
|
||||
|
||||
|
@ -4089,6 +4119,11 @@ bool Application::eventFilter(QObject* object, QEvent* event) {
|
|||
return true;
|
||||
}
|
||||
|
||||
auto eventType = event->type();
|
||||
if (eventType == QEvent::KeyPress || eventType == QEvent::KeyRelease || eventType == QEvent::MouseMove) {
|
||||
getRefreshRateManager().resetInactiveTimer();
|
||||
}
|
||||
|
||||
if (event->type() == QEvent::Leave) {
|
||||
getApplicationCompositor().handleLeaveEvent();
|
||||
}
|
||||
|
@ -4108,6 +4143,12 @@ bool Application::eventFilter(QObject* object, QEvent* event) {
|
|||
}
|
||||
}
|
||||
|
||||
if (event->type() == QEvent::WindowStateChange) {
|
||||
if (getWindow()->windowState() == Qt::WindowMinimized) {
|
||||
getRefreshRateManager().setRefreshRateRegime(RefreshRateManager::RefreshRateRegime::MINIMIZED);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -4272,6 +4313,10 @@ void Application::keyPressEvent(QKeyEvent* event) {
|
|||
if (isMeta) {
|
||||
auto audioClient = DependencyManager::get<AudioClient>();
|
||||
audioClient->setMuted(!audioClient->isMuted());
|
||||
QSharedPointer<scripting::Audio> audioScriptingInterface = qSharedPointerDynamicCast<scripting::Audio>(DependencyManager::get<AudioScriptingInterface>());
|
||||
if (audioScriptingInterface && audioScriptingInterface->getPTT()) {
|
||||
audioScriptingInterface->setPushingToTalk(!audioClient->isMuted());
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
|
@ -4401,7 +4446,6 @@ void Application::focusOutEvent(QFocusEvent* event) {
|
|||
inputPlugin->pluginFocusOutEvent();
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME spacemouse code still needs cleanup
|
||||
#if 0
|
||||
//SpacemouseDevice::getInstance().focusOutEvent();
|
||||
|
@ -5012,7 +5056,7 @@ void Application::idle() {
|
|||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
checkChangeCursor();
|
||||
|
||||
#if !defined(DISABLE_QML)
|
||||
|
@ -5323,8 +5367,10 @@ void Application::loadSettings() {
|
|||
}
|
||||
}
|
||||
|
||||
auto audioScriptingInterface = reinterpret_cast<scripting::Audio*>(DependencyManager::get<AudioScriptingInterface>().data());
|
||||
audioScriptingInterface->loadData();
|
||||
QSharedPointer<scripting::Audio> audioScriptingInterface = qSharedPointerDynamicCast<scripting::Audio>(DependencyManager::get<AudioScriptingInterface>());
|
||||
if (audioScriptingInterface) {
|
||||
audioScriptingInterface->loadData();
|
||||
}
|
||||
|
||||
getMyAvatar()->loadData();
|
||||
_settingsLoaded = true;
|
||||
|
@ -5335,8 +5381,10 @@ void Application::saveSettings() const {
|
|||
DependencyManager::get<AudioClient>()->saveSettings();
|
||||
DependencyManager::get<LODManager>()->saveSettings();
|
||||
|
||||
auto audioScriptingInterface = reinterpret_cast<scripting::Audio*>(DependencyManager::get<AudioScriptingInterface>().data());
|
||||
audioScriptingInterface->saveData();
|
||||
QSharedPointer<scripting::Audio> audioScriptingInterface = qSharedPointerDynamicCast<scripting::Audio>(DependencyManager::get<AudioScriptingInterface>());
|
||||
if (audioScriptingInterface) {
|
||||
audioScriptingInterface->saveData();
|
||||
}
|
||||
|
||||
Menu::getInstance()->saveSettings();
|
||||
getMyAvatar()->saveData();
|
||||
|
@ -5569,6 +5617,8 @@ void Application::resumeAfterLoginDialogActionTaken() {
|
|||
menu->getMenu("Developer")->setVisible(_developerMenuVisible);
|
||||
_myCamera.setMode(_previousCameraMode);
|
||||
cameraModeChanged();
|
||||
_startUpFinished = true;
|
||||
getRefreshRateManager().setRefreshRateRegime(RefreshRateManager::RefreshRateRegime::FOCUS_ACTIVE);
|
||||
}
|
||||
|
||||
void Application::loadAvatarScripts(const QVector<QString>& urls) {
|
||||
|
@ -5757,9 +5807,6 @@ void Application::cycleCamera() {
|
|||
menu->setIsOptionChecked(MenuOption::ThirdPerson, false);
|
||||
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
|
||||
}
|
||||
|
@ -5775,12 +5822,6 @@ void Application::cameraModeChanged() {
|
|||
case CAMERA_MODE_MIRROR:
|
||||
Menu::getInstance()->setIsOptionChecked(MenuOption::FullscreenMirror, true);
|
||||
break;
|
||||
case CAMERA_MODE_INDEPENDENT:
|
||||
Menu::getInstance()->setIsOptionChecked(MenuOption::IndependentMode, true);
|
||||
break;
|
||||
case CAMERA_MODE_ENTITY:
|
||||
Menu::getInstance()->setIsOptionChecked(MenuOption::CameraEntityMode, true);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -5824,14 +5865,6 @@ void Application::cameraMenuChanged() {
|
|||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6746,6 +6779,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);
|
||||
_myCamera.loadViewFrustum(_displayViewFrustum);
|
||||
|
@ -7363,6 +7401,7 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointe
|
|||
scriptEngine->registerGlobalObject("LODManager", DependencyManager::get<LODManager>().data());
|
||||
|
||||
scriptEngine->registerGlobalObject("Keyboard", DependencyManager::get<KeyboardScriptingInterface>().data());
|
||||
scriptEngine->registerGlobalObject("RefreshRate", new RefreshRateScriptingInterface);
|
||||
|
||||
scriptEngine->registerGlobalObject("Paths", DependencyManager::get<PathUtils>().data());
|
||||
|
||||
|
@ -8280,19 +8319,6 @@ void Application::packageModel() {
|
|||
ModelPackager::package();
|
||||
}
|
||||
|
||||
void Application::openUrl(const QUrl& url) const {
|
||||
if (!url.isEmpty()) {
|
||||
if (url.scheme() == URL_SCHEME_HIFI) {
|
||||
DependencyManager::get<AddressManager>()->handleLookupString(url.toString());
|
||||
} else if (url.scheme() == URL_SCHEME_HIFIAPP) {
|
||||
DependencyManager::get<QmlCommerce>()->openSystemApp(url.path());
|
||||
} else {
|
||||
// address manager did not handle - ask QDesktopServices to handle
|
||||
QDesktopServices::openUrl(url);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Application::loadDialog() {
|
||||
ModalDialogListener* dlg = OffscreenUi::getOpenFileNameAsync(_glWidget, tr("Open Script"),
|
||||
getPreviousScriptLocation(),
|
||||
|
@ -8433,32 +8459,47 @@ void Application::loadAvatarBrowser() const {
|
|||
DependencyManager::get<HMDScriptingInterface>()->openTablet();
|
||||
}
|
||||
|
||||
void Application::takeSnapshot(bool notify, bool includeAnimated, float aspectRatio, const QString& filename) {
|
||||
postLambdaEvent([notify, includeAnimated, aspectRatio, filename, this] {
|
||||
// Get a screenshot and save it
|
||||
QString path = DependencyManager::get<Snapshot>()->saveSnapshot(getActiveDisplayPlugin()->getScreenshot(aspectRatio), filename,
|
||||
TestScriptingInterface::getInstance()->getTestResultsLocation());
|
||||
void Application::addSnapshotOperator(const SnapshotOperator& snapshotOperator) {
|
||||
std::lock_guard<std::mutex> lock(_snapshotMutex);
|
||||
_snapshotOperators.push(snapshotOperator);
|
||||
_hasPrimarySnapshot = _hasPrimarySnapshot || std::get<2>(snapshotOperator);
|
||||
}
|
||||
|
||||
// If we're not doing an animated snapshot as well...
|
||||
if (!includeAnimated) {
|
||||
if (!path.isEmpty()) {
|
||||
// Tell the dependency manager that the capture of the still snapshot has taken place.
|
||||
emit DependencyManager::get<WindowScriptingInterface>()->stillSnapshotTaken(path, notify);
|
||||
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) {
|
||||
addSnapshotOperator(std::make_tuple([notify, includeAnimated, aspectRatio, filename](const QImage& snapshot) {
|
||||
qApp->postLambdaEvent([snapshot, notify, includeAnimated, aspectRatio, filename] {
|
||||
QString path = DependencyManager::get<Snapshot>()->saveSnapshot(snapshot, filename, TestScriptingInterface::getInstance()->getTestResultsLocation());
|
||||
|
||||
// If we're not doing an animated snapshot as well...
|
||||
if (!includeAnimated) {
|
||||
if (!path.isEmpty()) {
|
||||
// Tell the dependency manager that the capture of the still snapshot has taken place.
|
||||
emit DependencyManager::get<WindowScriptingInterface>()->stillSnapshotTaken(path, notify);
|
||||
}
|
||||
} else if (!SnapshotAnimated::isAlreadyTakingSnapshotAnimated()) {
|
||||
// Get an animated GIF snapshot and save it
|
||||
SnapshotAnimated::saveSnapshotAnimated(path, aspectRatio, DependencyManager::get<WindowScriptingInterface>());
|
||||
}
|
||||
} else if (!SnapshotAnimated::isAlreadyTakingSnapshotAnimated()) {
|
||||
// Get an animated GIF snapshot and save it
|
||||
SnapshotAnimated::saveSnapshotAnimated(path, aspectRatio, qApp, DependencyManager::get<WindowScriptingInterface>());
|
||||
}
|
||||
});
|
||||
});
|
||||
}, aspectRatio, true));
|
||||
}
|
||||
|
||||
void Application::takeSecondaryCameraSnapshot(const bool& notify, const QString& filename) {
|
||||
postLambdaEvent([notify, filename, this] {
|
||||
QString snapshotPath = DependencyManager::get<Snapshot>()->saveSnapshot(getActiveDisplayPlugin()->getSecondaryCameraScreenshot(), filename,
|
||||
TestScriptingInterface::getInstance()->getTestResultsLocation());
|
||||
addSnapshotOperator(std::make_tuple([notify, filename](const QImage& snapshot) {
|
||||
qApp->postLambdaEvent([snapshot, notify, filename] {
|
||||
QString snapshotPath = DependencyManager::get<Snapshot>()->saveSnapshot(snapshot, filename, TestScriptingInterface::getInstance()->getTestResultsLocation());
|
||||
|
||||
emit DependencyManager::get<WindowScriptingInterface>()->stillSnapshotTaken(snapshotPath, notify);
|
||||
});
|
||||
emit DependencyManager::get<WindowScriptingInterface>()->stillSnapshotTaken(snapshotPath, notify);
|
||||
});
|
||||
}, 0.0f, false));
|
||||
}
|
||||
|
||||
void Application::takeSecondaryCamera360Snapshot(const glm::vec3& cameraPosition, const bool& cubemapOutputFormat, const bool& notify, const QString& filename) {
|
||||
|
@ -8522,11 +8563,20 @@ void Application::activeChanged(Qt::ApplicationState state) {
|
|||
switch (state) {
|
||||
case Qt::ApplicationActive:
|
||||
_isForeground = true;
|
||||
if (!_aboutToQuit && _startUpFinished) {
|
||||
getRefreshRateManager().setRefreshRateRegime(RefreshRateManager::RefreshRateRegime::FOCUS_ACTIVE);
|
||||
}
|
||||
break;
|
||||
|
||||
case Qt::ApplicationSuspended:
|
||||
break;
|
||||
case Qt::ApplicationHidden:
|
||||
break;
|
||||
case Qt::ApplicationInactive:
|
||||
if (!_aboutToQuit && _startUpFinished) {
|
||||
getRefreshRateManager().setRefreshRateRegime(RefreshRateManager::RefreshRateRegime::UNFOCUS);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
_isForeground = false;
|
||||
break;
|
||||
|
@ -8763,6 +8813,7 @@ void Application::updateDisplayMode() {
|
|||
auto displayPlugins = getDisplayPlugins();
|
||||
|
||||
// Default to the first item on the list, in case none of the menu items match
|
||||
|
||||
DisplayPluginPointer newDisplayPlugin = displayPlugins.at(0);
|
||||
auto menu = getPrimaryMenu();
|
||||
if (menu) {
|
||||
|
@ -8852,6 +8903,14 @@ void Application::setDisplayPlugin(DisplayPluginPointer newDisplayPlugin) {
|
|||
if (desktop) {
|
||||
desktop->setProperty("repositionLocked", wasRepositionLocked);
|
||||
}
|
||||
|
||||
RefreshRateManager& refreshRateManager = getRefreshRateManager();
|
||||
refreshRateManager.setRefreshRateOperator(OpenGLDisplayPlugin::getRefreshRateOperator());
|
||||
bool isHmd = newDisplayPlugin->isHmd();
|
||||
RefreshRateManager::UXMode uxMode = isHmd ? RefreshRateManager::UXMode::VR :
|
||||
RefreshRateManager::UXMode::DESKTOP;
|
||||
|
||||
refreshRateManager.setUXMode(uxMode);
|
||||
}
|
||||
|
||||
bool isHmd = _displayPlugin->isHmd();
|
||||
|
@ -9161,7 +9220,7 @@ void Application::readArgumentsFromLocalSocket() const {
|
|||
|
||||
// If we received a message, try to open it as a URL
|
||||
if (message.length() > 0) {
|
||||
qApp->openUrl(QString::fromUtf8(message));
|
||||
DependencyManager::get<WindowScriptingInterface>()->openUrl(QString::fromUtf8(message));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -9278,6 +9337,44 @@ QString Application::getGraphicsCardType() {
|
|||
return GPUIdent::getInstance()->getName();
|
||||
}
|
||||
|
||||
void Application::openDirectory(const QString& path) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "openDirectory", Q_ARG(const QString&, path));
|
||||
return;
|
||||
}
|
||||
|
||||
QString dirPath = path;
|
||||
const QString FILE_SCHEME = "file:///";
|
||||
if (dirPath.startsWith(FILE_SCHEME)) {
|
||||
dirPath.remove(0, FILE_SCHEME.length());
|
||||
}
|
||||
QFileInfo fileInfo(dirPath);
|
||||
if (fileInfo.isDir()) {
|
||||
auto scheme = QUrl(path).scheme();
|
||||
QDesktopServices::unsetUrlHandler(scheme);
|
||||
QDesktopServices::openUrl(path);
|
||||
QDesktopServices::setUrlHandler(scheme, this, "showUrlHandler");
|
||||
}
|
||||
}
|
||||
|
||||
void Application::showUrlHandler(const QUrl& url) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "showUrlHandler", Q_ARG(const QUrl&, url));
|
||||
return;
|
||||
}
|
||||
|
||||
ModalDialogListener* dlg = OffscreenUi::asyncQuestion("Confirm openUrl", "Do you recognize this path or code and want to open or execute it: " + url.toDisplayString());
|
||||
QObject::connect(dlg, &ModalDialogListener::response, this, [=](QVariant answer) {
|
||||
QObject::disconnect(dlg, &ModalDialogListener::response, this, nullptr);
|
||||
if (QMessageBox::Yes == static_cast<QMessageBox::StandardButton>(answer.toInt())) {
|
||||
// Unset the handler, open the URL, and the reset the handler
|
||||
QDesktopServices::unsetUrlHandler(url.scheme());
|
||||
QDesktopServices::openUrl(url);
|
||||
QDesktopServices::setUrlHandler(url.scheme(), this, "showUrlHandler");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#if defined(Q_OS_ANDROID)
|
||||
void Application::beforeEnterBackground() {
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
|
|
@ -58,6 +58,7 @@
|
|||
#include "gpu/Context.h"
|
||||
#include "LoginStateManager.h"
|
||||
#include "Menu.h"
|
||||
#include "RefreshRateManager.h"
|
||||
#include "octree/OctreePacketProcessor.h"
|
||||
#include "render/Engine.h"
|
||||
#include "scripting/ControllerScriptingInterface.h"
|
||||
|
@ -203,6 +204,7 @@ public:
|
|||
CompositorHelper& getApplicationCompositor() const;
|
||||
|
||||
Overlays& getOverlays() { return _overlays; }
|
||||
RefreshRateManager& getRefreshRateManager() { return _refreshRateManager; }
|
||||
|
||||
size_t getRenderFrameCount() const { return _graphicsEngine.getRenderFrameCount(); }
|
||||
float getRenderLoopRate() const { return _graphicsEngine.getRenderLoopRate(); }
|
||||
|
@ -345,6 +347,12 @@ public:
|
|||
void toggleAwayMode();
|
||||
#endif
|
||||
|
||||
using SnapshotOperator = std::tuple<std::function<void(const QImage&)>, float, bool>;
|
||||
void addSnapshotOperator(const SnapshotOperator& snapshotOperator);
|
||||
bool takeSnapshotOperators(std::queue<SnapshotOperator>& snapshotOperators);
|
||||
|
||||
void openDirectory(const QString& path);
|
||||
|
||||
signals:
|
||||
void svoImportRequested(const QString& url);
|
||||
|
||||
|
@ -404,8 +412,6 @@ public slots:
|
|||
|
||||
static void packageModel();
|
||||
|
||||
void openUrl(const QUrl& url) const;
|
||||
|
||||
void resetSensors(bool andReload = false);
|
||||
void setActiveFaceTracker() const;
|
||||
|
||||
|
@ -472,6 +478,9 @@ public slots:
|
|||
|
||||
QString getGraphicsCardType();
|
||||
|
||||
bool gpuTextureMemSizeStable();
|
||||
void showUrlHandler(const QUrl& url);
|
||||
|
||||
private slots:
|
||||
void onDesktopRootItemCreated(QQuickItem* qmlContext);
|
||||
void onDesktopRootContextCreated(QQmlContext* qmlContext);
|
||||
|
@ -566,7 +575,6 @@ private:
|
|||
bool importFromZIP(const QString& filePath);
|
||||
bool importImage(const QString& urlString);
|
||||
|
||||
bool gpuTextureMemSizeStable();
|
||||
int processOctreeStats(ReceivedMessage& message, SharedNodePointer sendingNode);
|
||||
void trackIncomingOctreePacket(ReceivedMessage& message, SharedNodePointer sendingNode, bool wasStatsPacket);
|
||||
|
||||
|
@ -717,6 +725,7 @@ private:
|
|||
QUuid _loginDialogID;
|
||||
QUuid _avatarInputsBarID;
|
||||
LoginStateManager _loginStateManager;
|
||||
RefreshRateManager _refreshRateManager;
|
||||
|
||||
quint64 _lastFaceTrackerUpdate;
|
||||
|
||||
|
@ -789,6 +798,9 @@ private:
|
|||
AudioInjectorPointer _snapshotSoundInjector;
|
||||
SharedSoundPointer _snapshotSound;
|
||||
SharedSoundPointer _sampleSound;
|
||||
std::mutex _snapshotMutex;
|
||||
std::queue<SnapshotOperator> _snapshotOperators;
|
||||
bool _hasPrimarySnapshot { false };
|
||||
|
||||
DisplayPluginPointer _autoSwitchDisplayModeSupportedHMDPlugin;
|
||||
QString _autoSwitchDisplayModeSupportedHMDPluginName;
|
||||
|
@ -811,5 +823,6 @@ private:
|
|||
|
||||
bool _resumeAfterLoginDialogActionTaken_WasPostponed { false };
|
||||
bool _resumeAfterLoginDialogActionTaken_SafeToRun { false };
|
||||
bool _startUpFinished { false };
|
||||
};
|
||||
#endif // hifi_Application_h
|
||||
|
|
|
@ -194,20 +194,6 @@ Menu::Menu() {
|
|||
|
||||
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();
|
||||
|
||||
// View > Center Player In View
|
||||
|
@ -434,9 +420,21 @@ Menu::Menu() {
|
|||
MenuWrapper* resolutionMenu = renderOptionsMenu->addMenu(MenuOption::RenderResolution);
|
||||
QActionGroup* resolutionGroup = new QActionGroup(resolutionMenu);
|
||||
resolutionGroup->setExclusive(true);
|
||||
|
||||
#if defined(Q_OS_MAC)
|
||||
resolutionGroup->addAction(addCheckableActionToQMenuAndActionHash(resolutionMenu, MenuOption::RenderResolutionOne, 0, false));
|
||||
#else
|
||||
resolutionGroup->addAction(addCheckableActionToQMenuAndActionHash(resolutionMenu, MenuOption::RenderResolutionOne, 0, true));
|
||||
#endif
|
||||
|
||||
resolutionGroup->addAction(addCheckableActionToQMenuAndActionHash(resolutionMenu, MenuOption::RenderResolutionTwoThird, 0, false));
|
||||
|
||||
#if defined(Q_OS_MAC)
|
||||
resolutionGroup->addAction(addCheckableActionToQMenuAndActionHash(resolutionMenu, MenuOption::RenderResolutionHalf, 0, true));
|
||||
#else
|
||||
resolutionGroup->addAction(addCheckableActionToQMenuAndActionHash(resolutionMenu, MenuOption::RenderResolutionHalf, 0, false));
|
||||
#endif
|
||||
|
||||
resolutionGroup->addAction(addCheckableActionToQMenuAndActionHash(resolutionMenu, MenuOption::RenderResolutionThird, 0, false));
|
||||
resolutionGroup->addAction(addCheckableActionToQMenuAndActionHash(resolutionMenu, MenuOption::RenderResolutionQuarter, 0, false));
|
||||
|
||||
|
@ -627,6 +625,8 @@ Menu::Menu() {
|
|||
avatar.get(), SLOT(setEnableDebugDrawAnimPose(bool)));
|
||||
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::AnimDebugDrawPosition, 0, false,
|
||||
avatar.get(), SLOT(setEnableDebugDrawPosition(bool)));
|
||||
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::AnimDebugDrawOtherSkeletons, 0, false,
|
||||
avatarManager.data(), SLOT(setEnableDebugDrawOtherSkeletons(bool)));
|
||||
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::MeshVisible, 0, true,
|
||||
avatar.get(), SLOT(setEnableMeshVisible(bool)));
|
||||
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::DisableEyelidAdjustment, 0, false);
|
||||
|
|
|
@ -33,6 +33,7 @@ namespace MenuOption {
|
|||
const QString AnimDebugDrawBaseOfSupport = "Debug Draw Base of Support";
|
||||
const QString AnimDebugDrawDefaultPose = "Debug Draw Default Pose";
|
||||
const QString AnimDebugDrawPosition= "Debug Draw Position";
|
||||
const QString AnimDebugDrawOtherSkeletons = "Debug Draw Other Skeletons";
|
||||
const QString AskToResetSettings = "Ask To Reset Settings on Start";
|
||||
const QString AssetMigration = "ATP Asset Migration";
|
||||
const QString AssetServer = "Asset Browser";
|
||||
|
@ -53,7 +54,6 @@ namespace MenuOption {
|
|||
const QString BookmarkAvatarEntities = "Bookmark Avatar Entities";
|
||||
const QString BookmarkLocation = "Bookmark Location";
|
||||
const QString CalibrateCamera = "Calibrate Camera";
|
||||
const QString CameraEntityMode = "Entity Mode";
|
||||
const QString CenterPlayerInView = "Center Player In View";
|
||||
const QString Chat = "Chat...";
|
||||
const QString ClearDiskCache = "Clear Disk Cache";
|
||||
|
@ -120,7 +120,6 @@ namespace MenuOption {
|
|||
const QString Help = "Help...";
|
||||
const QString HomeLocation = "Home ";
|
||||
const QString IncreaseAvatarSize = "Increase Avatar Size";
|
||||
const QString IndependentMode = "Independent Mode";
|
||||
const QString ActionMotorControl = "Enable Default Motor Control";
|
||||
const QString LastLocation = "Last Location";
|
||||
const QString LoadScript = "Open and Run Script File...";
|
||||
|
|
|
@ -18,9 +18,6 @@
|
|||
#include <QPushButton>
|
||||
#include <QStandardPaths>
|
||||
|
||||
static const QString AVATAR_HEAD_AND_BODY_STRING = "Avatar Body with Head";
|
||||
static const QString ENTITY_MODEL_STRING = "Entity Model";
|
||||
|
||||
ModelSelector::ModelSelector() {
|
||||
QFormLayout* form = new QFormLayout(this);
|
||||
|
||||
|
|
158
interface/src/RefreshRateManager.cpp
Normal file
158
interface/src/RefreshRateManager.cpp
Normal file
|
@ -0,0 +1,158 @@
|
|||
//
|
||||
// RefreshRateManager.cpp
|
||||
// interface/src/
|
||||
//
|
||||
// Created by Dante Ruiz on 2019-04-15.
|
||||
// Copyright 2019 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
|
||||
#include "RefreshRateManager.h"
|
||||
|
||||
#include <array>
|
||||
#include <map>
|
||||
|
||||
|
||||
#include <Application.h>
|
||||
|
||||
#include <display-plugins/hmd/HmdDisplayPlugin.h>
|
||||
|
||||
static const int VR_TARGET_RATE = 90;
|
||||
|
||||
static const std::array<std::string, RefreshRateManager::RefreshRateProfile::PROFILE_NUM> REFRESH_RATE_PROFILE_TO_STRING =
|
||||
{ { "Eco", "Interactive", "Realtime" } };
|
||||
|
||||
static const std::array<std::string, RefreshRateManager::RefreshRateRegime::REGIME_NUM> REFRESH_RATE_REGIME_TO_STRING =
|
||||
{ { "FocusActive", "FocusInactive", "Unfocus", "Minimized", "StartUp", "ShutDown" } };
|
||||
|
||||
static const std::array<std::string, RefreshRateManager::UXMode::UX_NUM> UX_MODE_TO_STRING =
|
||||
{ { "Desktop", "VR" } };
|
||||
|
||||
static const std::map<std::string, RefreshRateManager::RefreshRateProfile> REFRESH_RATE_PROFILE_FROM_STRING =
|
||||
{ { "Eco", RefreshRateManager::RefreshRateProfile::ECO },
|
||||
{ "Interactive", RefreshRateManager::RefreshRateProfile::INTERACTIVE },
|
||||
{ "Realtime", RefreshRateManager::RefreshRateProfile::REALTIME } };
|
||||
|
||||
|
||||
// Porfile regimes are:
|
||||
// { { "FocusActive", "FocusInactive", "Unfocus", "Minimized", "StartUp", "ShutDown" } }
|
||||
|
||||
static const std::array<int, RefreshRateManager::RefreshRateRegime::REGIME_NUM> ECO_PROFILE =
|
||||
{ { 20, 10, 5, 2, 30, 30 } };
|
||||
|
||||
static const std::array<int, RefreshRateManager::RefreshRateRegime::REGIME_NUM> INTERACTIVE_PROFILE =
|
||||
{ { 30, 20, 10, 2, 30, 30 } };
|
||||
|
||||
static const std::array<int, RefreshRateManager::RefreshRateRegime::REGIME_NUM> REALTIME_PROFILE =
|
||||
{ { 60, 60, 10, 2, 30, 30} };
|
||||
|
||||
static const std::array<std::array<int, RefreshRateManager::RefreshRateRegime::REGIME_NUM>, RefreshRateManager::RefreshRateProfile::PROFILE_NUM> REFRESH_RATE_PROFILES =
|
||||
{ { ECO_PROFILE, INTERACTIVE_PROFILE, REALTIME_PROFILE } };
|
||||
|
||||
|
||||
static const int INACTIVE_TIMER_LIMIT = 3000;
|
||||
|
||||
|
||||
std::string RefreshRateManager::refreshRateProfileToString(RefreshRateManager::RefreshRateProfile refreshRateProfile) {
|
||||
return REFRESH_RATE_PROFILE_TO_STRING.at(refreshRateProfile);
|
||||
}
|
||||
|
||||
RefreshRateManager::RefreshRateProfile RefreshRateManager::refreshRateProfileFromString(std::string refreshRateProfile) {
|
||||
return REFRESH_RATE_PROFILE_FROM_STRING.at(refreshRateProfile);
|
||||
}
|
||||
|
||||
std::string RefreshRateManager::refreshRateRegimeToString(RefreshRateManager::RefreshRateRegime refreshRateRegime) {
|
||||
return REFRESH_RATE_REGIME_TO_STRING.at(refreshRateRegime);
|
||||
}
|
||||
|
||||
std::string RefreshRateManager::uxModeToString(RefreshRateManager::RefreshRateManager::UXMode uxMode) {
|
||||
return UX_MODE_TO_STRING.at(uxMode);
|
||||
}
|
||||
|
||||
RefreshRateManager::RefreshRateManager() {
|
||||
_refreshRateProfile = (RefreshRateManager::RefreshRateProfile) _refreshRateProfileSetting.get();
|
||||
_inactiveTimer->setInterval(INACTIVE_TIMER_LIMIT);
|
||||
_inactiveTimer->setSingleShot(true);
|
||||
QObject::connect(_inactiveTimer.get(), &QTimer::timeout, [&] {
|
||||
toggleInactive();
|
||||
});
|
||||
}
|
||||
|
||||
void RefreshRateManager::resetInactiveTimer() {
|
||||
if (_uxMode == RefreshRateManager::UXMode::DESKTOP) {
|
||||
auto regime = getRefreshRateRegime();
|
||||
if (regime == RefreshRateRegime::FOCUS_ACTIVE || regime == RefreshRateRegime::FOCUS_INACTIVE) {
|
||||
_inactiveTimer->start();
|
||||
setRefreshRateRegime(RefreshRateManager::RefreshRateRegime::FOCUS_ACTIVE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RefreshRateManager::toggleInactive() {
|
||||
if (_uxMode == RefreshRateManager::UXMode::DESKTOP &&
|
||||
getRefreshRateRegime() == RefreshRateManager::RefreshRateRegime::FOCUS_ACTIVE) {
|
||||
setRefreshRateRegime(RefreshRateManager::RefreshRateRegime::FOCUS_INACTIVE);
|
||||
}
|
||||
}
|
||||
|
||||
void RefreshRateManager::setRefreshRateProfile(RefreshRateManager::RefreshRateProfile refreshRateProfile) {
|
||||
if (_refreshRateProfile != refreshRateProfile) {
|
||||
_refreshRateProfileSettingLock.withWriteLock([&] {
|
||||
_refreshRateProfile = refreshRateProfile;
|
||||
_refreshRateProfileSetting.set((int) refreshRateProfile);
|
||||
});
|
||||
updateRefreshRateController();
|
||||
}
|
||||
}
|
||||
|
||||
RefreshRateManager::RefreshRateProfile RefreshRateManager::getRefreshRateProfile() const {
|
||||
RefreshRateManager::RefreshRateProfile profile = RefreshRateManager::RefreshRateProfile::REALTIME;
|
||||
|
||||
if (getUXMode() != RefreshRateManager::UXMode::VR) {
|
||||
profile =(RefreshRateManager::RefreshRateProfile) _refreshRateProfileSettingLock.resultWithReadLock<int>([&] {
|
||||
return _refreshRateProfileSetting.get();
|
||||
});
|
||||
}
|
||||
|
||||
return profile;
|
||||
}
|
||||
|
||||
RefreshRateManager::RefreshRateRegime RefreshRateManager::getRefreshRateRegime() const {
|
||||
if (getUXMode() == RefreshRateManager::UXMode::VR) {
|
||||
return RefreshRateManager::RefreshRateRegime::FOCUS_ACTIVE;
|
||||
} else {
|
||||
return _refreshRateRegime;
|
||||
}
|
||||
}
|
||||
|
||||
void RefreshRateManager::setRefreshRateRegime(RefreshRateManager::RefreshRateRegime refreshRateRegime) {
|
||||
if (_refreshRateRegime != refreshRateRegime) {
|
||||
_refreshRateRegime = refreshRateRegime;
|
||||
updateRefreshRateController();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void RefreshRateManager::setUXMode(RefreshRateManager::UXMode uxMode) {
|
||||
if (_uxMode != uxMode) {
|
||||
_uxMode = uxMode;
|
||||
updateRefreshRateController();
|
||||
}
|
||||
}
|
||||
|
||||
void RefreshRateManager::updateRefreshRateController() const {
|
||||
if (_refreshRateOperator) {
|
||||
int targetRefreshRate;
|
||||
if (_uxMode == RefreshRateManager::UXMode::DESKTOP) {
|
||||
targetRefreshRate = REFRESH_RATE_PROFILES[_refreshRateProfile][_refreshRateRegime];
|
||||
} else {
|
||||
targetRefreshRate = VR_TARGET_RATE;
|
||||
}
|
||||
|
||||
_refreshRateOperator(targetRefreshRate);
|
||||
_activeRefreshRate = targetRefreshRate;
|
||||
}
|
||||
}
|
86
interface/src/RefreshRateManager.h
Normal file
86
interface/src/RefreshRateManager.h
Normal file
|
@ -0,0 +1,86 @@
|
|||
//
|
||||
// RefreshRateManager.h
|
||||
// interface/src/
|
||||
//
|
||||
// Created by Dante Ruiz on 2019-04-15.
|
||||
// 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_RefreshRateManager_h
|
||||
#define hifi_RefreshRateManager_h
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#include <QTimer>
|
||||
|
||||
#include <SettingHandle.h>
|
||||
#include <shared/ReadWriteLockable.h>
|
||||
|
||||
class RefreshRateManager {
|
||||
public:
|
||||
enum RefreshRateProfile {
|
||||
ECO = 0,
|
||||
INTERACTIVE,
|
||||
REALTIME,
|
||||
PROFILE_NUM
|
||||
};
|
||||
|
||||
enum RefreshRateRegime {
|
||||
FOCUS_ACTIVE = 0,
|
||||
FOCUS_INACTIVE,
|
||||
UNFOCUS,
|
||||
MINIMIZED,
|
||||
STARTUP,
|
||||
SHUTDOWN,
|
||||
REGIME_NUM
|
||||
};
|
||||
|
||||
enum UXMode {
|
||||
DESKTOP = 0,
|
||||
VR,
|
||||
UX_NUM
|
||||
};
|
||||
|
||||
RefreshRateManager();
|
||||
~RefreshRateManager() = default;
|
||||
|
||||
void setRefreshRateProfile(RefreshRateProfile refreshRateProfile);
|
||||
RefreshRateProfile getRefreshRateProfile() const;
|
||||
|
||||
void setRefreshRateRegime(RefreshRateRegime refreshRateRegime);
|
||||
RefreshRateRegime getRefreshRateRegime() const;
|
||||
|
||||
void setUXMode(UXMode uxMode);
|
||||
UXMode getUXMode() const { return _uxMode; }
|
||||
|
||||
void setRefreshRateOperator(std::function<void(int)> refreshRateOperator) { _refreshRateOperator = refreshRateOperator; }
|
||||
int getActiveRefreshRate() const { return _activeRefreshRate; }
|
||||
void updateRefreshRateController() const;
|
||||
|
||||
void resetInactiveTimer();
|
||||
void toggleInactive();
|
||||
|
||||
static std::string refreshRateProfileToString(RefreshRateProfile refreshRateProfile);
|
||||
static RefreshRateProfile refreshRateProfileFromString(std::string refreshRateProfile);
|
||||
static std::string uxModeToString(UXMode uxMode);
|
||||
static std::string refreshRateRegimeToString(RefreshRateRegime refreshRateRegime);
|
||||
|
||||
private:
|
||||
mutable int _activeRefreshRate { 20 };
|
||||
RefreshRateProfile _refreshRateProfile { RefreshRateProfile::INTERACTIVE};
|
||||
RefreshRateRegime _refreshRateRegime { RefreshRateRegime::STARTUP };
|
||||
UXMode _uxMode;
|
||||
|
||||
mutable ReadWriteLockable _refreshRateProfileSettingLock;
|
||||
Setting::Handle<int> _refreshRateProfileSetting { "refreshRateProfile", RefreshRateProfile::INTERACTIVE };
|
||||
|
||||
std::function<void(int)> _refreshRateOperator { nullptr };
|
||||
|
||||
std::shared_ptr<QTimer> _inactiveTimer { std::make_shared<QTimer>() };
|
||||
};
|
||||
|
||||
#endif
|
|
@ -152,10 +152,12 @@ public:
|
|||
_cachedArgsPointer->_viewport = args->_viewport;
|
||||
_cachedArgsPointer->_displayMode = args->_displayMode;
|
||||
_cachedArgsPointer->_renderMode = args->_renderMode;
|
||||
_cachedArgsPointer->_stencilMaskMode = args->_stencilMaskMode;
|
||||
args->_blitFramebuffer = destFramebuffer;
|
||||
args->_viewport = glm::ivec4(0, 0, destFramebuffer->getWidth(), destFramebuffer->getHeight());
|
||||
args->_displayMode = RenderArgs::MONO;
|
||||
args->_renderMode = RenderArgs::RenderMode::SECONDARY_CAMERA_RENDER_MODE;
|
||||
args->_stencilMaskMode = StencilMaskMode::NONE;
|
||||
|
||||
gpu::doInBatch("SecondaryCameraJob::run", args->_context, [&](gpu::Batch& batch) {
|
||||
batch.disableContextStereo();
|
||||
|
@ -255,10 +257,11 @@ public:
|
|||
void run(const render::RenderContextPointer& renderContext, const RenderArgsPointer& cachedArgs) {
|
||||
auto args = renderContext->args;
|
||||
if (cachedArgs) {
|
||||
args->_blitFramebuffer = cachedArgs->_blitFramebuffer;
|
||||
args->_viewport = cachedArgs->_viewport;
|
||||
args->_displayMode = cachedArgs->_displayMode;
|
||||
args->_renderMode = cachedArgs->_renderMode;
|
||||
args->_blitFramebuffer = cachedArgs->_blitFramebuffer;
|
||||
args->_viewport = cachedArgs->_viewport;
|
||||
args->_displayMode = cachedArgs->_displayMode;
|
||||
args->_renderMode = cachedArgs->_renderMode;
|
||||
args->_stencilMaskMode = cachedArgs->_stencilMaskMode;
|
||||
}
|
||||
args->popViewFrustum();
|
||||
|
||||
|
|
|
@ -120,6 +120,8 @@ void AvatarManager::init() {
|
|||
_myAvatar->addToScene(_myAvatar, scene, transaction);
|
||||
scene->enqueueTransaction(transaction);
|
||||
}
|
||||
|
||||
setEnableDebugDrawOtherSkeletons(Menu::getInstance()->isOptionChecked(MenuOption::AnimDebugDrawOtherSkeletons));
|
||||
}
|
||||
|
||||
void AvatarManager::setSpace(workload::SpacePointer& space ) {
|
||||
|
@ -334,9 +336,14 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
|
|||
if (avatar->getSkeletonModel()->isLoaded() && avatar->getWorkloadRegion() == workload::Region::R1) {
|
||||
_myAvatar->addAvatarHandsToFlow(avatar);
|
||||
}
|
||||
if (_drawOtherAvatarSkeletons) {
|
||||
avatar->debugJointData();
|
||||
}
|
||||
avatar->setEnableMeshVisible(!_drawOtherAvatarSkeletons);
|
||||
avatar->updateRenderItem(renderTransaction);
|
||||
avatar->updateSpaceProxy(workloadTransaction);
|
||||
avatar->setLastRenderUpdateTime(startTime);
|
||||
|
||||
} else {
|
||||
// we've spent our time budget for this priority bucket
|
||||
// let's deal with the reminding avatars if this pass and BREAK from the for loop
|
||||
|
@ -727,7 +734,7 @@ RayToAvatarIntersectionResult AvatarManager::findRayIntersectionVector(const Pic
|
|||
boxHit._distance = FLT_MAX;
|
||||
|
||||
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]];
|
||||
if (mSphere.isValid()) {
|
||||
float boundDistance = FLT_MAX;
|
||||
|
@ -942,7 +949,8 @@ void AvatarManager::setAvatarSortCoefficient(const QString& name, const QScriptV
|
|||
* It is unique among all avatars present in the domain at the time.
|
||||
* @property {number} audioLoudness - The instantaneous loudness of the audio input that the avatar is injecting into the
|
||||
* domain.
|
||||
* @property {boolean} isReplicated - <strong>Deprecated.</strong>
|
||||
* @property {boolean} isReplicated - <span class="important">Deprecated: This property is deprecated and will be
|
||||
* removed.</span>
|
||||
* @property {Vec3} position - The position of the avatar.
|
||||
* @property {number} palOrbOffset - The vertical offset from the avatar's position that an overlay orb should be displayed at.
|
||||
*/
|
||||
|
|
|
@ -262,6 +262,15 @@ public slots:
|
|||
*/
|
||||
void updateAvatarRenderStatus(bool shouldRenderAvatars);
|
||||
|
||||
/**jsdoc
|
||||
* Displays other avatars skeletons debug graphics.
|
||||
* @function AvatarManager.setEnableDebugDrawOtherSkeletons
|
||||
* @param {boolean} enabled - <code>true</code> to show the debug graphics, <code>false</code> to hide.
|
||||
*/
|
||||
void setEnableDebugDrawOtherSkeletons(bool isEnabled) {
|
||||
_drawOtherAvatarSkeletons = isEnabled;
|
||||
}
|
||||
|
||||
protected:
|
||||
AvatarSharedPointer addAvatar(const QUuid& sessionUUID, const QWeakPointer<Node>& mixerWeakPointer) override;
|
||||
|
||||
|
@ -299,6 +308,7 @@ private:
|
|||
workload::SpacePointer _space;
|
||||
|
||||
AvatarTransit::TransitConfig _transitConfig;
|
||||
bool _drawOtherAvatarSkeletons { false };
|
||||
};
|
||||
|
||||
#endif // hifi_AvatarManager_h
|
||||
|
|
|
@ -168,6 +168,7 @@ MyAvatar::MyAvatar(QThread* thread) :
|
|||
_displayNameSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "displayName", ""),
|
||||
_collisionSoundURLSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "collisionSoundURL", QUrl(_collisionSoundURL)),
|
||||
_useSnapTurnSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "useSnapTurn", _useSnapTurn),
|
||||
_hoverWhenUnsupportedSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "hoverWhenUnsupported", _hoverWhenUnsupported),
|
||||
_userHeightSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "userHeight", DEFAULT_AVATAR_HEIGHT),
|
||||
_flyingHMDSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "flyingHMD", _flyingPrefHMD),
|
||||
_movementReferenceSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "movementReference", _movementReference),
|
||||
|
@ -948,6 +949,7 @@ void MyAvatar::simulate(float deltaTime, bool inView) {
|
|||
bool collisionlessAllowed = zoneInteractionProperties.second;
|
||||
_characterController.setZoneFlyingAllowed(zoneAllowsFlying || !isPhysicsEnabled);
|
||||
_characterController.setComfortFlyingAllowed(_enableFlying);
|
||||
_characterController.setHoverWhenUnsupported(_hoverWhenUnsupported);
|
||||
_characterController.setCollisionlessAllowed(collisionlessAllowed);
|
||||
}
|
||||
|
||||
|
@ -1041,11 +1043,15 @@ void MyAvatar::updateJointFromController(controller::Action poseKey, ThreadSafeV
|
|||
assert(QThread::currentThread() == thread());
|
||||
auto userInputMapper = DependencyManager::get<UserInputMapper>();
|
||||
controller::Pose controllerPose = userInputMapper->getPoseState(poseKey);
|
||||
Transform transform;
|
||||
transform.setTranslation(controllerPose.getTranslation());
|
||||
transform.setRotation(controllerPose.getRotation());
|
||||
glm::mat4 controllerMatrix = transform.getMatrix();
|
||||
matrixCache.set(controllerMatrix);
|
||||
if (controllerPose.isValid()) {
|
||||
Transform transform;
|
||||
transform.setTranslation(controllerPose.getTranslation());
|
||||
transform.setRotation(controllerPose.getRotation());
|
||||
glm::mat4 controllerMatrix = transform.getMatrix();
|
||||
matrixCache.set(controllerMatrix);
|
||||
} else {
|
||||
matrixCache.invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
// best called at end of main loop, after physics.
|
||||
|
@ -1305,6 +1311,7 @@ void MyAvatar::saveData() {
|
|||
_displayNameSetting.set(_displayName);
|
||||
_collisionSoundURLSetting.set(_collisionSoundURL);
|
||||
_useSnapTurnSetting.set(_useSnapTurn);
|
||||
_hoverWhenUnsupportedSetting.set(_hoverWhenUnsupported);
|
||||
_userHeightSetting.set(getUserHeight());
|
||||
_flyingHMDSetting.set(getFlyingHMDPref());
|
||||
_movementReferenceSetting.set(getMovementReference());
|
||||
|
@ -1630,7 +1637,9 @@ void MyAvatar::handleChangedAvatarEntityData() {
|
|||
if (!skip) {
|
||||
sanitizeAvatarEntityProperties(properties);
|
||||
entityTree->withWriteLock([&] {
|
||||
entityTree->updateEntity(id, properties);
|
||||
if (entityTree->updateEntity(id, properties)) {
|
||||
packetSender->queueEditAvatarEntityMessage(entityTree, id);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1907,6 +1916,7 @@ void MyAvatar::loadData() {
|
|||
setDisplayName(_displayNameSetting.get());
|
||||
setCollisionSoundURL(_collisionSoundURLSetting.get(QUrl(DEFAULT_AVATAR_COLLISION_SOUND_URL)).toString());
|
||||
setSnapTurn(_useSnapTurnSetting.get());
|
||||
setHoverWhenUnsupported(_hoverWhenUnsupportedSetting.get());
|
||||
setDominantHand(_dominantHandSetting.get(DOMINANT_RIGHT_HAND).toLower());
|
||||
setStrafeEnabled(_strafeEnabledSetting.get(DEFAULT_STRAFE_ENABLED));
|
||||
setHmdAvatarAlignmentType(_hmdAvatarAlignmentTypeSetting.get(DEFAULT_HMD_AVATAR_ALIGNMENT_TYPE).toLower());
|
||||
|
@ -5811,12 +5821,19 @@ void MyAvatar::releaseGrab(const QUuid& grabID) {
|
|||
}
|
||||
|
||||
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();
|
||||
for (auto &handJointName : HAND_COLLISION_JOINTS) {
|
||||
int jointIndex = otherAvatar->getJointIndex(handJointName);
|
||||
if (jointIndex != -1) {
|
||||
glm::vec3 position = otherAvatar->getJointPosition(jointIndex);
|
||||
flow.setOthersCollision(otherAvatar->getID(), jointIndex, position);
|
||||
if (otherAvatar != nullptr && flow.getActive()) {
|
||||
for (auto &handJointName : HAND_COLLISION_JOINTS) {
|
||||
int jointIndex = otherAvatar->getJointIndex(handJointName);
|
||||
if (jointIndex != -1) {
|
||||
glm::vec3 position = otherAvatar->getJointPosition(jointIndex);
|
||||
flow.setOthersCollision(otherAvatar->getID(), jointIndex, position);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -116,8 +116,8 @@ class MyAvatar : public Avatar {
|
|||
* @property {boolean} lookAtSnappingEnabled=true - <code>true</code> if the avatar's eyes snap to look at another avatar's
|
||||
* eyes when the other avatar is in the line of sight and also has <code>lookAtSnappingEnabled == true</code>.
|
||||
* @property {string} skeletonModelURL - The avatar's FST file.
|
||||
* @property {AttachmentData[]} attachmentData - Information on the avatar's attachments.<br />
|
||||
* <strong>Deprecated:</strong> Use avatar entities instead.
|
||||
* @property {AttachmentData[]} attachmentData - Information on the avatar's attachments.
|
||||
* <p class="important">Deprecated: This property is deprecated and will be removed. Use avatar entities instead.</p>
|
||||
* @property {string[]} jointNames - The list of joints in the current avatar model. <em>Read-only.</em>
|
||||
* @property {Uuid} sessionUUID - Unique ID of the avatar in the domain. <em>Read-only.</em>
|
||||
* @property {Mat4} sensorToWorldMatrix - The scale, rotation, and translation transform from the user's real world to the
|
||||
|
@ -198,7 +198,7 @@ class MyAvatar : public Avatar {
|
|||
* @property {Pose} rightHandTipPose - The right hand's pose as determined by the hand controllers, relative to the avatar,
|
||||
* with the position adjusted by 0.3m along the direction of the palm. <em>Read-only.</em>
|
||||
*
|
||||
* @property {number} energy - <strong>Deprecated:</strong> This property will be removed from the API.
|
||||
* @property {number} energy - <span class="important">Deprecated: This property will be removed.</span>
|
||||
* @property {boolean} isAway - <code>true</code> if your avatar is away (i.e., inactive), <code>false</code> if it is
|
||||
* active.
|
||||
*
|
||||
|
@ -213,8 +213,9 @@ class MyAvatar : public Avatar {
|
|||
* was set <code>false</code> because the zone may disallow collisionless avatars.
|
||||
* @property {boolean} otherAvatarsCollisionsEnabled - Set to <code>true</code> to enable the avatar to collide with other
|
||||
* avatars, <code>false</code> to disable collisions with other avatars.
|
||||
* @property {boolean} characterControllerEnabled - Synonym of <code>collisionsEnabled</code>.<br />
|
||||
* <strong>Deprecated:</strong> Use <code>collisionsEnabled</code> instead.
|
||||
* @property {boolean} characterControllerEnabled - Synonym of <code>collisionsEnabled</code>.
|
||||
* <p class="important">Deprecated: This property is deprecated and will be removed. Use <code>collisionsEnabled</code>
|
||||
* instead.</p>
|
||||
* @property {boolean} useAdvancedMovementControls - Returns and sets the value of the Interface setting, Settings >
|
||||
* Controls > Walking. Note: Setting the value has no effect unless Interface is restarted.
|
||||
* @property {boolean} showPlayArea - Returns and sets the value of the Interface setting, Settings > Controls > Show room
|
||||
|
@ -797,6 +798,18 @@ public:
|
|||
* @param {number} index
|
||||
*/
|
||||
Q_INVOKABLE void setControlScheme(int index) { _controlSchemeIndex = (index >= 0 && index <= 2) ? index : 0; }
|
||||
|
||||
/**jsdoc
|
||||
* @function MyAvatar.hoverWhenUnsupported
|
||||
* @returns {boolean}
|
||||
*/
|
||||
Q_INVOKABLE bool hoverWhenUnsupported() const { return _hoverWhenUnsupported; }
|
||||
/**jsdoc
|
||||
* @function MyAvatar.setHoverWhenUnsupported
|
||||
* @param {boolean} on
|
||||
*/
|
||||
Q_INVOKABLE void setHoverWhenUnsupported(bool on) { _hoverWhenUnsupported = on; }
|
||||
|
||||
/**jsdoc
|
||||
* Sets the avatar's dominant hand.
|
||||
* @function MyAvatar.setDominantHand
|
||||
|
@ -1557,14 +1570,14 @@ public:
|
|||
* @function MyAvatar.setCharacterControllerEnabled
|
||||
* @param {boolean} enabled - <code>true</code> to enable the avatar to collide with entities, <code>false</code> to
|
||||
* disable.
|
||||
* @deprecated Use {@link MyAvatar.setCollisionsEnabled} instead.
|
||||
* @deprecated This function is deprecated and will be removed. Use {@link MyAvatar.setCollisionsEnabled} instead.
|
||||
*/
|
||||
Q_INVOKABLE void setCharacterControllerEnabled(bool enabled); // deprecated
|
||||
|
||||
/**jsdoc
|
||||
* @function MyAvatar.getCharacterControllerEnabled
|
||||
* @returns {boolean} <code>true</code> if the avatar will currently collide with entities, <code>false</code> if it won't.
|
||||
* @deprecated Use {@link MyAvatar.getCollisionsEnabled} instead.
|
||||
* @deprecated This function is deprecated and will be removed. Use {@link MyAvatar.getCollisionsEnabled} instead.
|
||||
*/
|
||||
Q_INVOKABLE bool getCharacterControllerEnabled(); // deprecated
|
||||
|
||||
|
@ -1910,7 +1923,7 @@ public slots:
|
|||
|
||||
/**jsdoc
|
||||
* @function MyAvatar.clearScaleRestriction
|
||||
* @deprecated This function is deprecated and will be removed from the API.
|
||||
* @deprecated This function is deprecated and will be removed.
|
||||
*/
|
||||
void clearScaleRestriction();
|
||||
|
||||
|
@ -1919,7 +1932,8 @@ public slots:
|
|||
* Adds a thrust to your avatar's current thrust to be applied for a short while.
|
||||
* @function MyAvatar.addThrust
|
||||
* @param {Vec3} thrust - The thrust direction and magnitude.
|
||||
* @deprecated Use {@link MyAvatar|MyAvatar.motorVelocity} and related properties instead.
|
||||
* @deprecated This function is deprecated and will be removed. Use {@link MyAvatar|MyAvatar.motorVelocity} and related
|
||||
* properties instead.
|
||||
*/
|
||||
// Set/Get update the thrust that will move the avatar around
|
||||
void addThrust(glm::vec3 newThrust) { _thrust += newThrust; };
|
||||
|
@ -1928,7 +1942,8 @@ public slots:
|
|||
* Gets the thrust currently being applied to your avatar.
|
||||
* @function MyAvatar.getThrust
|
||||
* @returns {Vec3} The thrust currently being applied to your avatar.
|
||||
* @deprecated Use {@link MyAvatar|MyAvatar.motorVelocity} and related properties instead.
|
||||
* @deprecated This function is deprecated and will be removed. Use {@link MyAvatar|MyAvatar.motorVelocity} and related
|
||||
* properties instead.
|
||||
*/
|
||||
glm::vec3 getThrust() { return _thrust; };
|
||||
|
||||
|
@ -1936,7 +1951,8 @@ public slots:
|
|||
* Sets the thrust to be applied to your avatar for a short while.
|
||||
* @function MyAvatar.setThrust
|
||||
* @param {Vec3} thrust - The thrust direction and magnitude.
|
||||
* @deprecated Use {@link MyAvatar|MyAvatar.motorVelocity} and related properties instead.
|
||||
* @deprecated This function is deprecated and will be removed. Use {@link MyAvatar|MyAvatar.motorVelocity} and related
|
||||
* properties instead.
|
||||
*/
|
||||
void setThrust(glm::vec3 newThrust) { _thrust = newThrust; }
|
||||
|
||||
|
@ -2281,7 +2297,7 @@ signals:
|
|||
* {@link MyAvatar.setAttachmentData|setAttachmentData}.
|
||||
* @function MyAvatar.attachmentsChanged
|
||||
* @returns {Signal}
|
||||
* @deprecated Use avatar entities instead.
|
||||
* @deprecated This signal is deprecated and will be removed. Use avatar entities instead.
|
||||
*/
|
||||
void attachmentsChanged();
|
||||
|
||||
|
@ -2480,6 +2496,7 @@ private:
|
|||
ThreadSafeValueCache<QUrl> _prefOverrideAnimGraphUrl;
|
||||
QUrl _fstAnimGraphOverrideUrl;
|
||||
bool _useSnapTurn { true };
|
||||
bool _hoverWhenUnsupported{ true };
|
||||
ThreadSafeValueCache<QString> _dominantHand { DOMINANT_RIGHT_HAND };
|
||||
ThreadSafeValueCache<QString> _hmdAvatarAlignmentType { DEFAULT_HMD_AVATAR_ALIGNMENT_TYPE };
|
||||
ThreadSafeValueCache<bool> _strafeEnabled{ DEFAULT_STRAFE_ENABLED };
|
||||
|
@ -2675,6 +2692,7 @@ private:
|
|||
Setting::Handle<QString> _displayNameSetting;
|
||||
Setting::Handle<QUrl> _collisionSoundURLSetting;
|
||||
Setting::Handle<bool> _useSnapTurnSetting;
|
||||
Setting::Handle<bool> _hoverWhenUnsupportedSetting;
|
||||
Setting::Handle<float> _userHeightSetting;
|
||||
Setting::Handle<bool> _flyingHMDSetting;
|
||||
Setting::Handle<int> _movementReferenceSetting;
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#include "Application.h"
|
||||
#include "AvatarMotionState.h"
|
||||
#include "DetailedMotionState.h"
|
||||
#include "DebugDraw.h"
|
||||
|
||||
const float DISPLAYNAME_FADE_TIME = 0.5f;
|
||||
const float DISPLAYNAME_FADE_FACTOR = pow(0.01f, 1.0f / DISPLAYNAME_FADE_TIME);
|
||||
|
@ -358,6 +359,58 @@ void OtherAvatar::simulate(float deltaTime, bool inView) {
|
|||
}
|
||||
}
|
||||
|
||||
void OtherAvatar::debugJointData() const {
|
||||
// Get a copy of the joint data
|
||||
auto jointData = getJointData();
|
||||
auto skeletonData = getSkeletonData();
|
||||
if ((int)skeletonData.size() == jointData.size() && jointData.size() != 0) {
|
||||
const vec4 RED(1.0f, 0.0f, 0.0f, 1.0f);
|
||||
const vec4 GREEN(0.0f, 1.0f, 0.0f, 1.0f);
|
||||
const vec4 BLUE(0.0f, 0.0f, 1.0f, 1.0f);
|
||||
const vec4 LIGHT_RED(1.0f, 0.5f, 0.5f, 1.0f);
|
||||
const vec4 LIGHT_GREEN(0.5f, 1.0f, 0.5f, 1.0f);
|
||||
const vec4 LIGHT_BLUE(0.5f, 0.5f, 1.0f, 1.0f);
|
||||
const vec4 GREY(0.3f, 0.3f, 0.3f, 1.0f);
|
||||
const vec4 WHITE(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
const float AXIS_LENGTH = 0.1f;
|
||||
|
||||
AnimPoseVec absoluteJointPoses;
|
||||
AnimPose rigToAvatar = AnimPose(Quaternions::Y_180 * getWorldOrientation(), getWorldPosition());
|
||||
bool drawBones = false;
|
||||
for (int i = 0; i < jointData.size(); i++) {
|
||||
float jointScale = skeletonData[i].defaultScale * getTargetScale() * METERS_PER_CENTIMETER;
|
||||
auto absoluteRotation = jointData[i].rotationIsDefaultPose ? skeletonData[i].defaultRotation : jointData[i].rotation;
|
||||
auto localJointTranslation = jointScale * (jointData[i].translationIsDefaultPose ? skeletonData[i].defaultTranslation : jointData[i].translation);
|
||||
bool isHips = skeletonData[i].jointName == "Hips";
|
||||
if (isHips) {
|
||||
localJointTranslation = glm::vec3(0.0f);
|
||||
drawBones = true;
|
||||
}
|
||||
AnimPose absoluteParentPose;
|
||||
int parentIndex = skeletonData[i].parentIndex;
|
||||
if (parentIndex != -1 && parentIndex < (int)absoluteJointPoses.size()) {
|
||||
absoluteParentPose = absoluteJointPoses[parentIndex];
|
||||
}
|
||||
AnimPose absoluteJointPose = AnimPose(absoluteRotation, absoluteParentPose.trans() + absoluteParentPose.rot() * localJointTranslation);
|
||||
auto jointPose = rigToAvatar * absoluteJointPose;
|
||||
auto parentPose = rigToAvatar * absoluteParentPose;
|
||||
if (drawBones) {
|
||||
glm::vec3 xAxis = jointPose.rot() * Vectors::UNIT_X;
|
||||
glm::vec3 yAxis = jointPose.rot() * Vectors::UNIT_Y;
|
||||
glm::vec3 zAxis = jointPose.rot() * Vectors::UNIT_Z;
|
||||
|
||||
DebugDraw::getInstance().drawRay(jointPose.trans(), jointPose.trans() + AXIS_LENGTH * xAxis, jointData[i].rotationIsDefaultPose ? LIGHT_RED : RED);
|
||||
DebugDraw::getInstance().drawRay(jointPose.trans(), jointPose.trans() + AXIS_LENGTH * yAxis, jointData[i].rotationIsDefaultPose ? LIGHT_GREEN : GREEN);
|
||||
DebugDraw::getInstance().drawRay(jointPose.trans(), jointPose.trans() + AXIS_LENGTH * zAxis, jointData[i].rotationIsDefaultPose ? LIGHT_BLUE : BLUE);
|
||||
if (!isHips) {
|
||||
DebugDraw::getInstance().drawRay(jointPose.trans(), parentPose.trans(), jointData[i].translationIsDefaultPose ? WHITE : GREY);
|
||||
}
|
||||
}
|
||||
absoluteJointPoses.push_back(absoluteJointPose);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OtherAvatar::handleChangedAvatarEntityData() {
|
||||
PerformanceTimer perfTimer("attachments");
|
||||
|
||||
|
|
|
@ -66,7 +66,7 @@ public:
|
|||
void setCollisionWithOtherAvatarsFlags() override;
|
||||
|
||||
void simulate(float deltaTime, bool inView) override;
|
||||
|
||||
void debugJointData() const;
|
||||
friend AvatarManager;
|
||||
|
||||
protected:
|
||||
|
|
|
@ -41,326 +41,246 @@
|
|||
#include "ui/SecurityImageProvider.h"
|
||||
#include "scripting/HMDScriptingInterface.h"
|
||||
|
||||
static const char* KEY_FILE = "hifikey";
|
||||
static const char* INSTRUCTIONS_FILE = "backup_instructions.html";
|
||||
static const char* IMAGE_HEADER = "-----BEGIN SECURITY IMAGE-----\n";
|
||||
static const char* IMAGE_FOOTER = "-----END SECURITY IMAGE-----\n";
|
||||
namespace {
|
||||
const char* KEY_FILE = "hifikey";
|
||||
const char* INSTRUCTIONS_FILE = "backup_instructions.html";
|
||||
const char* IMAGE_HEADER = "-----BEGIN SECURITY IMAGE-----\n";
|
||||
const char* IMAGE_FOOTER = "-----END SECURITY IMAGE-----\n";
|
||||
|
||||
void initialize() {
|
||||
static bool initialized = false;
|
||||
if (!initialized) {
|
||||
SSL_load_error_strings();
|
||||
SSL_library_init();
|
||||
OpenSSL_add_all_algorithms();
|
||||
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;
|
||||
void initialize() {
|
||||
static bool initialized = false;
|
||||
if (!initialized) {
|
||||
SSL_load_error_strings();
|
||||
SSL_library_init();
|
||||
OpenSSL_add_all_algorithms();
|
||||
initialized = true;
|
||||
}
|
||||
}
|
||||
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
|
||||
int passwordCallback(char* password, int maxPasswordSize, int rwFlag, void* u) {
|
||||
// just return a hardcoded pwd for now
|
||||
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;
|
||||
QString keyFilePath() {
|
||||
auto accountManager = DependencyManager::get<AccountManager>();
|
||||
return PathUtils::getAppDataFilePath(QString("%1.%2").arg(accountManager->getAccountInfo().getUsername(), KEY_FILE));
|
||||
}
|
||||
}
|
||||
|
||||
EC_KEY* readKeys(QString filename) {
|
||||
QFile file(filename);
|
||||
EC_KEY* key = NULL;
|
||||
if (file.open(QFile::ReadOnly)) {
|
||||
// file opened successfully
|
||||
qCDebug(commerce) << "opened key file" << filename;
|
||||
// use the cached _passphrase if it exists, otherwise we need to prompt
|
||||
int passwordCallback(char* password, int maxPasswordSize, int rwFlag, void* u) {
|
||||
// just return a hardcoded pwd for now
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
EC_KEY* readKeys(QString filename) {
|
||||
QFile file(filename);
|
||||
EC_KEY* key = NULL;
|
||||
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 private key";
|
||||
BIO_free(bufio);
|
||||
file.close();
|
||||
qCDebug(commerce) << "read public key";
|
||||
|
||||
if ((key = PEM_read_bio_ECPrivateKey(bufio, &key, passwordCallback, NULL))) {
|
||||
qCDebug(commerce) << "read private key";
|
||||
} else {
|
||||
qCDebug(commerce) << "failed to read private key";
|
||||
}
|
||||
} 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);
|
||||
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;
|
||||
} 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("");
|
||||
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;
|
||||
}
|
||||
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");
|
||||
}
|
||||
}
|
||||
QFile file(filename);
|
||||
if (file.open(QIODevice::WriteOnly)) {
|
||||
const char* bio_data;
|
||||
long bio_size = BIO_get_mem_data(bio, &bio_data);
|
||||
|
||||
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);
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
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() {
|
||||
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) {
|
||||
if (_passphrase) {
|
||||
delete _passphrase;
|
||||
|
|
|
@ -244,6 +244,7 @@ void GraphicsEngine::render_performFrame() {
|
|||
finalFramebuffer = framebufferCache->getFramebuffer();
|
||||
}
|
||||
|
||||
std::queue<Application::SnapshotOperator> snapshotOperators;
|
||||
if (!_programsCompiled.load()) {
|
||||
gpu::doInBatch("splashFrame", _gpuContext, [&](gpu::Batch& batch) {
|
||||
batch.setFramebuffer(finalFramebuffer);
|
||||
|
@ -271,6 +272,7 @@ void GraphicsEngine::render_performFrame() {
|
|||
PROFILE_RANGE(render, "/runRenderFrame");
|
||||
renderArgs._hudOperator = displayPlugin->getHUDOperator();
|
||||
renderArgs._hudTexture = qApp->getApplicationOverlay().getOverlayTexture();
|
||||
renderArgs._takingSnapshot = qApp->takeSnapshotOperators(snapshotOperators);
|
||||
renderArgs._blitFramebuffer = finalFramebuffer;
|
||||
render_runRenderFrame(&renderArgs);
|
||||
}
|
||||
|
@ -285,6 +287,7 @@ void GraphicsEngine::render_performFrame() {
|
|||
frameBufferCache->releaseFramebuffer(framebuffer);
|
||||
}
|
||||
};
|
||||
frame->snapshotOperators = snapshotOperators;
|
||||
// deliver final scene rendering commands to the display plugin
|
||||
{
|
||||
PROFILE_RANGE(render, "/pluginOutput");
|
||||
|
|
|
@ -174,14 +174,10 @@ void Audio::setPTTDesktop(bool enabled) {
|
|||
_pttDesktop = enabled;
|
||||
}
|
||||
});
|
||||
if (!enabled) {
|
||||
// Set to default behavior (unmuted for Desktop) on Push-To-Talk disable.
|
||||
setMutedDesktop(true);
|
||||
} else {
|
||||
// Should be muted when not pushing to talk while PTT is enabled.
|
||||
if (enabled || _settingsLoaded) {
|
||||
// Set to default behavior (muted for Desktop) on Push-To-Talk disable or when enabled. Settings also need to be loaded.
|
||||
setMutedDesktop(true);
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
emit pushToTalkChanged(enabled);
|
||||
emit pushToTalkDesktopChanged(enabled);
|
||||
|
@ -202,12 +198,9 @@ void Audio::setPTTHMD(bool enabled) {
|
|||
_pttHMD = enabled;
|
||||
}
|
||||
});
|
||||
if (!enabled) {
|
||||
// Set to default behavior (unmuted for HMD) on Push-To-Talk disable.
|
||||
setMutedHMD(false);
|
||||
} else {
|
||||
// Should be muted when not pushing to talk while PTT is enabled.
|
||||
setMutedHMD(true);
|
||||
if (enabled || _settingsLoaded) {
|
||||
// Set to default behavior (unmuted for HMD) on Push-To-Talk disable or muted for when PTT is enabled.
|
||||
setMutedHMD(enabled);
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
|
@ -231,6 +224,7 @@ void Audio::loadData() {
|
|||
|
||||
auto client = DependencyManager::get<AudioClient>().data();
|
||||
QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted()), Q_ARG(bool, false));
|
||||
_settingsLoaded = true;
|
||||
}
|
||||
|
||||
bool Audio::getPTTHMD() const {
|
||||
|
@ -357,10 +351,12 @@ void Audio::onContextChanged() {
|
|||
changed = true;
|
||||
}
|
||||
});
|
||||
if (isHMD) {
|
||||
setMuted(getMutedHMD());
|
||||
} else {
|
||||
setMuted(getMutedDesktop());
|
||||
if (_settingsLoaded) {
|
||||
bool isMuted = isHMD ? getMutedHMD() : getMutedDesktop();
|
||||
setMuted(isMuted);
|
||||
// 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) {
|
||||
emit contextChanged(isHMD ? Audio::HMD : Audio::DESKTOP);
|
||||
|
|
|
@ -40,25 +40,40 @@ class Audio : public AudioScriptingInterface, protected ReadWriteLockable {
|
|||
* @hifi-server-entity
|
||||
* @hifi-assignment-client
|
||||
*
|
||||
* @property {boolean} muted - <code>true</code> if the audio input is muted, otherwise <code>false</code>.
|
||||
* @property {boolean} mutedDesktop - <code>true</code> if the audio input is muted, otherwise <code>false</code>.
|
||||
* @property {boolean} muted - <code>true</code> if the audio input is muted for the current user context (desktop or HMD),
|
||||
* otherwise <code>false</code>.
|
||||
* @property {boolean} mutedDesktop - <code>true</code> if desktop audio input is muted, otherwise <code>false</code>.
|
||||
* @property {boolean} mutedHMD - <code>true</code> if the HMD input is muted, otherwise <code>false</code>.
|
||||
* @property {boolean} warnWhenMuted - <code>true</code> if the "muted" warning is enabled, otherwise <code>false</code>.
|
||||
* When enabled, if you speak while your microphone is muted, "muted" is displayed on the screen as a warning.
|
||||
* @property {boolean} noiseReduction - <code>true</code> if noise reduction is enabled, otherwise <code>false</code>. When
|
||||
* enabled, the input audio signal is blocked (fully attenuated) when it falls below an adaptive threshold set just
|
||||
* above the noise floor.
|
||||
* @property {number} inputVolume - Adjusts the volume of the input audio, range <code>0.0</code> – <code>1.0</code>.
|
||||
* If set to a value, the resulting value depends on the input device: for example, the volume can't be changed on some
|
||||
* devices, and others might only support values of <code>0.0</code> and <code>1.0</code>.
|
||||
* @property {number} inputLevel - The loudness of the audio input, range <code>0.0</code> (no sound) –
|
||||
* <code>1.0</code> (the onset of clipping). <em>Read-only.</em>
|
||||
* @property {boolean} clipping - <code>true</code> if the audio input is clipping, otherwise <code>false</code>.
|
||||
* @property {number} inputVolume - Adjusts the volume of the input audio; range <code>0.0</code> – <code>1.0</code>.
|
||||
* If set to a value, the resulting value depends on the input device: for example, the volume can't be changed on some
|
||||
* devices, and others might only support values of <code>0.0</code> and <code>1.0</code>.
|
||||
* @property {boolean} isStereoInput - <code>true</code> if the input audio is being used in stereo, otherwise
|
||||
* <code>false</code>. Some devices do not support stereo, in which case the value is always <code>false</code>.
|
||||
* @property {string} context - The current context of the audio: either <code>"Desktop"</code> or <code>"HMD"</code>.
|
||||
* <em>Read-only.</em>
|
||||
* @property {object} devices <em>Read-only.</em> <strong>Deprecated:</strong> This property is deprecated and will be
|
||||
* removed.
|
||||
* @property {boolean} isSoloing <em>Read-only.</em> <code>true</code> if any nodes are soloed.
|
||||
* @property {Uuid[]} soloList <em>Read-only.</em> Get the list of currently soloed node UUIDs.
|
||||
* @property {object} devices - <em>Read-only.</em>
|
||||
* <p class="important">Deprecated: This property is deprecated and will be removed.
|
||||
* @property {boolean} pushToTalk - <code>true</code> if push-to-talk is enabled for the current user context (desktop or
|
||||
* HMD), otherwise <code>false</code>.
|
||||
* @property {boolean} pushToTalkDesktop - <code>true</code> if desktop push-to-talk is enabled, otherwise
|
||||
* <code>false</code>.
|
||||
* @property {boolean} pushToTalkHMD - <code>true</code> if HMD push-to-talk is enabled, otherwise <code>false</code>.
|
||||
* @property {boolean} pushingToTalk - <code>true</code> if the user is currently pushing-to-talk, otherwise
|
||||
* <code>false</code>.
|
||||
*
|
||||
* @comment The following properties are from AudioScriptingInterface.h.
|
||||
* @property {boolean} isStereoInput - <code>true</code> if the input audio is being used in stereo, otherwise
|
||||
* <code>false</code>. Some devices do not support stereo, in which case the value is always <code>false</code>.
|
||||
* @property {boolean} isSoloing - <code>true</code> if currently audio soloing, i.e., playing audio from only specific
|
||||
* avatars. <em>Read-only.</em>
|
||||
* @property {Uuid[]} soloList - The list of currently soloed avatar IDs. Empty list if not currently audio soloing.
|
||||
* <em>Read-only.</em>
|
||||
*/
|
||||
|
||||
Q_PROPERTY(bool muted READ isMuted WRITE setMuted NOTIFY mutedChanged)
|
||||
|
@ -117,23 +132,23 @@ public:
|
|||
|
||||
/**jsdoc
|
||||
* @function Audio.setInputDevice
|
||||
* @param {object} device
|
||||
* @param {boolean} isHMD
|
||||
* @param {object} device - Device.
|
||||
* @param {boolean} isHMD - Is HMD.
|
||||
* @deprecated This function is deprecated and will be removed.
|
||||
*/
|
||||
Q_INVOKABLE void setInputDevice(const QAudioDeviceInfo& device, bool isHMD);
|
||||
|
||||
/**jsdoc
|
||||
* @function Audio.setOutputDevice
|
||||
* @param {object} device
|
||||
* @param {boolean} isHMD
|
||||
* @param {object} device - Device.
|
||||
* @param {boolean} isHMD - Is HMD.
|
||||
* @deprecated This function is deprecated and will be removed.
|
||||
*/
|
||||
Q_INVOKABLE void setOutputDevice(const QAudioDeviceInfo& device, bool isHMD);
|
||||
|
||||
/**jsdoc
|
||||
* Enable or disable reverberation. Reverberation is done by the client, on the post-mix audio. The reverberation options
|
||||
* come from either the domain's audio zone if used — configured on the server — or as scripted by
|
||||
* Enables or disables reverberation. Reverberation is done by the client on the post-mix audio. The reverberation options
|
||||
* come from either the domain's audio zone configured on the server or settings scripted by
|
||||
* {@link Audio.setReverbOptions|setReverbOptions}.
|
||||
* @function Audio.setReverb
|
||||
* @param {boolean} enable - <code>true</code> to enable reverberation, <code>false</code> to disable.
|
||||
|
@ -165,69 +180,71 @@ public:
|
|||
Q_INVOKABLE void setReverb(bool enable);
|
||||
|
||||
/**jsdoc
|
||||
* Configure reverberation options. Use {@link Audio.setReverb|setReverb} to enable or disable reverberation.
|
||||
* Configures reverberation options. Use {@link Audio.setReverb|setReverb} to enable or disable reverberation.
|
||||
* @function Audio.setReverbOptions
|
||||
* @param {AudioEffectOptions} options - The reverberation options.
|
||||
*/
|
||||
Q_INVOKABLE void setReverbOptions(const AudioEffectOptions* options);
|
||||
|
||||
/**jsdoc
|
||||
* Sets the avatar gain at the server.
|
||||
* Units are Decibels (dB)
|
||||
* Sets the gain (relative volume) that avatars' voices are played at. This gain is used at the server.
|
||||
* @function Audio.setAvatarGain
|
||||
* @param {number} gain (in dB)
|
||||
*/
|
||||
* @param {number} gain - Avatar gain (dB) at the server.
|
||||
*/
|
||||
Q_INVOKABLE void setAvatarGain(float gain);
|
||||
|
||||
/**jsdoc
|
||||
* Gets the avatar gain at the server.
|
||||
* Gets the gain (relative volume) that avatars' voices are played at. This gain is used at the server.
|
||||
* @function Audio.getAvatarGain
|
||||
* @returns {number} gain (in dB)
|
||||
*/
|
||||
* @returns {number} Avatar gain (dB) at the server.
|
||||
* @example <caption>Report current audio gain settings.</caption>
|
||||
* // 0 value = normal volume; -ve value = quieter; +ve value = louder.
|
||||
* print("Avatar gain: " + Audio.getAvatarGain());
|
||||
* print("Environment server gain: " + Audio.getInjectorGain());
|
||||
* print("Environment local gain: " + Audio.getLocalInjectorGain());
|
||||
* print("System gain: " + Audio.getSystemInjectorGain());
|
||||
*/
|
||||
Q_INVOKABLE float getAvatarGain();
|
||||
|
||||
/**jsdoc
|
||||
* Sets the injector gain at the server.
|
||||
* Units are Decibels (dB)
|
||||
* Sets the gain (relative volume) that environment sounds from the server are played at.
|
||||
* @function Audio.setInjectorGain
|
||||
* @param {number} gain (in dB)
|
||||
*/
|
||||
* @param {number} gain - Injector gain (dB) at the server.
|
||||
*/
|
||||
Q_INVOKABLE void setInjectorGain(float gain);
|
||||
|
||||
/**jsdoc
|
||||
* Gets the injector gain at the server.
|
||||
* Gets the gain (relative volume) that environment sounds from the server are played at.
|
||||
* @function Audio.getInjectorGain
|
||||
* @returns {number} gain (in dB)
|
||||
*/
|
||||
* @returns {number} Injector gain (dB) at the server.
|
||||
*/
|
||||
Q_INVOKABLE float getInjectorGain();
|
||||
|
||||
/**jsdoc
|
||||
* Sets the local injector gain in the client.
|
||||
* Units are Decibels (dB)
|
||||
* Sets the gain (relative volume) that environment sounds from the client are played at.
|
||||
* @function Audio.setLocalInjectorGain
|
||||
* @param {number} gain (in dB)
|
||||
*/
|
||||
* @param {number} gain - Injector gain (dB) in the client.
|
||||
*/
|
||||
Q_INVOKABLE void setLocalInjectorGain(float gain);
|
||||
|
||||
/**jsdoc
|
||||
* Gets the local injector gain in the client.
|
||||
* Gets the gain (relative volume) that environment sounds from the client are played at.
|
||||
* @function Audio.getLocalInjectorGain
|
||||
* @returns {number} gain (in dB)
|
||||
*/
|
||||
* @returns {number} Injector gain (dB) in the client.
|
||||
*/
|
||||
Q_INVOKABLE float getLocalInjectorGain();
|
||||
|
||||
/**jsdoc
|
||||
* Sets the injector gain for system sounds.
|
||||
* Units are Decibels (dB)
|
||||
* Sets the gain (relative volume) that system sounds are played at.
|
||||
* @function Audio.setSystemInjectorGain
|
||||
* @param {number} gain (in dB)
|
||||
*/
|
||||
* @param {number} gain - Injector gain (dB) in the client.
|
||||
*/
|
||||
Q_INVOKABLE void setSystemInjectorGain(float gain);
|
||||
|
||||
/**jsdoc
|
||||
* Gets the injector gain for system sounds.
|
||||
* Gets the gain (relative volume) that system sounds are played at.
|
||||
* @function Audio.getSystemInjectorGain
|
||||
* @returns {number} gain (in dB)
|
||||
* @returns {number} Injector gain (dB) in the client.
|
||||
*/
|
||||
Q_INVOKABLE float getSystemInjectorGain();
|
||||
|
||||
|
@ -253,13 +270,13 @@ public:
|
|||
Q_INVOKABLE bool startRecording(const QString& filename);
|
||||
|
||||
/**jsdoc
|
||||
* Finish making an audio recording started with {@link Audio.startRecording|startRecording}.
|
||||
* Finishes making an audio recording started with {@link Audio.startRecording|startRecording}.
|
||||
* @function Audio.stopRecording
|
||||
*/
|
||||
Q_INVOKABLE void stopRecording();
|
||||
|
||||
/**jsdoc
|
||||
* Check whether an audio recording is currently being made.
|
||||
* Checks whether an audio recording is currently being made.
|
||||
* @function Audio.getRecording
|
||||
* @returns {boolean} <code>true</code> if an audio recording is currently being made, otherwise <code>false</code>.
|
||||
*/
|
||||
|
@ -275,9 +292,10 @@ signals:
|
|||
void nop();
|
||||
|
||||
/**jsdoc
|
||||
* Triggered when the audio input is muted or unmuted.
|
||||
* Triggered when the audio input is muted or unmuted for the current context (desktop or HMD).
|
||||
* @function Audio.mutedChanged
|
||||
* @param {boolean} isMuted - <code>true</code> if the audio input is muted, otherwise <code>false</code>.
|
||||
* @param {boolean} isMuted - <code>true</code> if the audio input is muted for the current context (desktop or HMD),
|
||||
* otherwise <code>false</code>.
|
||||
* @returns {Signal}
|
||||
* @example <caption>Report when audio input is muted or unmuted</caption>
|
||||
* Audio.mutedChanged.connect(function (isMuted) {
|
||||
|
@ -287,47 +305,55 @@ signals:
|
|||
void mutedChanged(bool isMuted);
|
||||
|
||||
/**jsdoc
|
||||
* Triggered when desktop audio input is muted or unmuted.
|
||||
* @function Audio.mutedDesktopChanged
|
||||
* @param {boolean} isMuted - <code>true</code> if the audio input is muted for desktop mode, otherwise <code>false</code>.
|
||||
* @returns {Signal}
|
||||
*/
|
||||
* Triggered when desktop audio input is muted or unmuted.
|
||||
* @function Audio.mutedDesektopChanged
|
||||
* @param {boolean} isMuted - <code>true</code> if desktop audio input is muted, otherwise <code>false</code>.
|
||||
* @returns {Signal}
|
||||
* @example <caption>Report when desktop muting changes.</caption>
|
||||
* Audio.mutedDesktopChanged.connect(function (isMuted) {
|
||||
* print("Desktop muted: " + isMuted);
|
||||
* });
|
||||
*/
|
||||
void mutedDesktopChanged(bool isMuted);
|
||||
|
||||
/**jsdoc
|
||||
* Triggered when HMD audio input is muted or unmuted.
|
||||
* @function Audio.mutedHMDChanged
|
||||
* @param {boolean} isMuted - <code>true</code> if the audio input is muted for HMD mode, otherwise <code>false</code>.
|
||||
* @returns {Signal}
|
||||
*/
|
||||
* Triggered when HMD audio input is muted or unmuted.
|
||||
* @function Audio.mutedHMDChanged
|
||||
* @param {boolean} isMuted - <code>true</code> if HMD audio input is muted, otherwise <code>false</code>.
|
||||
* @returns {Signal}
|
||||
*/
|
||||
void mutedHMDChanged(bool isMuted);
|
||||
|
||||
/**
|
||||
* Triggered when Push-to-Talk has been enabled or disabled.
|
||||
* @function Audio.pushToTalkChanged
|
||||
* @param {boolean} enabled - <code>true</code> if Push-to-Talk is enabled, otherwise <code>false</code>.
|
||||
* @returns {Signal}
|
||||
*/
|
||||
/**jsdoc
|
||||
* Triggered when push-to-talk is enabled or disabled for the current context (desktop or HMD).
|
||||
* @function Audio.pushToTalkChanged
|
||||
* @param {boolean} enabled - <code>true</code> if push-to-talk is enabled, otherwise <code>false</code>.
|
||||
* @returns {Signal}
|
||||
* @example <caption>Report when push-to-talk changes.</caption>
|
||||
* Audio.pushToTalkChanged.connect(function (enabled) {
|
||||
* print("Push to talk: " + (enabled ? "on" : "off"));
|
||||
* });
|
||||
*/
|
||||
void pushToTalkChanged(bool enabled);
|
||||
|
||||
/**
|
||||
* Triggered when Push-to-Talk has been enabled or disabled for desktop mode.
|
||||
* @function Audio.pushToTalkDesktopChanged
|
||||
* @param {boolean} enabled - <code>true</code> if Push-to-Talk is emabled for Desktop mode, otherwise <code>false</code>.
|
||||
* @returns {Signal}
|
||||
*/
|
||||
/**jsdoc
|
||||
* Triggered when push-to-talk is enabled or disabled for desktop mode.
|
||||
* @function Audio.pushToTalkDesktopChanged
|
||||
* @param {boolean} enabled - <code>true</code> if push-to-talk is enabled for desktop mode, otherwise <code>false</code>.
|
||||
* @returns {Signal}
|
||||
*/
|
||||
void pushToTalkDesktopChanged(bool enabled);
|
||||
|
||||
/**
|
||||
* Triggered when Push-to-Talk has been enabled or disabled for HMD mode.
|
||||
* @function Audio.pushToTalkHMDChanged
|
||||
* @param {boolean} enabled - <code>true</code> if Push-to-Talk is emabled for HMD mode, otherwise <code>false</code>.
|
||||
* @returns {Signal}
|
||||
*/
|
||||
/**jsdoc
|
||||
* Triggered when push-to-talk is enabled or disabled for HMD mode.
|
||||
* @function Audio.pushToTalkHMDChanged
|
||||
* @param {boolean} enabled - <code>true</code> if push-to-talk is enabled for HMD mode, otherwise <code>false</code>.
|
||||
* @returns {Signal}
|
||||
*/
|
||||
void pushToTalkHMDChanged(bool enabled);
|
||||
|
||||
/**jsdoc
|
||||
* Triggered when the audio input noise reduction is enabled or disabled.
|
||||
* Triggered when audio input noise reduction is enabled or disabled.
|
||||
* @function Audio.noiseReductionChanged
|
||||
* @param {boolean} isEnabled - <code>true</code> if audio input noise reduction is enabled, otherwise <code>false</code>.
|
||||
* @returns {Signal}
|
||||
|
@ -346,8 +372,8 @@ signals:
|
|||
* Triggered when the input audio volume changes.
|
||||
* @function Audio.inputVolumeChanged
|
||||
* @param {number} volume - The requested volume to be applied to the audio input, range <code>0.0</code> –
|
||||
* <code>1.0</code>. The resulting value of <code>Audio.inputVolume</code> depends on the capabilities of the device:
|
||||
* for example, the volume can't be changed on some devices, and others might only support values of <code>0.0</code>
|
||||
* <code>1.0</code>. The resulting value of <code>Audio.inputVolume</code> depends on the capabilities of the device.
|
||||
* For example, the volume can't be changed on some devices, while others might only support values of <code>0.0</code>
|
||||
* and <code>1.0</code>.
|
||||
* @returns {Signal}
|
||||
*/
|
||||
|
@ -379,11 +405,11 @@ signals:
|
|||
void contextChanged(const QString& context);
|
||||
|
||||
/**jsdoc
|
||||
* Triggered when pushing to talk.
|
||||
* @function Audio.pushingToTalkChanged
|
||||
* @param {boolean} talking - <code>true</code> if broadcasting with PTT, <code>false</code> otherwise.
|
||||
* @returns {Signal}
|
||||
*/
|
||||
* Triggered when the user starts or stops push-to-talk.
|
||||
* @function Audio.pushingToTalkChanged
|
||||
* @param {boolean} talking - <code>true</code> if started push-to-talk, <code>false</code> if stopped push-to-talk.
|
||||
* @returns {Signal}
|
||||
*/
|
||||
void pushingToTalkChanged(bool talking);
|
||||
|
||||
public slots:
|
||||
|
@ -409,6 +435,7 @@ protected:
|
|||
|
||||
private:
|
||||
|
||||
bool _settingsLoaded { false };
|
||||
float _inputVolume { 1.0f };
|
||||
float _inputLevel { 0.0f };
|
||||
float _localInjectorGain { 0.0f }; // in dB
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
#include <EntityItemID.h>
|
||||
|
||||
/**jsdoc
|
||||
* The Clipboard API enables you to export and import entities to and from JSON files.
|
||||
* The <code>Clipboard</code> API enables you to export and import entities to and from JSON files.
|
||||
*
|
||||
* @namespace Clipboard
|
||||
*
|
||||
|
@ -33,56 +33,92 @@ public:
|
|||
|
||||
public:
|
||||
/**jsdoc
|
||||
* Compute the extents of the contents held in the clipboard.
|
||||
* Gets the extents of the entities held in the clipboard.
|
||||
* @function Clipboard.getContentsDimensions
|
||||
* @returns {Vec3} The extents of the contents held in the clipboard.
|
||||
* @returns {Vec3} The extents of the content held in the clipboard.
|
||||
* @example <caption>Import entities to the clipboard and report their overall dimensions.</caption>
|
||||
* var filename = Window.browse("Import entities to clipboard", "", "*.json");
|
||||
* if (filename) {
|
||||
* if (Clipboard.importEntities(filename)) {
|
||||
* print("Clipboard dimensions: " + JSON.stringify(Clipboard.getContentsDimensions()));
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
Q_INVOKABLE glm::vec3 getContentsDimensions();
|
||||
|
||||
/**jsdoc
|
||||
* Compute the largest dimension of the extents of the contents held in the clipboard.
|
||||
* Gets the largest dimension of the extents of the entities held in the clipboard.
|
||||
* @function Clipboard.getClipboardContentsLargestDimension
|
||||
* @returns {number} The largest dimension computed.
|
||||
* @returns {number} The largest dimension of the extents of the content held in the clipboard.
|
||||
*/
|
||||
Q_INVOKABLE float getClipboardContentsLargestDimension();
|
||||
|
||||
/**jsdoc
|
||||
* Import entities from a JSON file containing entity data into the clipboard.
|
||||
* You can generate a JSON file using {@link Clipboard.exportEntities}.
|
||||
* Imports entities from a JSON file into the clipboard.
|
||||
* @function Clipboard.importEntities
|
||||
* @param {string} filename Path and name of file to import.
|
||||
* @param {boolean} does the ResourceRequestObserver observe this request?
|
||||
* @param {number} optional internal id of object causing this import.
|
||||
* @param {string} filename - The path and name of the JSON file to import.
|
||||
* @param {boolean} [isObservable=true] - <code>true</code> if the {@link ResourceRequestObserver} can observe this
|
||||
* request, <code>false</code> if it can't.
|
||||
* @param {number} [callerID=-1] - An integer ID that is passed through to the {@link ResourceRequestObserver}.
|
||||
* @returns {boolean} <code>true</code> if the import was successful, otherwise <code>false</code>.
|
||||
* @example <caption>Import entities and paste into the domain.</caption>
|
||||
* var filename = Window.browse("Import entities to clipboard", "", "*.json");
|
||||
* if (filename) {
|
||||
* if (Clipboard.importEntities(filename)) {
|
||||
* pastedEntities = Clipboard.pasteEntities(Vec3.sum(MyAvatar.position,
|
||||
* Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -3 })));
|
||||
* print("Entities pasted: " + JSON.stringify(pastedEntities));
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
Q_INVOKABLE bool importEntities(const QString& filename, const bool isObservable = true, const qint64 callerId = -1);
|
||||
|
||||
/**jsdoc
|
||||
* Export the entities specified to a JSON file.
|
||||
* Exports specified entities to a JSON file.
|
||||
* @function Clipboard.exportEntities
|
||||
* @param {string} filename Path and name of the file to export the entities to. Should have the extension ".json".
|
||||
* @param {Uuid[]} entityIDs Array of IDs of the entities to export.
|
||||
* @returns {boolean} <code>true</code> if the export was successful, otherwise <code>false</code>.
|
||||
* @param {string} filename - Path and name of the file to export the entities to. Should have the extension ".json".
|
||||
* @param {Uuid[]} entityIDs - The IDs of the entities to export.
|
||||
* @returns {boolean} <code>true</code> if entities were found and the file was written, otherwise <code>false</code>.
|
||||
* @example <caption>Create and export a cube and a sphere.</caption>
|
||||
* // Create entities.
|
||||
* var box = Entities.addEntity({
|
||||
* type: "Box",
|
||||
* position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: -0.2, y: 0, z: -3 })),
|
||||
* lifetime: 300 // Delete after 5 minutes.
|
||||
* });
|
||||
* var sphere = Entities.addEntity({
|
||||
* type: "Sphere",
|
||||
* position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0.2, y: 0, z: -3 })),
|
||||
* lifetime: 300 // Delete after 5 minutes.
|
||||
* });
|
||||
*
|
||||
* // Export entities.
|
||||
* var filename = Window.save("Export entities to JSON file", Paths.resources, "*.json");
|
||||
* if (filename) {
|
||||
* Clipboard.exportEntities(filename, [box, sphere]);
|
||||
* }
|
||||
*/
|
||||
Q_INVOKABLE bool exportEntities(const QString& filename, const QVector<QUuid>& entityIDs);
|
||||
|
||||
/**jsdoc
|
||||
* Export the entities with centers within a cube to a JSON file.
|
||||
* Exports all entities that have centers within a cube to a JSON file.
|
||||
* @function Clipboard.exportEntities
|
||||
* @param {string} filename Path and name of the file to export the entities to. Should have the extension ".json".
|
||||
* @param {number} x X-coordinate of the cube center.
|
||||
* @param {number} y Y-coordinate of the cube center.
|
||||
* @param {number} z Z-coordinate of the cube center.
|
||||
* @param {number} scale Half dimension of the cube.
|
||||
* @returns {boolean} <code>true</code> if the export was successful, otherwise <code>false</code>.
|
||||
* @variation 0
|
||||
* @param {string} filename - Path and name of the file to export the entities to. Should have the extension ".json".
|
||||
* @param {number} x - X-coordinate of the cube center.
|
||||
* @param {number} y - Y-coordinate of the cube center.
|
||||
* @param {number} z - Z-coordinate of the cube center.
|
||||
* @param {number} scale - Half dimension of the cube.
|
||||
* @returns {boolean} <code>true</code> if entities were found and the file was written, otherwise <code>false</code>.
|
||||
*/
|
||||
Q_INVOKABLE bool exportEntities(const QString& filename, float x, float y, float z, float scale);
|
||||
|
||||
/**jsdoc
|
||||
* Paste the contents of the clipboard into the world.
|
||||
* Pastes the contents of the clipboard into the domain.
|
||||
* @function Clipboard.pasteEntities
|
||||
* @param {Vec3} position Position to paste the clipboard contents at.
|
||||
* @returns {Uuid[]} Array of entity IDs for the new entities that were created as a result of the paste operation.
|
||||
* @param {Vec3} position - The position to paste the clipboard contents at.
|
||||
* @returns {Uuid[]} The IDs of the new entities that were created as a result of the paste operation. If entities couldn't
|
||||
* be created then an empty array is returned.
|
||||
*/
|
||||
Q_INVOKABLE QVector<EntityItemID> pasteEntities(glm::vec3 position);
|
||||
};
|
||||
|
|
|
@ -26,18 +26,20 @@ class ScriptEngine;
|
|||
|
||||
|
||||
/**jsdoc
|
||||
* The Controller API provides facilities to interact with computer and controller hardware.
|
||||
* The <code>Controller</code> API provides facilities to interact with computer and controller hardware.
|
||||
*
|
||||
* <h5>Functions</h5>
|
||||
* <h3>Facilities</h3>
|
||||
*
|
||||
* <p>Properties</p>
|
||||
* <h4>Properties</h4>
|
||||
* <p>Get <code>Controller</code> property trees.</p>
|
||||
* <ul>
|
||||
* <li>{@link Controller.getActions|getActions}</li>
|
||||
* <li>{@link Controller.getHardware|getHardware}</li>
|
||||
* <li>{@link Controller.getStandard|getStandard}</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>Mappings</p>
|
||||
* <h4>Mappings</h4>
|
||||
* <p>Create and enable or disable <code>Controller</code> mappings.</p>
|
||||
* <ul>
|
||||
* <li>{@link Controller.disableMapping|disableMapping}</li>
|
||||
* <li>{@link Controller.enableMapping|enableMapping}</li>
|
||||
|
@ -46,7 +48,8 @@ class ScriptEngine;
|
|||
* <li>{@link Controller.parseMapping|parseMapping}</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>Input, Hardware, and Action Reflection</p>
|
||||
* <h4>Input, Hardware, and Action Reflection</h4>
|
||||
* <p>Information on the devices and actions available.</p>
|
||||
* <ul>
|
||||
* <li>{@link Controller.findAction|findAction}</li>
|
||||
* <li>{@link Controller.findDevice|findDevice}</li>
|
||||
|
@ -55,16 +58,20 @@ class ScriptEngine;
|
|||
* <li>{@link Controller.getAvailableInputs|getAvailableInputs}</li>
|
||||
* <li>{@link Controller.getDeviceName|getDeviceName}</li>
|
||||
* <li>{@link Controller.getDeviceNames|getDeviceNames}</li>
|
||||
* <li>{@link Controller.getRunningInputDevices|getRunningInputDevices}</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>Input, Hardware, and Action Events</p>
|
||||
* <h4>Input, Hardware, and Action Signals</h4>
|
||||
* <p>Notifications of device and action events.</p>
|
||||
* <ul>
|
||||
* <li>{@link Controller.actionEvent|actionEvent}</li>
|
||||
* <li>{@link Controller.hardwareChanged|hardwareChanged}</li>
|
||||
* <li>{@link Controller.inputDeviceRunningChanged|inputDeviceRunningChanged}</li>
|
||||
* <li>{@link Controller.inputEvent|inputEvent}</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>Mouse, Keyboard, and Touch Events</p>
|
||||
* <h4>Mouse, Keyboard, and Touch Signals</h4>
|
||||
* <p>Notifications of mouse, keyboard, and touch events.</p>
|
||||
* <ul>
|
||||
* <li>{@link Controller.keyPressEvent|keyPressEvent}</li>
|
||||
* <li>{@link Controller.keyReleaseEvent|keyReleaseEvent}</li>
|
||||
|
@ -78,29 +85,32 @@ class ScriptEngine;
|
|||
* <li>{@link Controller.wheelEvent|wheelEvent}</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>Control Capturing</p>
|
||||
* <h4>Control Capturing</h4>
|
||||
* <p>Disable and enable the processing of mouse and touch events.</p>
|
||||
* <ul>
|
||||
* <li>{@link Controller.captureMouseEvents|captureMouseEvents}</li>
|
||||
* <li>{@link Controller.captureTouchEvents|captureTouchEvents}</li>
|
||||
* <li>{@link Controller.captureWheelEvents|captureWheelEvents}</li>
|
||||
* <li>{@link Controller.captureTouchEvents|captureTouchEvents}</li>
|
||||
* <li>{@link Controller.releaseMouseEvents|releaseMouseEvents}</li>
|
||||
* <li>{@link Controller.releaseTouchEvents|releaseTouchEvents}</li>
|
||||
* <li>{@link Controller.releaseWheelEvents|releaseWheelEvents}</li>
|
||||
* <li>{@link Controller.releaseTouchEvents|releaseTouchEvents}</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>Action Capturing</p>
|
||||
* <h4>Action Capturing</h4>
|
||||
* <p>Disable and enable controller actions.</p>
|
||||
* <ul>
|
||||
* <li>{@link Controller.captureActionEvents|captureActionEvents}</li>
|
||||
* <li>{@link Controller.captureEntityClickEvents|captureEntityClickEvents}</li>
|
||||
* <li>{@link Controller.captureJoystick|captureJoystick}</li>
|
||||
* <li>{@link Controller.captureKeyEvents|captureKeyEvents}</li>
|
||||
* <li>{@link Controller.captureJoystick|captureJoystick}</li>
|
||||
* <li>{@link Controller.captureEntityClickEvents|captureEntityClickEvents}</li>
|
||||
* <li>{@link Controller.releaseActionEvents|releaseActionEvents}</li>
|
||||
* <li>{@link Controller.releaseEntityClickEvents|releaseEntityClickEvents}</li>
|
||||
* <li>{@link Controller.releaseJoystick|releaseJoystick}</li>
|
||||
* <li>{@link Controller.releaseKeyEvents|releaseKeyEvents}</li>
|
||||
* <li>{@link Controller.releaseJoystick|releaseJoystick}</li>
|
||||
* <li>{@link Controller.releaseEntityClickEvents|releaseEntityClickEvents}</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>Controller and Action Values</p>
|
||||
* <h4>Controller and Action Values</h4>
|
||||
* <p>Get the current value of controller outputs and actions.</p>
|
||||
* <ul>
|
||||
* <li>{@link Controller.getValue|getValue}</li>
|
||||
* <li>{@link Controller.getAxisValue|getAxisValue}</li>
|
||||
|
@ -108,7 +118,8 @@ class ScriptEngine;
|
|||
* <li>{@link Controller.getActionValue|getActionValue}</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>Haptics</p>
|
||||
* <h4>Haptics</h4>
|
||||
* <p>Trigger haptic pulses.</p>
|
||||
* <ul>
|
||||
* <li>{@link Controller.triggerHapticPulse|triggerHapticPulse}</li>
|
||||
* <li>{@link Controller.triggerHapticPulseOnDevice|triggerHapticPulseOnDevice}</li>
|
||||
|
@ -116,20 +127,23 @@ class ScriptEngine;
|
|||
* <li>{@link Controller.triggerShortHapticPulseOnDevice|triggerShortHapticPulseOnDevice}</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>Display Information</p>
|
||||
* <h4>Display Information</h4>
|
||||
* <p>Get information on the display.</p>
|
||||
* <ul>
|
||||
* <li>{@link Controller.getViewportDimensions|getViewportDimensions}</li>
|
||||
* <li>{@link Controller.getRecommendedHUDRect|getRecommendedHUDRect}</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>Virtual Game Pad</p>
|
||||
* <h4>Virtual Game Pad</h4>
|
||||
* <p>Use the virtual game pad which is available on some devices.</p>
|
||||
* <ul>
|
||||
* <li>{@link Controller.setVPadEnabled|setVPadEnabled}</li>
|
||||
* <li>{@link Controller.setVPadHidden|setVPadHidden}</li>
|
||||
* <li>{@link Controller.setVPadExtraBottomMargin|setVPadExtraBottomMargin}</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>Input Recordings</p>
|
||||
* <h4>Input Recordings</h4>
|
||||
* <p>Create and play input recordings.</p>
|
||||
* <ul>
|
||||
* <li>{@link Controller.startInputRecording|startInputRecording}</li>
|
||||
* <li>{@link Controller.stopInputRecording|stopInputRecording}</li>
|
||||
|
@ -140,10 +154,10 @@ class ScriptEngine;
|
|||
* <li>{@link Controller.stopInputPlayback|stopInputPlayback}</li>
|
||||
* </ul>
|
||||
*
|
||||
* <h5>Entity Methods:</h5>
|
||||
* <h3>Entity Methods</h3>
|
||||
*
|
||||
* <p>The default scripts implement hand controller actions that use {@link Entities.callEntityMethod} to call entity script
|
||||
* methods, if present in the entity being interacted with.</p>
|
||||
* methods, if present, in the entity being interacted with.</p>
|
||||
*
|
||||
* <table>
|
||||
* <thead>
|
||||
|
@ -203,7 +217,7 @@ class ScriptEngine;
|
|||
*
|
||||
* @property {Controller.Actions} Actions - Predefined actions on Interface and the user's avatar. These can be used as end
|
||||
* points in a {@link RouteObject} mapping. A synonym for <code>Controller.Hardware.Actions</code>.
|
||||
* <em>Read-only.</em><br />
|
||||
* <em>Read-only.</em><br /><br />
|
||||
* Default mappings are provided from the <code>Controller.Hardware.Keyboard</code> and <code>Controller.Standard</code> to
|
||||
* actions in
|
||||
* <a href="https://github.com/highfidelity/hifi/blob/master/interface/resources/controllers/keyboardMouse.json">
|
||||
|
@ -217,7 +231,7 @@ class ScriptEngine;
|
|||
* controller outputs. <em>Read-only.</em>
|
||||
*
|
||||
* @property {Controller.Standard} Standard - Standard controller outputs that can be mapped to <code>Actions</code> or
|
||||
* functions in a {@link RouteObject} mapping. <em>Read-only.</em><br />
|
||||
* functions in a {@link RouteObject} mapping. <em>Read-only.</em><br /><br />
|
||||
* Each hardware device has a mapping from its outputs to <code>Controller.Standard</code> items, specified in a JSON file.
|
||||
* For example, <a href="https://github.com/highfidelity/hifi/blob/master/interface/resources/controllers/leapmotion.json">
|
||||
* leapmotion.json</a> and
|
||||
|
@ -253,7 +267,7 @@ public:
|
|||
public slots:
|
||||
|
||||
/**jsdoc
|
||||
* Disable default Interface actions for a particular key event.
|
||||
* Disables default Interface actions for a particular key event.
|
||||
* @function Controller.captureKeyEvents
|
||||
* @param {KeyEvent} event - Details of the key event to be captured. The <code>key</code> property must be specified. The
|
||||
* <code>text</code> property is ignored. The other properties default to <code>false</code>.
|
||||
|
@ -272,7 +286,7 @@ public slots:
|
|||
virtual void captureKeyEvents(const KeyEvent& event);
|
||||
|
||||
/**jsdoc
|
||||
* Re-enable default Interface actions for a particular key event that has been disabled using
|
||||
* Re-enables default Interface actions for a particular key event that has been disabled using
|
||||
* {@link Controller.captureKeyEvents|captureKeyEvents}.
|
||||
* @function Controller.releaseKeyEvents
|
||||
* @param {KeyEvent} event - Details of the key event to release from capture. The <code>key</code> property must be
|
||||
|
@ -281,24 +295,24 @@ public slots:
|
|||
virtual void releaseKeyEvents(const KeyEvent& event);
|
||||
|
||||
/**jsdoc
|
||||
* Disable default Interface actions for a joystick.
|
||||
* Disables default Interface actions for a joystick.
|
||||
* @function Controller.captureJoystick
|
||||
* @param {number} joystickID - The integer ID of the joystick.
|
||||
* @deprecated This function no longer has any effect.
|
||||
* @deprecated This function is deprecated and will be removed. It no longer has any effect.
|
||||
*/
|
||||
virtual void captureJoystick(int joystickIndex);
|
||||
|
||||
/**jsdoc
|
||||
* Re-enable default Interface actions for a joystick that has been disabled using
|
||||
* Re-enables default Interface actions for a joystick that has been disabled using
|
||||
* {@link Controller.captureJoystick|captureJoystick}.
|
||||
* @function Controller.releaseJoystick
|
||||
* @param {number} joystickID - The integer ID of the joystick.
|
||||
* @deprecated This function no longer has any effect.
|
||||
* @deprecated This function is deprecated and will be removed. It no longer has any effect.
|
||||
*/
|
||||
virtual void releaseJoystick(int joystickIndex);
|
||||
|
||||
/**jsdoc
|
||||
* Disable {@link Entities.mousePressOnEntity} and {@link Entities.mouseDoublePressOnEntity} events on entities.
|
||||
* Disables {@link Entities.mousePressOnEntity} and {@link Entities.mouseDoublePressOnEntity} events on entities.
|
||||
* @function Controller.captureEntityClickEvents
|
||||
* @example <caption>Disable entity click events for a short period.</caption>
|
||||
* Entities.mousePressOnEntity.connect(function (entityID, event) {
|
||||
|
@ -316,7 +330,7 @@ public slots:
|
|||
virtual void captureEntityClickEvents();
|
||||
|
||||
/**jsdoc
|
||||
* Re-enable {@link Entities.mousePressOnEntity} and {@link Entities.mouseDoublePressOnEntity} events on entities that were
|
||||
* Re-enables {@link Entities.mousePressOnEntity} and {@link Entities.mouseDoublePressOnEntity} events on entities that were
|
||||
* disabled using {@link Controller.captureEntityClickEvents|captureEntityClickEvents}.
|
||||
* @function Controller.releaseEntityClickEvents
|
||||
*/
|
||||
|
@ -324,14 +338,14 @@ public slots:
|
|||
|
||||
|
||||
/**jsdoc
|
||||
* Get the dimensions of the Interface window's interior if in desktop mode or the HUD surface if in HMD mode.
|
||||
* Gets the dimensions of the Interface window's interior if in desktop mode or the HUD surface if in HMD mode.
|
||||
* @function Controller.getViewportDimensions
|
||||
* @returns {Vec2} The dimensions of the Interface window interior if in desktop mode or HUD surface if in HMD mode.
|
||||
*/
|
||||
virtual glm::vec2 getViewportDimensions() const;
|
||||
|
||||
/**jsdoc
|
||||
* Get the recommended area to position UI on the HUD surface if in HMD mode or Interface's window interior if in desktop
|
||||
* Gets the recommended area to position UI on the HUD surface if in HMD mode or Interface's window interior if in desktop
|
||||
* mode.
|
||||
* @function Controller.getRecommendedHUDRect
|
||||
* @returns {Rect} The recommended area in which to position UI.
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
|
||||
//
|
||||
// HMDScriptingInterface.h
|
||||
// interface/src/scripting
|
||||
//
|
||||
|
@ -25,7 +25,7 @@ class QScriptEngine;
|
|||
#include <QReadWriteLock>
|
||||
|
||||
/**jsdoc
|
||||
* The HMD API provides access to the HMD used in VR display mode.
|
||||
* The <code>HMD</code> API provides access to the HMD used in VR display mode.
|
||||
*
|
||||
* @namespace HMD
|
||||
*
|
||||
|
@ -87,7 +87,7 @@ class HMDScriptingInterface : public AbstractHMDScriptingInterface, public Depen
|
|||
public:
|
||||
|
||||
/**jsdoc
|
||||
* Calculate the intersection of a ray with the HUD overlay.
|
||||
* Calculates the intersection of a ray with the HUD overlay.
|
||||
* @function HMD.calculateRayUICollisionPoint
|
||||
* @param {Vec3} position - The origin of the ray.
|
||||
* @param {Vec3} direction - The direction of the ray.
|
||||
|
@ -115,7 +115,7 @@ public:
|
|||
glm::vec3 calculateParabolaUICollisionPoint(const glm::vec3& position, const glm::vec3& velocity, const glm::vec3& acceleration, float& parabolicDistance) const;
|
||||
|
||||
/**jsdoc
|
||||
* Get the 2D HUD overlay coordinates of a 3D point on the HUD overlay.
|
||||
* Gets the 2D HUD overlay coordinates of a 3D point on the HUD overlay.
|
||||
* 2D HUD overlay coordinates are pixels with the origin at the top left of the overlay.
|
||||
* @function HMD.overlayFromWorldPoint
|
||||
* @param {Vec3} position - The point on the HUD overlay in world coordinates.
|
||||
|
@ -141,7 +141,7 @@ public:
|
|||
Q_INVOKABLE glm::vec2 overlayFromWorldPoint(const glm::vec3& position) const;
|
||||
|
||||
/**jsdoc
|
||||
* Get the 3D world coordinates of a 2D point on the HUD overlay.
|
||||
* Gets the 3D world coordinates of a 2D point on the HUD overlay.
|
||||
* 2D HUD overlay coordinates are pixels with the origin at the top left of the overlay.
|
||||
* @function HMD.worldPointFromOverlay
|
||||
* @param {Vec2} coordinates - The point on the HUD overlay in HUD coordinates.
|
||||
|
@ -150,7 +150,7 @@ public:
|
|||
Q_INVOKABLE glm::vec3 worldPointFromOverlay(const glm::vec2& overlay) const;
|
||||
|
||||
/**jsdoc
|
||||
* Get the 2D point on the HUD overlay represented by given spherical coordinates.
|
||||
* Gets the 2D point on the HUD overlay represented by given spherical coordinates.
|
||||
* 2D HUD overlay coordinates are pixels with the origin at the top left of the overlay.
|
||||
* Spherical coordinates are polar coordinates in radians with <code>{ x: 0, y: 0 }</code> being the center of the HUD
|
||||
* overlay.
|
||||
|
@ -161,7 +161,7 @@ public:
|
|||
Q_INVOKABLE glm::vec2 sphericalToOverlay(const glm::vec2 & sphericalPos) const;
|
||||
|
||||
/**jsdoc
|
||||
* Get the spherical coordinates of a 2D point on the HUD overlay.
|
||||
* Gets the spherical coordinates of a 2D point on the HUD overlay.
|
||||
* 2D HUD overlay coordinates are pixels with the origin at the top left of the overlay.
|
||||
* Spherical coordinates are polar coordinates in radians with <code>{ x: 0, y: 0 }</code> being the center of the HUD
|
||||
* overlay.
|
||||
|
@ -172,21 +172,21 @@ public:
|
|||
Q_INVOKABLE glm::vec2 overlayToSpherical(const glm::vec2 & overlayPos) const;
|
||||
|
||||
/**jsdoc
|
||||
* Recenter the HMD HUD to the current HMD position and orientation.
|
||||
* Recenters the HMD HUD to the current HMD position and orientation.
|
||||
* @function HMD.centerUI
|
||||
*/
|
||||
Q_INVOKABLE void centerUI();
|
||||
|
||||
|
||||
/**jsdoc
|
||||
* Get the name of the HMD audio input device.
|
||||
* Gets the name of the HMD audio input device.
|
||||
* @function HMD.preferredAudioInput
|
||||
* @returns {string} The name of the HMD audio input device if in HMD mode, otherwise an empty string.
|
||||
*/
|
||||
Q_INVOKABLE QString preferredAudioInput() const;
|
||||
|
||||
/**jsdoc
|
||||
* Get the name of the HMD audio output device.
|
||||
* Gets the name of the HMD audio output device.
|
||||
* @function HMD.preferredAudioOutput
|
||||
* @returns {string} The name of the HMD audio output device if in HMD mode, otherwise an empty string.
|
||||
*/
|
||||
|
@ -194,10 +194,10 @@ public:
|
|||
|
||||
|
||||
/**jsdoc
|
||||
* Check whether there is an HMD available.
|
||||
* Checks whether there is an HMD available.
|
||||
* @function HMD.isHMDAvailable
|
||||
* @param {string} [name=""] - The name of the HMD to check for, e.g., <code>"Oculus Rift"</code>. The name is the same as
|
||||
* may be displayed in Interface's "Display" menu. If no name is specified then any HMD matches.
|
||||
* may be displayed in Interface's "Display" menu. If no name is specified, then any HMD matches.
|
||||
* @returns {boolean} <code>true</code> if an HMD of the specified <code>name</code> is available, otherwise
|
||||
* <code>false</code>.
|
||||
* @example <caption>Report on HMD availability.</caption>
|
||||
|
@ -208,10 +208,10 @@ public:
|
|||
Q_INVOKABLE bool isHMDAvailable(const QString& name = "");
|
||||
|
||||
/**jsdoc
|
||||
* Check whether there is an HMD head controller available.
|
||||
* Checks whether there is an HMD head controller available.
|
||||
* @function HMD.isHeadControllerAvailable
|
||||
* @param {string} [name=""] - The name of the HMD head controller to check for, e.g., <code>"Oculus"</code>. If no name is
|
||||
* specified then any HMD head controller matches.
|
||||
* specified, then any HMD head controller matches.
|
||||
* @returns {boolean} <code>true</code> if an HMD head controller of the specified <code>name</code> is available,
|
||||
* otherwise <code>false</code>.
|
||||
* @example <caption>Report HMD head controller availability.</caption>
|
||||
|
@ -222,10 +222,10 @@ public:
|
|||
Q_INVOKABLE bool isHeadControllerAvailable(const QString& name = "");
|
||||
|
||||
/**jsdoc
|
||||
* Check whether there are HMD hand controllers available.
|
||||
* Checks whether there are HMD hand controllers available.
|
||||
* @function HMD.isHandControllerAvailable
|
||||
* @param {string} [name=""] - The name of the HMD hand controller to check for, e.g., <code>"Oculus"</code>. If no name is
|
||||
* specified then any HMD hand controller matches.
|
||||
* specified, then any HMD hand controller matches.
|
||||
* @returns {boolean} <code>true</code> if an HMD hand controller of the specified <code>name</code> is available,
|
||||
* otherwise <code>false</code>.
|
||||
* @example <caption>Report HMD hand controller availability.</caption>
|
||||
|
@ -236,7 +236,7 @@ public:
|
|||
Q_INVOKABLE bool isHandControllerAvailable(const QString& name = "");
|
||||
|
||||
/**jsdoc
|
||||
* Check whether there are specific HMD controllers available.
|
||||
* Checks whether there are specific HMD controllers available.
|
||||
* @function HMD.isSubdeviceContainingNameAvailable
|
||||
* @param {string} name - The name of the HMD controller to check for, e.g., <code>"OculusTouch"</code>.
|
||||
* @returns {boolean} <code>true</code> if an HMD controller with a name containing the specified <code>name</code> is
|
||||
|
@ -248,7 +248,7 @@ public:
|
|||
Q_INVOKABLE bool isSubdeviceContainingNameAvailable(const QString& name);
|
||||
|
||||
/**jsdoc
|
||||
* Signal that models of the HMD hand controllers being used should be displayed. The models are displayed at their actual,
|
||||
* Signals that models of the HMD hand controllers being used should be displayed. The models are displayed at their actual,
|
||||
* real-world locations.
|
||||
* @function HMD.requestShowHandControllers
|
||||
* @example <caption>Show your hand controllers for 10 seconds.</caption>
|
||||
|
@ -260,14 +260,14 @@ public:
|
|||
Q_INVOKABLE void requestShowHandControllers();
|
||||
|
||||
/**jsdoc
|
||||
* Signal that it is no longer necessary to display models of the HMD hand controllers being used. If no other scripts
|
||||
* Signals that it is no longer necessary to display models of the HMD hand controllers being used. If no other scripts
|
||||
* want the models displayed then they are no longer displayed.
|
||||
* @function HMD.requestHideHandControllers
|
||||
*/
|
||||
Q_INVOKABLE void requestHideHandControllers();
|
||||
|
||||
/**jsdoc
|
||||
* Check whether any script wants models of the HMD hand controllers displayed. Requests are made and canceled using
|
||||
* Checks whether any script wants models of the HMD hand controllers displayed. Requests are made and canceled using
|
||||
* {@link HMD.requestShowHandControllers|requestShowHandControllers} and
|
||||
* {@link HMD.requestHideHandControllers|requestHideHandControllers}.
|
||||
* @function HMD.shouldShowHandControllers
|
||||
|
@ -292,8 +292,8 @@ public:
|
|||
|
||||
|
||||
/**jsdoc
|
||||
* Suppress the activation of the HMD-provided keyboard, if any. Successful calls should be balanced with a call to
|
||||
* {@link HMD.unspressKeyboard|unspressKeyboard} within a reasonable amount of time.
|
||||
* Suppresses the activation of the HMD-provided keyboard, if any. Successful calls should be balanced with a call to
|
||||
* {@link HMD.unsuppressKeyboard|unsuppressKeyboard} within a reasonable amount of time.
|
||||
* @function HMD.suppressKeyboard
|
||||
* @returns {boolean} <code>true</code> if the current HMD provides a keyboard and it was successfully suppressed (e.g., it
|
||||
* isn't being displayed), otherwise <code>false</code>.
|
||||
|
@ -307,14 +307,14 @@ public:
|
|||
Q_INVOKABLE bool suppressKeyboard();
|
||||
|
||||
/**jsdoc
|
||||
* Unsuppress the activation of the HMD-provided keyboard, if any.
|
||||
* Unsuppresses the activation of the HMD-provided keyboard, if any.
|
||||
* @function HMD.unsuppressKeyboard
|
||||
*/
|
||||
/// Enable the keyboard following a suppressKeyboard call
|
||||
Q_INVOKABLE void unsuppressKeyboard();
|
||||
|
||||
/**jsdoc
|
||||
* Check whether the HMD-provided keyboard, if any, is visible.
|
||||
* Checks whether the HMD-provided keyboard, if any, is visible.
|
||||
* @function HMD.isKeyboardVisible
|
||||
* @returns {boolean} <code>true</code> if the current HMD provides a keyboard and it is visible, otherwise
|
||||
* <code>false</code>.
|
||||
|
@ -377,7 +377,19 @@ signals:
|
|||
|
||||
public:
|
||||
HMDScriptingInterface();
|
||||
|
||||
/**jsdoc
|
||||
* Gets the position on the HUD overlay that your HMD is looking at, in HUD coordinates.
|
||||
* @function HMD.getHUDLookAtPosition2D
|
||||
* @returns {Vec2} The position on the HUD overlay that your HMD is looking at, in pixels.
|
||||
*/
|
||||
static QScriptValue getHUDLookAtPosition2D(QScriptContext* context, QScriptEngine* engine);
|
||||
|
||||
/**jsdoc
|
||||
* Gets the position on the HUD overlay that your HMD is looking at, in world coordinates.
|
||||
* @function HMD.getHUDLookAtPosition3D
|
||||
* @returns {Vec3} The position on the HUD overlay the your HMD is looking at, in world coordinates.
|
||||
*/
|
||||
static QScriptValue getHUDLookAtPosition3D(QScriptContext* context, QScriptEngine* engine);
|
||||
|
||||
bool isMounted() const override;
|
||||
|
|
|
@ -18,18 +18,15 @@
|
|||
class MenuItemProperties;
|
||||
|
||||
/**jsdoc
|
||||
* The Menu API provides access to the menu that is displayed at the top of the window
|
||||
* on a user's desktop and in the tablet when the "MENU" button is pressed.
|
||||
*
|
||||
* <p />
|
||||
* The <code>Menu</code> API provides access to the menu that is displayed at the top of the window on a user's desktop and in
|
||||
* the tablet when the "MENU" button is pressed.
|
||||
*
|
||||
* <h3>Groupings</h3>
|
||||
*
|
||||
* A "grouping" provides a way to group a set of menus or menu items together so
|
||||
* that they can all be set visible or invisible as a group.
|
||||
* There are two available groups: <code>"Advanced"</code> and <code>"Developer"</code>.
|
||||
* These groupings can be toggled in the "Settings" menu.
|
||||
* If a menu item doesn't belong to a group it is always displayed.
|
||||
* <p>A "grouping" provides a way to group a set of menus or menu items together so that they can all be set visible or invisible
|
||||
* as a group.</p> There is currently only one available group: <code>"Developer"</code>. This grouping can be toggled in the
|
||||
* "Settings" menu.</p>
|
||||
* <p>If a menu item doesn't belong to a group, it is always displayed.</p>
|
||||
*
|
||||
* @namespace Menu
|
||||
*
|
||||
|
@ -60,22 +57,23 @@ private slots:
|
|||
|
||||
public slots:
|
||||
/**jsdoc
|
||||
* Add a new top-level menu.
|
||||
* Adds a new top-level menu.
|
||||
* @function Menu.addMenu
|
||||
* @param {string} menuName - Name that will be displayed for the menu. Nested menus can be described using the ">" symbol.
|
||||
* @param {string} menuName - Name that will be displayed for the menu. Nested menus can be specified using the
|
||||
* <code>">"</code> character.
|
||||
* @param {string} [grouping] - Name of the grouping, if any, to add this menu to.
|
||||
*
|
||||
* @example <caption>Add a menu and a nested submenu.</caption>
|
||||
* Menu.addMenu("Test Menu");
|
||||
* Menu.addMenu("Test Menu > Test Sub Menu");
|
||||
*
|
||||
* @example <caption>Add a menu to the Settings menu that is only visible if Settings > Advanced is enabled.</caption>
|
||||
* Menu.addMenu("Settings > Test Grouping Menu", "Advanced");
|
||||
* @example <caption>Add a menu to the Settings menu that is only visible if Settings > Developer is enabled.</caption>
|
||||
* Menu.addMenu("Settings > Test Grouping Menu", "Developer");
|
||||
*/
|
||||
void addMenu(const QString& menuName, const QString& grouping = QString());
|
||||
|
||||
/**jsdoc
|
||||
* Remove a top-level menu.
|
||||
* Removes a top-level menu.
|
||||
* @function Menu.removeMenu
|
||||
* @param {string} menuName - Name of the menu to remove.
|
||||
* @example <caption>Remove a menu and nested submenu.</caption>
|
||||
|
@ -85,9 +83,9 @@ public slots:
|
|||
void removeMenu(const QString& menuName);
|
||||
|
||||
/**jsdoc
|
||||
* Check whether a top-level menu exists.
|
||||
* Checks whether a top-level menu exists.
|
||||
* @function Menu.menuExists
|
||||
* @param {string} menuName - Name of the menu to check for existence.
|
||||
* @param {string} menuName - Name of the menu to check exists.
|
||||
* @returns {boolean} <code>true</code> if the menu exists, otherwise <code>false</code>.
|
||||
* @example <caption>Check if the "Developer" menu exists.</caption>
|
||||
* if (Menu.menuExists("Developer")) {
|
||||
|
@ -97,46 +95,45 @@ public slots:
|
|||
bool menuExists(const QString& menuName);
|
||||
|
||||
/**jsdoc
|
||||
* Add a separator with an unclickable label below it. The separator will be placed at the bottom of the menu.
|
||||
* If you want to add a separator at a specific point in the menu, use {@link Menu.addMenuItem} with
|
||||
* {@link Menu.MenuItemProperties} instead.
|
||||
* Adds a separator with an unclickable label below it. The separator will be placed at the bottom of the menu. To add a
|
||||
* separator at a specific point in the menu, use {@link Menu.addMenuItem} with {@link Menu.MenuItemProperties} instead.
|
||||
* @function Menu.addSeparator
|
||||
* @param {string} menuName - Name of the menu to add a separator to.
|
||||
* @param {string} menuName - Name of the menu to add the separator to.
|
||||
* @param {string} separatorName - Name of the separator that will be displayed as the label below the separator line.
|
||||
* @example <caption>Add a separator.</caption>
|
||||
* Menu.addSeparator("Developer","Test Separator");
|
||||
* Menu.addSeparator("Developer", "Test Separator");
|
||||
*/
|
||||
void addSeparator(const QString& menuName, const QString& separatorName);
|
||||
|
||||
/**jsdoc
|
||||
* Remove a separator from a menu.
|
||||
* Removes a separator from a menu.
|
||||
* @function Menu.removeSeparator
|
||||
* @param {string} menuName - Name of the menu to remove the separator from.
|
||||
* @param {string} separatorName - Name of the separator to remove.
|
||||
* @example <caption>Remove a separator.</caption>
|
||||
* Menu.removeSeparator("Developer","Test Separator");
|
||||
* Menu.removeSeparator("Developer", "Test Separator");
|
||||
*/
|
||||
void removeSeparator(const QString& menuName, const QString& separatorName);
|
||||
|
||||
/**jsdoc
|
||||
* Add a new menu item to a menu.
|
||||
* Adds a new menu item to a menu. The menu item is specified using {@link Menu.MenuItemProperties}.
|
||||
* @function Menu.addMenuItem
|
||||
* @param {Menu.MenuItemProperties} properties - Properties of the menu item to create.
|
||||
* @example <caption>Add a menu item using {@link Menu.MenuItemProperties}.</caption>
|
||||
* @example <caption>Add a menu item at a particular position in the "Developer" menu.</caption>
|
||||
* Menu.addMenuItem({
|
||||
* menuName: "Developer",
|
||||
* menuItemName: "Test",
|
||||
* afterItem: "Log",
|
||||
* shortcutKey: "Ctrl+Shift+T",
|
||||
* grouping: "Advanced"
|
||||
* shortcutKey: "Ctrl+Shift+T"
|
||||
* });
|
||||
*/
|
||||
void addMenuItem(const MenuItemProperties& properties);
|
||||
|
||||
/**jsdoc
|
||||
* Add a new menu item to a menu. The new item is added at the end of the menu.
|
||||
* Adds a new menu item to a menu. The new item is added at the end of the menu.
|
||||
* @function Menu.addMenuItem
|
||||
* @param {string} menuName - Name of the menu to add a menu item to.
|
||||
* @variation 0
|
||||
* @param {string} menuName - Name of the menu to add the menu item to.
|
||||
* @param {string} menuItem - Name of the menu item. This is what will be displayed in the menu.
|
||||
* @param {string} [shortcutKey] A shortcut key that can be used to trigger the menu item.
|
||||
* @example <caption>Add a menu item to the end of the "Developer" menu.</caption>
|
||||
|
@ -146,16 +143,17 @@ public slots:
|
|||
void addMenuItem(const QString& menuName, const QString& menuitem);
|
||||
|
||||
/**jsdoc
|
||||
* Remove a menu item from a menu.
|
||||
* Removes a menu item from a menu.
|
||||
* @function Menu.removeMenuItem
|
||||
* @param {string} menuName - Name of the menu to remove a menu item from.
|
||||
* @param {string} menuItem - Name of the menu item to remove.
|
||||
* @example <caption>Remove a menu item from the "Developer" menu.</caption>
|
||||
* Menu.removeMenuItem("Developer", "Test");
|
||||
*/
|
||||
void removeMenuItem(const QString& menuName, const QString& menuitem);
|
||||
|
||||
/**jsdoc
|
||||
* Check if a menu item exists.
|
||||
* Checks whether a menu item exists.
|
||||
* @function Menu.menuItemExists
|
||||
* @param {string} menuName - Name of the menu that the menu item is in.
|
||||
* @param {string} menuItem - Name of the menu item to check for existence of.
|
||||
|
@ -168,66 +166,66 @@ public slots:
|
|||
bool menuItemExists(const QString& menuName, const QString& menuitem);
|
||||
|
||||
/**jsdoc
|
||||
* Check whether a checkable menu item is checked.
|
||||
* Checks whether a checkable menu item is checked.
|
||||
* @function Menu.isOptionChecked
|
||||
* @param {string} menuOption - The name of the menu item.
|
||||
* @returns {boolean} <code>true</code> if the option is checked, otherwise <code>false</code>.
|
||||
* @example <caption>Report whether the Settings > Advanced menu item is turned on.</caption>
|
||||
* print(Menu.isOptionChecked("Advanced Menus")); // true or false
|
||||
* @example <caption>Report whether the Settings > Developer menu item is turned on.</caption>
|
||||
* print("Developer menu showing: " + Menu.isOptionChecked("Developer Menu"));
|
||||
*/
|
||||
bool isOptionChecked(const QString& menuOption);
|
||||
|
||||
/**jsdoc
|
||||
* Set a checkable menu item as checked or unchecked.
|
||||
* Sets a checkable menu item as checked or unchecked.
|
||||
* @function Menu.setIsOptionChecked
|
||||
* @param {string} menuOption - The name of the menu item to modify.
|
||||
* @param {boolean} isChecked - If <code>true</code>, the menu item will be checked, otherwise it will not be checked.
|
||||
* @example <caption>Turn on Settings > Advanced Menus.</caption>
|
||||
* Menu.setIsOptionChecked("Advanced Menus", true);
|
||||
* print(Menu.isOptionChecked("Advanced Menus")); // true
|
||||
* @example <caption>Turn on Settings > Developer Menu.</caption>
|
||||
* Menu.setIsOptionChecked("Developer Menu", true);
|
||||
* print("Developer menu showing: " + Menu.isOptionChecked("Developer Menu"));
|
||||
*/
|
||||
void setIsOptionChecked(const QString& menuOption, bool isChecked);
|
||||
|
||||
/**jsdoc
|
||||
* Trigger the menu item as if the user clicked on it.
|
||||
* Triggers a menu item as if the user clicked on it.
|
||||
* @function Menu.triggerOption
|
||||
* @param {string} menuOption - The name of the menu item to trigger.
|
||||
* @example <caption>Open the help window.</caption>
|
||||
* Menu.triggerOption('Help...');
|
||||
* @example <caption>Open the Asset Browser dialog.</caption>
|
||||
* Menu.triggerOption('Asset Browser');
|
||||
*/
|
||||
void triggerOption(const QString& menuOption);
|
||||
|
||||
/**jsdoc
|
||||
* Check whether a menu or menu item is enabled. If disabled, the item is grayed out and unusable.
|
||||
* Checks whether a menu or menu item is enabled. If disabled, the item is grayed out and unusable.
|
||||
* Menus are enabled by default.
|
||||
* @function Menu.isMenuEnabled
|
||||
* @param {string} menuName The name of the menu or menu item to check.
|
||||
* @returns {boolean} <code>true</code> if the menu is enabled, otherwise <code>false</code>.
|
||||
* @example <caption>Report with the Settings > Advanced Menus menu item is enabled.</caption>
|
||||
* print(Menu.isMenuEnabled("Settings > Advanced Menus")); // true or false
|
||||
* @example <caption>Report whether the Settings > Developer Menu item is enabled.</caption>
|
||||
* print("Developer Menu item enabled: " + Menu.isMenuEnabled("Settings > Developer Menu"));
|
||||
*/
|
||||
bool isMenuEnabled(const QString& menuName);
|
||||
|
||||
/**jsdoc
|
||||
* Set a menu or menu item to be enabled or disabled. If disabled, the item is grayed out and unusable.
|
||||
* Sets a menu or menu item to be enabled or disabled. If disabled, the item is grayed out and unusable.
|
||||
* @function Menu.setMenuEnabled
|
||||
* @param {string} menuName - The name of the menu or menu item to modify.
|
||||
* @param {boolean} isEnabled - If <code>true</code>, the menu will be enabled, otherwise it will be disabled.
|
||||
* @example <caption>Disable the Settings > Advanced Menus menu item.</caption>
|
||||
* Menu.setMenuEnabled("Settings > Advanced Menus", false);
|
||||
* print(Menu.isMenuEnabled("Settings > Advanced Menus")); // false
|
||||
* @example <caption>Disable the Settings > Developer Menu item.</caption>
|
||||
* Menu.setMenuEnabled("Settings > Developer Menu", false);
|
||||
* print("Developer Menu item enabled: " + Menu.isMenuEnabled("Settings > Developer Menu"));
|
||||
*/
|
||||
void setMenuEnabled(const QString& menuName, bool isEnabled);
|
||||
|
||||
signals:
|
||||
/**jsdoc
|
||||
* Triggered when a menu item is clicked (or triggered by {@link Menu.triggerOption}).
|
||||
* Triggered when a menu item is clicked or triggered by {@link Menu.triggerOption}.
|
||||
* @function Menu.menuItemEvent
|
||||
* @param {string} menuItem - Name of the menu item that was clicked.
|
||||
* @param {string} menuItem - Name of the menu item that was clicked or triggered.
|
||||
* @returns {Signal}
|
||||
* @example <caption>Detect menu item events.</caption>
|
||||
* function onMenuItemEvent(menuItem) {
|
||||
* print("You clicked on " + menuItem);
|
||||
* print("Menu item clicked: " + menuItem);
|
||||
* }
|
||||
*
|
||||
* Menu.menuItemEvent.connect(onMenuItemEvent);
|
||||
|
|
46
interface/src/scripting/RefreshRateScriptingInterface.h
Normal file
46
interface/src/scripting/RefreshRateScriptingInterface.h
Normal file
|
@ -0,0 +1,46 @@
|
|||
//
|
||||
// RefreshRateScriptingInterface.h
|
||||
// interface/src/scrfipting
|
||||
//
|
||||
// Created by Dante Ruiz on 2019-04-15.
|
||||
// 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_RefreshRateScriptingInterface_h
|
||||
#define hifi_RefreshRateScriptingInterface_h
|
||||
|
||||
#include <QtCore/QObject>
|
||||
|
||||
#include <Application.h>
|
||||
|
||||
class RefreshRateScriptingInterface : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
RefreshRateScriptingInterface() = default;
|
||||
~RefreshRateScriptingInterface() = default;
|
||||
|
||||
public:
|
||||
Q_INVOKABLE QString getRefreshRateProfile() {
|
||||
RefreshRateManager& refreshRateManager = qApp->getRefreshRateManager();
|
||||
return QString::fromStdString(RefreshRateManager::refreshRateProfileToString(refreshRateManager.getRefreshRateProfile()));
|
||||
}
|
||||
|
||||
Q_INVOKABLE QString getRefreshRateRegime() {
|
||||
RefreshRateManager& refreshRateManager = qApp->getRefreshRateManager();
|
||||
return QString::fromStdString(RefreshRateManager::refreshRateRegimeToString(refreshRateManager.getRefreshRateRegime()));
|
||||
}
|
||||
|
||||
Q_INVOKABLE QString getUXMode() {
|
||||
RefreshRateManager& refreshRateManager = qApp->getRefreshRateManager();
|
||||
return QString::fromStdString(RefreshRateManager::uxModeToString(refreshRateManager.getUXMode()));
|
||||
}
|
||||
|
||||
Q_INVOKABLE int getActiveRefreshRate() {
|
||||
return qApp->getRefreshRateManager().getActiveRefreshRate();
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
|
@ -199,3 +199,13 @@ void TestScriptingInterface::setOtherAvatarsReplicaCount(int count) {
|
|||
int TestScriptingInterface::getOtherAvatarsReplicaCount() {
|
||||
return qApp->getOtherAvatarsReplicaCount();
|
||||
}
|
||||
|
||||
void TestScriptingInterface::setMinimumGPUTextureMemStabilityCount(int count) {
|
||||
QMetaObject::invokeMethod(qApp, "setMinimumGPUTextureMemStabilityCount", Qt::DirectConnection, Q_ARG(int, count));
|
||||
}
|
||||
|
||||
bool TestScriptingInterface::isTextureLoadingComplete() {
|
||||
bool result;
|
||||
QMetaObject::invokeMethod(qApp, "gpuTextureMemSizeStable", Qt::DirectConnection, Q_RETURN_ARG(bool, result));
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -163,6 +163,20 @@ public slots:
|
|||
*/
|
||||
Q_INVOKABLE int getOtherAvatarsReplicaCount();
|
||||
|
||||
/**jsdoc
|
||||
* Set number of cycles texture size is required to be stable
|
||||
* @function Entities.setMinimumGPUTextureMemStabilityCount
|
||||
* @param {number} count - Number of cycles to wait
|
||||
*/
|
||||
Q_INVOKABLE void setMinimumGPUTextureMemStabilityCount(int count);
|
||||
|
||||
/**jsdoc
|
||||
* Check whether all textures have been loaded.
|
||||
* @function Entities.isTextureLoadingComplete
|
||||
* @returns {boolean} <code>true</code> texture memory usage is not increasing
|
||||
*/
|
||||
Q_INVOKABLE bool isTextureLoadingComplete();
|
||||
|
||||
private:
|
||||
bool waitForCondition(qint64 maxWaitMs, std::function<bool()> condition);
|
||||
QString _testResultsLocation;
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
#include "MainWindow.h"
|
||||
#include "Menu.h"
|
||||
#include "OffscreenUi.h"
|
||||
#include "commerce/QmlCommerce.h"
|
||||
|
||||
static const QString DESKTOP_LOCATION = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation);
|
||||
static const QString LAST_BROWSE_LOCATION_SETTING = "LastBrowseLocation";
|
||||
|
@ -134,15 +135,17 @@ void WindowScriptingInterface::disconnectedFromDomain() {
|
|||
|
||||
void WindowScriptingInterface::openUrl(const QUrl& url) {
|
||||
if (!url.isEmpty()) {
|
||||
if (url.scheme() == URL_SCHEME_HIFI) {
|
||||
auto scheme = url.scheme();
|
||||
if (scheme == URL_SCHEME_HIFI) {
|
||||
DependencyManager::get<AddressManager>()->handleLookupString(url.toString());
|
||||
} else if (scheme == URL_SCHEME_HIFIAPP) {
|
||||
DependencyManager::get<QmlCommerce>()->openSystemApp(url.path());
|
||||
} else {
|
||||
#if defined(Q_OS_ANDROID)
|
||||
QMap<QString, QString> args;
|
||||
args["url"] = url.toString();
|
||||
AndroidHelper::instance().requestActivity("WebView", true, args);
|
||||
#else
|
||||
// address manager did not handle - ask QDesktopServices to handle
|
||||
QDesktopServices::openUrl(url);
|
||||
#endif
|
||||
}
|
||||
|
|
|
@ -535,9 +535,10 @@ public slots:
|
|||
int openMessageBox(QString title, QString text, int buttons, int defaultButton);
|
||||
|
||||
/**jsdoc
|
||||
* Open a URL in the Interface window or other application, depending on the URL's scheme. If the URL starts with
|
||||
* <code>hifi://</code> then that URL is navigated to in Interface, otherwise the URL is opened in the application the OS
|
||||
* associates with the URL's scheme (e.g., a Web browser for <code>http://</code>).
|
||||
* Open a URL in the Interface window or other application, depending on the URL's scheme. The following schemes are supported:
|
||||
* <code>hifi</code> (navigate to the URL in Interface), <code>hifiapp<code> (open a system app in Interface). Other schemes will either be handled by the OS
|
||||
* (e.g. <code>http</code>, <code>https</code>, <code>mailto</code>) or will create a confirmation dialog asking the user to confirm that they want to try to open
|
||||
* the URL.
|
||||
* @function Window.openUrl
|
||||
* @param {string} url - The URL to open.
|
||||
*/
|
||||
|
|
|
@ -138,7 +138,7 @@ void LoginDialog::login(const QString& username, const QString& password) const
|
|||
void LoginDialog::loginThroughOculus() {
|
||||
qDebug() << "Attempting to login through Oculus";
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
@ -279,10 +279,6 @@ void LoginDialog::createAccountFromSteam(QString username) {
|
|||
}
|
||||
}
|
||||
|
||||
void LoginDialog::openUrl(const QString& url) const {
|
||||
QDesktopServices::openUrl(QUrl(url));
|
||||
}
|
||||
|
||||
void LoginDialog::linkCompleted(QNetworkReply* reply) {
|
||||
emit handleLinkCompleted();
|
||||
}
|
||||
|
|
|
@ -80,8 +80,6 @@ protected slots:
|
|||
|
||||
Q_INVOKABLE void signup(const QString& email, const QString& username, const QString& password);
|
||||
|
||||
Q_INVOKABLE void openUrl(const QString& url) const;
|
||||
|
||||
Q_INVOKABLE bool getLoginDialogPoppedUp() const;
|
||||
};
|
||||
|
||||
|
|
|
@ -82,6 +82,28 @@ void setupPreferences() {
|
|||
preferences->addPreference(new CheckPreference(GRAPHICS_QUALITY, "Show Shadows", getterShadow, setterShadow));
|
||||
}
|
||||
|
||||
{
|
||||
auto getter = []()->QString {
|
||||
RefreshRateManager::RefreshRateProfile refreshRateProfile = qApp->getRefreshRateManager().getRefreshRateProfile();
|
||||
return QString::fromStdString(RefreshRateManager::refreshRateProfileToString(refreshRateProfile));
|
||||
};
|
||||
|
||||
auto setter = [](QString value) {
|
||||
std::string profileName = value.toStdString();
|
||||
RefreshRateManager::RefreshRateProfile refreshRateProfile = RefreshRateManager::refreshRateProfileFromString(profileName);
|
||||
qApp->getRefreshRateManager().setRefreshRateProfile(refreshRateProfile);
|
||||
};
|
||||
|
||||
auto preference = new ComboBoxPreference(GRAPHICS_QUALITY, "Refresh Rate", getter, setter);
|
||||
QStringList refreshRateProfiles
|
||||
{ QString::fromStdString(RefreshRateManager::refreshRateProfileToString(RefreshRateManager::RefreshRateProfile::ECO)),
|
||||
QString::fromStdString(RefreshRateManager::refreshRateProfileToString(RefreshRateManager::RefreshRateProfile::INTERACTIVE)),
|
||||
QString::fromStdString(RefreshRateManager::refreshRateProfileToString(RefreshRateManager::RefreshRateProfile::REALTIME)) };
|
||||
|
||||
preference->setItems(refreshRateProfiles);
|
||||
preferences->addPreference(preference);
|
||||
}
|
||||
|
||||
// UI
|
||||
static const QString UI_CATEGORY { "User Interface" };
|
||||
{
|
||||
|
@ -278,6 +300,12 @@ void setupPreferences() {
|
|||
preference->setIndented(true);
|
||||
preferences->addPreference(preference);
|
||||
}
|
||||
{
|
||||
auto getter = [myAvatar]() -> bool { return myAvatar->hoverWhenUnsupported(); };
|
||||
auto setter = [myAvatar](bool value) { myAvatar->setHoverWhenUnsupported(value); };
|
||||
auto preference = new CheckPreference(VR_MOVEMENT, "Hover When Unsupported", getter, setter);
|
||||
preferences->addPreference(preference);
|
||||
}
|
||||
{
|
||||
auto getter = [myAvatar]()->int { return myAvatar->getMovementReference(); };
|
||||
auto setter = [myAvatar](int value) { myAvatar->setMovementReference(value); };
|
||||
|
|
|
@ -159,47 +159,57 @@ void Snapshot::save360Snapshot(const glm::vec3& cameraPosition,
|
|||
secondaryCameraRenderConfig->setOrientation(CAMERA_ORIENTATION_DOWN);
|
||||
|
||||
_snapshotIndex = 0;
|
||||
_taking360Snapshot = true;
|
||||
|
||||
_snapshotTimer.start(SNAPSHOT_360_TIMER_INTERVAL);
|
||||
}
|
||||
|
||||
void Snapshot::takeNextSnapshot() {
|
||||
SecondaryCameraJobConfig* config =
|
||||
static_cast<SecondaryCameraJobConfig*>(qApp->getRenderEngine()->getConfiguration()->getConfig("SecondaryCamera"));
|
||||
if (_taking360Snapshot) {
|
||||
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:
|
||||
// 0. Down
|
||||
// 1. Front
|
||||
// 2. Left
|
||||
// 3. Back
|
||||
// 4. Right
|
||||
// 5. Up
|
||||
if (_snapshotIndex < 6) {
|
||||
_imageArray[_snapshotIndex] = qApp->getActiveDisplayPlugin()->getSecondaryCameraScreenshot();
|
||||
}
|
||||
SecondaryCameraJobConfig* config = static_cast<SecondaryCameraJobConfig*>(qApp->getRenderEngine()->getConfiguration()->getConfig("SecondaryCamera"));
|
||||
if (_snapshotIndex == 0) {
|
||||
// Setup for Front Image capture
|
||||
config->setOrientation(CAMERA_ORIENTATION_FRONT);
|
||||
} else if (_snapshotIndex == 1) {
|
||||
// Setup for Left Image capture
|
||||
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) {
|
||||
_taking360Snapshot = false;
|
||||
}
|
||||
|
||||
if (_snapshotIndex == 0) {
|
||||
// Setup for Front Image capture
|
||||
config->setOrientation(CAMERA_ORIENTATION_FRONT);
|
||||
} else if (_snapshotIndex == 1) {
|
||||
// Setup for Left Image capture
|
||||
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) {
|
||||
_waitingOnSnapshot = false;
|
||||
_snapshotIndex++;
|
||||
}, 0.0f, false));
|
||||
}
|
||||
} else {
|
||||
_snapshotTimer.stop();
|
||||
|
||||
// Reset secondary camera render config
|
||||
static_cast<ToneMappingConfig*>(
|
||||
qApp->getRenderEngine()->getConfiguration()->getConfig("SecondaryCameraJob.ToneMapping"))
|
||||
->setCurve(1);
|
||||
SecondaryCameraJobConfig* config = static_cast<SecondaryCameraJobConfig*>(qApp->getRenderEngine()->getConfiguration()->getConfig("SecondaryCamera"));
|
||||
static_cast<ToneMappingConfig*>(qApp->getRenderEngine()->getConfiguration()->getConfig("SecondaryCameraJob.ToneMapping"))->setCurve(1);
|
||||
config->resetSizeSpectatorCamera(qApp->getWindow()->geometry().width(), qApp->getWindow()->geometry().height());
|
||||
config->setProperty("attachedEntityId", _oldAttachedEntityId);
|
||||
config->setProperty("vFoV", _oldvFoV);
|
||||
|
@ -217,8 +227,6 @@ void Snapshot::takeNextSnapshot() {
|
|||
QtConcurrent::run([this]() { convertToEquirectangular(); });
|
||||
}
|
||||
}
|
||||
|
||||
_snapshotIndex++;
|
||||
}
|
||||
|
||||
void Snapshot::convertToCubemap() {
|
||||
|
|
|
@ -97,6 +97,8 @@ private:
|
|||
bool _cubemapOutputFormat;
|
||||
QTimer _snapshotTimer;
|
||||
qint16 _snapshotIndex;
|
||||
bool _waitingOnSnapshot { false };
|
||||
bool _taking360Snapshot { false };
|
||||
bool _oldEnabled;
|
||||
QVariant _oldAttachedEntityId;
|
||||
QVariant _oldOrientation;
|
||||
|
|
|
@ -27,7 +27,6 @@ QString SnapshotAnimated::snapshotAnimatedPath;
|
|||
QString SnapshotAnimated::snapshotStillPath;
|
||||
QVector<QImage> SnapshotAnimated::snapshotAnimatedFrameVector;
|
||||
QVector<qint64> SnapshotAnimated::snapshotAnimatedFrameDelayVector;
|
||||
Application* SnapshotAnimated::app;
|
||||
float SnapshotAnimated::aspectRatio;
|
||||
QSharedPointer<WindowScriptingInterface> SnapshotAnimated::snapshotAnimatedDM;
|
||||
GifWriter SnapshotAnimated::snapshotAnimatedGifWriter;
|
||||
|
@ -36,12 +35,11 @@ GifWriter SnapshotAnimated::snapshotAnimatedGifWriter;
|
|||
Setting::Handle<bool> SnapshotAnimated::alsoTakeAnimatedSnapshot("alsoTakeAnimatedSnapshot", true);
|
||||
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 (SnapshotAnimated::snapshotAnimatedFirstFrameTimestamp == 0) {
|
||||
SnapshotAnimated::snapshotAnimatedTimer = new QTimer();
|
||||
SnapshotAnimated::aspectRatio = aspectRatio;
|
||||
SnapshotAnimated::app = app;
|
||||
SnapshotAnimated::snapshotAnimatedDM = dm;
|
||||
// Define the output location of the still and animated snapshots.
|
||||
SnapshotAnimated::snapshotStillPath = pathStill;
|
||||
|
@ -62,44 +60,45 @@ void SnapshotAnimated::saveSnapshotAnimated(QString pathStill, float aspectRatio
|
|||
|
||||
void SnapshotAnimated::captureFrames() {
|
||||
if (SnapshotAnimated::snapshotAnimatedTimerRunning) {
|
||||
// Get a screenshot from the display, then scale the screenshot down,
|
||||
// then convert it to the image format the GIF library needs,
|
||||
// then save all that to the QImage named "frame"
|
||||
QImage frame(SnapshotAnimated::app->getActiveDisplayPlugin()->getScreenshot(SnapshotAnimated::aspectRatio));
|
||||
frame = frame.scaledToWidth(SNAPSNOT_ANIMATED_WIDTH);
|
||||
SnapshotAnimated::snapshotAnimatedFrameVector.append(frame);
|
||||
qApp->addSnapshotOperator(std::make_tuple([](const QImage& snapshot) {
|
||||
// Get a screenshot from the display, then scale the screenshot down,
|
||||
// then convert it to the image format the GIF library needs,
|
||||
// then save all that to the QImage named "frame"
|
||||
QImage frame = snapshot.scaledToWidth(SNAPSNOT_ANIMATED_WIDTH);
|
||||
SnapshotAnimated::snapshotAnimatedFrameVector.append(frame);
|
||||
|
||||
// If that was the first frame...
|
||||
if (SnapshotAnimated::snapshotAnimatedFirstFrameTimestamp == 0) {
|
||||
// Record the current frame timestamp
|
||||
SnapshotAnimated::snapshotAnimatedTimestamp = QDateTime::currentMSecsSinceEpoch();
|
||||
// Record the first frame timestamp
|
||||
SnapshotAnimated::snapshotAnimatedFirstFrameTimestamp = SnapshotAnimated::snapshotAnimatedTimestamp;
|
||||
SnapshotAnimated::snapshotAnimatedFrameDelayVector.append(SNAPSNOT_ANIMATED_FRAME_DELAY_MSEC / 10);
|
||||
// If this is an intermediate or the final frame...
|
||||
} else {
|
||||
// Push the current frame delay onto the vector
|
||||
SnapshotAnimated::snapshotAnimatedFrameDelayVector.append(round(((float)(QDateTime::currentMSecsSinceEpoch() - SnapshotAnimated::snapshotAnimatedTimestamp)) / 10));
|
||||
// Record the current frame timestamp
|
||||
SnapshotAnimated::snapshotAnimatedTimestamp = QDateTime::currentMSecsSinceEpoch();
|
||||
// If that was the first frame...
|
||||
if (SnapshotAnimated::snapshotAnimatedFirstFrameTimestamp == 0) {
|
||||
// Record the current frame timestamp
|
||||
SnapshotAnimated::snapshotAnimatedTimestamp = QDateTime::currentMSecsSinceEpoch();
|
||||
// Record the first frame timestamp
|
||||
SnapshotAnimated::snapshotAnimatedFirstFrameTimestamp = SnapshotAnimated::snapshotAnimatedTimestamp;
|
||||
SnapshotAnimated::snapshotAnimatedFrameDelayVector.append(SNAPSNOT_ANIMATED_FRAME_DELAY_MSEC / 10);
|
||||
// If this is an intermediate or the final frame...
|
||||
} else {
|
||||
// Push the current frame delay onto the vector
|
||||
SnapshotAnimated::snapshotAnimatedFrameDelayVector.append(round(((float)(QDateTime::currentMSecsSinceEpoch() - SnapshotAnimated::snapshotAnimatedTimestamp)) / 10));
|
||||
// Record the current frame timestamp
|
||||
SnapshotAnimated::snapshotAnimatedTimestamp = QDateTime::currentMSecsSinceEpoch();
|
||||
|
||||
// If that was the last frame...
|
||||
if ((SnapshotAnimated::snapshotAnimatedTimestamp - SnapshotAnimated::snapshotAnimatedFirstFrameTimestamp) >= (SnapshotAnimated::snapshotAnimatedDuration.get() * MSECS_PER_SECOND)) {
|
||||
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;
|
||||
// If that was the last frame...
|
||||
if ((SnapshotAnimated::snapshotAnimatedTimestamp - SnapshotAnimated::snapshotAnimatedFirstFrameTimestamp) >= (SnapshotAnimated::snapshotAnimatedDuration.get() * MSECS_PER_SECOND)) {
|
||||
SnapshotAnimated::snapshotAnimatedTimerRunning = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 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<qint64> snapshotAnimatedFrameDelayVector;
|
||||
static QSharedPointer<WindowScriptingInterface> snapshotAnimatedDM;
|
||||
static Application* app;
|
||||
static float aspectRatio;
|
||||
|
||||
static GifWriter snapshotAnimatedGifWriter;
|
||||
|
@ -51,7 +50,7 @@ private:
|
|||
static void processFrames();
|
||||
static void clearTempVariables();
|
||||
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 Setting::Handle<bool> alsoTakeAnimatedSnapshot;
|
||||
static Setting::Handle<float> snapshotAnimatedDuration;
|
||||
|
|
|
@ -132,6 +132,14 @@ void Stats::updateStats(bool force) {
|
|||
STAT_UPDATE(notUpdatedAvatarCount, avatarManager->getNumAvatarsNotUpdated());
|
||||
STAT_UPDATE(serverCount, (int)nodeList->size());
|
||||
STAT_UPDATE_FLOAT(renderrate, qApp->getRenderLoopRate(), 0.1f);
|
||||
RefreshRateManager& refreshRateManager = qApp->getRefreshRateManager();
|
||||
std::string refreshRateMode = RefreshRateManager::refreshRateProfileToString(refreshRateManager.getRefreshRateProfile());
|
||||
std::string refreshRateRegime = RefreshRateManager::refreshRateRegimeToString(refreshRateManager.getRefreshRateRegime());
|
||||
std::string uxMode = RefreshRateManager::uxModeToString(refreshRateManager.getUXMode());
|
||||
STAT_UPDATE(refreshRateMode, QString::fromStdString(refreshRateMode));
|
||||
STAT_UPDATE(refreshRateRegime, QString::fromStdString(refreshRateRegime));
|
||||
STAT_UPDATE(uxMode, QString::fromStdString(uxMode));
|
||||
STAT_UPDATE(refreshRateTarget, refreshRateManager.getActiveRefreshRate());
|
||||
if (qApp->getActiveDisplayPlugin()) {
|
||||
auto displayPlugin = qApp->getActiveDisplayPlugin();
|
||||
auto stats = displayPlugin->getHardwareStats();
|
||||
|
|
|
@ -206,6 +206,10 @@ class Stats : public QQuickItem {
|
|||
STATS_PROPERTY(float, presentdroprate, 0)
|
||||
STATS_PROPERTY(int, gameLoopRate, 0)
|
||||
STATS_PROPERTY(int, avatarCount, 0)
|
||||
STATS_PROPERTY(int, refreshRateTarget, 0)
|
||||
STATS_PROPERTY(QString, refreshRateMode, QString())
|
||||
STATS_PROPERTY(QString, refreshRateRegime, QString())
|
||||
STATS_PROPERTY(QString, uxMode, QString())
|
||||
STATS_PROPERTY(int, heroAvatarCount, 0)
|
||||
STATS_PROPERTY(int, physicsObjectCount, 0)
|
||||
STATS_PROPERTY(int, updatedAvatarCount, 0)
|
||||
|
@ -1067,6 +1071,15 @@ signals:
|
|||
*/
|
||||
void decimatedTextureCountChanged();
|
||||
|
||||
|
||||
void refreshRateTargetChanged();
|
||||
|
||||
void refreshRateModeChanged();
|
||||
|
||||
void refreshRateRegimeChanged();
|
||||
|
||||
void uxModeChanged();
|
||||
|
||||
// QQuickItem signals.
|
||||
|
||||
/**jsdoc
|
||||
|
|
|
@ -11,11 +11,12 @@
|
|||
#include "AnimContext.h"
|
||||
|
||||
AnimContext::AnimContext(bool enableDebugDrawIKTargets, bool enableDebugDrawIKConstraints, bool enableDebugDrawIKChains,
|
||||
const glm::mat4& geometryToRigMatrix, const glm::mat4& rigToWorldMatrix) :
|
||||
const glm::mat4& geometryToRigMatrix, const glm::mat4& rigToWorldMatrix, int evaluationCount) :
|
||||
_enableDebugDrawIKTargets(enableDebugDrawIKTargets),
|
||||
_enableDebugDrawIKConstraints(enableDebugDrawIKConstraints),
|
||||
_enableDebugDrawIKChains(enableDebugDrawIKChains),
|
||||
_geometryToRigMatrix(geometryToRigMatrix),
|
||||
_rigToWorldMatrix(rigToWorldMatrix)
|
||||
_rigToWorldMatrix(rigToWorldMatrix),
|
||||
_evaluationCount(evaluationCount)
|
||||
{
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ enum class AnimNodeType {
|
|||
BlendLinearMove,
|
||||
Overlay,
|
||||
StateMachine,
|
||||
RandomSwitchStateMachine,
|
||||
Manipulator,
|
||||
InverseKinematics,
|
||||
DefaultPose,
|
||||
|
@ -37,13 +38,14 @@ class AnimContext {
|
|||
public:
|
||||
AnimContext() {}
|
||||
AnimContext(bool enableDebugDrawIKTargets, bool enableDebugDrawIKConstraints, bool enableDebugDrawIKChains,
|
||||
const glm::mat4& geometryToRigMatrix, const glm::mat4& rigToWorldMatrix);
|
||||
const glm::mat4& geometryToRigMatrix, const glm::mat4& rigToWorldMatrix, int evaluationCount);
|
||||
|
||||
bool getEnableDebugDrawIKTargets() const { return _enableDebugDrawIKTargets; }
|
||||
bool getEnableDebugDrawIKConstraints() const { return _enableDebugDrawIKConstraints; }
|
||||
bool getEnableDebugDrawIKChains() const { return _enableDebugDrawIKChains; }
|
||||
const glm::mat4& getGeometryToRigMatrix() const { return _geometryToRigMatrix; }
|
||||
const glm::mat4& getRigToWorldMatrix() const { return _rigToWorldMatrix; }
|
||||
int getEvaluationCount() const { return _evaluationCount; }
|
||||
|
||||
float getDebugAlpha(const QString& key) const {
|
||||
auto it = _debugAlphaMap.find(key);
|
||||
|
@ -85,6 +87,7 @@ protected:
|
|||
bool _enableDebugDrawIKChains { false };
|
||||
glm::mat4 _geometryToRigMatrix;
|
||||
glm::mat4 _rigToWorldMatrix;
|
||||
int _evaluationCount{ 0 };
|
||||
|
||||
// used for debugging internal state of animation system.
|
||||
mutable DebugAlphaMap _debugAlphaMap;
|
||||
|
|
|
@ -43,6 +43,7 @@ public:
|
|||
friend class AnimDebugDraw;
|
||||
friend void buildChildMap(std::map<QString, Pointer>& map, Pointer node);
|
||||
friend class AnimStateMachine;
|
||||
friend class AnimRandomSwitch;
|
||||
|
||||
AnimNode(Type type, const QString& id) : _type(type), _id(id) {}
|
||||
virtual ~AnimNode() {}
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
#include "AnimationLogging.h"
|
||||
#include "AnimOverlay.h"
|
||||
#include "AnimStateMachine.h"
|
||||
#include "AnimRandomSwitch.h"
|
||||
#include "AnimManipulator.h"
|
||||
#include "AnimInverseKinematics.h"
|
||||
#include "AnimDefaultPose.h"
|
||||
|
@ -38,6 +39,7 @@ static AnimNode::Pointer loadBlendLinearNode(const QJsonObject& jsonObj, const Q
|
|||
static AnimNode::Pointer loadBlendLinearMoveNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl);
|
||||
static AnimNode::Pointer loadOverlayNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl);
|
||||
static AnimNode::Pointer loadStateMachineNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl);
|
||||
static AnimNode::Pointer loadRandomSwitchStateMachineNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl);
|
||||
static AnimNode::Pointer loadManipulatorNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl);
|
||||
static AnimNode::Pointer loadInverseKinematicsNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl);
|
||||
static AnimNode::Pointer loadDefaultPoseNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl);
|
||||
|
@ -51,6 +53,7 @@ static const float ANIM_GRAPH_LOAD_PRIORITY = 10.0f;
|
|||
// returns node on success, nullptr on failure.
|
||||
static bool processDoNothing(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { return true; }
|
||||
bool processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl);
|
||||
bool processRandomSwitchStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl);
|
||||
|
||||
static const char* animNodeTypeToString(AnimNode::Type type) {
|
||||
switch (type) {
|
||||
|
@ -59,6 +62,7 @@ static const char* animNodeTypeToString(AnimNode::Type type) {
|
|||
case AnimNode::Type::BlendLinearMove: return "blendLinearMove";
|
||||
case AnimNode::Type::Overlay: return "overlay";
|
||||
case AnimNode::Type::StateMachine: return "stateMachine";
|
||||
case AnimNode::Type::RandomSwitchStateMachine: return "randomSwitchStateMachine";
|
||||
case AnimNode::Type::Manipulator: return "manipulator";
|
||||
case AnimNode::Type::InverseKinematics: return "inverseKinematics";
|
||||
case AnimNode::Type::DefaultPose: return "defaultPose";
|
||||
|
@ -92,6 +96,16 @@ static AnimStateMachine::InterpType stringToInterpType(const QString& str) {
|
|||
}
|
||||
}
|
||||
|
||||
static AnimRandomSwitch::InterpType stringToRandomInterpType(const QString& str) {
|
||||
if (str == "snapshotBoth") {
|
||||
return AnimRandomSwitch::InterpType::SnapshotBoth;
|
||||
} else if (str == "snapshotPrev") {
|
||||
return AnimRandomSwitch::InterpType::SnapshotPrev;
|
||||
} else {
|
||||
return AnimRandomSwitch::InterpType::NumTypes;
|
||||
}
|
||||
}
|
||||
|
||||
static const char* animManipulatorJointVarTypeToString(AnimManipulator::JointVar::Type type) {
|
||||
switch (type) {
|
||||
case AnimManipulator::JointVar::Type::Absolute: return "absolute";
|
||||
|
@ -122,6 +136,7 @@ static NodeLoaderFunc animNodeTypeToLoaderFunc(AnimNode::Type type) {
|
|||
case AnimNode::Type::BlendLinearMove: return loadBlendLinearMoveNode;
|
||||
case AnimNode::Type::Overlay: return loadOverlayNode;
|
||||
case AnimNode::Type::StateMachine: return loadStateMachineNode;
|
||||
case AnimNode::Type::RandomSwitchStateMachine: return loadRandomSwitchStateMachineNode;
|
||||
case AnimNode::Type::Manipulator: return loadManipulatorNode;
|
||||
case AnimNode::Type::InverseKinematics: return loadInverseKinematicsNode;
|
||||
case AnimNode::Type::DefaultPose: return loadDefaultPoseNode;
|
||||
|
@ -140,6 +155,7 @@ static NodeProcessFunc animNodeTypeToProcessFunc(AnimNode::Type type) {
|
|||
case AnimNode::Type::BlendLinearMove: return processDoNothing;
|
||||
case AnimNode::Type::Overlay: return processDoNothing;
|
||||
case AnimNode::Type::StateMachine: return processStateMachineNode;
|
||||
case AnimNode::Type::RandomSwitchStateMachine: return processRandomSwitchStateMachineNode;
|
||||
case AnimNode::Type::Manipulator: return processDoNothing;
|
||||
case AnimNode::Type::InverseKinematics: return processDoNothing;
|
||||
case AnimNode::Type::DefaultPose: return processDoNothing;
|
||||
|
@ -463,6 +479,11 @@ static AnimNode::Pointer loadStateMachineNode(const QJsonObject& jsonObj, const
|
|||
return node;
|
||||
}
|
||||
|
||||
static AnimNode::Pointer loadRandomSwitchStateMachineNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) {
|
||||
auto node = std::make_shared<AnimRandomSwitch>(id);
|
||||
return node;
|
||||
}
|
||||
|
||||
static AnimNode::Pointer loadManipulatorNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) {
|
||||
|
||||
READ_FLOAT(alpha, jsonObj, id, jsonUrl, nullptr);
|
||||
|
@ -780,6 +801,141 @@ bool processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj,
|
|||
return true;
|
||||
}
|
||||
|
||||
bool processRandomSwitchStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& nodeId, const QUrl& jsonUrl) {
|
||||
auto smNode = std::static_pointer_cast<AnimRandomSwitch>(node);
|
||||
assert(smNode);
|
||||
|
||||
READ_STRING(currentState, jsonObj, nodeId, jsonUrl, false);
|
||||
READ_OPTIONAL_FLOAT(randomSwitchTimeMin, jsonObj, -1.0f);
|
||||
READ_OPTIONAL_FLOAT(randomSwitchTimeMax, jsonObj, -1.0f);
|
||||
READ_OPTIONAL_STRING(triggerRandomSwitch, jsonObj);
|
||||
READ_OPTIONAL_FLOAT(triggerTimeMin, jsonObj, -1.0f);
|
||||
READ_OPTIONAL_FLOAT(triggerTimeMax, jsonObj, -1.0f);
|
||||
READ_OPTIONAL_STRING(transitionVar, jsonObj);
|
||||
|
||||
|
||||
|
||||
auto statesValue = jsonObj.value("states");
|
||||
if (!statesValue.isArray()) {
|
||||
qCCritical(animation) << "AnimNodeLoader, bad array \"states\" in random switch state Machine node, id =" << nodeId;
|
||||
return false;
|
||||
}
|
||||
|
||||
// build a map for all children by name.
|
||||
std::map<QString, int> childMap;
|
||||
buildChildMap(childMap, node);
|
||||
|
||||
// first pass parse all the states and build up the state and transition map.
|
||||
using StringPair = std::pair<QString, QString>;
|
||||
using TransitionMap = std::multimap<AnimRandomSwitch::RandomSwitchState::Pointer, StringPair>;
|
||||
TransitionMap transitionMap;
|
||||
|
||||
using RandomStateMap = std::map<QString, AnimRandomSwitch::RandomSwitchState::Pointer>;
|
||||
RandomStateMap randomStateMap;
|
||||
|
||||
auto randomStatesArray = statesValue.toArray();
|
||||
for (const auto& randomStateValue : randomStatesArray) {
|
||||
if (!randomStateValue.isObject()) {
|
||||
qCCritical(animation) << "AnimNodeLoader, bad state object in \"random states\", id =" << nodeId;
|
||||
return false;
|
||||
}
|
||||
auto stateObj = randomStateValue.toObject();
|
||||
|
||||
READ_STRING(id, stateObj, nodeId, jsonUrl, false);
|
||||
READ_FLOAT(interpTarget, stateObj, nodeId, jsonUrl, false);
|
||||
READ_FLOAT(interpDuration, stateObj, nodeId, jsonUrl, false);
|
||||
READ_OPTIONAL_STRING(interpType, stateObj);
|
||||
READ_FLOAT(priority, stateObj, nodeId, jsonUrl, false);
|
||||
READ_BOOL(resume, stateObj, nodeId, jsonUrl, false);
|
||||
|
||||
READ_OPTIONAL_STRING(interpTargetVar, stateObj);
|
||||
READ_OPTIONAL_STRING(interpDurationVar, stateObj);
|
||||
READ_OPTIONAL_STRING(interpTypeVar, stateObj);
|
||||
|
||||
auto iter = childMap.find(id);
|
||||
if (iter == childMap.end()) {
|
||||
qCCritical(animation) << "AnimNodeLoader, could not find random stateMachine child (state) with nodeId =" << nodeId << "random stateId =" << id;
|
||||
return false;
|
||||
}
|
||||
|
||||
AnimRandomSwitch::InterpType interpTypeEnum = AnimRandomSwitch::InterpType::SnapshotPrev; // default value
|
||||
if (!interpType.isEmpty()) {
|
||||
interpTypeEnum = stringToRandomInterpType(interpType);
|
||||
if (interpTypeEnum == AnimRandomSwitch::InterpType::NumTypes) {
|
||||
qCCritical(animation) << "AnimNodeLoader, bad interpType on random state Machine state, nodeId = " << nodeId << "random stateId =" << id;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
auto randomStatePtr = std::make_shared<AnimRandomSwitch::RandomSwitchState>(id, iter->second, interpTarget, interpDuration, interpTypeEnum, priority, resume);
|
||||
if (priority > 0.0f) {
|
||||
smNode->addToPrioritySum(priority);
|
||||
}
|
||||
assert(randomStatePtr);
|
||||
|
||||
if (!interpTargetVar.isEmpty()) {
|
||||
randomStatePtr->setInterpTargetVar(interpTargetVar);
|
||||
}
|
||||
if (!interpDurationVar.isEmpty()) {
|
||||
randomStatePtr->setInterpDurationVar(interpDurationVar);
|
||||
}
|
||||
if (!interpTypeVar.isEmpty()) {
|
||||
randomStatePtr->setInterpTypeVar(interpTypeVar);
|
||||
}
|
||||
|
||||
smNode->addState(randomStatePtr);
|
||||
randomStateMap.insert(RandomStateMap::value_type(randomStatePtr->getID(), randomStatePtr));
|
||||
|
||||
auto transitionsValue = stateObj.value("transitions");
|
||||
if (!transitionsValue.isArray()) {
|
||||
qCritical(animation) << "AnimNodeLoader, bad array \"transitions\" in random state Machine node, stateId =" << id << "nodeId =" << nodeId;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto transitionsArray = transitionsValue.toArray();
|
||||
for (const auto& transitionValue : transitionsArray) {
|
||||
if (!transitionValue.isObject()) {
|
||||
qCritical(animation) << "AnimNodeLoader, bad transition object in \"transitions\", random stateId =" << id << "nodeId =" << nodeId;
|
||||
return false;
|
||||
}
|
||||
auto transitionObj = transitionValue.toObject();
|
||||
|
||||
READ_STRING(var, transitionObj, nodeId, jsonUrl, false);
|
||||
READ_STRING(randomSwitchState, transitionObj, nodeId, jsonUrl, false);
|
||||
|
||||
transitionMap.insert(TransitionMap::value_type(randomStatePtr, StringPair(var, randomSwitchState)));
|
||||
}
|
||||
}
|
||||
|
||||
// second pass: now iterate thru all transitions and add them to the appropriate states.
|
||||
for (auto& transition : transitionMap) {
|
||||
AnimRandomSwitch::RandomSwitchState::Pointer srcState = transition.first;
|
||||
auto iter = randomStateMap.find(transition.second.second);
|
||||
if (iter != randomStateMap.end()) {
|
||||
srcState->addTransition(AnimRandomSwitch::RandomSwitchState::Transition(transition.second.first, iter->second));
|
||||
} else {
|
||||
qCCritical(animation) << "AnimNodeLoader, bad random state machine transition from srcState =" << srcState->_id << "dstState =" << transition.second.second << "nodeId =" << nodeId;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
auto iter = randomStateMap.find(currentState);
|
||||
if (iter == randomStateMap.end()) {
|
||||
qCCritical(animation) << "AnimNodeLoader, bad currentState =" << currentState << "could not find child node" << "id =" << nodeId;
|
||||
}
|
||||
smNode->setCurrentState(iter->second);
|
||||
smNode->setRandomSwitchTimeMin(randomSwitchTimeMin);
|
||||
smNode->setRandomSwitchTimeMax(randomSwitchTimeMax);
|
||||
smNode->setTriggerRandomSwitchVar(triggerRandomSwitch);
|
||||
smNode->setTriggerTimeMin(triggerTimeMin);
|
||||
smNode->setTriggerTimeMax(triggerTimeMax);
|
||||
smNode->setTransitionVar(transitionVar);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
AnimNodeLoader::AnimNodeLoader(const QUrl& url) :
|
||||
_url(url)
|
||||
{
|
||||
|
|
212
libraries/animation/src/AnimRandomSwitch.cpp
Normal file
212
libraries/animation/src/AnimRandomSwitch.cpp
Normal file
|
@ -0,0 +1,212 @@
|
|||
//
|
||||
// AnimRandomSwitch.cpp
|
||||
//
|
||||
// Created by Angus Antley on 4/8/2019.
|
||||
// Copyright (c) 2019 High Fidelity, Inc. All rights reserved.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "AnimRandomSwitch.h"
|
||||
#include "AnimUtil.h"
|
||||
#include "AnimationLogging.h"
|
||||
|
||||
AnimRandomSwitch::AnimRandomSwitch(const QString& id) :
|
||||
AnimNode(AnimNode::Type::RandomSwitchStateMachine, id) {
|
||||
|
||||
}
|
||||
|
||||
AnimRandomSwitch::~AnimRandomSwitch() {
|
||||
|
||||
}
|
||||
|
||||
const AnimPoseVec& AnimRandomSwitch::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) {
|
||||
float parentDebugAlpha = context.getDebugAlpha(_id);
|
||||
|
||||
AnimRandomSwitch::RandomSwitchState::Pointer desiredState = _currentState;
|
||||
if (abs(_randomSwitchEvaluationCount - context.getEvaluationCount()) > 1 || animVars.lookup(_triggerRandomSwitchVar, false)) {
|
||||
|
||||
// get a random number and decide which motion to choose.
|
||||
bool currentStateHasPriority = false;
|
||||
float dice = randFloatInRange(0.0f, 1.0f);
|
||||
float lowerBound = 0.0f;
|
||||
for (const RandomSwitchState::Pointer& randState : _randomStates) {
|
||||
if (randState->getPriority() > 0.0f) {
|
||||
float upperBound = lowerBound + (randState->getPriority() / _totalPriorities);
|
||||
if ((dice > lowerBound) && (dice < upperBound)) {
|
||||
desiredState = randState;
|
||||
}
|
||||
lowerBound = upperBound;
|
||||
|
||||
// this indicates if the curent state is one that can be selected randomly, or is one that was transitioned to by the random duration timer.
|
||||
currentStateHasPriority = currentStateHasPriority || (_currentState == randState);
|
||||
}
|
||||
}
|
||||
if (abs(_randomSwitchEvaluationCount - context.getEvaluationCount()) > 1) {
|
||||
_duringInterp = false;
|
||||
switchRandomState(animVars, context, desiredState, _duringInterp);
|
||||
} else {
|
||||
// firing a random switch, be sure that we aren't completing a previously triggered transition
|
||||
if (currentStateHasPriority) {
|
||||
if (desiredState->getID() != _currentState->getID()) {
|
||||
_duringInterp = true;
|
||||
switchRandomState(animVars, context, desiredState, _duringInterp);
|
||||
} else {
|
||||
_duringInterp = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
_triggerTime = randFloatInRange(_triggerTimeMin, _triggerTimeMax);
|
||||
_randomSwitchTime = randFloatInRange(_randomSwitchTimeMin, _randomSwitchTimeMax);
|
||||
|
||||
} else {
|
||||
|
||||
// here we are checking to see if we want a temporary movement
|
||||
// evaluate currentState transitions
|
||||
auto transitionState = evaluateTransitions(animVars);
|
||||
if (transitionState != _currentState) {
|
||||
_duringInterp = true;
|
||||
switchRandomState(animVars, context, transitionState, _duringInterp);
|
||||
_triggerTime = randFloatInRange(_triggerTimeMin, _triggerTimeMax);
|
||||
_randomSwitchTime = randFloatInRange(_randomSwitchTimeMin, _randomSwitchTimeMax);
|
||||
}
|
||||
}
|
||||
|
||||
_triggerTime -= dt;
|
||||
if ((_triggerTime < 0.0f) && (_triggerTimeMin > 0.0f) && (_triggerTimeMax > 0.0f)) {
|
||||
_triggerTime = randFloatInRange(_triggerTimeMin, _triggerTimeMax);
|
||||
triggersOut.setTrigger(_transitionVar);
|
||||
}
|
||||
|
||||
_randomSwitchTime -= dt;
|
||||
if ((_randomSwitchTime < 0.0f) && (_randomSwitchTimeMin > 0.0f) && (_randomSwitchTimeMax > 0.0f)) {
|
||||
_randomSwitchTime = randFloatInRange(_randomSwitchTimeMin, _randomSwitchTimeMax);
|
||||
// restart the trigger timer if it is also enabled
|
||||
_triggerTime = randFloatInRange(_triggerTimeMin, _triggerTimeMax);
|
||||
triggersOut.setTrigger(_triggerRandomSwitchVar);
|
||||
}
|
||||
|
||||
assert(_currentState);
|
||||
auto currentStateNode = _children[_currentState->getChildIndex()];
|
||||
assert(currentStateNode);
|
||||
|
||||
if (_duringInterp) {
|
||||
_alpha += _alphaVel * dt;
|
||||
if (_alpha < 1.0f) {
|
||||
AnimPoseVec* nextPoses = nullptr;
|
||||
AnimPoseVec* prevPoses = nullptr;
|
||||
AnimPoseVec localNextPoses;
|
||||
if (_interpType == InterpType::SnapshotBoth) {
|
||||
// interp between both snapshots
|
||||
prevPoses = &_prevPoses;
|
||||
nextPoses = &_nextPoses;
|
||||
} else if (_interpType == InterpType::SnapshotPrev) {
|
||||
// interp between the prev snapshot and evaluated next target.
|
||||
// this is useful for interping into a blend
|
||||
localNextPoses = currentStateNode->evaluate(animVars, context, dt, triggersOut);
|
||||
prevPoses = &_prevPoses;
|
||||
nextPoses = &localNextPoses;
|
||||
} else {
|
||||
assert(false);
|
||||
}
|
||||
if (_poses.size() > 0 && nextPoses && prevPoses && nextPoses->size() > 0 && prevPoses->size() > 0) {
|
||||
::blend(_poses.size(), &(prevPoses->at(0)), &(nextPoses->at(0)), _alpha, &_poses[0]);
|
||||
}
|
||||
context.setDebugAlpha(_currentState->getID(), _alpha * parentDebugAlpha, _children[_currentState->getChildIndex()]->getType());
|
||||
} else {
|
||||
_duringInterp = false;
|
||||
_prevPoses.clear();
|
||||
_nextPoses.clear();
|
||||
}
|
||||
}
|
||||
|
||||
if (!_duringInterp){
|
||||
context.setDebugAlpha(_currentState->getID(), parentDebugAlpha, _children[_currentState->getChildIndex()]->getType());
|
||||
_poses = currentStateNode->evaluate(animVars, context, dt, triggersOut);
|
||||
}
|
||||
|
||||
_randomSwitchEvaluationCount = context.getEvaluationCount();
|
||||
processOutputJoints(triggersOut);
|
||||
|
||||
context.addStateMachineInfo(_id, _currentState->getID(), _previousState->getID(), _duringInterp, _alpha);
|
||||
if (_duringInterp) {
|
||||
// hack: add previoius state to debug alpha map, with parens around it's name.
|
||||
context.setDebugAlpha(QString("(%1)").arg(_previousState->getID()), 1.0f - _alpha, AnimNodeType::Clip);
|
||||
}
|
||||
|
||||
return _poses;
|
||||
}
|
||||
|
||||
void AnimRandomSwitch::setCurrentState(RandomSwitchState::Pointer randomState) {
|
||||
_previousState = _currentState ? _currentState : randomState;
|
||||
_currentState = randomState;
|
||||
}
|
||||
|
||||
void AnimRandomSwitch::addState(RandomSwitchState::Pointer randomState) {
|
||||
_randomStates.push_back(randomState);
|
||||
}
|
||||
|
||||
void AnimRandomSwitch::switchRandomState(const AnimVariantMap& animVars, const AnimContext& context, RandomSwitchState::Pointer desiredState, bool shouldInterp) {
|
||||
|
||||
auto nextStateNode = _children[desiredState->getChildIndex()];
|
||||
if (shouldInterp) {
|
||||
|
||||
const float FRAMES_PER_SECOND = 30.0f;
|
||||
|
||||
auto prevStateNode = _children[_currentState->getChildIndex()];
|
||||
|
||||
_alpha = 0.0f;
|
||||
float duration = std::max(0.001f, animVars.lookup(desiredState->_interpDurationVar, desiredState->_interpDuration));
|
||||
_alphaVel = FRAMES_PER_SECOND / duration;
|
||||
_interpType = (InterpType)animVars.lookup(desiredState->_interpTypeVar, (int)desiredState->_interpType);
|
||||
|
||||
// because dt is 0, we should not encounter any triggers
|
||||
const float dt = 0.0f;
|
||||
AnimVariantMap triggers;
|
||||
|
||||
if (_interpType == InterpType::SnapshotBoth) {
|
||||
// snapshot previous pose.
|
||||
_prevPoses = _poses;
|
||||
// snapshot next pose at the target frame.
|
||||
if (!desiredState->getResume()) {
|
||||
nextStateNode->setCurrentFrame(desiredState->_interpTarget);
|
||||
}
|
||||
_nextPoses = nextStateNode->evaluate(animVars, context, dt, triggers);
|
||||
} else if (_interpType == InterpType::SnapshotPrev) {
|
||||
// snapshot previoius pose
|
||||
_prevPoses = _poses;
|
||||
// no need to evaluate _nextPoses we will do it dynamically during the interp,
|
||||
// however we need to set the current frame.
|
||||
if (!desiredState->getResume()) {
|
||||
nextStateNode->setCurrentFrame(desiredState->_interpTarget - duration);
|
||||
}
|
||||
} else {
|
||||
assert(false);
|
||||
}
|
||||
} else {
|
||||
if (!desiredState->getResume()) {
|
||||
nextStateNode->setCurrentFrame(desiredState->_interpTarget);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef WANT_DEBUG
|
||||
qCDebug(animation) << "AnimRandomSwitch::switchState:" << _currentState->getID() << "->" << desiredState->getID() << "duration =" << duration << "targetFrame =" << desiredState->_interpTarget << "interpType = " << (int)_interpType;
|
||||
#endif
|
||||
|
||||
setCurrentState(desiredState);
|
||||
}
|
||||
|
||||
AnimRandomSwitch::RandomSwitchState::Pointer AnimRandomSwitch::evaluateTransitions(const AnimVariantMap& animVars) const {
|
||||
assert(_currentState);
|
||||
for (auto& transition : _currentState->_transitions) {
|
||||
if (animVars.lookup(transition._var, false)) {
|
||||
return transition._randomSwitchState;
|
||||
}
|
||||
}
|
||||
return _currentState;
|
||||
}
|
||||
|
||||
const AnimPoseVec& AnimRandomSwitch::getPosesInternal() const {
|
||||
return _poses;
|
||||
}
|
184
libraries/animation/src/AnimRandomSwitch.h
Normal file
184
libraries/animation/src/AnimRandomSwitch.h
Normal file
|
@ -0,0 +1,184 @@
|
|||
//
|
||||
// AnimRandomSwitch.h
|
||||
//
|
||||
// Created by Angus Antley on 4/8/19.
|
||||
// Copyright (c) 2019 High Fidelity, Inc. All rights reserved.
|
||||
//
|
||||
// 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_AnimRandomSwitch_h
|
||||
#define hifi_AnimRandomSwitch_h
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "AnimNode.h"
|
||||
|
||||
// Random Switch State Machine for random transitioning between children AnimNodes
|
||||
//
|
||||
// This is mechanisim for choosing and playing a random animation and smoothly interpolating/fading
|
||||
// between them. A RandomSwitch has a set of States, which typically reference
|
||||
// child AnimNodes. Each Random Switch State has a list of Transitions, which are evaluated
|
||||
// to determine when we should switch to a new State. Parameters for the smooth
|
||||
// interpolation/fading are read from the Random Switch State that you are transitioning to.
|
||||
//
|
||||
// The currentState can be set directly via the setCurrentStateVar() and will override
|
||||
// any State transitions.
|
||||
//
|
||||
// Each Random Switch State has two parameters that can be changed via AnimVars,
|
||||
// * interpTarget - (frames) The destination frame of the interpolation. i.e. the first frame of the animation that will
|
||||
// visible after interpolation is complete.
|
||||
// * interpDuration - (frames) The total length of time it will take to interp between the current pose and the
|
||||
// interpTarget frame.
|
||||
// * interpType - How the interpolation is performed.
|
||||
// * priority - this number represents how likely this Random Switch State will be chosen.
|
||||
// the priority for each Random Switch State will be normalized, so their relative size is what is important
|
||||
// * resume - if resume is false then if this state is chosen twice in a row it will remember what frame it was playing on.
|
||||
// * SnapshotBoth: Stores two snapshots, the previous animation before interpolation begins and the target state at the
|
||||
// interTarget frame. Then during the interpolation period the two snapshots are interpolated to produce smooth motion between them.
|
||||
// * SnapshotPrev: Stores a snapshot of the previous animation before interpolation begins. However the target state is
|
||||
// evaluated dynamically. During the interpolation period the previous snapshot is interpolated with the target pose
|
||||
// to produce smooth motion between them. This mode is useful for interping into a blended animation where the actual
|
||||
// blend factor is not known at the start of the interp or is might change dramatically during the interp.
|
||||
//
|
||||
|
||||
class AnimRandomSwitch : public AnimNode {
|
||||
public:
|
||||
friend class AnimNodeLoader;
|
||||
friend bool processRandomSwitchStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& nodeId, const QUrl& jsonUrl);
|
||||
|
||||
enum class InterpType {
|
||||
SnapshotBoth = 0,
|
||||
SnapshotPrev,
|
||||
NumTypes
|
||||
};
|
||||
|
||||
protected:
|
||||
|
||||
class RandomSwitchState {
|
||||
public:
|
||||
friend AnimRandomSwitch;
|
||||
friend bool processRandomSwitchStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& nodeId, const QUrl& jsonUrl);
|
||||
|
||||
using Pointer = std::shared_ptr<RandomSwitchState>;
|
||||
using ConstPointer = std::shared_ptr<const RandomSwitchState>;
|
||||
|
||||
class Transition {
|
||||
public:
|
||||
friend AnimRandomSwitch;
|
||||
Transition(const QString& var, RandomSwitchState::Pointer randomState) : _var(var), _randomSwitchState(randomState) {}
|
||||
protected:
|
||||
QString _var;
|
||||
RandomSwitchState::Pointer _randomSwitchState;
|
||||
};
|
||||
|
||||
RandomSwitchState(const QString& id, int childIndex, float interpTarget, float interpDuration, InterpType interpType, float priority, bool resume) :
|
||||
_id(id),
|
||||
_childIndex(childIndex),
|
||||
_interpTarget(interpTarget),
|
||||
_interpDuration(interpDuration),
|
||||
_interpType(interpType),
|
||||
_priority(priority),
|
||||
_resume(resume){
|
||||
}
|
||||
|
||||
void setInterpTargetVar(const QString& interpTargetVar) { _interpTargetVar = interpTargetVar; }
|
||||
void setInterpDurationVar(const QString& interpDurationVar) { _interpDurationVar = interpDurationVar; }
|
||||
void setInterpTypeVar(const QString& interpTypeVar) { _interpTypeVar = interpTypeVar; }
|
||||
|
||||
int getChildIndex() const { return _childIndex; }
|
||||
float getPriority() const { return _priority; }
|
||||
bool getResume() const { return _resume; }
|
||||
const QString& getID() const { return _id; }
|
||||
|
||||
protected:
|
||||
|
||||
void setInterpTarget(float interpTarget) { _interpTarget = interpTarget; }
|
||||
void setInterpDuration(float interpDuration) { _interpDuration = interpDuration; }
|
||||
void setPriority(float priority) { _priority = priority; }
|
||||
void setResumeFlag(bool resume) { _resume = resume; }
|
||||
|
||||
void addTransition(const Transition& transition) { _transitions.push_back(transition); }
|
||||
|
||||
QString _id;
|
||||
int _childIndex;
|
||||
float _interpTarget; // frames
|
||||
float _interpDuration; // frames
|
||||
InterpType _interpType;
|
||||
float _priority {0.0f};
|
||||
bool _resume {false};
|
||||
|
||||
QString _interpTargetVar;
|
||||
QString _interpDurationVar;
|
||||
QString _interpTypeVar;
|
||||
|
||||
std::vector<Transition> _transitions;
|
||||
|
||||
private:
|
||||
// no copies
|
||||
RandomSwitchState(const RandomSwitchState&) = delete;
|
||||
RandomSwitchState& operator=(const RandomSwitchState&) = delete;
|
||||
};
|
||||
|
||||
public:
|
||||
|
||||
explicit AnimRandomSwitch(const QString& id);
|
||||
virtual ~AnimRandomSwitch() override;
|
||||
|
||||
virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) override;
|
||||
|
||||
void setCurrentStateVar(QString& currentStateVar) { _currentStateVar = currentStateVar; }
|
||||
|
||||
protected:
|
||||
|
||||
void setCurrentState(RandomSwitchState::Pointer randomState);
|
||||
void setTriggerRandomSwitchVar(const QString& triggerRandomSwitchVar) { _triggerRandomSwitchVar = triggerRandomSwitchVar; }
|
||||
void setRandomSwitchTimeMin(float randomSwitchTimeMin) { _randomSwitchTimeMin = randomSwitchTimeMin; }
|
||||
void setRandomSwitchTimeMax(float randomSwitchTimeMax) { _randomSwitchTimeMax = randomSwitchTimeMax; }
|
||||
void setTransitionVar(const QString& transitionVar) { _transitionVar = transitionVar; }
|
||||
void setTriggerTimeMin(float triggerTimeMin) { _triggerTimeMin = triggerTimeMin; }
|
||||
void setTriggerTimeMax(float triggerTimeMax) { _triggerTimeMax = triggerTimeMax; }
|
||||
void addToPrioritySum(float priority) { _totalPriorities += priority; }
|
||||
|
||||
void addState(RandomSwitchState::Pointer randomState);
|
||||
|
||||
void switchRandomState(const AnimVariantMap& animVars, const AnimContext& context, RandomSwitchState::Pointer desiredState, bool shouldInterp);
|
||||
RandomSwitchState::Pointer evaluateTransitions(const AnimVariantMap& animVars) const;
|
||||
|
||||
// for AnimDebugDraw rendering
|
||||
virtual const AnimPoseVec& getPosesInternal() const override;
|
||||
|
||||
AnimPoseVec _poses;
|
||||
|
||||
int _randomSwitchEvaluationCount { 0 };
|
||||
// interpolation state
|
||||
bool _duringInterp = false;
|
||||
InterpType _interpType{ InterpType::SnapshotPrev };
|
||||
float _alphaVel = 0.0f;
|
||||
float _alpha = 0.0f;
|
||||
AnimPoseVec _prevPoses;
|
||||
AnimPoseVec _nextPoses;
|
||||
float _totalPriorities { 0.0f };
|
||||
|
||||
RandomSwitchState::Pointer _currentState;
|
||||
RandomSwitchState::Pointer _previousState;
|
||||
std::vector<RandomSwitchState::Pointer> _randomStates;
|
||||
|
||||
QString _currentStateVar;
|
||||
QString _triggerRandomSwitchVar;
|
||||
QString _transitionVar;
|
||||
float _triggerTimeMin { 10.0f };
|
||||
float _triggerTimeMax { 20.0f };
|
||||
float _triggerTime { 0.0f };
|
||||
float _randomSwitchTimeMin { 10.0f };
|
||||
float _randomSwitchTimeMax { 20.0f };
|
||||
float _randomSwitchTime { 0.0f };
|
||||
|
||||
private:
|
||||
// no copies
|
||||
AnimRandomSwitch(const AnimRandomSwitch&) = delete;
|
||||
AnimRandomSwitch& operator=(const AnimRandomSwitch&) = delete;
|
||||
};
|
||||
|
||||
#endif // hifi_AnimRandomSwitch_h
|
|
@ -261,7 +261,7 @@ public:
|
|||
qCDebug(animation) << " " << pair.first << "=" << pair.second.getString();
|
||||
break;
|
||||
default:
|
||||
assert(("invalid AnimVariant::Type", false));
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,8 +26,9 @@ public:
|
|||
* <tr><td><code>0</code></td><td>RotationAndPosition</td><td>Attempt to reach the rotation and position end
|
||||
* effector.</td></tr>
|
||||
* <tr><td><code>1</code></td><td>RotationOnly</td><td>Attempt to reach the end effector rotation only.</td></tr>
|
||||
* <tr><td><code>2</code></td><td>HmdHead</td><td><strong>Deprecated:</strong> A special mode of IK that would attempt
|
||||
* to prevent unnecessary bending of the spine.</td></tr>
|
||||
* <tr><td><code>2</code></td><td>HmdHead</td><td>A special mode of IK that would attempt to prevent unnecessary
|
||||
* bending of the spine.<br />
|
||||
* <p class="important">Deprecated: This target type is deprecated and will be removed.</p></td></tr>
|
||||
* <tr><td><code>3</code></td><td>HipsRelativeRotationAndPosition</td><td>Attempt to reach a rotation and position end
|
||||
* effector that is not in absolute rig coordinates but is offset by the avatar hips translation.</td></tr>
|
||||
* <tr><td><code>4</code></td><td>Spline</td><td>Use a cubic Hermite spline to model the human spine. This prevents
|
||||
|
|
|
@ -1480,13 +1480,15 @@ void Rig::updateAnimations(float deltaTime, const glm::mat4& rootTransform, cons
|
|||
if (_animNode && _enabledAnimations) {
|
||||
DETAILED_PERFORMANCE_TIMER("handleTriggers");
|
||||
|
||||
++_evaluationCount;
|
||||
|
||||
updateAnimationStateHandlers();
|
||||
_animVars.setRigToGeometryTransform(_rigToGeometryTransform);
|
||||
if (_networkNode) {
|
||||
_networkVars.setRigToGeometryTransform(_rigToGeometryTransform);
|
||||
}
|
||||
AnimContext context(_enableDebugDrawIKTargets, _enableDebugDrawIKConstraints, _enableDebugDrawIKChains,
|
||||
getGeometryToRigTransform(), rigToWorldTransform);
|
||||
getGeometryToRigTransform(), rigToWorldTransform, _evaluationCount);
|
||||
|
||||
// evaluate the animation
|
||||
AnimVariantMap triggersOut;
|
||||
|
@ -2009,8 +2011,35 @@ void Rig::updateFromControllerParameters(const ControllerParameters& params, flo
|
|||
return;
|
||||
}
|
||||
|
||||
_animVars.set("isTalking", params.isTalking);
|
||||
_animVars.set("notIsTalking", !params.isTalking);
|
||||
if (_previousIsTalking != params.isTalking) {
|
||||
if (_talkIdleInterpTime < 1.0f) {
|
||||
_talkIdleInterpTime = 1.0f - _talkIdleInterpTime;
|
||||
} else {
|
||||
_talkIdleInterpTime = 0.0f;
|
||||
}
|
||||
}
|
||||
_previousIsTalking = params.isTalking;
|
||||
|
||||
const float TOTAL_EASE_IN_TIME = 0.75f;
|
||||
const float TOTAL_EASE_OUT_TIME = 1.5f;
|
||||
if (params.isTalking) {
|
||||
if (_talkIdleInterpTime < 1.0f) {
|
||||
_talkIdleInterpTime += dt / TOTAL_EASE_IN_TIME;
|
||||
float easeOutInValue = _talkIdleInterpTime < 0.5f ? 4.0f * powf(_talkIdleInterpTime, 3.0f) : 4.0f * powf((_talkIdleInterpTime - 1.0f), 3.0f) + 1.0f;
|
||||
_animVars.set("idleOverlayAlpha", easeOutInValue);
|
||||
} else {
|
||||
_animVars.set("idleOverlayAlpha", 1.0f);
|
||||
}
|
||||
} else {
|
||||
if (_talkIdleInterpTime < 1.0f) {
|
||||
_talkIdleInterpTime += dt / TOTAL_EASE_OUT_TIME;
|
||||
float easeOutInValue = _talkIdleInterpTime < 0.5f ? 4.0f * powf(_talkIdleInterpTime, 3.0f) : 4.0f * powf((_talkIdleInterpTime - 1.0f), 3.0f) + 1.0f;
|
||||
float talkAlpha = 1.0f - easeOutInValue;
|
||||
_animVars.set("idleOverlayAlpha", talkAlpha);
|
||||
} else {
|
||||
_animVars.set("idleOverlayAlpha", 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
_headEnabled = params.primaryControllerFlags[PrimaryControllerType_Head] & (uint8_t)ControllerFlags::Enabled;
|
||||
bool leftHandEnabled = params.primaryControllerFlags[PrimaryControllerType_LeftHand] & (uint8_t)ControllerFlags::Enabled;
|
||||
|
|
|
@ -418,9 +418,12 @@ protected:
|
|||
HandAnimState _rightHandAnimState;
|
||||
HandAnimState _leftHandAnimState;
|
||||
std::map<QString, RoleAnimState> _roleAnimStates;
|
||||
int _evaluationCount{ 0 };
|
||||
|
||||
float _leftHandOverlayAlpha { 0.0f };
|
||||
float _rightHandOverlayAlpha { 0.0f };
|
||||
float _talkIdleInterpTime { 0.0f };
|
||||
bool _previousIsTalking { false };
|
||||
|
||||
SimpleMovingAverage _averageForwardSpeed { 10 };
|
||||
SimpleMovingAverage _averageLateralSpeed { 10 };
|
||||
|
|
|
@ -48,21 +48,23 @@ QScriptValue injectorOptionsToScriptValue(QScriptEngine* engine, const AudioInje
|
|||
}
|
||||
|
||||
/**jsdoc
|
||||
* Configures how an audio injector plays its audio.
|
||||
* Configures where and how an audio injector plays its audio.
|
||||
* @typedef {object} AudioInjector.AudioInjectorOptions
|
||||
* @property {Vec3} position=Vec3.ZERO - The position in the domain to play the sound.
|
||||
* @property {Quat} orientation=Quat.IDENTITY - The orientation in the domain to play the sound in.
|
||||
* @property {number} volume=1.0 - Playback volume, between <code>0.0</code> and <code>1.0</code>.
|
||||
* @property {number} pitch=1.0 - Alter the pitch of the sound, within +/- 2 octaves. The value is the relative sample rate to
|
||||
* resample the sound at, range <code>0.0625</code> – <code>16.0</code>. A value of <code>0.0625</code> lowers the
|
||||
* pitch by 2 octaves; <code>1.0</code> is no change in pitch; <code>16.0</code> raises the pitch by 2 octaves.
|
||||
* resample the sound at, range <code>0.0625</code> – <code>16.0</code>.<br />
|
||||
* A value of <code>0.0625</code> lowers the pitch by 2 octaves.<br />
|
||||
* A value of <code>1.0</code> means there is no change in pitch.<br />
|
||||
* A value of <code>16.0</code> raises the pitch by 2 octaves.
|
||||
* @property {boolean} loop=false - If <code>true</code>, the sound is played repeatedly until playback is stopped.
|
||||
* @property {number} secondOffset=0 - Starts playback from a specified time (seconds) within the sound file, ≥
|
||||
* <code>0</code>.
|
||||
* @property {boolean} localOnly=false - IF <code>true</code>, the sound is played back locally on the client rather than to
|
||||
* @property {boolean} localOnly=false - If <code>true</code>, the sound is played back locally on the client rather than to
|
||||
* others via the audio mixer.
|
||||
* @property {boolean} ignorePenumbra=false - <strong>Deprecated:</strong> This property is deprecated and will be
|
||||
* removed.
|
||||
* @property {boolean} ignorePenumbra=false - <p class="important">Deprecated: This property is deprecated and will be
|
||||
* removed.</p>
|
||||
*/
|
||||
void injectorOptionsFromScriptValue(const QScriptValue& object, AudioInjectorOptions& injectorOptions) {
|
||||
if (!object.isObject()) {
|
||||
|
|
|
@ -124,7 +124,7 @@ typedef QSharedPointer<Sound> SharedSoundPointer;
|
|||
* An audio resource, created by {@link SoundCache.getSound}, to be played back using {@link Audio.playSound}.
|
||||
* <p>Supported formats:</p>
|
||||
* <ul>
|
||||
* <li>WAV: 16-bit uncompressed WAV at any sample rate, with 1 (mono), 2 (stereo), or 4 (ambisonic) channels.</li>
|
||||
* <li>WAV: 16-bit uncompressed at any sample rate, with 1 (mono), 2 (stereo), or 4 (ambisonic) channels.</li>
|
||||
* <li>MP3: Mono or stereo, at any sample rate.</li>
|
||||
* <li>RAW: 48khz 16-bit mono or stereo. File name must include <code>".stereo"</code> to be interpreted as stereo.</li>
|
||||
* </ul>
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue