mirror of
https://github.com/overte-org/overte.git
synced 2025-04-22 18:34:48 +02:00
Fixing th emerge conflict
This commit is contained in:
commit
d08e2e5ca1
128 changed files with 3551 additions and 1262 deletions
assignment-client/src
assets
avatars
octree
domain-server/src
interface
resources
avatar
animations
idleWS.fbxidleWS_all.fbxidle_LFF_all.fbxidle_RFF_all.fbxidle_lookaround01.fbxidle_once_armstretch.fbxidle_once_bigstretch.fbxidle_once_checkwatch.fbxidle_once_headtilt.fbxidle_once_lookaround.fbxidle_once_neckstretch.fbxidle_once_slownod.fbxtalk04.fbxtalk_righthand.fbx
avatar-animation.jsonqml
src
libraries
animation/src
AnimContext.cppAnimContext.hAnimNode.hAnimNodeLoader.cppAnimRandomSwitch.cppAnimRandomSwitch.hAnimVariant.hIKTarget.hRig.cppRig.h
audio/src
avatars-renderer/src/avatars-renderer
avatars/src
baking/src
controllers/src/controllers
display-plugins/src/display-plugins
entities/src
fbx/src
gpu/src/gpu
image/src/image
material-networking/src/material-networking
|
@ -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);
|
||||
|
||||
|
|
|
@ -179,7 +179,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);
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
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"
|
||||
|
||||
|
||||
|
||||
|
@ -820,7 +822,7 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) {
|
|||
audioDLLPath += "/audioWin7";
|
||||
}
|
||||
QCoreApplication::addLibraryPath(audioDLLPath);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
DependencyManager::registerInheritance<LimitedNodeList, NodeList>();
|
||||
DependencyManager::registerInheritance<AvatarHashMap, AvatarManager>();
|
||||
|
@ -1813,6 +1815,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
|
||||
|
@ -2331,7 +2335,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;
|
||||
|
@ -2359,7 +2363,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);
|
||||
|
@ -2399,7 +2403,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
|
||||
|
@ -2437,6 +2441,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();
|
||||
|
||||
|
@ -2613,6 +2625,8 @@ void Application::onAboutToQuit() {
|
|||
_aboutToQuit = true;
|
||||
|
||||
cleanupBeforeQuit();
|
||||
|
||||
getRefreshRateManager().setRefreshRateRegime(RefreshRateManager::RefreshRateRegime::SHUTDOWN);
|
||||
}
|
||||
|
||||
void Application::cleanupBeforeQuit() {
|
||||
|
@ -3222,6 +3236,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);
|
||||
|
@ -3370,6 +3385,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
|
||||
|
@ -4041,6 +4057,9 @@ bool Application::event(QEvent* event) {
|
|||
case QEvent::KeyRelease:
|
||||
keyReleaseEvent(static_cast<QKeyEvent*>(event));
|
||||
return true;
|
||||
case QEvent::FocusIn:
|
||||
focusInEvent(static_cast<QFocusEvent*>(event));
|
||||
return true;
|
||||
case QEvent::FocusOut:
|
||||
focusOutEvent(static_cast<QFocusEvent*>(event));
|
||||
return true;
|
||||
|
@ -4102,6 +4121,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;
|
||||
}
|
||||
|
||||
|
@ -4388,6 +4413,13 @@ void Application::keyReleaseEvent(QKeyEvent* event) {
|
|||
|
||||
}
|
||||
|
||||
void Application::focusInEvent(QFocusEvent* event) {
|
||||
if (!_aboutToQuit && _startUpFinished) {
|
||||
getRefreshRateManager().setRefreshRateRegime(RefreshRateManager::RefreshRateRegime::RUNNING);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Application::focusOutEvent(QFocusEvent* event) {
|
||||
auto inputPlugins = PluginManager::getInstance()->getInputPlugins();
|
||||
foreach(auto inputPlugin, inputPlugins) {
|
||||
|
@ -4396,6 +4428,9 @@ void Application::focusOutEvent(QFocusEvent* event) {
|
|||
}
|
||||
}
|
||||
|
||||
if (!_aboutToQuit && _startUpFinished) {
|
||||
getRefreshRateManager().setRefreshRateRegime(RefreshRateManager::RefreshRateRegime::UNFOCUS);
|
||||
}
|
||||
// FIXME spacemouse code still needs cleanup
|
||||
#if 0
|
||||
//SpacemouseDevice::getInstance().focusOutEvent();
|
||||
|
@ -5563,6 +5598,8 @@ void Application::resumeAfterLoginDialogActionTaken() {
|
|||
menu->getMenu("Developer")->setVisible(_developerMenuVisible);
|
||||
_myCamera.setMode(_previousCameraMode);
|
||||
cameraModeChanged();
|
||||
_startUpFinished = true;
|
||||
getRefreshRateManager().setRefreshRateRegime(RefreshRateManager::RefreshRateRegime::RUNNING);
|
||||
}
|
||||
|
||||
void Application::loadAvatarScripts(const QVector<QString>& urls) {
|
||||
|
@ -7345,6 +7382,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());
|
||||
|
||||
|
@ -8262,19 +8300,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(),
|
||||
|
@ -8758,6 +8783,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) {
|
||||
|
@ -8847,6 +8873,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::HMD :
|
||||
RefreshRateManager::UXMode::DESKTOP;
|
||||
|
||||
refreshRateManager.setUXMode(uxMode);
|
||||
}
|
||||
|
||||
bool isHmd = _displayPlugin->isHmd();
|
||||
|
@ -9156,7 +9190,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));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -9273,6 +9307,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(); }
|
||||
|
@ -349,6 +351,8 @@ public:
|
|||
void addSnapshotOperator(const SnapshotOperator& snapshotOperator);
|
||||
bool takeSnapshotOperators(std::queue<SnapshotOperator>& snapshotOperators);
|
||||
|
||||
void openDirectory(const QString& path);
|
||||
|
||||
signals:
|
||||
void svoImportRequested(const QString& url);
|
||||
|
||||
|
@ -408,8 +412,6 @@ public slots:
|
|||
|
||||
static void packageModel();
|
||||
|
||||
void openUrl(const QUrl& url) const;
|
||||
|
||||
void resetSensors(bool andReload = false);
|
||||
void setActiveFaceTracker() const;
|
||||
|
||||
|
@ -476,6 +478,9 @@ public slots:
|
|||
|
||||
QString getGraphicsCardType();
|
||||
|
||||
bool gpuTextureMemSizeStable();
|
||||
void showUrlHandler(const QUrl& url);
|
||||
|
||||
private slots:
|
||||
void onDesktopRootItemCreated(QQuickItem* qmlContext);
|
||||
void onDesktopRootContextCreated(QQmlContext* qmlContext);
|
||||
|
@ -570,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);
|
||||
|
||||
|
@ -721,6 +725,7 @@ private:
|
|||
QUuid _loginDialogID;
|
||||
QUuid _avatarInputsBarID;
|
||||
LoginStateManager _loginStateManager;
|
||||
RefreshRateManager _refreshRateManager;
|
||||
|
||||
quint64 _lastFaceTrackerUpdate;
|
||||
|
||||
|
@ -818,5 +823,6 @@ private:
|
|||
|
||||
bool _resumeAfterLoginDialogActionTaken_WasPostponed { false };
|
||||
bool _resumeAfterLoginDialogActionTaken_SafeToRun { false };
|
||||
bool _startUpFinished { false };
|
||||
};
|
||||
#endif // hifi_Application_h
|
||||
|
|
|
@ -420,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));
|
||||
|
||||
|
@ -613,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";
|
||||
|
|
149
interface/src/RefreshRateManager.cpp
Normal file
149
interface/src/RefreshRateManager.cpp
Normal file
|
@ -0,0 +1,149 @@
|
|||
//
|
||||
// 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 HMD_TARGET_RATE = 90;
|
||||
|
||||
static const std::array<std::string, RefreshRateManager::RefreshRateProfile::PROFILE_NUM> REFRESH_RATE_PROFILE_TO_STRING =
|
||||
{ { "Eco", "Interactive", "Realtime" } };
|
||||
|
||||
static const std::array<std::string, RefreshRateManager::RefreshRateRegime::REGIME_NUM> REFRESH_RATE_REGIME_TO_STRING =
|
||||
{ { "Running", "Unfocus", "Minimized", "StartUp", "ShutDown" } };
|
||||
|
||||
static const std::array<std::string, RefreshRateManager::UXMode::UX_NUM> UX_MODE_TO_STRING =
|
||||
{ { "Desktop", "HMD" } };
|
||||
|
||||
static const std::map<std::string, RefreshRateManager::RefreshRateProfile> REFRESH_RATE_PROFILE_FROM_STRING =
|
||||
{ { "Eco", RefreshRateManager::RefreshRateProfile::ECO },
|
||||
{ "Interactive", RefreshRateManager::RefreshRateProfile::INTERACTIVE },
|
||||
{ "Realtime", RefreshRateManager::RefreshRateProfile::REALTIME } };
|
||||
|
||||
static const std::array<int, RefreshRateManager::RefreshRateProfile::PROFILE_NUM> RUNNING_REGIME_PROFILES =
|
||||
{ { 5, 20, 60 } };
|
||||
|
||||
static const std::array<int, RefreshRateManager::RefreshRateProfile::PROFILE_NUM> UNFOCUS_REGIME_PROFILES =
|
||||
{ { 5, 5, 10 } };
|
||||
|
||||
static const std::array<int, RefreshRateManager::RefreshRateProfile::PROFILE_NUM> MINIMIZED_REGIME_PROFILE =
|
||||
{ { 2, 2, 2 } };
|
||||
|
||||
static const std::array<int, RefreshRateManager::RefreshRateProfile::PROFILE_NUM> START_AND_SHUTDOWN_REGIME_PROFILES =
|
||||
{ { 30, 30, 30 } };
|
||||
|
||||
static const std::array<std::array<int, RefreshRateManager::RefreshRateProfile::PROFILE_NUM>, RefreshRateManager::RefreshRateRegime::REGIME_NUM> REFRESH_RATE_REGIMES =
|
||||
{ { RUNNING_REGIME_PROFILES, UNFOCUS_REGIME_PROFILES, MINIMIZED_REGIME_PROFILE,
|
||||
START_AND_SHUTDOWN_REGIME_PROFILES, START_AND_SHUTDOWN_REGIME_PROFILES } };
|
||||
|
||||
|
||||
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) _refreshRateMode.get();
|
||||
}
|
||||
|
||||
void RefreshRateManager::setRefreshRateProfile(RefreshRateManager::RefreshRateProfile refreshRateProfile) {
|
||||
if (_refreshRateProfile != refreshRateProfile) {
|
||||
_refreshRateModeLock.withWriteLock([&] {
|
||||
_refreshRateProfile = refreshRateProfile;
|
||||
_refreshRateMode.set((int) refreshRateProfile);
|
||||
});
|
||||
updateRefreshRateController();
|
||||
}
|
||||
}
|
||||
|
||||
RefreshRateManager::RefreshRateProfile RefreshRateManager::getRefreshRateProfile() const {
|
||||
RefreshRateManager::RefreshRateProfile profile = RefreshRateManager::RefreshRateProfile::REALTIME;
|
||||
|
||||
if (getUXMode() != RefreshRateManager::UXMode::HMD) {
|
||||
profile =(RefreshRateManager::RefreshRateProfile) _refreshRateModeLock.resultWithReadLock<int>([&] {
|
||||
return _refreshRateMode.get();
|
||||
});
|
||||
}
|
||||
|
||||
return profile;
|
||||
}
|
||||
|
||||
RefreshRateManager::RefreshRateRegime RefreshRateManager::getRefreshRateRegime() const {
|
||||
return getUXMode() == RefreshRateManager::UXMode::HMD ? RefreshRateManager::RefreshRateRegime::RUNNING :
|
||||
_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) {
|
||||
if (_refreshRateRegime == RefreshRateManager::RefreshRateRegime::RUNNING &&
|
||||
_refreshRateProfile == RefreshRateManager::RefreshRateProfile::INTERACTIVE) {
|
||||
targetRefreshRate = getInteractiveRefreshRate();
|
||||
} else {
|
||||
targetRefreshRate = REFRESH_RATE_REGIMES[_refreshRateRegime][_refreshRateProfile];
|
||||
}
|
||||
} else {
|
||||
targetRefreshRate = HMD_TARGET_RATE;
|
||||
}
|
||||
|
||||
_refreshRateOperator(targetRefreshRate);
|
||||
_activeRefreshRate = targetRefreshRate;
|
||||
}
|
||||
}
|
||||
|
||||
void RefreshRateManager::setInteractiveRefreshRate(int refreshRate) {
|
||||
_refreshRateLock.withWriteLock([&] {
|
||||
_interactiveRefreshRate.set(refreshRate);
|
||||
});
|
||||
updateRefreshRateController();
|
||||
}
|
||||
|
||||
|
||||
int RefreshRateManager::getInteractiveRefreshRate() const {
|
||||
return _refreshRateLock.resultWithReadLock<int>([&] {
|
||||
return _interactiveRefreshRate.get();
|
||||
});
|
||||
}
|
83
interface/src/RefreshRateManager.h
Normal file
83
interface/src/RefreshRateManager.h
Normal file
|
@ -0,0 +1,83 @@
|
|||
//
|
||||
// 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 <SettingHandle.h>
|
||||
#include <shared/ReadWriteLockable.h>
|
||||
|
||||
class RefreshRateManager {
|
||||
public:
|
||||
enum RefreshRateProfile {
|
||||
ECO = 0,
|
||||
INTERACTIVE,
|
||||
REALTIME,
|
||||
PROFILE_NUM
|
||||
};
|
||||
|
||||
enum RefreshRateRegime {
|
||||
RUNNING = 0,
|
||||
UNFOCUS,
|
||||
MINIMIZED,
|
||||
STARTUP,
|
||||
SHUTDOWN,
|
||||
REGIME_NUM
|
||||
};
|
||||
|
||||
enum UXMode {
|
||||
DESKTOP = 0,
|
||||
HMD,
|
||||
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 setInteractiveRefreshRate(int refreshRate);
|
||||
int getInteractiveRefreshRate() const;
|
||||
|
||||
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 ReadWriteLockable _refreshRateLock;
|
||||
mutable ReadWriteLockable _refreshRateModeLock;
|
||||
|
||||
mutable int _activeRefreshRate { 20 };
|
||||
RefreshRateProfile _refreshRateProfile { RefreshRateProfile::INTERACTIVE};
|
||||
RefreshRateRegime _refreshRateRegime { RefreshRateRegime::STARTUP };
|
||||
UXMode _uxMode;
|
||||
|
||||
Setting::Handle<int> _interactiveRefreshRate { "interactiveRefreshRate", 20};
|
||||
Setting::Handle<int> _refreshRateMode { "refreshRateProfile", INTERACTIVE };
|
||||
|
||||
std::function<void(int)> _refreshRateOperator { nullptr };
|
||||
};
|
||||
|
||||
#endif
|
|
@ -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
|
||||
|
|
|
@ -1041,11 +1041,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.
|
||||
|
@ -1630,7 +1634,9 @@ void MyAvatar::handleChangedAvatarEntityData() {
|
|||
if (!skip) {
|
||||
sanitizeAvatarEntityProperties(properties);
|
||||
entityTree->withWriteLock([&] {
|
||||
entityTree->updateEntity(id, properties);
|
||||
if (entityTree->updateEntity(id, properties)) {
|
||||
packetSender->queueEditAvatarEntityMessage(entityTree, id);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -5811,12 +5817,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
|
||||
|
@ -1557,14 +1558,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 +1911,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 +1920,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 +1930,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 +1939,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 +2285,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();
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -126,7 +126,7 @@ namespace {
|
|||
}
|
||||
|
||||
QFile file(filename);
|
||||
if (!file.open(QIODevice::WriteOnly)) {
|
||||
if (file.open(QIODevice::WriteOnly)) {
|
||||
const char* bio_data;
|
||||
long bio_size = BIO_get_mem_data(bio, &bio_data);
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -284,7 +284,7 @@ public slots:
|
|||
* Disable 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);
|
||||
|
||||
|
@ -293,7 +293,7 @@ public slots:
|
|||
* {@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);
|
||||
|
||||
|
|
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" };
|
||||
{
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -1469,6 +1469,37 @@ QStringList Avatar::getJointNames() const {
|
|||
return result;
|
||||
}
|
||||
|
||||
std::vector<AvatarSkeletonTrait::UnpackedJointData> Avatar::getSkeletonDefaultData() {
|
||||
std::vector<AvatarSkeletonTrait::UnpackedJointData> defaultSkeletonData;
|
||||
if (_skeletonModel->isLoaded()) {
|
||||
auto& model = _skeletonModel->getHFMModel();
|
||||
auto& rig = _skeletonModel->getRig();
|
||||
float geometryToRigScale = extractScale(rig.getGeometryToRigTransform())[0];
|
||||
QStringList jointNames = getJointNames();
|
||||
int sizeCount = 0;
|
||||
for (int i = 0; i < jointNames.size(); i++) {
|
||||
AvatarSkeletonTrait::UnpackedJointData jointData;
|
||||
jointData.jointIndex = i;
|
||||
jointData.parentIndex = rig.getJointParentIndex(i);
|
||||
if (jointData.parentIndex == -1) {
|
||||
jointData.boneType = model.joints[i].isSkeletonJoint ? AvatarSkeletonTrait::BoneType::SkeletonRoot : AvatarSkeletonTrait::BoneType::NonSkeletonRoot;
|
||||
} else {
|
||||
jointData.boneType = model.joints[i].isSkeletonJoint ? AvatarSkeletonTrait::BoneType::SkeletonChild : AvatarSkeletonTrait::BoneType::NonSkeletonChild;
|
||||
}
|
||||
jointData.defaultRotation = rig.getAbsoluteDefaultPose(i).rot();
|
||||
jointData.defaultTranslation = getDefaultJointTranslation(i);
|
||||
float jointLocalScale = extractScale(model.joints[i].transform)[0];
|
||||
jointData.defaultScale = jointLocalScale / geometryToRigScale;
|
||||
jointData.jointName = jointNames[i];
|
||||
jointData.stringLength = jointNames[i].size();
|
||||
jointData.stringStart = sizeCount;
|
||||
sizeCount += jointNames[i].size();
|
||||
defaultSkeletonData.push_back(jointData);
|
||||
}
|
||||
}
|
||||
return defaultSkeletonData;
|
||||
}
|
||||
|
||||
glm::vec3 Avatar::getJointPosition(int index) const {
|
||||
glm::vec3 position;
|
||||
_skeletonModel->getJointPositionInWorldFrame(index, position);
|
||||
|
@ -1535,6 +1566,8 @@ void Avatar::rigReady() {
|
|||
buildSpine2SplineRatioCache();
|
||||
computeMultiSphereShapes();
|
||||
buildSpine2SplineRatioCache();
|
||||
setSkeletonData(getSkeletonDefaultData());
|
||||
sendSkeletonData();
|
||||
}
|
||||
|
||||
// rig has been reset.
|
||||
|
|
|
@ -199,6 +199,8 @@ public:
|
|||
virtual int getJointIndex(const QString& name) const override;
|
||||
virtual QStringList getJointNames() const override;
|
||||
|
||||
std::vector<AvatarSkeletonTrait::UnpackedJointData> getSkeletonDefaultData();
|
||||
|
||||
/**jsdoc
|
||||
* Gets the default rotation of a joint (in the current avatar) relative to its parent.
|
||||
* <p>For information on the joint hierarchy used, see
|
||||
|
|
|
@ -55,7 +55,7 @@ using namespace std;
|
|||
const QString AvatarData::FRAME_NAME = "com.highfidelity.recording.AvatarData";
|
||||
|
||||
static const int TRANSLATION_COMPRESSION_RADIX = 14;
|
||||
static const int FAUX_JOINT_COMPRESSION_RADIX = 12;
|
||||
static const int HAND_CONTROLLER_COMPRESSION_RADIX = 12;
|
||||
static const int SENSOR_TO_WORLD_SCALE_RADIX = 10;
|
||||
static const float AUDIO_LOUDNESS_SCALE = 1024.0f;
|
||||
static const float DEFAULT_AVATAR_DENSITY = 1000.0f; // density of water
|
||||
|
@ -66,7 +66,7 @@ size_t AvatarDataPacket::maxFaceTrackerInfoSize(size_t numBlendshapeCoefficients
|
|||
return FACE_TRACKER_INFO_SIZE + numBlendshapeCoefficients * sizeof(float);
|
||||
}
|
||||
|
||||
size_t AvatarDataPacket::maxJointDataSize(size_t numJoints, bool hasGrabJoints) {
|
||||
size_t AvatarDataPacket::maxJointDataSize(size_t numJoints) {
|
||||
const size_t validityBitsSize = calcBitVectorSize((int)numJoints);
|
||||
|
||||
size_t totalSize = sizeof(uint8_t); // numJoints
|
||||
|
@ -76,14 +76,6 @@ size_t AvatarDataPacket::maxJointDataSize(size_t numJoints, bool hasGrabJoints)
|
|||
totalSize += validityBitsSize; // Translations mask
|
||||
totalSize += sizeof(float); // maxTranslationDimension
|
||||
totalSize += numJoints * sizeof(SixByteTrans); // Translations
|
||||
|
||||
size_t NUM_FAUX_JOINT = 2;
|
||||
totalSize += NUM_FAUX_JOINT * (sizeof(SixByteQuat) + sizeof(SixByteTrans)); // faux joints
|
||||
|
||||
if (hasGrabJoints) {
|
||||
totalSize += sizeof(AvatarDataPacket::FarGrabJoints);
|
||||
}
|
||||
|
||||
return totalSize;
|
||||
}
|
||||
|
||||
|
@ -98,9 +90,6 @@ size_t AvatarDataPacket::minJointDataSize(size_t numJoints) {
|
|||
totalSize += sizeof(float); // maxTranslationDimension
|
||||
// assume no valid translations
|
||||
|
||||
size_t NUM_FAUX_JOINT = 2;
|
||||
totalSize += NUM_FAUX_JOINT * (sizeof(SixByteQuat) + sizeof(SixByteTrans)); // faux joints
|
||||
|
||||
return totalSize;
|
||||
}
|
||||
|
||||
|
@ -329,6 +318,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
|
|||
// separately
|
||||
bool hasParentInfo = false;
|
||||
bool hasAvatarLocalPosition = false;
|
||||
bool hasHandControllers = false;
|
||||
|
||||
bool hasFaceTrackerInfo = false;
|
||||
|
||||
|
@ -346,7 +336,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
|
|||
hasAvatarLocalPosition = hasParent() && (sendAll ||
|
||||
tranlationChangedSince(lastSentTime) ||
|
||||
parentInfoChangedSince(lastSentTime));
|
||||
|
||||
hasHandControllers = _controllerLeftHandMatrixCache.isValid() || _controllerRightHandMatrixCache.isValid();
|
||||
hasFaceTrackerInfo = !dropFaceTracking && (hasFaceTracker() || getHasScriptedBlendshapes()) &&
|
||||
(sendAll || faceTrackerInfoChangedSince(lastSentTime));
|
||||
hasJointData = !sendMinimum;
|
||||
|
@ -364,6 +354,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
|
|||
| (hasAdditionalFlags ? AvatarDataPacket::PACKET_HAS_ADDITIONAL_FLAGS : 0)
|
||||
| (hasParentInfo ? AvatarDataPacket::PACKET_HAS_PARENT_INFO : 0)
|
||||
| (hasAvatarLocalPosition ? AvatarDataPacket::PACKET_HAS_AVATAR_LOCAL_POSITION : 0)
|
||||
| (hasHandControllers ? AvatarDataPacket::PACKET_HAS_HAND_CONTROLLERS : 0)
|
||||
| (hasFaceTrackerInfo ? AvatarDataPacket::PACKET_HAS_FACE_TRACKER_INFO : 0)
|
||||
| (hasJointData ? AvatarDataPacket::PACKET_HAS_JOINT_DATA : 0)
|
||||
| (hasJointDefaultPoseFlags ? AvatarDataPacket::PACKET_HAS_JOINT_DEFAULT_POSE_FLAGS : 0)
|
||||
|
@ -406,7 +397,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
|
|||
|
||||
const size_t byteArraySize = AvatarDataPacket::MAX_CONSTANT_HEADER_SIZE + NUM_BYTES_RFC4122_UUID +
|
||||
AvatarDataPacket::maxFaceTrackerInfoSize(_headData->getBlendshapeCoefficients().size()) +
|
||||
AvatarDataPacket::maxJointDataSize(_jointData.size(), true) +
|
||||
AvatarDataPacket::maxJointDataSize(_jointData.size()) +
|
||||
AvatarDataPacket::maxJointDefaultPoseFlagsSize(_jointData.size());
|
||||
|
||||
if (maxDataSize == 0) {
|
||||
|
@ -592,7 +583,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
|
|||
}
|
||||
}
|
||||
|
||||
IF_AVATAR_SPACE(PACKET_HAS_AVATAR_LOCAL_POSITION, sizeof(getLocalPosition()) ) {
|
||||
IF_AVATAR_SPACE(PACKET_HAS_AVATAR_LOCAL_POSITION, AvatarDataPacket::AVATAR_LOCAL_POSITION_SIZE) {
|
||||
auto startSection = destinationBuffer;
|
||||
const auto localPosition = getLocalPosition();
|
||||
AVATAR_MEMCPY(localPosition);
|
||||
|
@ -603,6 +594,23 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
|
|||
}
|
||||
}
|
||||
|
||||
IF_AVATAR_SPACE(PACKET_HAS_HAND_CONTROLLERS, AvatarDataPacket::HAND_CONTROLLERS_SIZE) {
|
||||
auto startSection = destinationBuffer;
|
||||
|
||||
Transform controllerLeftHandTransform = Transform(getControllerLeftHandMatrix());
|
||||
destinationBuffer += packOrientationQuatToSixBytes(destinationBuffer, controllerLeftHandTransform.getRotation());
|
||||
destinationBuffer += packFloatVec3ToSignedTwoByteFixed(destinationBuffer, controllerLeftHandTransform.getTranslation(), HAND_CONTROLLER_COMPRESSION_RADIX);
|
||||
|
||||
Transform controllerRightHandTransform = Transform(getControllerRightHandMatrix());
|
||||
destinationBuffer += packOrientationQuatToSixBytes(destinationBuffer, controllerRightHandTransform.getRotation());
|
||||
destinationBuffer += packFloatVec3ToSignedTwoByteFixed(destinationBuffer, controllerRightHandTransform.getTranslation(), HAND_CONTROLLER_COMPRESSION_RADIX);
|
||||
|
||||
int numBytes = destinationBuffer - startSection;
|
||||
if (outboundDataRateOut) {
|
||||
outboundDataRateOut->handControllersRate.increment(numBytes);
|
||||
}
|
||||
}
|
||||
|
||||
const auto& blendshapeCoefficients = _headData->getBlendshapeCoefficients();
|
||||
// If it is connected, pack up the data
|
||||
IF_AVATAR_SPACE(PACKET_HAS_FACE_TRACKER_INFO, sizeof(AvatarDataPacket::FaceTrackerInfo) + (size_t)blendshapeCoefficients.size() * sizeof(float)) {
|
||||
|
@ -638,9 +646,8 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
|
|||
// include jointData if there is room for the most minimal section. i.e. no translations or rotations.
|
||||
IF_AVATAR_SPACE(PACKET_HAS_JOINT_DATA, AvatarDataPacket::minJointDataSize(numJoints)) {
|
||||
// Minimum space required for another rotation joint -
|
||||
// size of joint + following translation bit-vector + translation scale + faux joints:
|
||||
const ptrdiff_t minSizeForJoint = sizeof(AvatarDataPacket::SixByteQuat) + jointBitVectorSize +
|
||||
sizeof(float) + AvatarDataPacket::FAUX_JOINTS_SIZE;
|
||||
// size of joint + following translation bit-vector + translation scale:
|
||||
const ptrdiff_t minSizeForJoint = sizeof(AvatarDataPacket::SixByteQuat) + jointBitVectorSize + sizeof(float);
|
||||
|
||||
auto startSection = destinationBuffer;
|
||||
|
||||
|
@ -759,17 +766,6 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
|
|||
}
|
||||
sendStatus.translationsSent = i;
|
||||
|
||||
// faux joints
|
||||
Transform controllerLeftHandTransform = Transform(getControllerLeftHandMatrix());
|
||||
destinationBuffer += packOrientationQuatToSixBytes(destinationBuffer, controllerLeftHandTransform.getRotation());
|
||||
destinationBuffer += packFloatVec3ToSignedTwoByteFixed(destinationBuffer, controllerLeftHandTransform.getTranslation(),
|
||||
FAUX_JOINT_COMPRESSION_RADIX);
|
||||
|
||||
Transform controllerRightHandTransform = Transform(getControllerRightHandMatrix());
|
||||
destinationBuffer += packOrientationQuatToSixBytes(destinationBuffer, controllerRightHandTransform.getRotation());
|
||||
destinationBuffer += packFloatVec3ToSignedTwoByteFixed(destinationBuffer, controllerRightHandTransform.getTranslation(),
|
||||
FAUX_JOINT_COMPRESSION_RADIX);
|
||||
|
||||
IF_AVATAR_SPACE(PACKET_HAS_GRAB_JOINTS, sizeof (AvatarDataPacket::FarGrabJoints)) {
|
||||
// the far-grab joints may range further than 3 meters, so we can't use packFloatVec3ToSignedTwoByteFixed etc
|
||||
auto startSection = destinationBuffer;
|
||||
|
@ -902,12 +898,12 @@ bool AvatarData::shouldLogError(const quint64& now) {
|
|||
}
|
||||
|
||||
|
||||
const unsigned char* unpackFauxJoint(const unsigned char* sourceBuffer, ThreadSafeValueCache<glm::mat4>& matrixCache) {
|
||||
const unsigned char* unpackHandController(const unsigned char* sourceBuffer, ThreadSafeValueCache<glm::mat4>& matrixCache) {
|
||||
glm::quat orientation;
|
||||
glm::vec3 position;
|
||||
Transform transform;
|
||||
sourceBuffer += unpackOrientationQuatFromSixBytes(sourceBuffer, orientation);
|
||||
sourceBuffer += unpackFloatVec3FromSignedTwoByteFixed(sourceBuffer, position, FAUX_JOINT_COMPRESSION_RADIX);
|
||||
sourceBuffer += unpackFloatVec3FromSignedTwoByteFixed(sourceBuffer, position, HAND_CONTROLLER_COMPRESSION_RADIX);
|
||||
transform.setTranslation(position);
|
||||
transform.setRotation(orientation);
|
||||
matrixCache.set(transform.getMatrix());
|
||||
|
@ -952,6 +948,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) {
|
|||
bool hasAdditionalFlags = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_ADDITIONAL_FLAGS);
|
||||
bool hasParentInfo = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_PARENT_INFO);
|
||||
bool hasAvatarLocalPosition = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_AVATAR_LOCAL_POSITION);
|
||||
bool hasHandControllers = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_HAND_CONTROLLERS);
|
||||
bool hasFaceTrackerInfo = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_FACE_TRACKER_INFO);
|
||||
bool hasJointData = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_JOINT_DATA);
|
||||
bool hasJointDefaultPoseFlags = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_JOINT_DEFAULT_POSE_FLAGS);
|
||||
|
@ -1240,6 +1237,20 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) {
|
|||
_localPositionUpdateRate.increment();
|
||||
}
|
||||
|
||||
if (hasHandControllers) {
|
||||
auto startSection = sourceBuffer;
|
||||
|
||||
sourceBuffer = unpackHandController(sourceBuffer, _controllerLeftHandMatrixCache);
|
||||
sourceBuffer = unpackHandController(sourceBuffer, _controllerRightHandMatrixCache);
|
||||
|
||||
int numBytesRead = sourceBuffer - startSection;
|
||||
_handControllersRate.increment(numBytesRead);
|
||||
_handControllersUpdateRate.increment();
|
||||
} else {
|
||||
_controllerLeftHandMatrixCache.invalidate();
|
||||
_controllerRightHandMatrixCache.invalidate();
|
||||
}
|
||||
|
||||
if (hasFaceTrackerInfo) {
|
||||
auto startSection = sourceBuffer;
|
||||
|
||||
|
@ -1351,10 +1362,6 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) {
|
|||
<< "size:" << (int)(sourceBuffer - startPosition);
|
||||
}
|
||||
#endif
|
||||
// faux joints
|
||||
sourceBuffer = unpackFauxJoint(sourceBuffer, _controllerLeftHandMatrixCache);
|
||||
sourceBuffer = unpackFauxJoint(sourceBuffer, _controllerRightHandMatrixCache);
|
||||
|
||||
int numBytesRead = sourceBuffer - startSection;
|
||||
_jointDataRate.increment(numBytesRead);
|
||||
_jointDataUpdateRate.increment();
|
||||
|
@ -1445,6 +1452,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) {
|
|||
* <tbody>
|
||||
* <tr><td><code>"globalPosition"</code></td><td>Incoming global position.</td></tr>
|
||||
* <tr><td><code>"localPosition"</code></td><td>Incoming local position.</td></tr>
|
||||
* <tr><td><code>"handControllers"</code></td><td>Incoming hand controllers.</td></tr>
|
||||
* <tr><td><code>"avatarBoundingBox"</code></td><td>Incoming avatar bounding box.</td></tr>
|
||||
* <tr><td><code>"avatarOrientation"</code></td><td>Incoming avatar orientation.</td></tr>
|
||||
* <tr><td><code>"avatarScale"</code></td><td>Incoming avatar scale.</td></tr>
|
||||
|
@ -1483,6 +1491,8 @@ float AvatarData::getDataRate(const QString& rateName) const {
|
|||
return _globalPositionRate.rate() / BYTES_PER_KILOBIT;
|
||||
} else if (rateName == "localPosition") {
|
||||
return _localPositionRate.rate() / BYTES_PER_KILOBIT;
|
||||
} else if (rateName == "handControllers") {
|
||||
return _handControllersRate.rate() / BYTES_PER_KILOBIT;
|
||||
} else if (rateName == "avatarBoundingBox") {
|
||||
return _avatarBoundingBoxRate.rate() / BYTES_PER_KILOBIT;
|
||||
} else if (rateName == "avatarOrientation") {
|
||||
|
@ -1547,6 +1557,7 @@ float AvatarData::getDataRate(const QString& rateName) const {
|
|||
* <tbody>
|
||||
* <tr><td><code>"globalPosition"</code></td><td>Global position.</td></tr>
|
||||
* <tr><td><code>"localPosition"</code></td><td>Local position.</td></tr>
|
||||
* <tr><td><code>"handControllers"</code></td><td>Hand controller positions and orientations.</td></tr>
|
||||
* <tr><td><code>"avatarBoundingBox"</code></td><td>Avatar bounding box.</td></tr>
|
||||
* <tr><td><code>"avatarOrientation"</code></td><td>Avatar orientation.</td></tr>
|
||||
* <tr><td><code>"avatarScale"</code></td><td>Avatar scale.</td></tr>
|
||||
|
@ -1571,6 +1582,8 @@ float AvatarData::getUpdateRate(const QString& rateName) const {
|
|||
return _globalPositionUpdateRate.rate();
|
||||
} else if (rateName == "localPosition") {
|
||||
return _localPositionUpdateRate.rate();
|
||||
} else if (rateName == "handControllers") {
|
||||
return _handControllersUpdateRate.rate();
|
||||
} else if (rateName == "avatarBoundingBox") {
|
||||
return _avatarBoundingBoxUpdateRate.rate();
|
||||
} else if (rateName == "avatarOrientation") {
|
||||
|
@ -1633,6 +1646,13 @@ void AvatarData::setJointData(int index, const glm::quat& rotation, const glm::v
|
|||
data.translationIsDefaultPose = false;
|
||||
}
|
||||
|
||||
QVector<JointData> AvatarData::getJointData() const {
|
||||
QVector<JointData> jointData;
|
||||
QReadLocker readLock(&_jointDataLock);
|
||||
jointData = _jointData;
|
||||
return jointData;
|
||||
}
|
||||
|
||||
void AvatarData::clearJointData(int index) {
|
||||
if (index < 0 || index >= LOWEST_PSEUDO_JOINT_INDEX) {
|
||||
return;
|
||||
|
@ -1987,11 +2007,98 @@ QUrl AvatarData::getWireSafeSkeletonModelURL() const {
|
|||
return QUrl();
|
||||
}
|
||||
}
|
||||
QByteArray AvatarData::packSkeletonData() const {
|
||||
// Send an avatar trait packet with the skeleton data before the mesh is loaded
|
||||
int avatarDataSize = 0;
|
||||
QByteArray avatarDataByteArray;
|
||||
_avatarSkeletonDataLock.withReadLock([&] {
|
||||
// Add header
|
||||
AvatarSkeletonTrait::Header header;
|
||||
header.maxScaleDimension = 0.0f;
|
||||
header.maxTranslationDimension = 0.0f;
|
||||
header.numJoints = (uint8_t)_avatarSkeletonData.size();
|
||||
header.stringTableLength = 0;
|
||||
|
||||
for (size_t i = 0; i < _avatarSkeletonData.size(); i++) {
|
||||
header.stringTableLength += (uint16_t)_avatarSkeletonData[i].jointName.size();
|
||||
auto& translation = _avatarSkeletonData[i].defaultTranslation;
|
||||
header.maxTranslationDimension = std::max(header.maxTranslationDimension, std::max(std::max(translation.x, translation.y), translation.z));
|
||||
header.maxScaleDimension = std::max(header.maxScaleDimension, _avatarSkeletonData[i].defaultScale);
|
||||
}
|
||||
|
||||
const int byteArraySize = (int)sizeof(AvatarSkeletonTrait::Header) + (int)(header.numJoints * sizeof(AvatarSkeletonTrait::JointData)) + header.stringTableLength;
|
||||
avatarDataByteArray = QByteArray(byteArraySize, 0);
|
||||
unsigned char* destinationBuffer = reinterpret_cast<unsigned char*>(avatarDataByteArray.data());
|
||||
const unsigned char* const startPosition = destinationBuffer;
|
||||
|
||||
memcpy(destinationBuffer, &header, sizeof(header));
|
||||
destinationBuffer += sizeof(AvatarSkeletonTrait::Header);
|
||||
|
||||
QString stringTable = "";
|
||||
for (size_t i = 0; i < _avatarSkeletonData.size(); i++) {
|
||||
AvatarSkeletonTrait::JointData jdata;
|
||||
jdata.boneType = _avatarSkeletonData[i].boneType;
|
||||
jdata.parentIndex = _avatarSkeletonData[i].parentIndex;
|
||||
packFloatRatioToTwoByte((uint8_t*)(&jdata.defaultScale), _avatarSkeletonData[i].defaultScale / header.maxScaleDimension);
|
||||
packOrientationQuatToSixBytes(jdata.defaultRotation, _avatarSkeletonData[i].defaultRotation);
|
||||
packFloatVec3ToSignedTwoByteFixed(jdata.defaultTranslation, _avatarSkeletonData[i].defaultTranslation / header.maxTranslationDimension, TRANSLATION_COMPRESSION_RADIX);
|
||||
jdata.jointIndex = (uint16_t)i;
|
||||
jdata.stringStart = (uint16_t)_avatarSkeletonData[i].stringStart;
|
||||
jdata.stringLength = (uint8_t)_avatarSkeletonData[i].stringLength;
|
||||
stringTable += _avatarSkeletonData[i].jointName;
|
||||
memcpy(destinationBuffer, &jdata, sizeof(AvatarSkeletonTrait::JointData));
|
||||
destinationBuffer += sizeof(AvatarSkeletonTrait::JointData);
|
||||
}
|
||||
|
||||
memcpy(destinationBuffer, stringTable.toUtf8(), header.stringTableLength);
|
||||
destinationBuffer += header.stringTableLength;
|
||||
|
||||
avatarDataSize = destinationBuffer - startPosition;
|
||||
});
|
||||
return avatarDataByteArray.left(avatarDataSize);
|
||||
}
|
||||
|
||||
QByteArray AvatarData::packSkeletonModelURL() const {
|
||||
return getWireSafeSkeletonModelURL().toEncoded();
|
||||
}
|
||||
|
||||
void AvatarData::unpackSkeletonData(const QByteArray& data) {
|
||||
|
||||
const unsigned char* startPosition = reinterpret_cast<const unsigned char*>(data.data());
|
||||
const unsigned char* sourceBuffer = startPosition;
|
||||
|
||||
auto header = reinterpret_cast<const AvatarSkeletonTrait::Header*>(sourceBuffer);
|
||||
sourceBuffer += sizeof(const AvatarSkeletonTrait::Header);
|
||||
|
||||
std::vector<AvatarSkeletonTrait::UnpackedJointData> joints;
|
||||
for (uint8_t i = 0; i < header->numJoints; i++) {
|
||||
auto jointData = reinterpret_cast<const AvatarSkeletonTrait::JointData*>(sourceBuffer);
|
||||
sourceBuffer += sizeof(const AvatarSkeletonTrait::JointData);
|
||||
AvatarSkeletonTrait::UnpackedJointData uJointData;
|
||||
uJointData.boneType = (int)jointData->boneType;
|
||||
uJointData.jointIndex = (int)i;
|
||||
uJointData.stringLength = (int)jointData->stringLength;
|
||||
uJointData.stringStart = (int)jointData->stringStart;
|
||||
uJointData.parentIndex = ((uJointData.boneType == AvatarSkeletonTrait::BoneType::SkeletonRoot) ||
|
||||
(uJointData.boneType == AvatarSkeletonTrait::BoneType::NonSkeletonRoot)) ? -1 : (int)jointData->parentIndex;
|
||||
unpackOrientationQuatFromSixBytes(reinterpret_cast<const unsigned char*>(&jointData->defaultRotation), uJointData.defaultRotation);
|
||||
unpackFloatVec3FromSignedTwoByteFixed(reinterpret_cast<const unsigned char*>(&jointData->defaultTranslation), uJointData.defaultTranslation, TRANSLATION_COMPRESSION_RADIX);
|
||||
unpackFloatRatioFromTwoByte(reinterpret_cast<const unsigned char*>(&jointData->defaultScale), uJointData.defaultScale);
|
||||
uJointData.defaultTranslation *= header->maxTranslationDimension;
|
||||
uJointData.defaultScale *= header->maxScaleDimension;
|
||||
joints.push_back(uJointData);
|
||||
}
|
||||
QString table = QString::fromUtf8(reinterpret_cast<const char*>(sourceBuffer), (int)header->stringTableLength);
|
||||
for (size_t i = 0; i < joints.size(); i++) {
|
||||
QStringRef subString(&table, joints[i].stringStart, joints[i].stringLength);
|
||||
joints[i].jointName = subString.toString();
|
||||
}
|
||||
if (_clientTraitsHandler) {
|
||||
_clientTraitsHandler->markTraitUpdated(AvatarTraits::SkeletonData);
|
||||
}
|
||||
setSkeletonData(joints);
|
||||
}
|
||||
|
||||
void AvatarData::unpackSkeletonModelURL(const QByteArray& data) {
|
||||
auto skeletonModelURL = QUrl::fromEncoded(data);
|
||||
setSkeletonModelURL(skeletonModelURL);
|
||||
|
@ -2027,6 +2134,8 @@ QByteArray AvatarData::packTrait(AvatarTraits::TraitType traitType) const {
|
|||
// Call packer function
|
||||
if (traitType == AvatarTraits::SkeletonModelURL) {
|
||||
traitBinaryData = packSkeletonModelURL();
|
||||
} else if (traitType == AvatarTraits::SkeletonData) {
|
||||
traitBinaryData = packSkeletonData();
|
||||
}
|
||||
|
||||
return traitBinaryData;
|
||||
|
@ -2048,6 +2157,8 @@ QByteArray AvatarData::packTraitInstance(AvatarTraits::TraitType traitType, Avat
|
|||
void AvatarData::processTrait(AvatarTraits::TraitType traitType, QByteArray traitBinaryData) {
|
||||
if (traitType == AvatarTraits::SkeletonModelURL) {
|
||||
unpackSkeletonModelURL(traitBinaryData);
|
||||
} else if (traitType == AvatarTraits::SkeletonData) {
|
||||
unpackSkeletonData(traitBinaryData);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2110,7 +2221,6 @@ void AvatarData::setSkeletonModelURL(const QUrl& skeletonModelURL) {
|
|||
}
|
||||
|
||||
_skeletonModelURL = expanded;
|
||||
|
||||
if (_clientTraitsHandler) {
|
||||
_clientTraitsHandler->markTraitUpdated(AvatarTraits::SkeletonModelURL);
|
||||
}
|
||||
|
@ -3008,6 +3118,26 @@ AABox AvatarData::computeBubbleBox(float bubbleScale) const {
|
|||
return box;
|
||||
}
|
||||
|
||||
void AvatarData::setSkeletonData(const std::vector<AvatarSkeletonTrait::UnpackedJointData>& skeletonData) {
|
||||
_avatarSkeletonDataLock.withWriteLock([&] {
|
||||
_avatarSkeletonData = skeletonData;
|
||||
});
|
||||
}
|
||||
|
||||
std::vector<AvatarSkeletonTrait::UnpackedJointData> AvatarData::getSkeletonData() const {
|
||||
std::vector<AvatarSkeletonTrait::UnpackedJointData> skeletonData;
|
||||
_avatarSkeletonDataLock.withReadLock([&] {
|
||||
skeletonData = _avatarSkeletonData;
|
||||
});
|
||||
return skeletonData;
|
||||
}
|
||||
|
||||
void AvatarData::sendSkeletonData() const{
|
||||
if (_clientTraitsHandler) {
|
||||
_clientTraitsHandler->markTraitUpdated(AvatarTraits::SkeletonData);
|
||||
}
|
||||
}
|
||||
|
||||
AABox AvatarData::getDefaultBubbleBox() const {
|
||||
AABox bubbleBox(_defaultBubbleBox);
|
||||
bubbleBox.translate(_globalPosition);
|
||||
|
|
|
@ -145,6 +145,45 @@ const char AVATARDATA_FLAGS_MINIMUM = 0;
|
|||
|
||||
using SmallFloat = uint16_t; // a compressed float with less precision, user defined radix
|
||||
|
||||
namespace AvatarSkeletonTrait {
|
||||
enum BoneType {
|
||||
SkeletonRoot = 0,
|
||||
SkeletonChild,
|
||||
NonSkeletonRoot,
|
||||
NonSkeletonChild
|
||||
};
|
||||
|
||||
PACKED_BEGIN struct Header {
|
||||
float maxTranslationDimension;
|
||||
float maxScaleDimension;
|
||||
uint8_t numJoints;
|
||||
uint16_t stringTableLength;
|
||||
} PACKED_END;
|
||||
|
||||
PACKED_BEGIN struct JointData {
|
||||
uint16_t stringStart;
|
||||
uint8_t stringLength;
|
||||
uint8_t boneType;
|
||||
uint8_t defaultTranslation[6];
|
||||
uint8_t defaultRotation[6];
|
||||
uint16_t defaultScale;
|
||||
uint16_t jointIndex;
|
||||
uint16_t parentIndex;
|
||||
} PACKED_END;
|
||||
|
||||
struct UnpackedJointData {
|
||||
int stringStart;
|
||||
int stringLength;
|
||||
int boneType;
|
||||
glm::vec3 defaultTranslation;
|
||||
glm::quat defaultRotation;
|
||||
float defaultScale;
|
||||
int jointIndex;
|
||||
int parentIndex;
|
||||
QString jointName;
|
||||
};
|
||||
}
|
||||
|
||||
namespace AvatarDataPacket {
|
||||
|
||||
// NOTE: every time AvatarData is sent from mixer to client, it also includes the GUIID for the session
|
||||
|
@ -164,10 +203,11 @@ namespace AvatarDataPacket {
|
|||
const HasFlags PACKET_HAS_ADDITIONAL_FLAGS = 1U << 7;
|
||||
const HasFlags PACKET_HAS_PARENT_INFO = 1U << 8;
|
||||
const HasFlags PACKET_HAS_AVATAR_LOCAL_POSITION = 1U << 9;
|
||||
const HasFlags PACKET_HAS_FACE_TRACKER_INFO = 1U << 10;
|
||||
const HasFlags PACKET_HAS_JOINT_DATA = 1U << 11;
|
||||
const HasFlags PACKET_HAS_JOINT_DEFAULT_POSE_FLAGS = 1U << 12;
|
||||
const HasFlags PACKET_HAS_GRAB_JOINTS = 1U << 13;
|
||||
const HasFlags PACKET_HAS_HAND_CONTROLLERS = 1U << 10;
|
||||
const HasFlags PACKET_HAS_FACE_TRACKER_INFO = 1U << 11;
|
||||
const HasFlags PACKET_HAS_JOINT_DATA = 1U << 12;
|
||||
const HasFlags PACKET_HAS_JOINT_DEFAULT_POSE_FLAGS = 1U << 13;
|
||||
const HasFlags PACKET_HAS_GRAB_JOINTS = 1U << 14;
|
||||
const size_t AVATAR_HAS_FLAGS_SIZE = 2;
|
||||
|
||||
using SixByteQuat = uint8_t[6];
|
||||
|
@ -230,7 +270,7 @@ namespace AvatarDataPacket {
|
|||
//
|
||||
// POTENTIAL SAVINGS - 20 bytes
|
||||
|
||||
SixByteQuat sensorToWorldQuat; // 6 byte compressed quaternion part of sensor to world matrix
|
||||
SixByteQuat sensorToWorldQuat; // 6 byte compressed quaternion part of sensor to world matrix
|
||||
uint16_t sensorToWorldScale; // uniform scale of sensor to world matrix
|
||||
float sensorToWorldTrans[3]; // fourth column of sensor to world matrix
|
||||
// FIXME - sensorToWorldTrans might be able to be better compressed if it was
|
||||
|
@ -258,6 +298,7 @@ namespace AvatarDataPacket {
|
|||
PACKED_BEGIN struct AvatarLocalPosition {
|
||||
float localPosition[3]; // parent frame translation of the avatar
|
||||
} PACKED_END;
|
||||
|
||||
const size_t AVATAR_LOCAL_POSITION_SIZE = 12;
|
||||
static_assert(sizeof(AvatarLocalPosition) == AVATAR_LOCAL_POSITION_SIZE, "AvatarDataPacket::AvatarLocalPosition size doesn't match.");
|
||||
|
||||
|
@ -273,6 +314,15 @@ namespace AvatarDataPacket {
|
|||
PARENT_INFO_SIZE +
|
||||
AVATAR_LOCAL_POSITION_SIZE;
|
||||
|
||||
PACKED_BEGIN struct HandControllers {
|
||||
SixByteQuat leftHandRotation;
|
||||
SixByteTrans leftHandTranslation;
|
||||
SixByteQuat rightHandRotation;
|
||||
SixByteTrans rightHandTranslation;
|
||||
} PACKED_END;
|
||||
static const size_t HAND_CONTROLLERS_SIZE = 24;
|
||||
static_assert(sizeof(HandControllers) == HAND_CONTROLLERS_SIZE, "AvatarDataPacket::HandControllers size doesn't match.");
|
||||
|
||||
|
||||
// variable length structure follows
|
||||
|
||||
|
@ -303,7 +353,7 @@ namespace AvatarDataPacket {
|
|||
SixByteTrans rightHandControllerTranslation;
|
||||
};
|
||||
*/
|
||||
size_t maxJointDataSize(size_t numJoints, bool hasGrabJoints);
|
||||
size_t maxJointDataSize(size_t numJoints);
|
||||
size_t minJointDataSize(size_t numJoints);
|
||||
|
||||
/*
|
||||
|
@ -327,7 +377,6 @@ namespace AvatarDataPacket {
|
|||
static_assert(sizeof(FarGrabJoints) == FAR_GRAB_JOINTS_SIZE, "AvatarDataPacket::FarGrabJoints size doesn't match.");
|
||||
|
||||
static const size_t MIN_BULK_PACKET_SIZE = NUM_BYTES_RFC4122_UUID + HEADER_SIZE;
|
||||
static const size_t FAUX_JOINTS_SIZE = 2 * (sizeof(SixByteQuat) + sizeof(SixByteTrans));
|
||||
|
||||
struct SendStatus {
|
||||
HasFlags itemFlags { 0 };
|
||||
|
@ -404,6 +453,7 @@ class AvatarDataRate {
|
|||
public:
|
||||
RateCounter<> globalPositionRate;
|
||||
RateCounter<> localPositionRate;
|
||||
RateCounter<> handControllersRate;
|
||||
RateCounter<> avatarBoundingBoxRate;
|
||||
RateCounter<> avatarOrientationRate;
|
||||
RateCounter<> avatarScaleRate;
|
||||
|
@ -467,8 +517,8 @@ class AvatarData : public QObject, public SpatiallyNestable {
|
|||
* @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
|
||||
|
@ -1076,7 +1126,7 @@ public:
|
|||
* Gets information about the models currently attached to your avatar.
|
||||
* @function Avatar.getAttachmentsVariant
|
||||
* @returns {AttachmentData[]} Information about all models attached to your avatar.
|
||||
* @deprecated Use avatar entities instead.
|
||||
* @deprecated This function is deprecated and will be removed. Use avatar entities instead.
|
||||
*/
|
||||
// FIXME: Can this name be improved? Can it be deprecated?
|
||||
Q_INVOKABLE virtual QVariantList getAttachmentsVariant() const;
|
||||
|
@ -1087,7 +1137,7 @@ public:
|
|||
* update your avatar's attachments per the changed data.
|
||||
* @function Avatar.setAttachmentsVariant
|
||||
* @param {AttachmentData[]} variant - The attachment data defining the models to have attached to your avatar.
|
||||
* @deprecated Use avatar entities instead.
|
||||
* @deprecated This function is deprecated and will be removed. Use avatar entities instead.
|
||||
*/
|
||||
// FIXME: Can this name be improved? Can it be deprecated?
|
||||
Q_INVOKABLE virtual void setAttachmentsVariant(const QVariantList& variant);
|
||||
|
@ -1168,7 +1218,7 @@ public:
|
|||
* Gets information about the models currently attached to your avatar.
|
||||
* @function Avatar.getAttachmentData
|
||||
* @returns {AttachmentData[]} Information about all models attached to your avatar.
|
||||
* @deprecated Use avatar entities instead.
|
||||
* @deprecated This function is deprecated and will be removed. Use avatar entities instead.
|
||||
* @example <caption>Report the URLs of all current attachments.</caption>
|
||||
* var attachments = MyAvatar.getaAttachmentData();
|
||||
* for (var i = 0; i < attachments.length; i++) {
|
||||
|
@ -1186,7 +1236,7 @@ public:
|
|||
* @function Avatar.setAttachmentData
|
||||
* @param {AttachmentData[]} attachmentData - The attachment data defining the models to have attached to your avatar. Use
|
||||
* <code>null</code> to remove all attachments.
|
||||
* @deprecated Use avatar entities instead.
|
||||
* @deprecated This function is deprecated and will be removed. Use avatar entities instead.
|
||||
* @example <caption>Remove a hat attachment if your avatar is wearing it.</caption>
|
||||
* var hatURL = "https://s3.amazonaws.com/hifi-public/tony/cowboy-hat.fbx";
|
||||
* var attachments = MyAvatar.getAttachmentData();
|
||||
|
@ -1223,7 +1273,7 @@ public:
|
|||
* @param {boolean} [allowDuplicates=false] - If <code>true</code> then more than one copy of any particular model may be
|
||||
* attached to the same joint; if <code>false</code> then the same model cannot be attached to the same joint.
|
||||
* @param {boolean} [useSaved=true] - <em>Not used.</em>
|
||||
* @deprecated Use avatar entities instead.
|
||||
* @deprecated This function is deprecated and will be removed. Use avatar entities instead.
|
||||
* @example <caption>Attach a cowboy hat to your avatar's head.</caption>
|
||||
* var attachment = {
|
||||
* modelURL: "https://s3.amazonaws.com/hifi-public/tony/cowboy-hat.fbx",
|
||||
|
@ -1254,7 +1304,7 @@ public:
|
|||
* @param {string} modelURL - The URL of the model to detach.
|
||||
* @param {string} [jointName=""] - The name of the joint to detach the model from. If <code>""</code>, then the most
|
||||
* recently attached model is removed from which ever joint it was attached to.
|
||||
* @deprecated Use avatar entities instead.
|
||||
* @deprecated This function is deprecated and will be removed. Use avatar entities instead.
|
||||
*/
|
||||
Q_INVOKABLE virtual void detachOne(const QString& modelURL, const QString& jointName = QString());
|
||||
|
||||
|
@ -1264,7 +1314,7 @@ public:
|
|||
* @param {string} modelURL - The URL of the model to detach.
|
||||
* @param {string} [jointName=""] - The name of the joint to detach the model from. If <code>""</code>, then the model is
|
||||
* detached from all joints.
|
||||
* @deprecated Use avatar entities instead.
|
||||
* @deprecated This function is deprecated and will be removed. Use avatar entities instead.
|
||||
*/
|
||||
Q_INVOKABLE virtual void detachAll(const QString& modelURL, const QString& jointName = QString());
|
||||
|
||||
|
@ -1420,6 +1470,10 @@ public:
|
|||
void setIsNewAvatar(bool isNewAvatar) { _isNewAvatar = isNewAvatar; }
|
||||
bool getIsNewAvatar() { return _isNewAvatar; }
|
||||
void setIsClientAvatar(bool isClientAvatar) { _isClientAvatar = isClientAvatar; }
|
||||
void setSkeletonData(const std::vector<AvatarSkeletonTrait::UnpackedJointData>& skeletonData);
|
||||
std::vector<AvatarSkeletonTrait::UnpackedJointData> getSkeletonData() const;
|
||||
void sendSkeletonData() const;
|
||||
QVector<JointData> getJointData() const;
|
||||
|
||||
signals:
|
||||
|
||||
|
@ -1598,12 +1652,13 @@ protected:
|
|||
bool hasParent() const { return !getParentID().isNull(); }
|
||||
bool hasFaceTracker() const { return _headData ? _headData->_isFaceTrackerConnected : false; }
|
||||
|
||||
QByteArray packSkeletonData() const;
|
||||
QByteArray packSkeletonModelURL() const;
|
||||
QByteArray packAvatarEntityTraitInstance(AvatarTraits::TraitInstanceID traitInstanceID);
|
||||
QByteArray packGrabTraitInstance(AvatarTraits::TraitInstanceID traitInstanceID);
|
||||
|
||||
void unpackSkeletonModelURL(const QByteArray& data);
|
||||
|
||||
void unpackSkeletonData(const QByteArray& data);
|
||||
|
||||
// isReplicated will be true on downstream Avatar Mixers and their clients, but false on the upstream "master"
|
||||
// Audio Mixer that the replicated avatar is connected to.
|
||||
|
@ -1671,6 +1726,7 @@ protected:
|
|||
RateCounter<> _parseBufferRate;
|
||||
RateCounter<> _globalPositionRate;
|
||||
RateCounter<> _localPositionRate;
|
||||
RateCounter<> _handControllersRate;
|
||||
RateCounter<> _avatarBoundingBoxRate;
|
||||
RateCounter<> _avatarOrientationRate;
|
||||
RateCounter<> _avatarScaleRate;
|
||||
|
@ -1688,6 +1744,7 @@ protected:
|
|||
RateCounter<> _parseBufferUpdateRate;
|
||||
RateCounter<> _globalPositionUpdateRate;
|
||||
RateCounter<> _localPositionUpdateRate;
|
||||
RateCounter<> _handControllersUpdateRate;
|
||||
RateCounter<> _avatarBoundingBoxUpdateRate;
|
||||
RateCounter<> _avatarOrientationUpdateRate;
|
||||
RateCounter<> _avatarScaleUpdateRate;
|
||||
|
@ -1720,6 +1777,9 @@ protected:
|
|||
AvatarGrabDataMap _avatarGrabData;
|
||||
bool _avatarGrabDataChanged { false }; // by network
|
||||
|
||||
mutable ReadWriteLockable _avatarSkeletonDataLock;
|
||||
std::vector<AvatarSkeletonTrait::UnpackedJointData> _avatarSkeletonData;
|
||||
|
||||
// used to transform any sensor into world space, including the _hmdSensorMat, or hand controllers.
|
||||
ThreadSafeValueCache<glm::mat4> _sensorToWorldMatrixCache { glm::mat4() };
|
||||
ThreadSafeValueCache<glm::mat4> _controllerLeftHandMatrixCache { glm::mat4() };
|
||||
|
|
|
@ -29,7 +29,7 @@ namespace AvatarTraits {
|
|||
|
||||
// Simple traits
|
||||
SkeletonModelURL = 0,
|
||||
|
||||
SkeletonData,
|
||||
// Instanced traits
|
||||
FirstInstancedTrait,
|
||||
AvatarEntity = FirstInstancedTrait,
|
||||
|
|
|
@ -107,8 +107,7 @@ int ClientTraitsHandler::sendChangedTraitsToMixer() {
|
|||
|
||||
if (initialSend || *simpleIt == Updated) {
|
||||
bytesWritten += AvatarTraits::packTrait(traitType, *traitsPacketList, *_owningAvatar);
|
||||
|
||||
|
||||
|
||||
if (traitType == AvatarTraits::SkeletonModelURL) {
|
||||
// keep track of our skeleton version in case we get an override back
|
||||
_currentSkeletonVersion = _currentTraitVersion;
|
||||
|
|
|
@ -40,13 +40,14 @@
|
|||
* @property {string} displayName - The avatar's display name.
|
||||
* @property {string} sessionDisplayName - The avatar's display name, sanitized and versioned, as defined by the avatar mixer.
|
||||
* It is unique among all avatars present in the domain at the time.
|
||||
* @property {boolean} isReplicated - <strong>Deprecated.</strong>
|
||||
* @property {boolean} isReplicated - <span class="important">Deprecated: This property is deprecated and will be
|
||||
* removed.</span>
|
||||
* @property {boolean} lookAtSnappingEnabled - <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.
|
||||
*
|
||||
* @property {number} audioLoudness - The instantaneous loudness of the audio input that the avatar is injecting into the
|
||||
|
|
|
@ -31,7 +31,6 @@
|
|||
#include <FBXWriter.h>
|
||||
|
||||
#include "ModelBakingLoggingCategory.h"
|
||||
#include "TextureBaker.h"
|
||||
|
||||
FBXBaker::FBXBaker(const QUrl& inputModelURL, const QString& bakedOutputDirectory, const QString& originalOutputDirectory, bool hasBeenBaked) :
|
||||
ModelBaker(inputModelURL, bakedOutputDirectory, originalOutputDirectory, hasBeenBaked) {
|
||||
|
|
|
@ -18,15 +18,11 @@
|
|||
#include <QtNetwork/QNetworkReply>
|
||||
|
||||
#include "Baker.h"
|
||||
#include "TextureBaker.h"
|
||||
#include "ModelBaker.h"
|
||||
#include "ModelBakingLoggingCategory.h"
|
||||
|
||||
#include <gpu/Texture.h>
|
||||
|
||||
#include <FBX.h>
|
||||
|
||||
using TextureBakerThreadGetter = std::function<QThread*()>;
|
||||
|
||||
class FBXBaker : public ModelBaker {
|
||||
Q_OBJECT
|
||||
|
|
|
@ -133,12 +133,12 @@ void MaterialBaker::processMaterial() {
|
|||
QString extension = idx >= 0 ? cleanURL.mid(idx + 1).toLower() : "";
|
||||
|
||||
if (QImageReader::supportedImageFormats().contains(extension.toLatin1())) {
|
||||
QPair<QUrl, image::TextureUsage::Type> textureKey(textureURL, type);
|
||||
TextureKey textureKey(textureURL, type);
|
||||
if (!_textureBakers.contains(textureKey)) {
|
||||
auto baseTextureFileName = _textureFileNamer.createBaseTextureFileName(textureURL.fileName(), type);
|
||||
|
||||
QSharedPointer<TextureBaker> textureBaker {
|
||||
new TextureBaker(textureURL, type, _textureOutputDir, "", baseTextureFileName, content),
|
||||
new TextureBaker(textureURL, type, _textureOutputDir, baseTextureFileName, content),
|
||||
&TextureBaker::deleteLater
|
||||
};
|
||||
textureBaker->setMapChannel(mapChannel);
|
||||
|
@ -170,7 +170,7 @@ void MaterialBaker::handleFinishedTextureBaker() {
|
|||
auto baker = qobject_cast<TextureBaker*>(sender());
|
||||
|
||||
if (baker) {
|
||||
QPair<QUrl, image::TextureUsage::Type> textureKey = { baker->getTextureURL(), baker->getTextureType() };
|
||||
TextureKey textureKey = { baker->getTextureURL(), baker->getTextureType() };
|
||||
if (!baker->hasErrors()) {
|
||||
// this TextureBaker is done and everything went according to plan
|
||||
qCDebug(material_baking) << "Re-writing texture references to" << baker->getTextureURL();
|
||||
|
|
|
@ -21,6 +21,8 @@
|
|||
|
||||
static const QString BAKED_MATERIAL_EXTENSION = ".baked.json";
|
||||
|
||||
using TextureKey = QPair<QUrl, image::TextureUsage::Type>;
|
||||
|
||||
class MaterialBaker : public Baker {
|
||||
Q_OBJECT
|
||||
public:
|
||||
|
@ -58,8 +60,8 @@ private:
|
|||
|
||||
NetworkMaterialResourcePointer _materialResource;
|
||||
|
||||
QHash<QPair<QUrl, image::TextureUsage::Type>, QSharedPointer<TextureBaker>> _textureBakers;
|
||||
QMultiHash<QPair<QUrl, image::TextureUsage::Type>, std::shared_ptr<NetworkMaterial>> _materialsNeedingRewrite;
|
||||
QHash<TextureKey, QSharedPointer<TextureBaker>> _textureBakers;
|
||||
QMultiHash<TextureKey, std::shared_ptr<NetworkMaterial>> _materialsNeedingRewrite;
|
||||
|
||||
QString _bakedOutputDir;
|
||||
QString _textureOutputDir;
|
||||
|
|
|
@ -13,13 +13,9 @@
|
|||
#define hifi_OBJBaker_h
|
||||
|
||||
#include "Baker.h"
|
||||
#include "TextureBaker.h"
|
||||
#include "ModelBaker.h"
|
||||
|
||||
#include "ModelBakingLoggingCategory.h"
|
||||
|
||||
using TextureBakerThreadGetter = std::function<QThread*()>;
|
||||
|
||||
using NodeID = qlonglong;
|
||||
|
||||
class OBJBaker : public ModelBaker {
|
||||
|
|
|
@ -33,14 +33,13 @@ const QString BAKED_META_TEXTURE_SUFFIX = ".texmeta.json";
|
|||
bool TextureBaker::_compressionEnabled = true;
|
||||
|
||||
TextureBaker::TextureBaker(const QUrl& textureURL, image::TextureUsage::Type textureType,
|
||||
const QDir& outputDirectory, const QString& metaTexturePathPrefix,
|
||||
const QString& baseFilename, const QByteArray& textureContent) :
|
||||
const QDir& outputDirectory, const QString& baseFilename,
|
||||
const QByteArray& textureContent) :
|
||||
_textureURL(textureURL),
|
||||
_originalTexture(textureContent),
|
||||
_textureType(textureType),
|
||||
_baseFilename(baseFilename),
|
||||
_outputDirectory(outputDirectory),
|
||||
_metaTexturePathPrefix(metaTexturePathPrefix)
|
||||
_outputDirectory(outputDirectory)
|
||||
{
|
||||
if (baseFilename.isEmpty()) {
|
||||
// figure out the baked texture filename
|
||||
|
@ -151,7 +150,7 @@ void TextureBaker::processTexture() {
|
|||
// IMPORTANT: _originalTexture is empty past this point
|
||||
_originalTexture.clear();
|
||||
_outputFiles.push_back(originalCopyFilePath);
|
||||
meta.original = _metaTexturePathPrefix + _originalCopyFilePath.fileName();
|
||||
meta.original = _originalCopyFilePath.fileName();
|
||||
}
|
||||
|
||||
// Load the copy of the original file from the baked output directory. New images will be created using the original as the source data.
|
||||
|
@ -204,7 +203,7 @@ void TextureBaker::processTexture() {
|
|||
return;
|
||||
}
|
||||
_outputFiles.push_back(filePath);
|
||||
meta.availableTextureTypes[memKTX->_header.getGLInternaFormat()] = _metaTexturePathPrefix + fileName;
|
||||
meta.availableTextureTypes[memKTX->_header.getGLInternaFormat()] = fileName;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -240,7 +239,7 @@ void TextureBaker::processTexture() {
|
|||
return;
|
||||
}
|
||||
_outputFiles.push_back(filePath);
|
||||
meta.uncompressed = _metaTexturePathPrefix + fileName;
|
||||
meta.uncompressed = fileName;
|
||||
} else {
|
||||
buffer.reset();
|
||||
}
|
||||
|
|
|
@ -32,8 +32,8 @@ class TextureBaker : public Baker {
|
|||
|
||||
public:
|
||||
TextureBaker(const QUrl& textureURL, image::TextureUsage::Type textureType,
|
||||
const QDir& outputDirectory, const QString& metaTexturePathPrefix = "",
|
||||
const QString& baseFilename = QString(), const QByteArray& textureContent = QByteArray());
|
||||
const QDir& outputDirectory, const QString& baseFilename = QString(),
|
||||
const QByteArray& textureContent = QByteArray());
|
||||
|
||||
const QByteArray& getOriginalTexture() const { return _originalTexture; }
|
||||
|
||||
|
@ -74,7 +74,6 @@ private:
|
|||
QString _baseFilename;
|
||||
QDir _outputDirectory;
|
||||
QString _metaTextureFileName;
|
||||
QString _metaTexturePathPrefix;
|
||||
QUrl _originalCopyFilePath;
|
||||
|
||||
std::atomic<bool> _abortProcessing { false };
|
||||
|
|
|
@ -201,15 +201,16 @@ namespace controller {
|
|||
* <tr><td><code>UiNavSelect</code></td><td>number</td><td>number</td><td>Generate a keyboard Enter key event.
|
||||
* </td></tr>
|
||||
* <tr><td><code>UiNavBack</code></td><td>number</td><td>number</td><td>Generate a keyboard Esc key event.</td></tr>
|
||||
* <tr><td><code>LeftHandClick</code></td><td>number</td><td>number</td><td><strong>Deprecated: </strong> No action.
|
||||
* </td></tr>
|
||||
* <tr><td><code>RightHandClick</code></td><td>number</td><td>number</td><td><strong>Deprecated:</strong> No action.
|
||||
* </td></tr>
|
||||
* <tr><td><code>Shift</code></td><td>number</td><td>number</td><td><strong>Deprecated:</strong> No action.</td></tr>
|
||||
* <tr><td><code>PrimaryAction</code></td><td>number</td><td>number</td><td><strong>Deprecated:</strong> No action.
|
||||
* </td></tr>
|
||||
* <tr><td><code>SecondaryAction</code></td><td>number</td><td>number</td><td><strong>Deprecated:</strong> No action.
|
||||
* </td></tr>
|
||||
* <tr><td><code>LeftHandClick</code></td><td>number</td><td>number</td><td><span class="important">Deprecated: This
|
||||
* action is deprecated and will be removed. It takes no action.</span></td></tr>
|
||||
* <tr><td><code>RightHandClick</code></td><td>number</td><td>number</td><td><span class="important">Deprecated: This
|
||||
* action is deprecated and will be removed. It takes no action.</span></td></tr>
|
||||
* <tr><td><code>Shift</code></td><td>number</td><td>number</td><td><span class="important">Deprecated: This
|
||||
* action is deprecated and will be removed. It takes no action.</span></td></tr>
|
||||
* <tr><td><code>PrimaryAction</code></td><td>number</td><td>number</td><td><span class="important">Deprecated: This
|
||||
* action is deprecated and will be removed. It takes no action.</span></td></tr>
|
||||
* <tr><td><code>SecondaryAction</code></td><td>number</td><td>number</td><td><span class="important">Deprecated: This
|
||||
* action is deprecated and will be removed. It takes no action.</span></td></tr>
|
||||
*
|
||||
* <tr><td colSpan=4><strong>Aliases</strong></td>
|
||||
* <tr><td><code>Backward</code></td><td>number</td><td>number</td><td>Alias for <code>TranslateZ</code> in the
|
||||
|
@ -234,86 +235,108 @@ namespace controller {
|
|||
* direction.</td></tr>
|
||||
*
|
||||
* <tr><td colSpan=4><strong>Deprecated Aliases</strong></td>
|
||||
* <tr><td><code>LEFT_HAND</code></td><td>number</td><td>{@link Pose}</td><td><strong>Deprecated:</strong> Use
|
||||
* <code>LeftHand</code> instead.</td></tr>
|
||||
* <tr><td><code>RIGHT_HAND</code></td><td>number</td><td>{@link Pose}</td><td><strong>Deprecated:</strong> Use
|
||||
* <code>RightHand</code> instead.</td></tr>
|
||||
* <tr><td><code>BOOM_IN</code></td><td>number</td><td>number</td><td><strong>Deprecated:</strong> Use
|
||||
* <code>BoomIn</code> instead.</td></tr>
|
||||
* <tr><td><code>BOOM_OUT</code></td><td>number</td><td>number</td><td><strong>Deprecated:</strong> Use
|
||||
* <code>BoomOut</code> instead.</td></tr>
|
||||
* <tr><td><code>CONTEXT_MENU</code></td><td>number</td><td>number</td><td><strong>Deprecated:</strong> Use
|
||||
* <code>ContextMenu</code> instead.</td></tr>
|
||||
* <tr><td><code>TOGGLE_MUTE</code></td><td>number</td><td>number</td><td><strong>Deprecated:</strong> Use
|
||||
* <code>ToggleMute</code> instead.</td></tr>
|
||||
* <tr><td><code>TOGGLE_PUSHTOTALK</code></td><td>number</td><td>number</td><td><strong>Deprecated:</strong> Use
|
||||
* <code>TogglePushToTalk</code> instead.</td></tr>
|
||||
* <tr><td><code>SPRINT</code></td><td>number</td><td>number</td><td><strong>Deprecated:</strong> Use
|
||||
* <code>Sprint</code> instead.</td></tr>
|
||||
* <tr><td><code>LONGITUDINAL_BACKWARD</code></td><td>number</td><td>number</td><td><strong>Deprecated:</strong> Use
|
||||
* <code>Backward</code> instead.</td></tr>
|
||||
* <tr><td><code>LONGITUDINAL_FORWARD</code></td><td>number</td><td>number</td><td><strong>Deprecated:</strong> Use
|
||||
* <code>Forward</code> instead.</td></tr>
|
||||
* <tr><td><code>LATERAL_LEFT</code></td><td>number</td><td>number</td><td><strong>Deprecated:</strong> Use
|
||||
* <code>StrafeLeft</code> instead.</td></tr>
|
||||
* <tr><td><code>LATERAL_RIGHT</code></td><td>number</td><td>number</td><td><strong>Deprecated:</strong> Use
|
||||
* <code>StrafeRight</code> instead.</td></tr>
|
||||
* <tr><td><code>VERTICAL_UP</code></td><td>number</td><td>number</td><td><strong>Deprecated:</strong> Use
|
||||
* <code>Up</code> instead.</td></tr>
|
||||
* <tr><td><code>VERTICAL_DOWN</code></td><td>number</td><td>number</td><td><strong>Deprecated:</strong> Use
|
||||
* <code>Down</code> instead.</td></tr>
|
||||
* <tr><td><code>PITCH_DOWN</code></td><td>number</td><td>number</td><td><strong>Deprecated:</strong> Use
|
||||
* <code>PitchDown</code> instead.</td></tr>
|
||||
* <tr><td><code>PITCH_UP</code></td><td>number</td><td>number</td><td><strong>Deprecated:</strong> Use
|
||||
* <code>PitchUp</code> instead.</td></tr>
|
||||
* <tr><td><code>YAW_LEFT</code></td><td>number</td><td>number</td><td><strong>Deprecated:</strong> Use
|
||||
* <code>YawLeft</code> instead.</td></tr>
|
||||
* <tr><td><code>YAW_RIGHT</code></td><td>number</td><td>number</td><td><strong>Deprecated:</strong> Use
|
||||
* <code>YawRight</code> instead.</td></tr>
|
||||
* <tr><td><code>LEFT_HAND_CLICK</code></td><td>number</td><td>number</td><td><strong>Deprecated:</strong> Use
|
||||
* <code>LeftHandClick</code> instead.</td></tr>
|
||||
* <tr><td><code>RIGHT_HAND_CLICK</code></td><td>number</td><td>number</td><td><strong>Deprecated:</strong> Use
|
||||
* <code>RightHandClick</code> instead.</td></tr>
|
||||
* <tr><td><code>SHIFT</code></td><td>number</td><td>number</td><td><strong>Deprecated:</strong> Use
|
||||
* <code>Shift</code> instead.</td></tr>
|
||||
* <tr><td><code>ACTION1</code></td><td>number</td><td>number</td><td><strong>Deprecated:</strong> Use
|
||||
* <code>PrimaryAction</code> instead.</td></tr>
|
||||
* <tr><td><code>ACTION2</code></td><td>number</td><td>number</td><td><strong>Deprecated:</strong> Use
|
||||
* <code>SecondaryAction</code> instead.</td></tr>
|
||||
* <tr><td><code>LEFT_HAND</code></td><td>number</td><td>{@link Pose}</td><td><span class="important">Deprecated: This
|
||||
* action is deprecated and will be removed. Use <code>LeftHand</code> instead.</span></td></tr>
|
||||
* <tr><td><code>RIGHT_HAND</code></td><td>number</td><td>{@link Pose}</td><td><span class="important">Deprecated: This
|
||||
* action is deprecated and will be removed. Use
|
||||
* <code>RightHand</code> instead.</span></td></tr>
|
||||
* <tr><td><code>BOOM_IN</code></td><td>number</td><td>number</td><td><span class="important">Deprecated: This
|
||||
* action is deprecated and will be removed. Use
|
||||
* <code>BoomIn</code> instead.</span></td></tr>
|
||||
* <tr><td><code>BOOM_OUT</code></td><td>number</td><td>number</td><td><span class="important">Deprecated: This
|
||||
* action is deprecated and will be removed. Use
|
||||
* <code>BoomOut</code> instead.</span></td></tr>
|
||||
* <tr><td><code>CONTEXT_MENU</code></td><td>number</td><td>number</td><td><span class="important">Deprecated: This
|
||||
* action is deprecated and will be removed. Use
|
||||
* <code>ContextMenu</code> instead.</span></td></tr>
|
||||
* <tr><td><code>TOGGLE_MUTE</code></td><td>number</td><td>number</td><td><span class="important">Deprecated: This
|
||||
* action is deprecated and will be removed. Use
|
||||
* <code>ToggleMute</code> instead.</span></td></tr>
|
||||
* <tr><td><code>TOGGLE_PUSHTOTALK</code></td><td>number</td><td>number</td><td><span class="important">Deprecated: This
|
||||
* action is deprecated and will be removed. Use
|
||||
* <code>TogglePushToTalk</code> instead.</span></td></tr>
|
||||
* <tr><td><code>SPRINT</code></td><td>number</td><td>number</td><td><span class="important">Deprecated: This
|
||||
* action is deprecated and will be removed. Use
|
||||
* <code>Sprint</code> instead.</span></td></tr>
|
||||
* <tr><td><code>LONGITUDINAL_BACKWARD</code></td><td>number</td><td>number</td><td><span class="important">Deprecated: This
|
||||
* action is deprecated and will be removed. Use
|
||||
* <code>Backward</code> instead.</span></td></tr>
|
||||
* <tr><td><code>LONGITUDINAL_FORWARD</code></td><td>number</td><td>number</td><td><span class="important">Deprecated: This
|
||||
* action is deprecated and will be removed. Use
|
||||
* <code>Forward</code> instead.</span></td></tr>
|
||||
* <tr><td><code>LATERAL_LEFT</code></td><td>number</td><td>number</td><td><span class="important">Deprecated: This
|
||||
* action is deprecated and will be removed. Use
|
||||
* <code>StrafeLeft</code> instead.</span></td></tr>
|
||||
* <tr><td><code>LATERAL_RIGHT</code></td><td>number</td><td>number</td><td><span class="important">Deprecated: This
|
||||
* action is deprecated and will be removed. Use
|
||||
* <code>StrafeRight</code> instead.</span></td></tr>
|
||||
* <tr><td><code>VERTICAL_UP</code></td><td>number</td><td>number</td><td><span class="important">Deprecated: This
|
||||
* action is deprecated and will be removed. Use
|
||||
* <code>Up</code> instead.</span></td></tr>
|
||||
* <tr><td><code>VERTICAL_DOWN</code></td><td>number</td><td>number</td><td><span class="important">Deprecated: This
|
||||
* action is deprecated and will be removed. Use
|
||||
* <code>Down</code> instead.</span></td></tr>
|
||||
* <tr><td><code>PITCH_DOWN</code></td><td>number</td><td>number</td><td><span class="important">Deprecated: This
|
||||
* action is deprecated and will be removed. Use
|
||||
* <code>PitchDown</code> instead.</span></td></tr>
|
||||
* <tr><td><code>PITCH_UP</code></td><td>number</td><td>number</td><td><span class="important">Deprecated: This
|
||||
* action is deprecated and will be removed. Use
|
||||
* <code>PitchUp</code> instead.</span></td></tr>
|
||||
* <tr><td><code>YAW_LEFT</code></td><td>number</td><td>number</td><td><span class="important">Deprecated: This
|
||||
* action is deprecated and will be removed. Use
|
||||
* <code>YawLeft</code> instead.</span></td></tr>
|
||||
* <tr><td><code>YAW_RIGHT</code></td><td>number</td><td>number</td><td><span class="important">Deprecated: This
|
||||
* action is deprecated and will be removed. Use
|
||||
* <code>YawRight</code> instead.</span></td></tr>
|
||||
* <tr><td><code>LEFT_HAND_CLICK</code></td><td>number</td><td>number</td><td><span class="important">Deprecated: This
|
||||
* action is deprecated and will be removed. Use
|
||||
* <code>LeftHandClick</code> instead.</span></td></tr>
|
||||
* <tr><td><code>RIGHT_HAND_CLICK</code></td><td>number</td><td>number</td><td><span class="important">Deprecated: This
|
||||
* action is deprecated and will be removed. Use
|
||||
* <code>RightHandClick</code> instead.</span></td></tr>
|
||||
* <tr><td><code>SHIFT</code></td><td>number</td><td>number</td><td><span class="important">Deprecated: This
|
||||
* action is deprecated and will be removed. Use
|
||||
* <code>Shift</code> instead.</span></td></tr>
|
||||
* <tr><td><code>ACTION1</code></td><td>number</td><td>number</td><td><span class="important">Deprecated: This
|
||||
* action is deprecated and will be removed. Use
|
||||
* <code>PrimaryAction</code> instead.</span></td></tr>
|
||||
* <tr><td><code>ACTION2</code></td><td>number</td><td>number</td><td><span class="important">Deprecated: This
|
||||
* action is deprecated and will be removed. Use
|
||||
* <code>SecondaryAction</code> instead.</span></td></tr>
|
||||
*
|
||||
* <tr><td colSpan=4><strong>Deprecated Trackers</strong></td>
|
||||
* <tr><td><code>TrackedObject00</code></td><td>number</td><td>{@link Pose}</td><td><strong>Deprecated: </strong> No
|
||||
* action.</td></tr>
|
||||
* <tr><td><code>TrackedObject01</code></td><td>number</td><td>{@link Pose}</td><td><strong>Deprecated: </strong> No
|
||||
* action.</td></tr>
|
||||
* <tr><td><code>TrackedObject02</code></td><td>number</td><td>{@link Pose}</td><td><strong>Deprecated: </strong> No
|
||||
* action.</td></tr>
|
||||
* <tr><td><code>TrackedObject03</code></td><td>number</td><td>{@link Pose}</td><td><strong>Deprecated: </strong> No
|
||||
* action.</td></tr>
|
||||
* <tr><td><code>TrackedObject04</code></td><td>number</td><td>{@link Pose}</td><td><strong>Deprecated: </strong> No
|
||||
* action.</td></tr>
|
||||
* <tr><td><code>TrackedObject05</code></td><td>number</td><td>{@link Pose}</td><td><strong>Deprecated: </strong> No
|
||||
* action.</td></tr>
|
||||
* <tr><td><code>TrackedObject06</code></td><td>number</td><td>{@link Pose}</td><td><strong>Deprecated: </strong> No
|
||||
* action.</td></tr>
|
||||
* <tr><td><code>TrackedObject07</code></td><td>number</td><td>{@link Pose}</td><td><strong>Deprecated: </strong> No
|
||||
* action.</td></tr>
|
||||
* <tr><td><code>TrackedObject08</code></td><td>number</td><td>{@link Pose}</td><td><strong>Deprecated: </strong> No
|
||||
* action.</td></tr>
|
||||
* <tr><td><code>TrackedObject09</code></td><td>number</td><td>{@link Pose}</td><td><strong>Deprecated: </strong> No
|
||||
* action.</td></tr>
|
||||
* <tr><td><code>TrackedObject10</code></td><td>number</td><td>{@link Pose}</td><td><strong>Deprecated: </strong> No
|
||||
* action.</td></tr>
|
||||
* <tr><td><code>TrackedObject11</code></td><td>number</td><td>{@link Pose}</td><td><strong>Deprecated: </strong> No
|
||||
* action.</td></tr>
|
||||
* <tr><td><code>TrackedObject12</code></td><td>number</td><td>{@link Pose}</td><td><strong>Deprecated: </strong> No
|
||||
* action.</td></tr>
|
||||
* <tr><td><code>TrackedObject13</code></td><td>number</td><td>{@link Pose}</td><td><strong>Deprecated: </strong> No
|
||||
* action.</td></tr>
|
||||
* <tr><td><code>TrackedObject14</code></td><td>number</td><td>{@link Pose}</td><td><strong>Deprecated: </strong> No
|
||||
* action.</td></tr>
|
||||
* <tr><td><code>TrackedObject15</code></td><td>number</td><td>{@link Pose}</td><td><strong>Deprecated: </strong> No
|
||||
* action.</td></tr>
|
||||
* <tr><td><code>TrackedObject00</code></td><td>number</td><td>{@link Pose}</td><td><span class="important">Deprecated:
|
||||
* This action is deprecated and will be removed. It takes no action.</span></td></tr>
|
||||
* <tr><td><code>TrackedObject01</code></td><td>number</td><td>{@link Pose}</td><td><span class="important">Deprecated:
|
||||
* This action is deprecated and will be removed. It takes no action.</span></td></tr>
|
||||
* <tr><td><code>TrackedObject02</code></td><td>number</td><td>{@link Pose}</td><td><span class="important">Deprecated:
|
||||
* This action is deprecated and will be removed. It takes no action.</span></td></tr>
|
||||
* <tr><td><code>TrackedObject03</code></td><td>number</td><td>{@link Pose}</td><td><span class="important">Deprecated:
|
||||
* This action is deprecated and will be removed. It takes no action.</span></td></tr>
|
||||
* <tr><td><code>TrackedObject04</code></td><td>number</td><td>{@link Pose}</td><td><span class="important">Deprecated:
|
||||
* This action is deprecated and will be removed. It takes no action.</span></td></tr>
|
||||
* <tr><td><code>TrackedObject05</code></td><td>number</td><td>{@link Pose}</td><td><span class="important">Deprecated:
|
||||
* This action is deprecated and will be removed. It takes no action.</span></td></tr>
|
||||
* <tr><td><code>TrackedObject06</code></td><td>number</td><td>{@link Pose}</td><td><span class="important">Deprecated:
|
||||
* This action is deprecated and will be removed. It takes no action.</span></td></tr>
|
||||
* <tr><td><code>TrackedObject07</code></td><td>number</td><td>{@link Pose}</td><td><span class="important">Deprecated:
|
||||
* This action is deprecated and will be removed. It takes no action.</span></td></tr>
|
||||
* <tr><td><code>TrackedObject08</code></td><td>number</td><td>{@link Pose}</td><td><span class="important">Deprecated:
|
||||
* This action is deprecated and will be removed. It takes no action.</span></td></tr>
|
||||
* <tr><td><code>TrackedObject09</code></td><td>number</td><td>{@link Pose}</td><td><span class="important">Deprecated:
|
||||
* This action is deprecated and will be removed. It takes no action.</span></td></tr>
|
||||
* <tr><td><code>TrackedObject10</code></td><td>number</td><td>{@link Pose}</td><td><span class="important">Deprecated:
|
||||
* This action is deprecated and will be removed. It takes no action.</span></td></tr>
|
||||
* <tr><td><code>TrackedObject11</code></td><td>number</td><td>{@link Pose}</td><td><span class="important">Deprecated:
|
||||
* This action is deprecated and will be removed. It takes no action.</span></td></tr>
|
||||
* <tr><td><code>TrackedObject12</code></td><td>number</td><td>{@link Pose}</td><td><span class="important">Deprecated:
|
||||
* This action is deprecated and will be removed. It takes no action.</span></td></tr>
|
||||
* <tr><td><code>TrackedObject13</code></td><td>number</td><td>{@link Pose}</td><td><span class="important">Deprecated:
|
||||
* This action is deprecated and will be removed. It takes no action.</span></td></tr>
|
||||
* <tr><td><code>TrackedObject14</code></td><td>number</td><td>{@link Pose}</td><td><span class="important">Deprecated:
|
||||
* This action is deprecated and will be removed. It takes no action.</span></td></tr>
|
||||
* <tr><td><code>TrackedObject15</code></td><td>number</td><td>{@link Pose}</td><td><span class="important">Deprecated:
|
||||
* This action is deprecated and will be removed. It takes no action.</span></td></tr>
|
||||
* </tbody>
|
||||
* </table>
|
||||
* @typedef {object} Controller.Actions
|
||||
|
|
|
@ -76,7 +76,7 @@ namespace controller {
|
|||
* Get a list of all available actions.
|
||||
* @function Controller.getAllActions
|
||||
* @returns {Action[]} All available actions.
|
||||
* @deprecated This function no longer works.
|
||||
* @deprecated This function is deprecated and will be removed. It no longer works.
|
||||
*/
|
||||
// FIXME: This function causes a JavaScript crash: https://highfidelity.manuscript.com/f/cases/edit/13921
|
||||
Q_INVOKABLE QVector<Action> getAllActions();
|
||||
|
@ -86,7 +86,7 @@ namespace controller {
|
|||
* @function Controller.getAvailableInputs
|
||||
* @param {number} deviceID - Integer ID of the hardware device.
|
||||
* @returns {NamedPair[]} All available inputs for the device.
|
||||
* @deprecated This function no longer works.
|
||||
* @deprecated This function is deprecated and will be removed. It no longer works.
|
||||
*/
|
||||
// FIXME: This function causes a JavaScript crash: https://highfidelity.manuscript.com/f/cases/edit/13922
|
||||
Q_INVOKABLE QVector<Input::NamedPair> getAvailableInputs(unsigned int device);
|
||||
|
|
|
@ -46,7 +46,7 @@
|
|||
#include <TextureCache.h>
|
||||
#include "CompositorHelper.h"
|
||||
#include "Logging.h"
|
||||
|
||||
#include "RefreshRateController.h"
|
||||
extern QThread* RENDER_THREAD;
|
||||
|
||||
class PresentThread : public QThread, public Dependency {
|
||||
|
@ -60,12 +60,16 @@ public:
|
|||
shutdown();
|
||||
});
|
||||
setObjectName("Present");
|
||||
|
||||
_refreshRateController = std::make_shared<RefreshRateController>();
|
||||
}
|
||||
|
||||
~PresentThread() {
|
||||
shutdown();
|
||||
}
|
||||
|
||||
auto getRefreshRateController() { return _refreshRateController; }
|
||||
|
||||
void shutdown() {
|
||||
if (isRunning()) {
|
||||
// First ensure that we turn off any current display plugin
|
||||
|
@ -185,13 +189,15 @@ public:
|
|||
{
|
||||
PROFILE_RANGE(render, "PluginPresent")
|
||||
gl::globalLock();
|
||||
currentPlugin->present();
|
||||
currentPlugin->present(_refreshRateController);
|
||||
gl::globalRelease(false);
|
||||
CHECK_GL_ERROR();
|
||||
}
|
||||
#if defined(Q_OS_MAC)
|
||||
_context->doneCurrent();
|
||||
#endif
|
||||
|
||||
_refreshRateController->sleepThreadIfNeeded(this, currentPlugin->isHmd());
|
||||
}
|
||||
|
||||
_context->doneCurrent();
|
||||
|
@ -240,6 +246,7 @@ private:
|
|||
bool _finishedOtherThreadOperation { false };
|
||||
std::queue<OpenGLDisplayPlugin*> _newPluginQueue;
|
||||
gl::Context* _context { nullptr };
|
||||
std::shared_ptr<RefreshRateController> _refreshRateController { nullptr };
|
||||
};
|
||||
|
||||
bool OpenGLDisplayPlugin::activate() {
|
||||
|
@ -693,11 +700,11 @@ void OpenGLDisplayPlugin::internalPresent() {
|
|||
_presentRate.increment();
|
||||
}
|
||||
|
||||
void OpenGLDisplayPlugin::present() {
|
||||
void OpenGLDisplayPlugin::present(const std::shared_ptr<RefreshRateController>& refreshRateController) {
|
||||
auto frameId = (uint64_t)presentCount();
|
||||
PROFILE_RANGE_EX(render, __FUNCTION__, 0xffffff00, frameId)
|
||||
uint64_t startPresent = usecTimestampNow();
|
||||
|
||||
refreshRateController->clockStartTime();
|
||||
{
|
||||
PROFILE_RANGE_EX(render, "updateFrameData", 0xff00ff00, frameId)
|
||||
updateFrameData();
|
||||
|
@ -741,6 +748,7 @@ void OpenGLDisplayPlugin::present() {
|
|||
}
|
||||
|
||||
// Take the composite framebuffer and send it to the output device
|
||||
refreshRateController->clockEndTime();
|
||||
{
|
||||
PROFILE_RANGE_EX(render, "internalPresent", 0xff00ffff, frameId)
|
||||
internalPresent();
|
||||
|
@ -748,7 +756,10 @@ void OpenGLDisplayPlugin::present() {
|
|||
|
||||
gpu::Backend::freeGPUMemSize.set(gpu::gl::getFreeDedicatedMemory());
|
||||
} else if (alwaysPresent()) {
|
||||
refreshRateController->clockEndTime();
|
||||
internalPresent();
|
||||
} else {
|
||||
refreshRateController->clockEndTime();
|
||||
}
|
||||
_movingAveragePresent.addSample((float)(usecTimestampNow() - startPresent));
|
||||
}
|
||||
|
@ -765,6 +776,13 @@ float OpenGLDisplayPlugin::presentRate() const {
|
|||
return _presentRate.rate();
|
||||
}
|
||||
|
||||
std::function<void(int)> OpenGLDisplayPlugin::getRefreshRateOperator() {
|
||||
return [](int targetRefreshRate) {
|
||||
auto refreshRateController = DependencyManager::get<PresentThread>()->getRefreshRateController();
|
||||
refreshRateController->setRefreshRateLimitPeriod(targetRefreshRate);
|
||||
};
|
||||
}
|
||||
|
||||
void OpenGLDisplayPlugin::resetPresentRate() {
|
||||
// FIXME
|
||||
// _presentRate = RateCounter<100>();
|
||||
|
|
|
@ -29,6 +29,8 @@ namespace gpu {
|
|||
}
|
||||
}
|
||||
|
||||
class RefreshRateController;
|
||||
|
||||
class OpenGLDisplayPlugin : public DisplayPlugin {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(float hudAlpha MEMBER _hudAlpha)
|
||||
|
@ -41,6 +43,9 @@ public:
|
|||
~OpenGLDisplayPlugin();
|
||||
// These must be final to ensure proper ordering of operations
|
||||
// between the main thread and the presentation thread
|
||||
|
||||
static std::function<void(int)> getRefreshRateOperator();
|
||||
|
||||
bool activate() override final;
|
||||
void deactivate() override final;
|
||||
bool startStandBySession() override final;
|
||||
|
@ -123,7 +128,7 @@ protected:
|
|||
|
||||
void withOtherThreadContext(std::function<void()> f) const;
|
||||
|
||||
void present();
|
||||
void present(const std::shared_ptr<RefreshRateController>& refreshRateController);
|
||||
virtual void swapBuffers();
|
||||
ivec4 eyeViewport(Eye eye) const;
|
||||
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
//
|
||||
// RefreshRateController.cpp
|
||||
// libraries/display-pluging/src/display-plugin
|
||||
//
|
||||
// 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 "RefreshRateController.h"
|
||||
|
||||
#include <QtCore/QThread>
|
||||
#include <NumericalConstants.h>
|
||||
|
||||
long int hzToDurationNanoseconds(int refreshRate) {
|
||||
return (int64_t) (NSECS_PER_SECOND / (quint64) refreshRate);
|
||||
}
|
||||
|
||||
int durationNanosecondsToHz(int64_t refreshRateLimitPeriod) {
|
||||
return (int) (NSECS_PER_SECOND / (quint64) refreshRateLimitPeriod);
|
||||
}
|
||||
|
||||
void RefreshRateController::setRefreshRateLimitPeriod(int refreshRateLimit) {
|
||||
_refreshRateLimitPeriod = hzToDurationNanoseconds(refreshRateLimit);
|
||||
}
|
||||
|
||||
int RefreshRateController::getRefreshRateLimitPeriod() const {
|
||||
return durationNanosecondsToHz(_refreshRateLimitPeriod);
|
||||
}
|
||||
|
||||
void RefreshRateController::sleepThreadIfNeeded(QThread* thread, bool isHmd) {
|
||||
if (!isHmd) {
|
||||
static const std::chrono::nanoseconds EPSILON = std::chrono::milliseconds(1);
|
||||
auto duration = std::chrono::duration_cast<std::chrono::nanoseconds>(_endTime - _startTime);
|
||||
auto refreshRateLimitPeriod = std::chrono::nanoseconds(_refreshRateLimitPeriod);
|
||||
auto sleepDuration = refreshRateLimitPeriod - (duration + EPSILON);
|
||||
if (sleepDuration.count() > 0) {
|
||||
thread->msleep(std::chrono::duration_cast<std::chrono::milliseconds>(sleepDuration).count());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
//
|
||||
// RefreshRateController.h
|
||||
// libraries/display-pluging/src/display-plugin
|
||||
//
|
||||
// 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_RefreshRateController_h
|
||||
#define hifi_RefreshRateController_h
|
||||
|
||||
#include <DependencyManager.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <atomic>
|
||||
|
||||
class QThread;
|
||||
|
||||
class RefreshRateController {
|
||||
public:
|
||||
RefreshRateController() = default;
|
||||
~RefreshRateController() = default;
|
||||
|
||||
void setRefreshRateLimitPeriod(int refreshRateLimit);
|
||||
int getRefreshRateLimitPeriod() const;
|
||||
|
||||
void clockStartTime() { _startTime = std::chrono::high_resolution_clock::now(); }
|
||||
void clockEndTime() { _endTime = std::chrono::high_resolution_clock::now(); }
|
||||
void sleepThreadIfNeeded(QThread* thread, bool isHmd);
|
||||
private:
|
||||
std::chrono::time_point<std::chrono::high_resolution_clock> _startTime { std::chrono::high_resolution_clock::now() };
|
||||
std::chrono::time_point<std::chrono::high_resolution_clock> _endTime { std::chrono::high_resolution_clock::now() };
|
||||
std::atomic<int64_t> _refreshRateLimitPeriod { 50 };
|
||||
|
||||
};
|
||||
|
||||
#endif
|
|
@ -791,7 +791,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
|
|||
bool otherOverwrites = overwriteLocalData && !weOwnSimulation;
|
||||
// calculate hasGrab once outside the lambda rather than calling it every time inside
|
||||
bool hasGrab = stillHasGrabAction();
|
||||
auto shouldUpdate = [this, lastEdited, otherOverwrites, filterRejection, hasGrab](quint64 updatedTimestamp, bool valueChanged) {
|
||||
auto shouldUpdate = [lastEdited, otherOverwrites, filterRejection, hasGrab](quint64 updatedTimestamp, bool valueChanged) {
|
||||
if (hasGrab) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -1550,7 +1550,7 @@ public slots:
|
|||
* @function Entities.getMeshes
|
||||
* @param {Uuid} entityID - The ID of the <code>Model</code> or <code>PolyVox</code> entity to get the meshes of.
|
||||
* @param {Entities~getMeshesCallback} callback - The function to call upon completion.
|
||||
* @deprecated Use the {@link Graphics} API instead.
|
||||
* @deprecated This function is deprecated and will be removed. Use the {@link Graphics} API instead.
|
||||
*/
|
||||
/**jsdoc
|
||||
* Called when {@link Entities.getMeshes} is complete.
|
||||
|
@ -1559,7 +1559,7 @@ public slots:
|
|||
* <code>Model</code> or <code>PolyVox</code> entity; otherwise <code>undefined</code>.
|
||||
* @param {boolean} success - <code>true</code> if the {@link Entities.getMeshes} call was successful, <code>false</code>
|
||||
* otherwise. The call may be unsuccessful if the requested entity could not be found.
|
||||
* @deprecated Use the {@link Graphics} API instead.
|
||||
* @deprecated This function is deprecated and will be removed. Use the {@link Graphics} API instead.
|
||||
*/
|
||||
// FIXME move to a renderable entity interface
|
||||
Q_INVOKABLE void getMeshes(const QUuid& entityID, QScriptValue callback);
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
#include <ResourceManager.h>
|
||||
#include <PathUtils.h>
|
||||
#include <image/ColorChannel.h>
|
||||
#include <FaceshiftConstants.h>
|
||||
|
||||
#include "FBXSerializer.h"
|
||||
|
||||
|
@ -483,7 +484,7 @@ bool GLTFSerializer::addMesh(const QJsonObject& object) {
|
|||
GLTFMeshPrimitiveAttr target;
|
||||
foreach(const QString & tarKey, tarKeys) {
|
||||
int tarVal;
|
||||
getIntVal(jsAttributes, tarKey, tarVal, target.defined);
|
||||
getIntVal(jsTarget, tarKey, tarVal, target.defined);
|
||||
target.values.insert(tarKey, tarVal);
|
||||
}
|
||||
primitive.targets.push_back(target);
|
||||
|
@ -493,7 +494,18 @@ bool GLTFSerializer::addMesh(const QJsonObject& object) {
|
|||
mesh.primitives.push_back(primitive);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QJsonObject jsExtras;
|
||||
GLTFMeshExtra extras;
|
||||
if (getObjectVal(object, "extras", jsExtras, mesh.defined)) {
|
||||
QJsonArray jsTargetNames;
|
||||
if (getObjectArrayVal(jsExtras, "targetNames", jsTargetNames, extras.defined)) {
|
||||
foreach (const QJsonValue& tarName, jsTargetNames) {
|
||||
extras.targetNames.push_back(tarName.toString());
|
||||
}
|
||||
}
|
||||
mesh.extras = extras;
|
||||
}
|
||||
|
||||
_file.meshes.push_back(mesh);
|
||||
|
@ -751,7 +763,24 @@ void GLTFSerializer::getSkinInverseBindMatrices(std::vector<std::vector<float>>&
|
|||
}
|
||||
}
|
||||
|
||||
bool GLTFSerializer::buildGeometry(HFMModel& hfmModel, const hifi::URL& url) {
|
||||
void GLTFSerializer::generateTargetData(int index, float weight, QVector<glm::vec3>& returnVector) {
|
||||
GLTFAccessor& accessor = _file.accessors[index];
|
||||
GLTFBufferView& bufferview = _file.bufferviews[accessor.bufferView];
|
||||
GLTFBuffer& buffer = _file.buffers[bufferview.buffer];
|
||||
int accBoffset = accessor.defined["byteOffset"] ? accessor.byteOffset : 0;
|
||||
QVector<float> storedValues;
|
||||
addArrayOfType(buffer.blob,
|
||||
bufferview.byteOffset + accBoffset,
|
||||
accessor.count,
|
||||
storedValues,
|
||||
accessor.type,
|
||||
accessor.componentType);
|
||||
for (int n = 0; n < storedValues.size(); n = n + 3) {
|
||||
returnVector.push_back(glm::vec3(weight * storedValues[n], weight * storedValues[n + 1], weight * storedValues[n + 2]));
|
||||
}
|
||||
}
|
||||
|
||||
bool GLTFSerializer::buildGeometry(HFMModel& hfmModel, const hifi::VariantHash& mapping, const hifi::URL& url) {
|
||||
int numNodes = _file.nodes.size();
|
||||
|
||||
// Build dependencies
|
||||
|
@ -1138,6 +1167,82 @@ bool GLTFSerializer::buildGeometry(HFMModel& hfmModel, const hifi::URL& url) {
|
|||
if (mesh.texCoords.size() == 0 && !hfmModel.hasSkeletonJoints) {
|
||||
for (int i = 0; i < part.triangleIndices.size(); i++) { mesh.texCoords.push_back(glm::vec2(0.0, 1.0)); }
|
||||
}
|
||||
|
||||
// Build morph targets (blend shapes)
|
||||
if (!primitive.targets.isEmpty()) {
|
||||
|
||||
// Build list of blendshapes from FST
|
||||
typedef QPair<int, float> WeightedIndex;
|
||||
hifi::VariantHash blendshapeMappings = mapping.value("bs").toHash();
|
||||
QMultiHash<QString, WeightedIndex> blendshapeIndices;
|
||||
|
||||
for (int i = 0;; i++) {
|
||||
hifi::ByteArray blendshapeName = FACESHIFT_BLENDSHAPES[i];
|
||||
if (blendshapeName.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
QList<QVariant> mappings = blendshapeMappings.values(blendshapeName);
|
||||
foreach (const QVariant& mapping, mappings) {
|
||||
QVariantList blendshapeMapping = mapping.toList();
|
||||
blendshapeIndices.insert(blendshapeMapping.at(0).toByteArray(), WeightedIndex(i, blendshapeMapping.at(1).toFloat()));
|
||||
}
|
||||
}
|
||||
|
||||
// glTF morph targets may or may not have names. if they are labeled, add them based on
|
||||
// the corresponding names from the FST. otherwise, just add them in the order they are given
|
||||
mesh.blendshapes.resize(blendshapeMappings.size());
|
||||
auto values = blendshapeIndices.values();
|
||||
auto keys = blendshapeIndices.keys();
|
||||
auto names = _file.meshes[node.mesh].extras.targetNames;
|
||||
QVector<double> weights = _file.meshes[node.mesh].weights;
|
||||
|
||||
for (int weightedIndex = 0; weightedIndex < values.size(); weightedIndex++) {
|
||||
float weight = 0.1f;
|
||||
int indexFromMapping = weightedIndex;
|
||||
int targetIndex = weightedIndex;
|
||||
hfmModel.blendshapeChannelNames.push_back("target_" + QString::number(weightedIndex));
|
||||
|
||||
if (!names.isEmpty()) {
|
||||
targetIndex = names.indexOf(keys[weightedIndex]);
|
||||
indexFromMapping = values[weightedIndex].first;
|
||||
weight = weight * values[weightedIndex].second;
|
||||
hfmModel.blendshapeChannelNames[weightedIndex] = keys[weightedIndex];
|
||||
}
|
||||
HFMBlendshape& blendshape = mesh.blendshapes[indexFromMapping];
|
||||
blendshape.indices = part.triangleIndices;
|
||||
auto target = primitive.targets[targetIndex];
|
||||
|
||||
QVector<glm::vec3> normals;
|
||||
QVector<glm::vec3> vertices;
|
||||
|
||||
if (weights.size() == primitive.targets.size()) {
|
||||
int targetWeight = weights[targetIndex];
|
||||
if (targetWeight != 0) {
|
||||
weight = weight * targetWeight;
|
||||
}
|
||||
}
|
||||
|
||||
if (target.values.contains((QString) "NORMAL")) {
|
||||
generateTargetData(target.values.value((QString) "NORMAL"), weight, normals);
|
||||
}
|
||||
if (target.values.contains((QString) "POSITION")) {
|
||||
generateTargetData(target.values.value((QString) "POSITION"), weight, vertices);
|
||||
}
|
||||
bool isNewBlendshape = blendshape.vertices.size() < vertices.size();
|
||||
int count = 0;
|
||||
for (int i : blendshape.indices) {
|
||||
if (isNewBlendshape) {
|
||||
blendshape.vertices.push_back(vertices[i]);
|
||||
blendshape.normals.push_back(normals[i]);
|
||||
} else {
|
||||
blendshape.vertices[count] = blendshape.vertices[count] + vertices[i];
|
||||
blendshape.normals[count] = blendshape.normals[count] + normals[i];
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mesh.meshExtents.reset();
|
||||
foreach(const glm::vec3& vertex, mesh.vertices) {
|
||||
mesh.meshExtents.addPoint(vertex);
|
||||
|
@ -1183,7 +1288,7 @@ HFMModel::Pointer GLTFSerializer::read(const hifi::ByteArray& data, const hifi::
|
|||
//_file.dump();
|
||||
auto hfmModelPtr = std::make_shared<HFMModel>();
|
||||
HFMModel& hfmModel = *hfmModelPtr;
|
||||
buildGeometry(hfmModel, _url);
|
||||
buildGeometry(hfmModel, mapping, _url);
|
||||
|
||||
//hfmDebugDump(data);
|
||||
return hfmModelPtr;
|
||||
|
|
|
@ -159,9 +159,20 @@ struct GLTFMeshPrimitive {
|
|||
}
|
||||
};
|
||||
|
||||
struct GLTFMeshExtra {
|
||||
QVector<QString> targetNames;
|
||||
QMap<QString, bool> defined;
|
||||
void dump() {
|
||||
if (defined["targetNames"]) {
|
||||
qCDebug(modelformat) << "targetNames: " << targetNames;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct GLTFMesh {
|
||||
QString name;
|
||||
QVector<GLTFMeshPrimitive> primitives;
|
||||
GLTFMeshExtra extras;
|
||||
QVector<double> weights;
|
||||
QMap<QString, bool> defined;
|
||||
void dump() {
|
||||
|
@ -172,6 +183,10 @@ struct GLTFMesh {
|
|||
qCDebug(modelformat) << "primitives: ";
|
||||
foreach(auto prim, primitives) prim.dump();
|
||||
}
|
||||
if (defined["extras"]) {
|
||||
qCDebug(modelformat) << "extras: ";
|
||||
extras.dump();
|
||||
}
|
||||
if (defined["weights"]) {
|
||||
qCDebug(modelformat) << "weights: " << weights;
|
||||
}
|
||||
|
@ -713,8 +728,9 @@ private:
|
|||
|
||||
glm::mat4 getModelTransform(const GLTFNode& node);
|
||||
void getSkinInverseBindMatrices(std::vector<std::vector<float>>& inverseBindMatrixValues);
|
||||
void generateTargetData(int index, float weight, QVector<glm::vec3>& returnVector);
|
||||
|
||||
bool buildGeometry(HFMModel& hfmModel, const hifi::URL& url);
|
||||
bool buildGeometry(HFMModel& hfmModel, const hifi::VariantHash& mapping, const hifi::URL& url);
|
||||
bool parseGLTF(const hifi::ByteArray& data);
|
||||
|
||||
bool getStringVal(const QJsonObject& object, const QString& fieldname,
|
||||
|
|
|
@ -37,7 +37,6 @@ ContextMetricCount Texture::_textureCPUCount;
|
|||
ContextMetricSize Texture::_textureCPUMemSize;
|
||||
std::atomic<Texture::Size> Texture::_allowedCPUMemoryUsage { 0 };
|
||||
|
||||
|
||||
#define MIN_CORES_FOR_INCREMENTAL_TEXTURES 5
|
||||
bool recommendedSparseTextures = (QThread::idealThreadCount() >= MIN_CORES_FOR_INCREMENTAL_TEXTURES);
|
||||
|
||||
|
|
|
@ -550,7 +550,7 @@ public:
|
|||
void setUsage(const Usage& usage) { _usage = usage; }
|
||||
Usage getUsage() const { return _usage; }
|
||||
|
||||
// For Cube Texture, it's possible to generate the irradiance spherical harmonics and make them availalbe with the texture
|
||||
// For Cube Texture, it's possible to generate the irradiance spherical harmonics and make them available with the texture
|
||||
bool generateIrradiance(gpu::BackendTarget target);
|
||||
const SHPointer& getIrradiance(uint16 slice = 0) const { return _irradiance; }
|
||||
void overrideIrradiance(SHPointer irradiance) { _irradiance = irradiance; }
|
||||
|
|
|
@ -103,7 +103,7 @@ gpu::Element getHDRTextureFormatForTarget(BackendTarget target, bool compressed)
|
|||
}
|
||||
}
|
||||
|
||||
TextureUsage::TextureLoader TextureUsage::getTextureLoaderForType(Type type, const QVariantMap& options) {
|
||||
TextureUsage::TextureLoader TextureUsage::getTextureLoaderForType(Type type) {
|
||||
switch (type) {
|
||||
case ALBEDO_TEXTURE:
|
||||
return image::TextureUsage::createAlbedoTextureFromImage;
|
||||
|
@ -114,11 +114,7 @@ TextureUsage::TextureLoader TextureUsage::getTextureLoaderForType(Type type, con
|
|||
case SKY_TEXTURE:
|
||||
return image::TextureUsage::createCubeTextureFromImage;
|
||||
case AMBIENT_TEXTURE:
|
||||
if (options.value("generateIrradiance", true).toBool()) {
|
||||
return image::TextureUsage::createAmbientCubeTextureAndIrradianceFromImage;
|
||||
} else {
|
||||
return image::TextureUsage::createAmbientCubeTextureFromImage;
|
||||
}
|
||||
return image::TextureUsage::createAmbientCubeTextureAndIrradianceFromImage;
|
||||
case BUMP_TEXTURE:
|
||||
return image::TextureUsage::createNormalTextureFromBumpImage;
|
||||
case NORMAL_TEXTURE:
|
||||
|
@ -188,21 +184,11 @@ gpu::TexturePointer TextureUsage::createMetallicTextureFromImage(Image&& srcImag
|
|||
return process2DTextureGrayscaleFromImage(std::move(srcImage), srcImageName, compress, target, false, abortProcessing);
|
||||
}
|
||||
|
||||
gpu::TexturePointer TextureUsage::createCubeTextureAndIrradianceFromImage(Image&& srcImage, const std::string& srcImageName,
|
||||
bool compress, BackendTarget target, const std::atomic<bool>& abortProcessing) {
|
||||
return processCubeTextureColorFromImage(std::move(srcImage), srcImageName, compress, target, CUBE_GENERATE_IRRADIANCE, abortProcessing);
|
||||
}
|
||||
|
||||
gpu::TexturePointer TextureUsage::createCubeTextureFromImage(Image&& srcImage, const std::string& srcImageName,
|
||||
bool compress, BackendTarget target, const std::atomic<bool>& abortProcessing) {
|
||||
return processCubeTextureColorFromImage(std::move(srcImage), srcImageName, compress, target, CUBE_DEFAULT, abortProcessing);
|
||||
}
|
||||
|
||||
gpu::TexturePointer TextureUsage::createAmbientCubeTextureFromImage(Image&& image, const std::string& srcImageName,
|
||||
bool compress, gpu::BackendTarget target, const std::atomic<bool>& abortProcessing) {
|
||||
return processCubeTextureColorFromImage(std::move(image), srcImageName, compress, target, CUBE_GGX_CONVOLVE, abortProcessing);
|
||||
}
|
||||
|
||||
gpu::TexturePointer TextureUsage::createAmbientCubeTextureAndIrradianceFromImage(Image&& image, const std::string& srcImageName,
|
||||
bool compress, gpu::BackendTarget target, const std::atomic<bool>& abortProcessing) {
|
||||
return processCubeTextureColorFromImage(std::move(image), srcImageName, compress, target, CUBE_GENERATE_IRRADIANCE | CUBE_GGX_CONVOLVE, abortProcessing);
|
||||
|
@ -388,7 +374,7 @@ gpu::TexturePointer processImage(std::shared_ptr<QIODevice> content, const std::
|
|||
if (sourceChannel != ColorChannel::NONE) {
|
||||
mapToRedChannel(image, sourceChannel);
|
||||
}
|
||||
|
||||
|
||||
auto loader = TextureUsage::getTextureLoaderForType(textureType);
|
||||
auto texture = loader(std::move(image), filename, compress, target, abortProcessing);
|
||||
|
||||
|
|
|
@ -76,7 +76,7 @@ enum Type {
|
|||
};
|
||||
|
||||
using TextureLoader = std::function<gpu::TexturePointer(Image&&, const std::string&, bool, gpu::BackendTarget, const std::atomic<bool>&)>;
|
||||
TextureLoader getTextureLoaderForType(Type type, const QVariantMap& options = QVariantMap());
|
||||
TextureLoader getTextureLoaderForType(Type type);
|
||||
|
||||
gpu::TexturePointer create2DTextureFromImage(Image&& image, const std::string& srcImageName,
|
||||
bool compress, gpu::BackendTarget target, const std::atomic<bool>& abortProcessing);
|
||||
|
@ -98,10 +98,6 @@ gpu::TexturePointer createMetallicTextureFromImage(Image&& image, const std::str
|
|||
bool compress, gpu::BackendTarget target, const std::atomic<bool>& abortProcessing);
|
||||
gpu::TexturePointer createCubeTextureFromImage(Image&& image, const std::string& srcImageName,
|
||||
bool compress, gpu::BackendTarget target, const std::atomic<bool>& abortProcessing);
|
||||
gpu::TexturePointer createCubeTextureAndIrradianceFromImage(Image&& image, const std::string& srcImageName,
|
||||
bool compress, gpu::BackendTarget target, const std::atomic<bool>& abortProcessing);
|
||||
gpu::TexturePointer createAmbientCubeTextureFromImage(Image&& image, const std::string& srcImageName,
|
||||
bool compress, gpu::BackendTarget target, const std::atomic<bool>& abortProcessing);
|
||||
gpu::TexturePointer createAmbientCubeTextureAndIrradianceFromImage(Image&& image, const std::string& srcImageName,
|
||||
bool compress, gpu::BackendTarget target, const std::atomic<bool>& abortProcessing);
|
||||
gpu::TexturePointer createLightmapTextureFromImage(Image&& image, const std::string& srcImageName,
|
||||
|
|
|
@ -311,13 +311,13 @@ gpu::BackendTarget getBackendTarget() {
|
|||
}
|
||||
|
||||
/// Returns a texture version of an image file
|
||||
gpu::TexturePointer TextureCache::getImageTexture(const QString& path, image::TextureUsage::Type type, QVariantMap options) {
|
||||
gpu::TexturePointer TextureCache::getImageTexture(const QString& path, image::TextureUsage::Type type) {
|
||||
QImage image = QImage(path);
|
||||
if (image.isNull()) {
|
||||
qCWarning(networking) << "Unable to load required resource texture" << path;
|
||||
return nullptr;
|
||||
}
|
||||
auto loader = image::TextureUsage::getTextureLoaderForType(type, options);
|
||||
auto loader = image::TextureUsage::getTextureLoaderForType(type);
|
||||
|
||||
#ifdef USE_GLES
|
||||
constexpr bool shouldCompress = true;
|
||||
|
@ -635,11 +635,9 @@ void NetworkTexture::makeLocalRequest() {
|
|||
}
|
||||
|
||||
bool NetworkTexture::handleFailedRequest(ResourceRequest::Result result) {
|
||||
if (_currentlyLoadingResourceType != ResourceType::KTX
|
||||
&& result == ResourceRequest::Result::RedirectFail) {
|
||||
|
||||
if (_shouldFailOnRedirect && result == ResourceRequest::Result::RedirectFail) {
|
||||
auto newPath = _request->getRelativePathUrl();
|
||||
if (newPath.fileName().endsWith(".ktx")) {
|
||||
if (newPath.fileName().toLower().endsWith(".ktx")) {
|
||||
_currentlyLoadingResourceType = ResourceType::KTX;
|
||||
_activeUrl = newPath;
|
||||
_shouldFailOnRedirect = false;
|
||||
|
|
|
@ -176,7 +176,7 @@ public:
|
|||
const gpu::TexturePointer& getBlackTexture();
|
||||
|
||||
/// Returns a texture version of an image file
|
||||
static gpu::TexturePointer getImageTexture(const QString& path, image::TextureUsage::Type type = image::TextureUsage::DEFAULT_TEXTURE, QVariantMap options = QVariantMap());
|
||||
static gpu::TexturePointer getImageTexture(const QString& path, image::TextureUsage::Type type = image::TextureUsage::DEFAULT_TEXTURE);
|
||||
|
||||
/// Loads a texture from the specified URL.
|
||||
NetworkTexturePointer getTexture(const QUrl& url, image::TextureUsage::Type type = image::TextureUsage::DEFAULT_TEXTURE,
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue