mirror of
https://github.com/overte-org/overte.git
synced 2025-07-29 01:00:35 +02:00
Merge branch 'master' of https://github.com/highfidelity/hifi into 19942-MacInstall
This commit is contained in:
commit
cbfe9f5440
30 changed files with 440 additions and 207 deletions
|
@ -435,7 +435,11 @@ void AudioMixer::start() {
|
||||||
QCoreApplication::processEvents();
|
QCoreApplication::processEvents();
|
||||||
}
|
}
|
||||||
|
|
||||||
int numToRetain = nodeList->size() * (1 - _throttlingRatio);
|
int numToRetain = -1;
|
||||||
|
assert(_throttlingRatio >= 0.0f && _throttlingRatio <= 1.0f);
|
||||||
|
if (_throttlingRatio > EPSILON) {
|
||||||
|
numToRetain = nodeList->size() * (1.0f - _throttlingRatio);
|
||||||
|
}
|
||||||
nodeList->nestedEach([&](NodeList::const_iterator cbegin, NodeList::const_iterator cend) {
|
nodeList->nestedEach([&](NodeList::const_iterator cbegin, NodeList::const_iterator cend) {
|
||||||
// mix across slave threads
|
// mix across slave threads
|
||||||
auto mixTimer = _mixTiming.timer();
|
auto mixTimer = _mixTiming.timer();
|
||||||
|
|
|
@ -416,7 +416,8 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
|
||||||
// NOTE: Here's where we determine if we are over budget and drop remaining avatars,
|
// NOTE: Here's where we determine if we are over budget and drop remaining avatars,
|
||||||
// or send minimal avatar data in uncommon case of PALIsOpen.
|
// or send minimal avatar data in uncommon case of PALIsOpen.
|
||||||
int minimRemainingAvatarBytes = minimumBytesPerAvatar * remainingAvatars;
|
int minimRemainingAvatarBytes = minimumBytesPerAvatar * remainingAvatars;
|
||||||
bool overBudget = (identityBytesSent + numAvatarDataBytes + minimRemainingAvatarBytes) > maxAvatarBytesPerFrame;
|
auto frameByteEstimate = identityBytesSent + traitBytesSent + numAvatarDataBytes + minimRemainingAvatarBytes;
|
||||||
|
bool overBudget = frameByteEstimate > maxAvatarBytesPerFrame;
|
||||||
if (overBudget) {
|
if (overBudget) {
|
||||||
if (PALIsOpen) {
|
if (PALIsOpen) {
|
||||||
_stats.overBudgetAvatars++;
|
_stats.overBudgetAvatars++;
|
||||||
|
@ -497,8 +498,11 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
|
||||||
_stats.avatarDataPackingElapsedTime +=
|
_stats.avatarDataPackingElapsedTime +=
|
||||||
(quint64) chrono::duration_cast<chrono::microseconds>(endAvatarDataPacking - startAvatarDataPacking).count();
|
(quint64) chrono::duration_cast<chrono::microseconds>(endAvatarDataPacking - startAvatarDataPacking).count();
|
||||||
|
|
||||||
|
if (!overBudget) {
|
||||||
// use helper to add any changed traits to our packet list
|
// use helper to add any changed traits to our packet list
|
||||||
traitBytesSent += addChangedTraitsToBulkPacket(nodeData, otherNodeData, *traitsPacketList);
|
traitBytesSent += addChangedTraitsToBulkPacket(nodeData, otherNodeData, *traitsPacketList);
|
||||||
|
}
|
||||||
|
|
||||||
remainingAvatars--;
|
remainingAvatars--;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,10 +10,85 @@ $(document).ready(function(){
|
||||||
function progressBarHTML(extraClass, label) {
|
function progressBarHTML(extraClass, label) {
|
||||||
var html = "<div class='progress'>";
|
var html = "<div class='progress'>";
|
||||||
html += "<div class='" + extraClass + " progress-bar progress-bar-success progress-bar-striped active' role='progressbar' aria-valuemin='0' aria-valuemax='100'>";
|
html += "<div class='" + extraClass + " progress-bar progress-bar-success progress-bar-striped active' role='progressbar' aria-valuemin='0' aria-valuemax='100'>";
|
||||||
html += label + "<span class='sr-only'></span></div></div>";
|
html += "<span class='ongoing-msg'></span></div></div>";
|
||||||
return html;
|
return html;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function showUploadProgress(title) {
|
||||||
|
swal({
|
||||||
|
title: title,
|
||||||
|
text: progressBarHTML('upload-content-progress', 'Upload'),
|
||||||
|
html: true,
|
||||||
|
showConfirmButton: false,
|
||||||
|
allowEscapeKey: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function uploadNextChunk(file, offset, id) {
|
||||||
|
if (offset == undefined) {
|
||||||
|
offset = 0;
|
||||||
|
}
|
||||||
|
if (id == undefined) {
|
||||||
|
// Identify this upload session
|
||||||
|
id = Math.round(Math.random() * 2147483647);
|
||||||
|
}
|
||||||
|
|
||||||
|
var fileSize = file.size;
|
||||||
|
var filename = file.name;
|
||||||
|
|
||||||
|
var CHUNK_SIZE = 1048576; // 1 MiB
|
||||||
|
|
||||||
|
var isFinal = Boolean(fileSize - offset <= CHUNK_SIZE);
|
||||||
|
var nextChunkSize = Math.min(fileSize - offset, CHUNK_SIZE);
|
||||||
|
var chunk = file.slice(offset, offset + nextChunkSize, file.type);
|
||||||
|
var chunkFormData = new FormData();
|
||||||
|
|
||||||
|
var formItemName = 'restore-file-chunk';
|
||||||
|
if (offset == 0) {
|
||||||
|
formItemName = isFinal ? 'restore-file-chunk-only' : 'restore-file-chunk-initial';
|
||||||
|
} else if (isFinal) {
|
||||||
|
formItemName = 'restore-file-chunk-final';
|
||||||
|
}
|
||||||
|
|
||||||
|
chunkFormData.append(formItemName, chunk, filename);
|
||||||
|
var ajaxParams = {
|
||||||
|
url: '/content/upload',
|
||||||
|
type: 'POST',
|
||||||
|
timeout: 30000, // 30 s
|
||||||
|
headers: {"X-Session-Id": id},
|
||||||
|
cache: false,
|
||||||
|
processData: false,
|
||||||
|
contentType: false,
|
||||||
|
data: chunkFormData
|
||||||
|
};
|
||||||
|
|
||||||
|
var ajaxObject = $.ajax(ajaxParams);
|
||||||
|
ajaxObject.fail(function (jqXHR, textStatus, errorThrown) {
|
||||||
|
showErrorMessage(
|
||||||
|
"Error",
|
||||||
|
"There was a problem restoring domain content.\n"
|
||||||
|
+ "Please ensure that the content archive or entity file is valid and try again."
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
updateProgressBars($('.upload-content-progress'), (offset + nextChunkSize) * 100 / fileSize);
|
||||||
|
|
||||||
|
if (!isFinal) {
|
||||||
|
ajaxObject.done(function (data, textStatus, jqXHR)
|
||||||
|
{ uploadNextChunk(file, offset + CHUNK_SIZE, id); });
|
||||||
|
} else {
|
||||||
|
ajaxObject.done(function(data, textStatus, jqXHR) {
|
||||||
|
isRestoring = true;
|
||||||
|
|
||||||
|
// immediately reload backup information since one should be restoring now
|
||||||
|
reloadBackupInformation();
|
||||||
|
|
||||||
|
swal.close();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
function setupBackupUpload() {
|
function setupBackupUpload() {
|
||||||
// construct the HTML needed for the settings backup panel
|
// construct the HTML needed for the settings backup panel
|
||||||
var html = "<div class='form-group'><div id='" + UPLOAD_CONTENT_ALLOWED_DIV_ID + "'>";
|
var html = "<div class='form-group'><div id='" + UPLOAD_CONTENT_ALLOWED_DIV_ID + "'>";
|
||||||
|
@ -50,34 +125,10 @@ $(document).ready(function(){
|
||||||
"Restore content",
|
"Restore content",
|
||||||
function() {
|
function() {
|
||||||
var files = $('#' + RESTORE_SETTINGS_FILE_ID).prop('files');
|
var files = $('#' + RESTORE_SETTINGS_FILE_ID).prop('files');
|
||||||
|
var file = files[0];
|
||||||
|
|
||||||
var fileFormData = new FormData();
|
showUploadProgress("Uploading " + file.name);
|
||||||
fileFormData.append('restore-file', files[0]);
|
uploadNextChunk(file);
|
||||||
|
|
||||||
showSpinnerAlert("Uploading content to restore");
|
|
||||||
|
|
||||||
$.ajax({
|
|
||||||
url: '/content/upload',
|
|
||||||
type: 'POST',
|
|
||||||
timeout: 3600000, // Set timeout to 1h
|
|
||||||
cache: false,
|
|
||||||
processData: false,
|
|
||||||
contentType: false,
|
|
||||||
data: fileFormData
|
|
||||||
}).done(function(data, textStatus, jqXHR) {
|
|
||||||
isRestoring = true;
|
|
||||||
|
|
||||||
// immediately reload backup information since one should be restoring now
|
|
||||||
reloadBackupInformation();
|
|
||||||
|
|
||||||
swal.close();
|
|
||||||
}).fail(function(jqXHR, textStatus, errorThrown) {
|
|
||||||
showErrorMessage(
|
|
||||||
"Error",
|
|
||||||
"There was a problem restoring domain content.\n"
|
|
||||||
+ "Please ensure that the content archive or entity file is valid and try again."
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -168,6 +219,11 @@ $(document).ready(function(){
|
||||||
checkBackupStatus();
|
checkBackupStatus();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function updateProgressBars($progressBar, value) {
|
||||||
|
$progressBar.attr('aria-valuenow', value).attr('style', 'width: ' + value + '%');
|
||||||
|
$progressBar.find('.ongoing-msg').html(" " + Math.round(value) + "%");
|
||||||
|
}
|
||||||
|
|
||||||
function reloadBackupInformation() {
|
function reloadBackupInformation() {
|
||||||
// make a GET request to get backup information to populate the table
|
// make a GET request to get backup information to populate the table
|
||||||
$.ajax({
|
$.ajax({
|
||||||
|
@ -204,11 +260,6 @@ $(document).ready(function(){
|
||||||
+ "<li><a class='" + BACKUP_DELETE_LINK_CLASS + "' href='#' target='_blank'>Delete</a></li></ul></div></td>";
|
+ "<li><a class='" + BACKUP_DELETE_LINK_CLASS + "' href='#' target='_blank'>Delete</a></li></ul></div></td>";
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateProgressBars($progressBar, value) {
|
|
||||||
$progressBar.attr('aria-valuenow', value).attr('style', 'width: ' + value + '%');
|
|
||||||
$progressBar.find('.sr-only').html(value + "% Complete");
|
|
||||||
}
|
|
||||||
|
|
||||||
// before we add any new rows and update existing ones
|
// before we add any new rows and update existing ones
|
||||||
// remove our flag for active rows
|
// remove our flag for active rows
|
||||||
$('.' + ACTIVE_BACKUP_ROW_CLASS).removeClass(ACTIVE_BACKUP_ROW_CLASS);
|
$('.' + ACTIVE_BACKUP_ROW_CLASS).removeClass(ACTIVE_BACKUP_ROW_CLASS);
|
||||||
|
|
|
@ -348,6 +348,27 @@ void DomainContentBackupManager::recoverFromUploadedBackup(MiniPromise::Promise
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DomainContentBackupManager::recoverFromUploadedFile(MiniPromise::Promise promise, QString uploadedFilename) {
|
||||||
|
if (QThread::currentThread() != thread()) {
|
||||||
|
QMetaObject::invokeMethod(this, "recoverFromUploadedFile", Q_ARG(MiniPromise::Promise, promise),
|
||||||
|
Q_ARG(QString, uploadedFilename));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "Recovering from uploaded file -" << uploadedFilename;
|
||||||
|
|
||||||
|
QFile uploadedFile(uploadedFilename);
|
||||||
|
QuaZip uploadedZip { &uploadedFile };
|
||||||
|
|
||||||
|
QString backupName = MANUAL_BACKUP_PREFIX + "uploaded.zip";
|
||||||
|
|
||||||
|
bool success = recoverFromBackupZip(backupName, uploadedZip);
|
||||||
|
|
||||||
|
promise->resolve({
|
||||||
|
{ "success", success }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<BackupItemInfo> DomainContentBackupManager::getAllBackups() {
|
std::vector<BackupItemInfo> DomainContentBackupManager::getAllBackups() {
|
||||||
|
|
||||||
QDir backupDir { _backupDirectory };
|
QDir backupDir { _backupDirectory };
|
||||||
|
|
|
@ -86,6 +86,7 @@ public slots:
|
||||||
void createManualBackup(MiniPromise::Promise promise, const QString& name);
|
void createManualBackup(MiniPromise::Promise promise, const QString& name);
|
||||||
void recoverFromBackup(MiniPromise::Promise promise, const QString& backupName);
|
void recoverFromBackup(MiniPromise::Promise promise, const QString& backupName);
|
||||||
void recoverFromUploadedBackup(MiniPromise::Promise promise, QByteArray uploadedBackup);
|
void recoverFromUploadedBackup(MiniPromise::Promise promise, QByteArray uploadedBackup);
|
||||||
|
void recoverFromUploadedFile(MiniPromise::Promise promise, QString uploadedFilename);
|
||||||
void deleteBackup(MiniPromise::Promise promise, const QString& backupName);
|
void deleteBackup(MiniPromise::Promise promise, const QString& backupName);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
|
|
|
@ -2258,46 +2258,18 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
|
||||||
// check the file extension to see what kind of file this is
|
// check the file extension to see what kind of file this is
|
||||||
// to make sure we handle this filetype for a content restore
|
// to make sure we handle this filetype for a content restore
|
||||||
auto dispositionValue = QString(firstFormData.first.value("Content-Disposition"));
|
auto dispositionValue = QString(firstFormData.first.value("Content-Disposition"));
|
||||||
auto formDataFilenameRegex = QRegExp("filename=\"(.+)\"");
|
QRegExp formDataFieldsRegex(R":(name="(restore-file.*)".*filename="(.+)"):");
|
||||||
auto matchIndex = formDataFilenameRegex.indexIn(dispositionValue);
|
auto matchIndex = formDataFieldsRegex.indexIn(dispositionValue);
|
||||||
|
|
||||||
|
QString formItemName = "";
|
||||||
QString uploadedFilename = "";
|
QString uploadedFilename = "";
|
||||||
if (matchIndex != -1) {
|
if (matchIndex != -1) {
|
||||||
uploadedFilename = formDataFilenameRegex.cap(1);
|
formItemName = formDataFieldsRegex.cap(1);
|
||||||
}
|
uploadedFilename = formDataFieldsRegex.cap(2);
|
||||||
|
|
||||||
if (uploadedFilename.endsWith(".json", Qt::CaseInsensitive)
|
|
||||||
|| uploadedFilename.endsWith(".json.gz", Qt::CaseInsensitive)) {
|
|
||||||
// invoke our method to hand the new octree file off to the octree server
|
|
||||||
QMetaObject::invokeMethod(this, "handleOctreeFileReplacement",
|
|
||||||
Qt::QueuedConnection, Q_ARG(QByteArray, firstFormData.second));
|
|
||||||
|
|
||||||
// respond with a 200 for success
|
|
||||||
connection->respond(HTTPConnection::StatusCode200);
|
|
||||||
} else if (uploadedFilename.endsWith(".zip", Qt::CaseInsensitive)) {
|
|
||||||
auto deferred = makePromise("recoverFromUploadedBackup");
|
|
||||||
|
|
||||||
deferred->then([connectionPtr, JSON_MIME_TYPE](QString error, QVariantMap result) {
|
|
||||||
if (!connectionPtr) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
QJsonObject rootJSON;
|
|
||||||
auto success = result["success"].toBool();
|
|
||||||
rootJSON["success"] = success;
|
|
||||||
QJsonDocument docJSON(rootJSON);
|
|
||||||
connectionPtr->respond(success ? HTTPConnection::StatusCode200 : HTTPConnection::StatusCode400, docJSON.toJson(),
|
|
||||||
JSON_MIME_TYPE.toUtf8());
|
|
||||||
});
|
|
||||||
|
|
||||||
_contentManager->recoverFromUploadedBackup(deferred, firstFormData.second);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
// we don't have handling for this filetype, send back a 400 for failure
|
|
||||||
connection->respond(HTTPConnection::StatusCode400);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Received a chunk
|
||||||
|
processPendingContent(connection, formItemName, uploadedFilename, firstFormData.second);
|
||||||
} else {
|
} else {
|
||||||
// respond with a 400 for failure
|
// respond with a 400 for failure
|
||||||
connection->respond(HTTPConnection::StatusCode400);
|
connection->respond(HTTPConnection::StatusCode400);
|
||||||
|
@ -2546,6 +2518,72 @@ bool DomainServer::handleHTTPSRequest(HTTPSConnection* connection, const QUrl &u
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool DomainServer::processPendingContent(HTTPConnection* connection, QString itemName, QString filename, QByteArray dataChunk) {
|
||||||
|
static const QString UPLOAD_SESSION_KEY { "X-Session-Id" };
|
||||||
|
QByteArray sessionIdBytes = connection->requestHeader(UPLOAD_SESSION_KEY);
|
||||||
|
int sessionId = sessionIdBytes.toInt();
|
||||||
|
|
||||||
|
bool newUpload = itemName == "restore-file" || itemName == "restore-file-chunk-initial" || itemName == "restore-file-chunk-only";
|
||||||
|
|
||||||
|
if (filename.endsWith(".zip", Qt::CaseInsensitive)) {
|
||||||
|
static const QString TEMPORARY_CONTENT_FILEPATH { QDir::tempPath() + "/hifiUploadContent_XXXXXX.zip" };
|
||||||
|
|
||||||
|
if (_pendingContentFiles.find(sessionId) == _pendingContentFiles.end()) {
|
||||||
|
if (!newUpload) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
std::unique_ptr<QTemporaryFile> newTemp(new QTemporaryFile(TEMPORARY_CONTENT_FILEPATH));
|
||||||
|
_pendingContentFiles[sessionId] = std::move(newTemp);
|
||||||
|
} else if (newUpload) {
|
||||||
|
qCDebug(domain_server) << "New upload received using existing session ID";
|
||||||
|
_pendingContentFiles[sessionId]->resize(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
QTemporaryFile& _pendingFileContent = *_pendingContentFiles[sessionId];
|
||||||
|
if (!_pendingFileContent.open()) {
|
||||||
|
_pendingContentFiles.erase(sessionId);
|
||||||
|
connection->respond(HTTPConnection::StatusCode400);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
_pendingFileContent.seek(_pendingFileContent.size());
|
||||||
|
_pendingFileContent.write(dataChunk);
|
||||||
|
_pendingFileContent.close();
|
||||||
|
|
||||||
|
// Respond immediately - will timeout if we wait for restore.
|
||||||
|
connection->respond(HTTPConnection::StatusCode200);
|
||||||
|
if (itemName == "restore-file" || itemName == "restore-file-chunk-final" || itemName == "restore-file-chunk-only") {
|
||||||
|
auto deferred = makePromise("recoverFromUploadedBackup");
|
||||||
|
|
||||||
|
deferred->then([this, sessionId](QString error, QVariantMap result) {
|
||||||
|
_pendingContentFiles.erase(sessionId);
|
||||||
|
});
|
||||||
|
|
||||||
|
_contentManager->recoverFromUploadedFile(deferred, _pendingFileContent.fileName());
|
||||||
|
}
|
||||||
|
} else if (filename.endsWith(".json", Qt::CaseInsensitive)
|
||||||
|
|| filename.endsWith(".json.gz", Qt::CaseInsensitive)) {
|
||||||
|
if (_pendingUploadedContents.find(sessionId) == _pendingUploadedContents.end() && !newUpload) {
|
||||||
|
qCDebug(domain_server) << "Json upload with invalid session ID received";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
QByteArray& _pendingUploadedContent = _pendingUploadedContents[sessionId];
|
||||||
|
_pendingUploadedContent += dataChunk;
|
||||||
|
connection->respond(HTTPConnection::StatusCode200);
|
||||||
|
|
||||||
|
if (itemName == "restore-file" || itemName == "restore-file-chunk-final" || itemName == "restore-file-chunk-only") {
|
||||||
|
// invoke our method to hand the new octree file off to the octree server
|
||||||
|
QMetaObject::invokeMethod(this, "handleOctreeFileReplacement",
|
||||||
|
Qt::QueuedConnection, Q_ARG(QByteArray, _pendingUploadedContent));
|
||||||
|
_pendingUploadedContents.erase(sessionId);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
connection->respond(HTTPConnection::StatusCode400);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
HTTPSConnection* DomainServer::connectionFromReplyWithState(QNetworkReply* reply) {
|
HTTPSConnection* DomainServer::connectionFromReplyWithState(QNetworkReply* reply) {
|
||||||
// grab the UUID state property from the reply
|
// grab the UUID state property from the reply
|
||||||
QUuid stateUUID = reply->property(STATE_QUERY_KEY.toLocal8Bit()).toUuid();
|
QUuid stateUUID = reply->property(STATE_QUERY_KEY.toLocal8Bit()).toUuid();
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
#include <QtCore/QStringList>
|
#include <QtCore/QStringList>
|
||||||
#include <QtCore/QThread>
|
#include <QtCore/QThread>
|
||||||
#include <QtCore/QUrl>
|
#include <QtCore/QUrl>
|
||||||
|
#include <QHostAddress>
|
||||||
#include <QAbstractNativeEventFilter>
|
#include <QAbstractNativeEventFilter>
|
||||||
|
|
||||||
#include <Assignment.h>
|
#include <Assignment.h>
|
||||||
|
@ -209,6 +210,8 @@ private:
|
||||||
|
|
||||||
HTTPSConnection* connectionFromReplyWithState(QNetworkReply* reply);
|
HTTPSConnection* connectionFromReplyWithState(QNetworkReply* reply);
|
||||||
|
|
||||||
|
bool processPendingContent(HTTPConnection* connection, QString itemName, QString filename, QByteArray dataChunk);
|
||||||
|
|
||||||
bool forwardMetaverseAPIRequest(HTTPConnection* connection,
|
bool forwardMetaverseAPIRequest(HTTPConnection* connection,
|
||||||
const QString& metaversePath,
|
const QString& metaversePath,
|
||||||
const QString& requestSubobject,
|
const QString& requestSubobject,
|
||||||
|
@ -281,6 +284,9 @@ private:
|
||||||
|
|
||||||
QHash<QUuid, QPointer<HTTPSConnection>> _pendingOAuthConnections;
|
QHash<QUuid, QPointer<HTTPSConnection>> _pendingOAuthConnections;
|
||||||
|
|
||||||
|
std::unordered_map<int, QByteArray> _pendingUploadedContents;
|
||||||
|
std::unordered_map<int, std::unique_ptr<QTemporaryFile>> _pendingContentFiles;
|
||||||
|
|
||||||
QThread _assetClientThread;
|
QThread _assetClientThread;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -701,9 +701,9 @@
|
||||||
"y": 0.04787999764084816
|
"y": 0.04787999764084816
|
||||||
},
|
},
|
||||||
"position": {
|
"position": {
|
||||||
"x": -0.53203323516845703,
|
"x": -0.59333323516845703,
|
||||||
"y": 0.019300000742077827,
|
"y": 0.019300000742077827,
|
||||||
"z": -0.07286686894893646
|
"z": 0.037454843521118164
|
||||||
},
|
},
|
||||||
"modelURL": "meshes/keyboard/SM_key.fbx",
|
"modelURL": "meshes/keyboard/SM_key.fbx",
|
||||||
"texture": {
|
"texture": {
|
||||||
|
@ -752,7 +752,7 @@
|
||||||
"y": 0.04787999764084816
|
"y": 0.04787999764084816
|
||||||
},
|
},
|
||||||
"position": {
|
"position": {
|
||||||
"x": -0.59333323516845703,
|
"x": -0.65333323516845703,
|
||||||
"y": 0.019300000742077827,
|
"y": 0.019300000742077827,
|
||||||
"z": 0.037454843521118164
|
"z": 0.037454843521118164
|
||||||
},
|
},
|
||||||
|
@ -777,9 +777,9 @@
|
||||||
"y": 0.04787999764084816
|
"y": 0.04787999764084816
|
||||||
},
|
},
|
||||||
"position": {
|
"position": {
|
||||||
"x": -0.5103323516845703,
|
"x": -0.5503323516845703,
|
||||||
"y": 0.019300000742077827,
|
"y": 0.019300000742077827,
|
||||||
"z": -0.127054843521118164
|
"z": -0.07282185554504395
|
||||||
},
|
},
|
||||||
"modelURL": "meshes/keyboard/SM_enter.fbx",
|
"modelURL": "meshes/keyboard/SM_enter.fbx",
|
||||||
"texture": {
|
"texture": {
|
||||||
|
@ -1479,9 +1479,9 @@
|
||||||
"y": 0.04787999764084816
|
"y": 0.04787999764084816
|
||||||
},
|
},
|
||||||
"position": {
|
"position": {
|
||||||
"x": -0.53203323516845703,
|
"x": -0.59333323516845703,
|
||||||
"y": 0.019300000742077827,
|
"y": 0.019300000742077827,
|
||||||
"z": -0.07286686894893646
|
"z": 0.037454843521118164
|
||||||
},
|
},
|
||||||
"modelURL": "meshes/keyboard/SM_key.fbx",
|
"modelURL": "meshes/keyboard/SM_key.fbx",
|
||||||
"texture": {
|
"texture": {
|
||||||
|
@ -1530,7 +1530,7 @@
|
||||||
"y": 0.04787999764084816
|
"y": 0.04787999764084816
|
||||||
},
|
},
|
||||||
"position": {
|
"position": {
|
||||||
"x": -0.59333323516845703,
|
"x": -0.65333323516845703,
|
||||||
"y": 0.019300000742077827,
|
"y": 0.019300000742077827,
|
||||||
"z": 0.037454843521118164
|
"z": 0.037454843521118164
|
||||||
},
|
},
|
||||||
|
@ -1555,9 +1555,9 @@
|
||||||
"y": 0.04787999764084816
|
"y": 0.04787999764084816
|
||||||
},
|
},
|
||||||
"position": {
|
"position": {
|
||||||
"x": -0.5103323516845703,
|
"x": -0.5503323516845703,
|
||||||
"y": 0.019300000742077827,
|
"y": 0.019300000742077827,
|
||||||
"z": -0.127054843521118164
|
"z": -0.07282185554504395
|
||||||
},
|
},
|
||||||
"modelURL": "meshes/keyboard/SM_enter.fbx",
|
"modelURL": "meshes/keyboard/SM_enter.fbx",
|
||||||
"texture": {
|
"texture": {
|
||||||
|
@ -2305,9 +2305,9 @@
|
||||||
"y": 0.04787999764084816
|
"y": 0.04787999764084816
|
||||||
},
|
},
|
||||||
"position": {
|
"position": {
|
||||||
"x": -0.53203323516845703,
|
"x": -0.59333323516845703,
|
||||||
"y": 0.019300000742077827,
|
"y": 0.019300000742077827,
|
||||||
"z": -0.07286686894893646
|
"z": 0.037454843521118164
|
||||||
},
|
},
|
||||||
"modelURL": "meshes/keyboard/SM_key.fbx",
|
"modelURL": "meshes/keyboard/SM_key.fbx",
|
||||||
"texture": {
|
"texture": {
|
||||||
|
@ -2356,7 +2356,7 @@
|
||||||
"y": 0.04787999764084816
|
"y": 0.04787999764084816
|
||||||
},
|
},
|
||||||
"position": {
|
"position": {
|
||||||
"x": -0.59333323516845703,
|
"x": -0.65333323516845703,
|
||||||
"y": 0.019300000742077827,
|
"y": 0.019300000742077827,
|
||||||
"z": 0.037454843521118164
|
"z": 0.037454843521118164
|
||||||
},
|
},
|
||||||
|
@ -2381,9 +2381,9 @@
|
||||||
"y": 0.04787999764084816
|
"y": 0.04787999764084816
|
||||||
},
|
},
|
||||||
"position": {
|
"position": {
|
||||||
"x": -0.5103323516845703,
|
"x": -0.5503323516845703,
|
||||||
"y": 0.019300000742077827,
|
"y": 0.019300000742077827,
|
||||||
"z": -0.127054843521118164
|
"z": -0.07282185554504395
|
||||||
},
|
},
|
||||||
"modelURL": "meshes/keyboard/SM_enter.fbx",
|
"modelURL": "meshes/keyboard/SM_enter.fbx",
|
||||||
"texture": {
|
"texture": {
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
window.isKeyboardRaised = false;
|
window.isKeyboardRaised = false;
|
||||||
window.isNumericKeyboard = false;
|
window.isNumericKeyboard = false;
|
||||||
window.isPasswordField = false;
|
window.isPasswordField = false;
|
||||||
window.lastActiveElement = null;
|
window.lastActiveInputElement = null;
|
||||||
|
|
||||||
function getActiveElement() {
|
function getActiveElement() {
|
||||||
return document.activeElement;
|
return document.activeElement;
|
||||||
|
@ -70,11 +70,15 @@
|
||||||
var keyboardRaised = shouldRaiseKeyboard();
|
var keyboardRaised = shouldRaiseKeyboard();
|
||||||
var numericKeyboard = shouldSetNumeric();
|
var numericKeyboard = shouldSetNumeric();
|
||||||
var passwordField = shouldSetPasswordField();
|
var passwordField = shouldSetPasswordField();
|
||||||
var activeElement = getActiveElement();
|
var activeInputElement = null;
|
||||||
|
// Only set the active input element when there is an input element focussed, otherwise it will scroll on body focus as well.
|
||||||
|
if (keyboardRaised) {
|
||||||
|
activeInputElement = getActiveElement();
|
||||||
|
}
|
||||||
|
|
||||||
if (isWindowFocused &&
|
if (isWindowFocused &&
|
||||||
(keyboardRaised !== window.isKeyboardRaised || numericKeyboard !== window.isNumericKeyboard
|
(keyboardRaised !== window.isKeyboardRaised || numericKeyboard !== window.isNumericKeyboard
|
||||||
|| passwordField !== window.isPasswordField || activeElement !== window.lastActiveElement)) {
|
|| passwordField !== window.isPasswordField || activeInputElement !== window.lastActiveInputElement)) {
|
||||||
|
|
||||||
if (typeof EventBridge !== "undefined" && EventBridge !== null) {
|
if (typeof EventBridge !== "undefined" && EventBridge !== null) {
|
||||||
EventBridge.emitWebEvent(
|
EventBridge.emitWebEvent(
|
||||||
|
@ -96,7 +100,7 @@
|
||||||
window.isKeyboardRaised = keyboardRaised;
|
window.isKeyboardRaised = keyboardRaised;
|
||||||
window.isNumericKeyboard = numericKeyboard;
|
window.isNumericKeyboard = numericKeyboard;
|
||||||
window.isPasswordField = passwordField;
|
window.isPasswordField = passwordField;
|
||||||
window.lastActiveElement = activeElement;
|
window.lastActiveInputElement = activeInputElement;
|
||||||
}
|
}
|
||||||
}, POLL_FREQUENCY);
|
}, POLL_FREQUENCY);
|
||||||
|
|
||||||
|
|
Binary file not shown.
|
@ -141,6 +141,7 @@ TabletModalWindow {
|
||||||
|
|
||||||
Component.onDestruction: {
|
Component.onDestruction: {
|
||||||
loginKeyboard.raised = false;
|
loginKeyboard.raised = false;
|
||||||
|
KeyboardScriptingInterface.raised = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
|
|
BIN
interface/resources/sounds/keyboardPress.mp3
Normal file
BIN
interface/resources/sounds/keyboardPress.mp3
Normal file
Binary file not shown.
Binary file not shown.
|
@ -45,6 +45,7 @@
|
||||||
#include "scripting/HMDScriptingInterface.h"
|
#include "scripting/HMDScriptingInterface.h"
|
||||||
#include "scripting/WindowScriptingInterface.h"
|
#include "scripting/WindowScriptingInterface.h"
|
||||||
#include "scripting/SelectionScriptingInterface.h"
|
#include "scripting/SelectionScriptingInterface.h"
|
||||||
|
#include "scripting/HMDScriptingInterface.h"
|
||||||
#include "DependencyManager.h"
|
#include "DependencyManager.h"
|
||||||
|
|
||||||
#include "raypick/StylusPointer.h"
|
#include "raypick/StylusPointer.h"
|
||||||
|
@ -54,9 +55,9 @@
|
||||||
static const int LEFT_HAND_CONTROLLER_INDEX = 0;
|
static const int LEFT_HAND_CONTROLLER_INDEX = 0;
|
||||||
static const int RIGHT_HAND_CONTROLLER_INDEX = 1;
|
static const int RIGHT_HAND_CONTROLLER_INDEX = 1;
|
||||||
|
|
||||||
static const float MALLET_LENGTH = 0.2f;
|
static const float MALLET_LENGTH = 0.18f;
|
||||||
static const float MALLET_TOUCH_Y_OFFSET = 0.052f;
|
static const float MALLET_TOUCH_Y_OFFSET = 0.050f;
|
||||||
static const float MALLET_Y_OFFSET = 0.180f;
|
static const float MALLET_Y_OFFSET = 0.160f;
|
||||||
|
|
||||||
static const glm::quat MALLET_ROTATION_OFFSET{0.70710678f, 0.0f, -0.70710678f, 0.0f};
|
static const glm::quat MALLET_ROTATION_OFFSET{0.70710678f, 0.0f, -0.70710678f, 0.0f};
|
||||||
static const glm::vec3 MALLET_MODEL_DIMENSIONS{0.03f, MALLET_LENGTH, 0.03f};
|
static const glm::vec3 MALLET_MODEL_DIMENSIONS{0.03f, MALLET_LENGTH, 0.03f};
|
||||||
|
@ -65,14 +66,14 @@ static const glm::vec3 MALLET_TIP_OFFSET{0.0f, MALLET_LENGTH - MALLET_TOUCH_Y_OF
|
||||||
|
|
||||||
|
|
||||||
static const glm::vec3 Z_AXIS {0.0f, 0.0f, 1.0f};
|
static const glm::vec3 Z_AXIS {0.0f, 0.0f, 1.0f};
|
||||||
static const glm::vec3 KEYBOARD_TABLET_OFFSET{0.28f, -0.3f, -0.05f};
|
static const glm::vec3 KEYBOARD_TABLET_OFFSET{0.30f, -0.38f, -0.04f};
|
||||||
static const glm::vec3 KEYBOARD_TABLET_DEGREES_OFFSET{-45.0f, 0.0f, 0.0f};
|
static const glm::vec3 KEYBOARD_TABLET_DEGREES_OFFSET{-45.0f, 0.0f, 0.0f};
|
||||||
static const glm::vec3 KEYBOARD_TABLET_LANDSCAPE_OFFSET{-0.2f, -0.27f, -0.05f};
|
static const glm::vec3 KEYBOARD_TABLET_LANDSCAPE_OFFSET{-0.2f, -0.27f, -0.05f};
|
||||||
static const glm::vec3 KEYBOARD_TABLET_LANDSCAPE_DEGREES_OFFSET{-45.0f, 0.0f, -90.0f};
|
static const glm::vec3 KEYBOARD_TABLET_LANDSCAPE_DEGREES_OFFSET{-45.0f, 0.0f, -90.0f};
|
||||||
static const glm::vec3 KEYBOARD_AVATAR_OFFSET{-0.6f, 0.3f, -0.7f};
|
static const glm::vec3 KEYBOARD_AVATAR_OFFSET{-0.6f, 0.3f, -0.7f};
|
||||||
static const glm::vec3 KEYBOARD_AVATAR_DEGREES_OFFSET{0.0f, 180.0f, 0.0f};
|
static const glm::vec3 KEYBOARD_AVATAR_DEGREES_OFFSET{0.0f, 180.0f, 0.0f};
|
||||||
|
|
||||||
static const QString SOUND_FILE = PathUtils::resourcesUrl() + "sounds/keyboard_key.mp3";
|
static const QString SOUND_FILE = PathUtils::resourcesUrl() + "sounds/keyboardPress.mp3";
|
||||||
static const QString MALLET_MODEL_URL = PathUtils::resourcesUrl() + "meshes/drumstick.fbx";
|
static const QString MALLET_MODEL_URL = PathUtils::resourcesUrl() + "meshes/drumstick.fbx";
|
||||||
|
|
||||||
static const float PULSE_STRENGTH = 0.6f;
|
static const float PULSE_STRENGTH = 0.6f;
|
||||||
|
@ -221,6 +222,7 @@ Keyboard::Keyboard() {
|
||||||
auto pointerManager = DependencyManager::get<PointerManager>();
|
auto pointerManager = DependencyManager::get<PointerManager>();
|
||||||
auto windowScriptingInterface = DependencyManager::get<WindowScriptingInterface>();
|
auto windowScriptingInterface = DependencyManager::get<WindowScriptingInterface>();
|
||||||
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
|
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
|
||||||
|
auto hmdScriptingInterface = DependencyManager::get<HMDScriptingInterface>();
|
||||||
connect(pointerManager.data(), &PointerManager::triggerBeginOverlay, this, &Keyboard::handleTriggerBegin, Qt::QueuedConnection);
|
connect(pointerManager.data(), &PointerManager::triggerBeginOverlay, this, &Keyboard::handleTriggerBegin, Qt::QueuedConnection);
|
||||||
connect(pointerManager.data(), &PointerManager::triggerContinueOverlay, this, &Keyboard::handleTriggerContinue, Qt::QueuedConnection);
|
connect(pointerManager.data(), &PointerManager::triggerContinueOverlay, this, &Keyboard::handleTriggerContinue, Qt::QueuedConnection);
|
||||||
connect(pointerManager.data(), &PointerManager::triggerEndOverlay, this, &Keyboard::handleTriggerEnd, Qt::QueuedConnection);
|
connect(pointerManager.data(), &PointerManager::triggerEndOverlay, this, &Keyboard::handleTriggerEnd, Qt::QueuedConnection);
|
||||||
|
@ -228,6 +230,7 @@ Keyboard::Keyboard() {
|
||||||
connect(pointerManager.data(), &PointerManager::hoverEndOverlay, this, &Keyboard::handleHoverEnd, Qt::QueuedConnection);
|
connect(pointerManager.data(), &PointerManager::hoverEndOverlay, this, &Keyboard::handleHoverEnd, Qt::QueuedConnection);
|
||||||
connect(myAvatar.get(), &MyAvatar::sensorToWorldScaleChanged, this, &Keyboard::scaleKeyboard, Qt::QueuedConnection);
|
connect(myAvatar.get(), &MyAvatar::sensorToWorldScaleChanged, this, &Keyboard::scaleKeyboard, Qt::QueuedConnection);
|
||||||
connect(windowScriptingInterface.data(), &WindowScriptingInterface::domainChanged, [&]() { setRaised(false); });
|
connect(windowScriptingInterface.data(), &WindowScriptingInterface::domainChanged, [&]() { setRaised(false); });
|
||||||
|
connect(hmdScriptingInterface.data(), &HMDScriptingInterface::displayModeChanged, [&]() { setRaised(false); });
|
||||||
}
|
}
|
||||||
|
|
||||||
void Keyboard::registerKeyboardHighlighting() {
|
void Keyboard::registerKeyboardHighlighting() {
|
||||||
|
@ -483,7 +486,7 @@ void Keyboard::handleTriggerBegin(const OverlayID& overlayID, const PointerEvent
|
||||||
AudioInjectorOptions audioOptions;
|
AudioInjectorOptions audioOptions;
|
||||||
audioOptions.localOnly = true;
|
audioOptions.localOnly = true;
|
||||||
audioOptions.position = keyWorldPosition;
|
audioOptions.position = keyWorldPosition;
|
||||||
audioOptions.volume = 0.1f;
|
audioOptions.volume = 0.05f;
|
||||||
|
|
||||||
AudioInjector::playSoundAndDelete(_keySound, audioOptions);
|
AudioInjector::playSoundAndDelete(_keySound, audioOptions);
|
||||||
|
|
||||||
|
@ -835,8 +838,8 @@ void Keyboard::loadKeyboardFile(const QString& keyboardFile) {
|
||||||
_textDisplay = textDisplay;
|
_textDisplay = textDisplay;
|
||||||
|
|
||||||
_ignoreItemsLock.withWriteLock([&] {
|
_ignoreItemsLock.withWriteLock([&] {
|
||||||
_itemsToIgnore.push_back(_textDisplay.overlayID);
|
_itemsToIgnore.append(_textDisplay.overlayID);
|
||||||
_itemsToIgnore.push_back(_anchor.overlayID);
|
_itemsToIgnore.append(_anchor.overlayID);
|
||||||
});
|
});
|
||||||
_layerIndex = 0;
|
_layerIndex = 0;
|
||||||
auto pointerManager = DependencyManager::get<PointerManager>();
|
auto pointerManager = DependencyManager::get<PointerManager>();
|
||||||
|
|
|
@ -535,7 +535,7 @@ RayToOverlayIntersectionResult Overlays::findRayIntersectionVector(const PickRay
|
||||||
bool bestIsFront = false;
|
bool bestIsFront = false;
|
||||||
bool bestIsTablet = false;
|
bool bestIsTablet = false;
|
||||||
auto tabletIDs = qApp->getTabletIDs();
|
auto tabletIDs = qApp->getTabletIDs();
|
||||||
|
const QVector<OverlayID> keyboardKeysToDiscard = DependencyManager::get<Keyboard>()->getKeysID();
|
||||||
QMutexLocker locker(&_mutex);
|
QMutexLocker locker(&_mutex);
|
||||||
RayToOverlayIntersectionResult result;
|
RayToOverlayIntersectionResult result;
|
||||||
QMapIterator<OverlayID, Overlay::Pointer> i(_overlaysWorld);
|
QMapIterator<OverlayID, Overlay::Pointer> i(_overlaysWorld);
|
||||||
|
@ -545,7 +545,8 @@ RayToOverlayIntersectionResult Overlays::findRayIntersectionVector(const PickRay
|
||||||
auto thisOverlay = std::dynamic_pointer_cast<Base3DOverlay>(i.value());
|
auto thisOverlay = std::dynamic_pointer_cast<Base3DOverlay>(i.value());
|
||||||
|
|
||||||
if ((overlaysToDiscard.size() > 0 && overlaysToDiscard.contains(thisID)) ||
|
if ((overlaysToDiscard.size() > 0 && overlaysToDiscard.contains(thisID)) ||
|
||||||
(overlaysToInclude.size() > 0 && !overlaysToInclude.contains(thisID))) {
|
(overlaysToInclude.size() > 0 && !overlaysToInclude.contains(thisID)) ||
|
||||||
|
(keyboardKeysToDiscard.size() > 0 && keyboardKeysToDiscard.contains(thisID))) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,11 +23,39 @@ AnimSkeleton::AnimSkeleton(const HFMModel& hfmModel) {
|
||||||
for (auto& joint : hfmModel.joints) {
|
for (auto& joint : hfmModel.joints) {
|
||||||
joints.push_back(joint);
|
joints.push_back(joint);
|
||||||
}
|
}
|
||||||
buildSkeletonFromJoints(joints);
|
buildSkeletonFromJoints(joints, hfmModel.jointRotationOffsets);
|
||||||
|
|
||||||
|
// we make a copy of the inverseBindMatrices in order to prevent mutating the model bind pose
|
||||||
|
// when we are dealing with a joint offset in the model
|
||||||
|
for (int i = 0; i < (int)hfmModel.meshes.size(); i++) {
|
||||||
|
const HFMMesh& mesh = hfmModel.meshes.at(i);
|
||||||
|
std::vector<HFMCluster> dummyClustersList;
|
||||||
|
|
||||||
|
for (int j = 0; j < mesh.clusters.size(); j++) {
|
||||||
|
std::vector<glm::mat4> bindMatrices;
|
||||||
|
// cast into a non-const reference, so we can mutate the FBXCluster
|
||||||
|
HFMCluster& cluster = const_cast<HFMCluster&>(mesh.clusters.at(j));
|
||||||
|
|
||||||
|
HFMCluster localCluster;
|
||||||
|
localCluster.jointIndex = cluster.jointIndex;
|
||||||
|
localCluster.inverseBindMatrix = cluster.inverseBindMatrix;
|
||||||
|
localCluster.inverseBindTransform.evalFromRawMatrix(localCluster.inverseBindMatrix);
|
||||||
|
|
||||||
|
// if we have a joint offset in the fst file then multiply its inverse by the
|
||||||
|
// model cluster inverse bind matrix
|
||||||
|
if (hfmModel.jointRotationOffsets.contains(cluster.jointIndex)) {
|
||||||
|
AnimPose localOffset(hfmModel.jointRotationOffsets[cluster.jointIndex], glm::vec3());
|
||||||
|
localCluster.inverseBindMatrix = (glm::mat4)localOffset.inverse() * cluster.inverseBindMatrix;
|
||||||
|
localCluster.inverseBindTransform.evalFromRawMatrix(localCluster.inverseBindMatrix);
|
||||||
|
}
|
||||||
|
dummyClustersList.push_back(localCluster);
|
||||||
|
}
|
||||||
|
_clusterBindMatrixOriginalValues.push_back(dummyClustersList);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AnimSkeleton::AnimSkeleton(const std::vector<HFMJoint>& joints) {
|
AnimSkeleton::AnimSkeleton(const std::vector<HFMJoint>& joints, const QMap<int, glm::quat> jointOffsets) {
|
||||||
buildSkeletonFromJoints(joints);
|
buildSkeletonFromJoints(joints, jointOffsets);
|
||||||
}
|
}
|
||||||
|
|
||||||
int AnimSkeleton::nameToJointIndex(const QString& jointName) const {
|
int AnimSkeleton::nameToJointIndex(const QString& jointName) const {
|
||||||
|
@ -166,7 +194,8 @@ void AnimSkeleton::mirrorAbsolutePoses(AnimPoseVec& poses) const {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AnimSkeleton::buildSkeletonFromJoints(const std::vector<HFMJoint>& joints) {
|
void AnimSkeleton::buildSkeletonFromJoints(const std::vector<HFMJoint>& joints, const QMap<int, glm::quat> jointOffsets) {
|
||||||
|
|
||||||
_joints = joints;
|
_joints = joints;
|
||||||
_jointsSize = (int)joints.size();
|
_jointsSize = (int)joints.size();
|
||||||
// build a cache of bind poses
|
// build a cache of bind poses
|
||||||
|
@ -189,7 +218,7 @@ void AnimSkeleton::buildSkeletonFromJoints(const std::vector<HFMJoint>& joints)
|
||||||
// build relative and absolute default poses
|
// build relative and absolute default poses
|
||||||
glm::mat4 relDefaultMat = glm::translate(_joints[i].translation) * preRotationTransform * glm::mat4_cast(_joints[i].rotation) * postRotationTransform;
|
glm::mat4 relDefaultMat = glm::translate(_joints[i].translation) * preRotationTransform * glm::mat4_cast(_joints[i].rotation) * postRotationTransform;
|
||||||
AnimPose relDefaultPose(relDefaultMat);
|
AnimPose relDefaultPose(relDefaultMat);
|
||||||
_relativeDefaultPoses.push_back(relDefaultPose);
|
|
||||||
int parentIndex = getParentIndex(i);
|
int parentIndex = getParentIndex(i);
|
||||||
if (parentIndex >= 0) {
|
if (parentIndex >= 0) {
|
||||||
_absoluteDefaultPoses.push_back(_absoluteDefaultPoses[parentIndex] * relDefaultPose);
|
_absoluteDefaultPoses.push_back(_absoluteDefaultPoses[parentIndex] * relDefaultPose);
|
||||||
|
@ -198,6 +227,16 @@ void AnimSkeleton::buildSkeletonFromJoints(const std::vector<HFMJoint>& joints)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (int k = 0; k < _jointsSize; k++) {
|
||||||
|
if (jointOffsets.contains(k)) {
|
||||||
|
AnimPose localOffset(jointOffsets[k], glm::vec3());
|
||||||
|
_absoluteDefaultPoses[k] = _absoluteDefaultPoses[k] * localOffset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// re-compute relative poses
|
||||||
|
_relativeDefaultPoses = _absoluteDefaultPoses;
|
||||||
|
convertAbsolutePosesToRelative(_relativeDefaultPoses);
|
||||||
|
|
||||||
for (int i = 0; i < _jointsSize; i++) {
|
for (int i = 0; i < _jointsSize; i++) {
|
||||||
_jointIndicesByName[_joints[i].name] = i;
|
_jointIndicesByName[_joints[i].name] = i;
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,8 @@ public:
|
||||||
using ConstPointer = std::shared_ptr<const AnimSkeleton>;
|
using ConstPointer = std::shared_ptr<const AnimSkeleton>;
|
||||||
|
|
||||||
explicit AnimSkeleton(const HFMModel& hfmModel);
|
explicit AnimSkeleton(const HFMModel& hfmModel);
|
||||||
explicit AnimSkeleton(const std::vector<HFMJoint>& joints);
|
explicit AnimSkeleton(const std::vector<HFMJoint>& joints, const QMap<int, glm::quat> jointOffsets);
|
||||||
|
|
||||||
int nameToJointIndex(const QString& jointName) const;
|
int nameToJointIndex(const QString& jointName) const;
|
||||||
const QString& getJointName(int jointIndex) const;
|
const QString& getJointName(int jointIndex) const;
|
||||||
int getNumJoints() const;
|
int getNumJoints() const;
|
||||||
|
@ -62,9 +63,10 @@ public:
|
||||||
void dump(const AnimPoseVec& poses) const;
|
void dump(const AnimPoseVec& poses) const;
|
||||||
|
|
||||||
std::vector<int> lookUpJointIndices(const std::vector<QString>& jointNames) const;
|
std::vector<int> lookUpJointIndices(const std::vector<QString>& jointNames) const;
|
||||||
|
const HFMCluster getClusterBindMatricesOriginalValues(const int meshIndex, const int clusterIndex) const { return _clusterBindMatrixOriginalValues[meshIndex][clusterIndex]; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void buildSkeletonFromJoints(const std::vector<HFMJoint>& joints);
|
void buildSkeletonFromJoints(const std::vector<HFMJoint>& joints, const QMap<int, glm::quat> jointOffsets);
|
||||||
|
|
||||||
std::vector<HFMJoint> _joints;
|
std::vector<HFMJoint> _joints;
|
||||||
int _jointsSize { 0 };
|
int _jointsSize { 0 };
|
||||||
|
@ -76,6 +78,7 @@ protected:
|
||||||
std::vector<int> _nonMirroredIndices;
|
std::vector<int> _nonMirroredIndices;
|
||||||
std::vector<int> _mirrorMap;
|
std::vector<int> _mirrorMap;
|
||||||
QHash<QString, int> _jointIndicesByName;
|
QHash<QString, int> _jointIndicesByName;
|
||||||
|
std::vector<std::vector<HFMCluster>> _clusterBindMatrixOriginalValues;
|
||||||
|
|
||||||
// no copies
|
// no copies
|
||||||
AnimSkeleton(const AnimSkeleton&) = delete;
|
AnimSkeleton(const AnimSkeleton&) = delete;
|
||||||
|
|
|
@ -360,8 +360,10 @@ void Rig::initJointStates(const HFMModel& hfmModel, const glm::mat4& modelOffset
|
||||||
void Rig::reset(const HFMModel& hfmModel) {
|
void Rig::reset(const HFMModel& hfmModel) {
|
||||||
_geometryOffset = AnimPose(hfmModel.offset);
|
_geometryOffset = AnimPose(hfmModel.offset);
|
||||||
_invGeometryOffset = _geometryOffset.inverse();
|
_invGeometryOffset = _geometryOffset.inverse();
|
||||||
|
|
||||||
_animSkeleton = std::make_shared<AnimSkeleton>(hfmModel);
|
_animSkeleton = std::make_shared<AnimSkeleton>(hfmModel);
|
||||||
|
|
||||||
|
|
||||||
_internalPoseSet._relativePoses.clear();
|
_internalPoseSet._relativePoses.clear();
|
||||||
_internalPoseSet._relativePoses = _animSkeleton->getRelativeDefaultPoses();
|
_internalPoseSet._relativePoses = _animSkeleton->getRelativeDefaultPoses();
|
||||||
|
|
||||||
|
|
|
@ -417,6 +417,30 @@ QByteArray fileOnUrl(const QByteArray& filepath, const QString& url) {
|
||||||
return filepath.mid(filepath.lastIndexOf('/') + 1);
|
return filepath.mid(filepath.lastIndexOf('/') + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QMap<QString, glm::quat> getJointRotationOffsets(const QVariantHash& mapping) {
|
||||||
|
QMap<QString, glm::quat> jointRotationOffsets;
|
||||||
|
static const QString JOINT_ROTATION_OFFSET_FIELD = "jointRotationOffset";
|
||||||
|
if (!mapping.isEmpty() && mapping.contains(JOINT_ROTATION_OFFSET_FIELD) && mapping[JOINT_ROTATION_OFFSET_FIELD].type() == QVariant::Hash) {
|
||||||
|
auto offsets = mapping[JOINT_ROTATION_OFFSET_FIELD].toHash();
|
||||||
|
for (auto itr = offsets.begin(); itr != offsets.end(); itr++) {
|
||||||
|
QString jointName = itr.key();
|
||||||
|
QString line = itr.value().toString();
|
||||||
|
auto quatCoords = line.split(',');
|
||||||
|
if (quatCoords.size() == 4) {
|
||||||
|
float quatX = quatCoords[0].mid(1).toFloat();
|
||||||
|
float quatY = quatCoords[1].toFloat();
|
||||||
|
float quatZ = quatCoords[2].toFloat();
|
||||||
|
float quatW = quatCoords[3].mid(0, quatCoords[3].size() - 1).toFloat();
|
||||||
|
if (!isNaN(quatX) && !isNaN(quatY) && !isNaN(quatZ) && !isNaN(quatW)) {
|
||||||
|
glm::quat rotationOffset = glm::quat(quatW, quatX, quatY, quatZ);
|
||||||
|
jointRotationOffsets.insert(jointName, rotationOffset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return jointRotationOffsets;
|
||||||
|
}
|
||||||
|
|
||||||
HFMModel* FBXReader::extractHFMModel(const QVariantHash& mapping, const QString& url) {
|
HFMModel* FBXReader::extractHFMModel(const QVariantHash& mapping, const QString& url) {
|
||||||
const FBXNode& node = _rootNode;
|
const FBXNode& node = _rootNode;
|
||||||
QMap<QString, ExtractedMesh> meshes;
|
QMap<QString, ExtractedMesh> meshes;
|
||||||
|
@ -1793,6 +1817,19 @@ HFMModel* FBXReader::extractHFMModel(const QVariantHash& mapping, const QString&
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto offsets = getJointRotationOffsets(mapping);
|
||||||
|
hfmModel.jointRotationOffsets.clear();
|
||||||
|
for (auto itr = offsets.begin(); itr != offsets.end(); itr++) {
|
||||||
|
QString jointName = itr.key();
|
||||||
|
glm::quat rotationOffset = itr.value();
|
||||||
|
int jointIndex = hfmModel.getJointIndex(jointName);
|
||||||
|
if (jointIndex != -1) {
|
||||||
|
hfmModel.jointRotationOffsets.insert(jointIndex, rotationOffset);
|
||||||
|
}
|
||||||
|
qCDebug(modelformat) << "Joint Rotation Offset added to Rig._jointRotationOffsets : " << " jointName: " << jointName << " jointIndex: " << jointIndex << " rotation offset: " << rotationOffset;
|
||||||
|
}
|
||||||
|
|
||||||
return hfmModelPtr;
|
return hfmModelPtr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -311,6 +311,8 @@ public:
|
||||||
QString getModelNameOfMesh(int meshIndex) const;
|
QString getModelNameOfMesh(int meshIndex) const;
|
||||||
|
|
||||||
QList<QString> blendshapeChannelNames;
|
QList<QString> blendshapeChannelNames;
|
||||||
|
|
||||||
|
QMap<int, glm::quat> jointRotationOffsets;
|
||||||
};
|
};
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -243,6 +243,13 @@ void GeometryReader::run() {
|
||||||
QMetaObject::invokeMethod(resource.data(), "finishedLoading",
|
QMetaObject::invokeMethod(resource.data(), "finishedLoading",
|
||||||
Q_ARG(bool, false));
|
Q_ARG(bool, false));
|
||||||
}
|
}
|
||||||
|
} catch (QString& e) {
|
||||||
|
qCWarning(modelnetworking) << "Exception while loading model --" << e;
|
||||||
|
auto resource = _resource.toStrongRef();
|
||||||
|
if (resource) {
|
||||||
|
QMetaObject::invokeMethod(resource.data(), "finishedLoading",
|
||||||
|
Q_ARG(bool, false));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -46,8 +46,13 @@ bool OctreeEntitiesFileParser::parseEntities(QVariantMap& parsedEntities) {
|
||||||
bool gotId = false;
|
bool gotId = false;
|
||||||
bool gotVersion = false;
|
bool gotVersion = false;
|
||||||
|
|
||||||
while (!(gotDataVersion && gotEntities && gotId && gotVersion)) {
|
int token = nextToken();
|
||||||
if (nextToken() != '"') {
|
|
||||||
|
while (true) {
|
||||||
|
if (token == '}') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (token != '"') {
|
||||||
_errorString = "Incorrect key string";
|
_errorString = "Incorrect key string";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -144,15 +149,13 @@ bool OctreeEntitiesFileParser::parseEntities(QVariantMap& parsedEntities) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (gotDataVersion && gotEntities && gotId && gotVersion) {
|
token = nextToken();
|
||||||
break;
|
if (token == ',') {
|
||||||
} else if (nextToken() != ',') {
|
token = nextToken();
|
||||||
_errorString = "Id/value incorrectly terminated";
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nextToken() != '}' || nextToken() != -1) {
|
if (nextToken() != -1) {
|
||||||
_errorString = "Ill-formed end of object";
|
_errorString = "Ill-formed end of object";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -114,18 +114,22 @@ void CauterizedModel::updateClusterMatrices() {
|
||||||
for (int i = 0; i < (int)_meshStates.size(); i++) {
|
for (int i = 0; i < (int)_meshStates.size(); i++) {
|
||||||
Model::MeshState& state = _meshStates[i];
|
Model::MeshState& state = _meshStates[i];
|
||||||
const HFMMesh& mesh = hfmModel.meshes.at(i);
|
const HFMMesh& mesh = hfmModel.meshes.at(i);
|
||||||
|
int meshIndex = i;
|
||||||
|
|
||||||
for (int j = 0; j < mesh.clusters.size(); j++) {
|
for (int j = 0; j < mesh.clusters.size(); j++) {
|
||||||
const HFMCluster& cluster = mesh.clusters.at(j);
|
const HFMCluster& cluster = mesh.clusters.at(j);
|
||||||
|
int clusterIndex = j;
|
||||||
|
|
||||||
if (_useDualQuaternionSkinning) {
|
if (_useDualQuaternionSkinning) {
|
||||||
auto jointPose = _rig.getJointPose(cluster.jointIndex);
|
auto jointPose = _rig.getJointPose(cluster.jointIndex);
|
||||||
Transform jointTransform(jointPose.rot(), jointPose.scale(), jointPose.trans());
|
Transform jointTransform(jointPose.rot(), jointPose.scale(), jointPose.trans());
|
||||||
Transform clusterTransform;
|
Transform clusterTransform;
|
||||||
Transform::mult(clusterTransform, jointTransform, cluster.inverseBindTransform);
|
Transform::mult(clusterTransform, jointTransform, _rig.getAnimSkeleton()->getClusterBindMatricesOriginalValues(meshIndex, clusterIndex).inverseBindTransform);
|
||||||
state.clusterDualQuaternions[j] = Model::TransformDualQuaternion(clusterTransform);
|
state.clusterDualQuaternions[j] = Model::TransformDualQuaternion(clusterTransform);
|
||||||
state.clusterDualQuaternions[j].setCauterizationParameters(0.0f, jointPose.trans());
|
state.clusterDualQuaternions[j].setCauterizationParameters(0.0f, jointPose.trans());
|
||||||
} else {
|
} else {
|
||||||
auto jointMatrix = _rig.getJointTransform(cluster.jointIndex);
|
auto jointMatrix = _rig.getJointTransform(cluster.jointIndex);
|
||||||
glm_mat4u_mul(jointMatrix, cluster.inverseBindMatrix, state.clusterMatrices[j]);
|
glm_mat4u_mul(jointMatrix, _rig.getAnimSkeleton()->getClusterBindMatricesOriginalValues(meshIndex, clusterIndex).inverseBindMatrix, state.clusterMatrices[j]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -146,9 +150,11 @@ void CauterizedModel::updateClusterMatrices() {
|
||||||
for (int i = 0; i < _cauterizeMeshStates.size(); i++) {
|
for (int i = 0; i < _cauterizeMeshStates.size(); i++) {
|
||||||
Model::MeshState& state = _cauterizeMeshStates[i];
|
Model::MeshState& state = _cauterizeMeshStates[i];
|
||||||
const HFMMesh& mesh = hfmModel.meshes.at(i);
|
const HFMMesh& mesh = hfmModel.meshes.at(i);
|
||||||
|
int meshIndex = i;
|
||||||
|
|
||||||
for (int j = 0; j < mesh.clusters.size(); j++) {
|
for (int j = 0; j < mesh.clusters.size(); j++) {
|
||||||
const HFMCluster& cluster = mesh.clusters.at(j);
|
const HFMCluster& cluster = mesh.clusters.at(j);
|
||||||
|
int clusterIndex = j;
|
||||||
|
|
||||||
if (_useDualQuaternionSkinning) {
|
if (_useDualQuaternionSkinning) {
|
||||||
if (_cauterizeBoneSet.find(cluster.jointIndex) == _cauterizeBoneSet.end()) {
|
if (_cauterizeBoneSet.find(cluster.jointIndex) == _cauterizeBoneSet.end()) {
|
||||||
|
@ -157,7 +163,7 @@ void CauterizedModel::updateClusterMatrices() {
|
||||||
} else {
|
} else {
|
||||||
Transform jointTransform(cauterizePose.rot(), cauterizePose.scale(), cauterizePose.trans());
|
Transform jointTransform(cauterizePose.rot(), cauterizePose.scale(), cauterizePose.trans());
|
||||||
Transform clusterTransform;
|
Transform clusterTransform;
|
||||||
Transform::mult(clusterTransform, jointTransform, cluster.inverseBindTransform);
|
Transform::mult(clusterTransform, jointTransform, _rig.getAnimSkeleton()->getClusterBindMatricesOriginalValues(meshIndex, clusterIndex).inverseBindTransform);
|
||||||
state.clusterDualQuaternions[j] = Model::TransformDualQuaternion(clusterTransform);
|
state.clusterDualQuaternions[j] = Model::TransformDualQuaternion(clusterTransform);
|
||||||
state.clusterDualQuaternions[j].setCauterizationParameters(1.0f, cauterizePose.trans());
|
state.clusterDualQuaternions[j].setCauterizationParameters(1.0f, cauterizePose.trans());
|
||||||
}
|
}
|
||||||
|
@ -166,7 +172,7 @@ void CauterizedModel::updateClusterMatrices() {
|
||||||
// not cauterized so just copy the value from the non-cauterized version.
|
// not cauterized so just copy the value from the non-cauterized version.
|
||||||
state.clusterMatrices[j] = _meshStates[i].clusterMatrices[j];
|
state.clusterMatrices[j] = _meshStates[i].clusterMatrices[j];
|
||||||
} else {
|
} else {
|
||||||
glm_mat4u_mul(cauterizeMatrix, cluster.inverseBindMatrix, state.clusterMatrices[j]);
|
glm_mat4u_mul(cauterizeMatrix, _rig.getAnimSkeleton()->getClusterBindMatricesOriginalValues(meshIndex, clusterIndex).inverseBindMatrix, state.clusterMatrices[j]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1418,18 +1418,21 @@ void Model::updateClusterMatrices() {
|
||||||
const HFMModel& hfmModel = getHFMModel();
|
const HFMModel& hfmModel = getHFMModel();
|
||||||
for (int i = 0; i < (int) _meshStates.size(); i++) {
|
for (int i = 0; i < (int) _meshStates.size(); i++) {
|
||||||
MeshState& state = _meshStates[i];
|
MeshState& state = _meshStates[i];
|
||||||
|
int meshIndex = i;
|
||||||
const HFMMesh& mesh = hfmModel.meshes.at(i);
|
const HFMMesh& mesh = hfmModel.meshes.at(i);
|
||||||
for (int j = 0; j < mesh.clusters.size(); j++) {
|
for (int j = 0; j < mesh.clusters.size(); j++) {
|
||||||
const HFMCluster& cluster = mesh.clusters.at(j);
|
const HFMCluster& cluster = mesh.clusters.at(j);
|
||||||
|
int clusterIndex = j;
|
||||||
|
|
||||||
if (_useDualQuaternionSkinning) {
|
if (_useDualQuaternionSkinning) {
|
||||||
auto jointPose = _rig.getJointPose(cluster.jointIndex);
|
auto jointPose = _rig.getJointPose(cluster.jointIndex);
|
||||||
Transform jointTransform(jointPose.rot(), jointPose.scale(), jointPose.trans());
|
Transform jointTransform(jointPose.rot(), jointPose.scale(), jointPose.trans());
|
||||||
Transform clusterTransform;
|
Transform clusterTransform;
|
||||||
Transform::mult(clusterTransform, jointTransform, cluster.inverseBindTransform);
|
Transform::mult(clusterTransform, jointTransform, _rig.getAnimSkeleton()->getClusterBindMatricesOriginalValues(meshIndex, clusterIndex).inverseBindTransform);
|
||||||
state.clusterDualQuaternions[j] = Model::TransformDualQuaternion(clusterTransform);
|
state.clusterDualQuaternions[j] = Model::TransformDualQuaternion(clusterTransform);
|
||||||
} else {
|
} else {
|
||||||
auto jointMatrix = _rig.getJointTransform(cluster.jointIndex);
|
auto jointMatrix = _rig.getJointTransform(cluster.jointIndex);
|
||||||
glm_mat4u_mul(jointMatrix, cluster.inverseBindMatrix, state.clusterMatrices[j]);
|
glm_mat4u_mul(jointMatrix, _rig.getAnimSkeleton()->getClusterBindMatricesOriginalValues(meshIndex, clusterIndex).inverseBindMatrix, state.clusterMatrices[j]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,32 +46,25 @@ void SoftAttachmentModel::updateClusterMatrices() {
|
||||||
for (int i = 0; i < (int) _meshStates.size(); i++) {
|
for (int i = 0; i < (int) _meshStates.size(); i++) {
|
||||||
MeshState& state = _meshStates[i];
|
MeshState& state = _meshStates[i];
|
||||||
const HFMMesh& mesh = hfmModel.meshes.at(i);
|
const HFMMesh& mesh = hfmModel.meshes.at(i);
|
||||||
|
int meshIndex = i;
|
||||||
for (int j = 0; j < mesh.clusters.size(); j++) {
|
for (int j = 0; j < mesh.clusters.size(); j++) {
|
||||||
const HFMCluster& cluster = mesh.clusters.at(j);
|
const HFMCluster& cluster = mesh.clusters.at(j);
|
||||||
|
|
||||||
|
int clusterIndex = j;
|
||||||
// TODO: cache these look-ups as an optimization
|
// TODO: cache these look-ups as an optimization
|
||||||
int jointIndexOverride = getJointIndexOverride(cluster.jointIndex);
|
int jointIndexOverride = getJointIndexOverride(cluster.jointIndex);
|
||||||
if (_useDualQuaternionSkinning) {
|
|
||||||
glm::mat4 jointMatrix;
|
glm::mat4 jointMatrix;
|
||||||
if (jointIndexOverride >= 0 && jointIndexOverride < _rigOverride.getJointStateCount()) {
|
if (jointIndexOverride >= 0 && jointIndexOverride < _rigOverride.getJointStateCount()) {
|
||||||
jointMatrix = _rigOverride.getJointTransform(jointIndexOverride);
|
jointMatrix = _rigOverride.getJointTransform(jointIndexOverride);
|
||||||
} else {
|
} else {
|
||||||
jointMatrix = _rig.getJointTransform(cluster.jointIndex);
|
jointMatrix = _rig.getJointTransform(cluster.jointIndex);
|
||||||
}
|
}
|
||||||
|
if (_useDualQuaternionSkinning) {
|
||||||
glm::mat4 m;
|
glm::mat4 m;
|
||||||
glm_mat4u_mul(jointMatrix, cluster.inverseBindMatrix, m);
|
glm_mat4u_mul(jointMatrix, _rig.getAnimSkeleton()->getClusterBindMatricesOriginalValues(meshIndex, clusterIndex).inverseBindMatrix, m);
|
||||||
state.clusterDualQuaternions[j] = Model::TransformDualQuaternion(m);
|
state.clusterDualQuaternions[j] = Model::TransformDualQuaternion(m);
|
||||||
} else {
|
} else {
|
||||||
glm::mat4 jointMatrix;
|
glm_mat4u_mul(jointMatrix, _rig.getAnimSkeleton()->getClusterBindMatricesOriginalValues(meshIndex, clusterIndex).inverseBindMatrix, state.clusterMatrices[j]);
|
||||||
if (jointIndexOverride >= 0 && jointIndexOverride < _rigOverride.getJointStateCount()) {
|
|
||||||
jointMatrix = _rigOverride.getJointTransform(jointIndexOverride);
|
|
||||||
} else {
|
|
||||||
jointMatrix = _rig.getJointTransform(cluster.jointIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
glm_mat4u_mul(jointMatrix, cluster.inverseBindMatrix, state.clusterMatrices[j]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -590,9 +590,6 @@ div.section[collapsed="true"], div.section[collapsed="true"] > .section-header {
|
||||||
background-color: #373737;
|
background-color: #373737;
|
||||||
}
|
}
|
||||||
|
|
||||||
.section-header {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-header span {
|
.section-header span {
|
||||||
font-size: 30px;
|
font-size: 30px;
|
||||||
|
@ -948,12 +945,12 @@ div.refresh input[type="button"] {
|
||||||
}
|
}
|
||||||
.draggable-number.left-arrow {
|
.draggable-number.left-arrow {
|
||||||
top: -5px;
|
top: -5px;
|
||||||
right: 106px;
|
left: 0px;
|
||||||
transform: rotate(180deg);
|
transform: rotate(180deg);
|
||||||
}
|
}
|
||||||
.draggable-number.right-arrow {
|
.draggable-number.right-arrow {
|
||||||
top: -5px;
|
top: -5px;
|
||||||
left: 106px;
|
right: 0px;
|
||||||
}
|
}
|
||||||
.draggable-number input[type=number] {
|
.draggable-number input[type=number] {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
@ -1123,7 +1120,7 @@ textarea:enabled[scrolling="true"]::-webkit-resizer {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
body#entity-list-body {
|
div#grid-section, body#entity-list-body {
|
||||||
padding-bottom: 0;
|
padding-bottom: 0;
|
||||||
margin: 16px;
|
margin: 16px;
|
||||||
}
|
}
|
||||||
|
@ -1552,14 +1549,15 @@ input.rename-entity {
|
||||||
.container {
|
.container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-flow: row nowrap;
|
flex-flow: row nowrap;
|
||||||
justify-content: space-around;
|
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
min-height: 28px;
|
min-height: 28px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.container > label {
|
.container > label {
|
||||||
margin-top: 6px;
|
margin-top: 6px;
|
||||||
width: 200px;
|
width: 160px;
|
||||||
|
min-width: 160px;
|
||||||
|
max-width: 160px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.container > div.checkbox {
|
.container > div.checkbox {
|
||||||
|
@ -1600,6 +1598,8 @@ input.rename-entity {
|
||||||
.xyz.fstuple, .pyr.fstuple {
|
.xyz.fstuple, .pyr.fstuple {
|
||||||
position: relative;
|
position: relative;
|
||||||
left: -12px;
|
left: -12px;
|
||||||
|
min-width: 50px;
|
||||||
|
width: 100px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.rgb.fstuple .tuple {
|
.rgb.fstuple .tuple {
|
||||||
|
@ -1650,3 +1650,15 @@ input.number-slider {
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.spacemode-hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#placeholder-property-type {
|
||||||
|
min-width: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapse-icon {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
|
@ -19,46 +19,51 @@
|
||||||
<script type="text/javascript" src="js/gridControls.js"></script>
|
<script type="text/javascript" src="js/gridControls.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body onload='loaded();'>
|
<body onload='loaded();'>
|
||||||
<div id="grid-section">
|
<div id="grid-section" class="section">
|
||||||
<div class="section-header">
|
<div class="property container">
|
||||||
<label>Editing Grid</label>
|
|
||||||
</div>
|
|
||||||
<div class="property checkbox">
|
|
||||||
<input type='checkbox' id="horiz-grid-visible">
|
|
||||||
<label for="horiz-grid-visible">Visible</label>
|
<label for="horiz-grid-visible">Visible</label>
|
||||||
|
<div style="width: 100%">
|
||||||
|
<input type='checkbox' id="horiz-grid-visible" style="width: 100%">
|
||||||
|
<label for="horiz-grid-visible"> </label>
|
||||||
</div>
|
</div>
|
||||||
<div class="property checkbox">
|
</div>
|
||||||
<input type="checkbox" id="snap-to-grid">
|
<div class="property container">
|
||||||
<label for="snap-to-grid">Snap entities to grid</label>
|
<label for="snap-to-grid">Snap entities to grid</label>
|
||||||
|
<div style="width: 100%">
|
||||||
|
<input type="checkbox" id="snap-to-grid">
|
||||||
|
<label for="snap-to-grid"> </label>
|
||||||
</div>
|
</div>
|
||||||
<div class="property">
|
</div>
|
||||||
<div class="number">
|
<div class="property container">
|
||||||
<label for="major-spacing">Major grid size <span class="unit">m</span></label>
|
<label for="major-spacing">Major grid size <span class="unit">m</span></label>
|
||||||
|
<div class="number">
|
||||||
<input type="number" id="major-spacing" min="1" step="1" />
|
<input type="number" id="major-spacing" min="1" step="1" />
|
||||||
</div>
|
</div>
|
||||||
<div class="number">
|
</div>
|
||||||
|
<div class="property container">
|
||||||
<label for="minor-spacing">Minor grid size <span class="unit">m</span></label>
|
<label for="minor-spacing">Minor grid size <span class="unit">m</span></label>
|
||||||
|
<div class="number">
|
||||||
<input type="number" id="minor-spacing" min="0.2" step="0.2" />
|
<input type="number" id="minor-spacing" min="0.2" step="0.2" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="property number">
|
<div class="property container">
|
||||||
<label for="horiz-y">Position (Y axis) <span class="unit">m</span></label>
|
<label for="horiz-y">Position (Y axis) <span class="unit">m</span></label>
|
||||||
|
<div style="width: 100%">
|
||||||
<input type="number" id="horiz-y" step="0.1" />
|
<input type="number" id="horiz-y" step="0.1" />
|
||||||
</div>
|
</div>
|
||||||
<div class="property rgb">
|
</div>
|
||||||
<div id="grid-color" class="color-picker"></div>
|
<div class="property container">
|
||||||
<label>Grid line color</label>
|
<label>Grid line color</label>
|
||||||
<div class="tuple">
|
<div style="width: 100%">
|
||||||
<div><input type="number" class="red" id="grid-color-red"><label for="grid-color-red">Red:</label></div>
|
<div id="grid-color" class="color-picker"></div>
|
||||||
<div><input type="number" class="green" id="grid-color-green"><label for="grid-color-green">Green:</label></div>
|
|
||||||
<div><input type="number" class="blue" id="grid-color-blue"><label for="grid-color-blue">Blue:</label></div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="property">
|
<div class="property container">
|
||||||
<span>
|
<label>Move Grid</label>
|
||||||
|
<div style="width: 100%">
|
||||||
<input type="button" id="move-to-selection" value="Align To Selection">
|
<input type="button" id="move-to-selection" value="Align To Selection">
|
||||||
<input type="button" id="move-to-avatar" value="Align To Avatar">
|
<input type="button" id="move-to-avatar" value="Align To Avatar">
|
||||||
</span>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
@ -32,7 +32,7 @@ DraggableNumber.prototype = {
|
||||||
mouseUp: function(event) {
|
mouseUp: function(event) {
|
||||||
if (event.target === this.elText && this.initialMouseEvent) {
|
if (event.target === this.elText && this.initialMouseEvent) {
|
||||||
let dx = event.clientX - this.initialMouseEvent.clientX;
|
let dx = event.clientX - this.initialMouseEvent.clientX;
|
||||||
if (dx <= DELTA_X_FOCUS_THRESHOLD) {
|
if (Math.abs(dx) <= DELTA_X_FOCUS_THRESHOLD) {
|
||||||
this.elInput.style.visibility = "visible";
|
this.elInput.style.visibility = "visible";
|
||||||
this.elText.style.visibility = "hidden";
|
this.elText.style.visibility = "hidden";
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,7 @@ const ICON_FOR_TYPE = {
|
||||||
|
|
||||||
const DEGREES_TO_RADIANS = Math.PI / 180.0;
|
const DEGREES_TO_RADIANS = Math.PI / 180.0;
|
||||||
|
|
||||||
const NO_SELECTION = "w";
|
const NO_SELECTION = ",";
|
||||||
|
|
||||||
const PROPERTY_SPACE_MODE = {
|
const PROPERTY_SPACE_MODE = {
|
||||||
ALL: 0,
|
ALL: 0,
|
||||||
|
@ -1158,18 +1158,21 @@ const GROUPS = [
|
||||||
label: "Link",
|
label: "Link",
|
||||||
type: "string",
|
type: "string",
|
||||||
propertyID: "href",
|
propertyID: "href",
|
||||||
|
placeholder: "URL",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Script",
|
label: "Script",
|
||||||
type: "string",
|
type: "string",
|
||||||
buttons: [ { id: "reload", label: "F", className: "glyph", onClick: reloadScripts } ],
|
buttons: [ { id: "reload", label: "F", className: "glyph", onClick: reloadScripts } ],
|
||||||
propertyID: "script",
|
propertyID: "script",
|
||||||
|
placeholder: "URL",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Server Script",
|
label: "Server Script",
|
||||||
type: "string",
|
type: "string",
|
||||||
buttons: [ { id: "reload", label: "F", className: "glyph", onClick: reloadServerScripts } ],
|
buttons: [ { id: "reload", label: "F", className: "glyph", onClick: reloadServerScripts } ],
|
||||||
propertyID: "serverScripts",
|
propertyID: "serverScripts",
|
||||||
|
placeholder: "URL",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Server Script Status",
|
label: "Server Script Status",
|
||||||
|
@ -1244,8 +1247,9 @@ const GROUPS = [
|
||||||
showPropertyRule: { "collisionless": "false" },
|
showPropertyRule: { "collisionless": "false" },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Collision sound URL",
|
label: "Collision Sound",
|
||||||
type: "string",
|
type: "string",
|
||||||
|
placeholder: "URL",
|
||||||
propertyID: "collisionSoundURL",
|
propertyID: "collisionSoundURL",
|
||||||
showPropertyRule: { "collisionless": "false" },
|
showPropertyRule: { "collisionless": "false" },
|
||||||
},
|
},
|
||||||
|
@ -1500,7 +1504,7 @@ function disableProperties() {
|
||||||
|
|
||||||
function showPropertyElement(propertyID, show) {
|
function showPropertyElement(propertyID, show) {
|
||||||
let elProperty = properties[propertyID].elContainer;
|
let elProperty = properties[propertyID].elContainer;
|
||||||
elProperty.style.display = show ? "flex" : "none";
|
elProperty.style.display = show ? "" : "none";
|
||||||
}
|
}
|
||||||
|
|
||||||
function resetProperties() {
|
function resetProperties() {
|
||||||
|
@ -1622,10 +1626,11 @@ function updateVisibleSpaceModeProperties() {
|
||||||
if (properties.hasOwnProperty(propertyID)) {
|
if (properties.hasOwnProperty(propertyID)) {
|
||||||
let property = properties[propertyID];
|
let property = properties[propertyID];
|
||||||
let propertySpaceMode = property.spaceMode;
|
let propertySpaceMode = property.spaceMode;
|
||||||
if (propertySpaceMode !== PROPERTY_SPACE_MODE.ALL) {
|
let elProperty = properties[propertyID].elContainer;
|
||||||
showPropertyElement(propertyID, propertySpaceMode === currentSpaceMode);
|
if (propertySpaceMode !== PROPERTY_SPACE_MODE.ALL && propertySpaceMode !== currentSpaceMode) {
|
||||||
|
elProperty.classList.add('spacemode-hidden');
|
||||||
} else {
|
} else {
|
||||||
showPropertyElement(propertyID, true);
|
elProperty.classList.remove('spacemode-hidden');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2769,7 +2774,7 @@ function loaded() {
|
||||||
elLegend.appendChild(createElementFromHTML(`<div class="label">${group.label}</div>`));
|
elLegend.appendChild(createElementFromHTML(`<div class="label">${group.label}</div>`));
|
||||||
|
|
||||||
let elSpan = document.createElement('span');
|
let elSpan = document.createElement('span');
|
||||||
elSpan.className = ".collapse-icon";
|
elSpan.className = "collapse-icon";
|
||||||
elSpan.innerText = "M";
|
elSpan.innerText = "M";
|
||||||
elLegend.appendChild(elSpan);
|
elLegend.appendChild(elSpan);
|
||||||
elGroup.appendChild(elLegend);
|
elGroup.appendChild(elLegend);
|
||||||
|
@ -3309,14 +3314,14 @@ function loaded() {
|
||||||
getPropertyInputElement("image").addEventListener('change', createImageURLUpdateFunction('textures', false));
|
getPropertyInputElement("image").addEventListener('change', createImageURLUpdateFunction('textures', false));
|
||||||
|
|
||||||
// Collapsible sections
|
// Collapsible sections
|
||||||
let elCollapsible = document.getElementsByClassName("section-header");
|
let elCollapsible = document.getElementsByClassName("collapse-icon");
|
||||||
|
|
||||||
let toggleCollapsedEvent = function(event) {
|
let toggleCollapsedEvent = function(event) {
|
||||||
let element = this.parentNode;
|
let element = this.parentNode.parentNode;
|
||||||
let isCollapsed = element.dataset.collapsed !== "true";
|
let isCollapsed = element.dataset.collapsed !== "true";
|
||||||
element.dataset.collapsed = isCollapsed ? "true" : false;
|
element.dataset.collapsed = isCollapsed ? "true" : false;
|
||||||
element.setAttribute("collapsed", isCollapsed ? "true" : "false");
|
element.setAttribute("collapsed", isCollapsed ? "true" : "false");
|
||||||
element.getElementsByClassName(".collapse-icon")[0].textContent = isCollapsed ? "L" : "M";
|
this.textContent = isCollapsed ? "L" : "M";
|
||||||
};
|
};
|
||||||
|
|
||||||
for (let collapseIndex = 0, numCollapsibles = elCollapsible.length; collapseIndex < numCollapsibles; ++collapseIndex) {
|
for (let collapseIndex = 0, numCollapsibles = elCollapsible.length; collapseIndex < numCollapsibles; ++collapseIndex) {
|
||||||
|
|
|
@ -83,44 +83,26 @@ function loaded() {
|
||||||
|
|
||||||
var gridColor = { red: 255, green: 255, blue: 255 };
|
var gridColor = { red: 255, green: 255, blue: 255 };
|
||||||
var elColor = document.getElementById("grid-color");
|
var elColor = document.getElementById("grid-color");
|
||||||
var elColorRed = document.getElementById("grid-color-red");
|
|
||||||
var elColorGreen = document.getElementById("grid-color-green");
|
|
||||||
var elColorBlue = document.getElementById("grid-color-blue");
|
|
||||||
elColor.style.backgroundColor = "rgb(" + gridColor.red + "," + gridColor.green + "," + gridColor.blue + ")";
|
elColor.style.backgroundColor = "rgb(" + gridColor.red + "," + gridColor.green + "," + gridColor.blue + ")";
|
||||||
elColorRed.value = gridColor.red;
|
|
||||||
elColorGreen.value = gridColor.green;
|
|
||||||
elColorBlue.value = gridColor.blue;
|
|
||||||
|
|
||||||
var colorChangeFunction = function () {
|
|
||||||
gridColor = { red: elColorRed.value, green: elColorGreen.value, blue: elColorBlue.value };
|
|
||||||
elColor.style.backgroundColor = "rgb(" + gridColor.red + "," + gridColor.green + "," + gridColor.blue + ")";
|
|
||||||
emitUpdate();
|
|
||||||
};
|
|
||||||
|
|
||||||
var colorPickFunction = function (red, green, blue) {
|
var colorPickFunction = function (red, green, blue) {
|
||||||
elColorRed.value = red;
|
|
||||||
elColorGreen.value = green;
|
|
||||||
elColorBlue.value = blue;
|
|
||||||
gridColor = { red: red, green: green, blue: blue };
|
gridColor = { red: red, green: green, blue: blue };
|
||||||
emitUpdate();
|
emitUpdate();
|
||||||
};
|
};
|
||||||
|
|
||||||
elColorRed.addEventListener('change', colorChangeFunction);
|
|
||||||
elColorGreen.addEventListener('change', colorChangeFunction);
|
|
||||||
elColorBlue.addEventListener('change', colorChangeFunction);
|
|
||||||
$('#grid-color').colpick({
|
$('#grid-color').colpick({
|
||||||
colorScheme: 'dark',
|
colorScheme: 'dark',
|
||||||
layout: 'hex',
|
layout: 'rgbhex',
|
||||||
color: { r: gridColor.red, g: gridColor.green, b: gridColor.blue },
|
color: { r: gridColor.red, g: gridColor.green, b: gridColor.blue },
|
||||||
|
submit: false,
|
||||||
onShow: function (colpick) {
|
onShow: function (colpick) {
|
||||||
$('#grid-color').attr('active', 'true');
|
$('#grid-color').attr('active', 'true');
|
||||||
},
|
},
|
||||||
onHide: function (colpick) {
|
onHide: function (colpick) {
|
||||||
$('#grid-color').attr('active', 'false');
|
$('#grid-color').attr('active', 'false');
|
||||||
},
|
},
|
||||||
onSubmit: function (hsb, hex, rgb, el) {
|
onChange: function (hsb, hex, rgb, el) {
|
||||||
$(el).css('background-color', '#' + hex);
|
$(el).css('background-color', '#' + hex);
|
||||||
$(el).colpickHide();
|
|
||||||
colorPickFunction(rgb.r, rgb.g, rgb.b);
|
colorPickFunction(rgb.r, rgb.g, rgb.b);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue