Fixing th emerge conflict

This commit is contained in:
Sam Gateau 2019-04-26 13:44:49 -07:00
commit d08e2e5ca1
128 changed files with 3551 additions and 1262 deletions
assignment-client/src
domain-server/src
interface
libraries

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -115,7 +115,6 @@ void DomainMetadata::securityChanged(bool send) {
auto& state = *static_cast<QVariantMap*>(_metadata[DESCRIPTORS].data());
const QString RESTRICTION_OPEN = "open";
const QString RESTRICTION_ANON = "anon";
const QString RESTRICTION_HIFI = "hifi";
const QString RESTRICTION_ACL = "acl";
@ -127,7 +126,7 @@ void DomainMetadata::securityChanged(bool send) {
bool hasHifiAccess = settingsManager.getStandardPermissionsForName(NodePermissions::standardNameLoggedIn).can(
NodePermissions::Permission::canConnectToDomain);
if (hasAnonymousAccess) {
restriction = hasHifiAccess ? RESTRICTION_OPEN : RESTRICTION_ANON;
restriction = RESTRICTION_OPEN;
} else if (hasHifiAccess) {
restriction = RESTRICTION_HIFI;
} else {

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -952,7 +952,7 @@ Rectangle {
text: "LOG IN"
onClicked: {
sendToScript({method: 'needsLogIn_loginClicked'});
sendToScript({method: 'marketplace_loginClicked'});
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

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

View 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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -66,7 +66,7 @@ public:
void setCollisionWithOtherAvatarsFlags() override;
void simulate(float deltaTime, bool inView) override;
void debugJointData() const;
friend AvatarManager;
protected:

View file

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

View file

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

View file

@ -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> &ndash; <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) &ndash;
* <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> &ndash; <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 &mdash; configured on the server &mdash; 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> &ndash;
* <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

View file

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

View 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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

View 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

View file

@ -261,7 +261,7 @@ public:
qCDebug(animation) << " " << pair.first << "=" << pair.second.getString();
break;
default:
assert(("invalid AnimVariant::Type", false));
assert(false);
}
}
}

View file

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

View file

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

View file

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

View file

@ -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> &ndash; <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> &ndash; <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, &ge;
* <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()) {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -29,7 +29,7 @@ namespace AvatarTraits {
// Simple traits
SkeletonModelURL = 0,
SkeletonData,
// Instanced traits
FirstInstancedTrait,
AvatarEntity = FirstInstancedTrait,

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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